/* CodeQ: an online programming tutor. Copyright (C) 2015,2016 UL FRI This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ (function() { "use strict"; var jqScreen = $('#screen-prolog'), // the screen container element jqConsole = jqScreen.find('.block-console'), jqBtnPlan = jqScreen.find('.btn-plan'), jqBtnTest = jqScreen.find('.btn-test').ladda(), jqAllButtons = jqBtnPlan.add(jqBtnTest), // all the buttons prologHandler; // created when we enter the prolog state and destroyed once we leave it var enterFun = function(problemDef, commonDef, currentSolution) { $('#navigation-problem-list').css('display', ''); var navigationProlog = $("#navigation-prolog"); navigationProlog.addClass("active"); navigationProlog.css('display', ''); jqScreen.css('display', ''); // we have to show the screen now so the code editor shows its initial values correctly prologHandler = createPrologHandler(problemDef, commonDef, currentSolution); }; codeq.globalStateMachine.register('prolog', { 'jqScreen': jqScreen, 'enter': function (ref, data) { codeq.loadProblemData(ref, data).then(function (problem) { enterFun(problem.generalProblemData, data.commonDef, problem.solution); }) .fail(function (error) { var message = 'Could not obtain problem definition: ' + error.message; codeq.log.error(message, error); alert(message); history.back(); }) .done(); }, 'exit': function () { jqScreen.css('display', 'none'); if (prologHandler) prologHandler.destroy(); prologHandler = null; $('#navigation-problem-list').css('display', 'none'); var navigationProlog = $("#navigation-prolog"); navigationProlog.removeClass("active"); navigationProlog.css('display', 'none'); } }); // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var promptMode = true, // default: query composition; alternative: query result browsing manualStop = false,// if the user stopped showing next answers (false) or if there are no more answers (true) terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Prolog terminal proxy', 'autoHistory': true }), tcs = function terminalCommandSuccess (data) { var t, lines, i; if (data.code === 0) { t = data.terminal; terminal.append(t.messages.join('\n'), 'output'); promptMode = !t.have_more; } else { alert(data.message); promptMode = true; } if (promptMode) { terminal.setLineBuffered(); terminal.append(manualStop ? '?- ' : '.\n?- ', 'output'); } }, tcf = function terminalCommandFailed (error) { alert(error); promptMode = true; terminal.setLineBuffered(); terminal.append('?- ', 'output'); }; terminal.onKeypress = function (c) { if (promptMode) return c; // query composition: return the character unchanged switch (c) { case ' ': case ';': case 'n': case 'r': return ';'; // show next answer on space, semicolon, 'n' or 'r' default: return '.'; // everything else: stop searching for answers } }; terminal.onInput = function (command) { if (promptMode) { if (!command) { terminal.append('?- ', 'output'); return; } if (command.match(/^clear\.*$/)) { terminal.clear(); terminal.append('?- ', 'output'); return; } promptMode = false; manualStop = false; terminal.setNotBuffered(); return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'run', 'program': editor.getDoc().getValue(), 'query': command, 'trace': activityHandler.addAndPurge({'typ': 'prolog_solve', 'query': command}) }, problem_id).then(tcs, tcf); } else { terminal.append('\n', 'input'); if (command == ';') { // show next answer return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'next', 'trace': activityHandler.addAndPurge({'typ': 'prolog_next'}) }, problem_id).then(tcs, tcf); } else { // stop searching for answers manualStop = true; return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'end', 'trace': activityHandler.addAndPurge({'typ': 'prolog_end'}) }, problem_id).then(tcs, tcf); } } }; terminal.leftmostCol = 3; terminal.append('?- ', 'output'); return terminal; }; codeq.on('init', function (args) { codeq.tr.registerDictionary('prolog', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active }); var createPrologHandler = function (problemDef, commonDef, currentSolution) { var jqEditor = jqScreen.find('.code-editor'), jqHints = jqScreen.find('.hints'), jqTerminal = jqConsole.find('.console'), editor = codeq.makeEditor(jqEditor[0], { mode: 'prolog', indentUnit: 4, value: currentSolution || problemDef.initial || '' }, function () { jqTerminal.click(); }), activityHandler = codeq.makeActivityHandler(editor, problemDef.id), terminal = makePrologTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler), hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'prolog_hints', problemDef, commonDef, activityHandler); codeq.template.processDictionary(problemDef.translations.description, [problemDef.language, problemDef.group, problemDef.problem]); codeq.tr.registerDictionary('prolog', problemDef.translations); codeq.tr.translateDom(jqScreen); if (hinter.hasNextPlan()) { jqBtnPlan.show(); } else { jqBtnPlan.hide(); } editor.on('change', function (instance, changeObj) { var doc = editor.getDoc(), pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)), text; text = changeObj.removed.join('\n'); if (text) { activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'txt': text}); } text = changeObj.text.join('\n'); if (text) { activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': text}); } }); jqBtnPlan.on('click', function () { hinter.clear(); if (!hinter.planNext()) { jqBtnPlan.hide(); } }); jqBtnTest.on('click', function () { // random timeout before returning results to the user, helps // maintain the illusion var timeout = 250 + Math.random() * 500; hinter.clear(); $(editor.getWrapperElement()).addClass('disabled'); editor.setOption('readOnly', 'nocursor'); terminal.inputDisable(); jqBtnTest.ladda('start'); codeq.throttle( codeq.comms.sendTest({ 'program': editor.getDoc().getValue(), 'problem_id': problemDef.id }), timeout) .then(function (data) { if (data.code === 0) { hinter.handle(data.hints); } else { alert(data.message); } }) .fail(alert) .fin(function () { editor.setOption('readOnly', false); $(editor.getWrapperElement()).removeClass('disabled'); terminal.inputEnable(); jqBtnTest.ladda('stop'); }) .done(); }); codeq.comms.loadProblem(problemDef.id).done(); activityHandler.queueTrace({ 'typ': 'open', 'time': Date.now(), 'content': editor.getDoc().getValue() }); return { destroy: function () { codeq.comms.endProblem().done(); $('#screen-prolog .title').text(''); //empty the title text jqAllButtons.off(); editor.off('change'); activityHandler.queueTrace({'typ': 'close'}); activityHandler.flush(); hinter.destroy(); terminal.destroy(); jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it jqTerminal.empty(); // TODO: the same with the console jqEditor = null; jqTerminal = null; jqHints = null; codeq.tr.registerDictionary('prolog', codeq.tr.emptyDictionary); } }; }; })();