/* 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-python'), // the screen container element jqConsole = jqScreen.find('.block-console'), jqBtnPlan = jqScreen.find('.btn-plan'), jqBtnTest = jqScreen.find('.btn-test').ladda(), jqBtnRun = jqScreen.find('.btn-run'), jqBtnStop = jqScreen.find('.btn-stop'), jqAllButtons = jqBtnPlan.add(jqBtnTest).add(jqBtnRun).add(jqBtnStop), // all buttons pythonHandler; // created when we enter the python state and destroyed once we leave it var enterFun = function(problemDef, commonDef, currentSolution) { $('#navigation-problem-list').css('display', ''); var navigationPhython = $("#navigation-python"); navigationPhython.addClass("active"); navigationPhython.css('display', ''); jqScreen.css('display', ''); // we have to show the screen now so the code editor shows its initial values correctly pythonHandler = createPythonHandler(problemDef, commonDef, currentSolution); }; codeq.globalStateMachine.register('python', { '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 (pythonHandler) pythonHandler.destroy(); pythonHandler = null; $('#navigation-problem-list').css('display', 'none'); var navigationPhython = $("#navigation-python"); navigationPhython.removeClass("active"); navigationPhython.css('display', 'none'); } }); // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Python terminal proxy', 'autoHistory': true }), tcs = function terminalCommandSuccess (data) { if (data.code !== 0) { alert(data.message); } }, tcf = function terminalCommandFailed (error) { alert(error); }; terminal.onInput = function (text) { terminal.leftmostCol = 0; activityHandler.queueTrace({'typ': 'python_input', 'txt': text + '\n'}); return codeq.comms.sendPythonPush({ 'text': text + '\n' }).then(tcs, tcf); }; codeq.comms.on('terminal_output', function (data) { var text = data.text, lines = text.split('\n'); terminal.append(text, 'output'); terminal.leftmostCol = lines[lines.length-1].length; }); terminal.leftmostCol = 1; return terminal; }; codeq.on('init', function (args) { codeq.tr.registerDictionary('python', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active }); var createPythonHandler = function (problemDef, commonDef, currentSolution) { var jqEditor = jqScreen.find('.code-editor'), jqHints = jqScreen.find('.hints'), jqTerminal = jqConsole.find('.console'), editor = codeq.makeEditor(jqEditor[0], { mode: 'python', indentUnit: 4, value: currentSolution || problemDef.initial || '' }, function () { jqTerminal.click() }), activityHandler = codeq.makeActivityHandler(editor, problemDef.id), terminal = makePythonTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler), hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'python_hints', problemDef, commonDef, activityHandler); codeq.template.processDictionary(problemDef.translations.description, [problemDef.language, problemDef.group, problemDef.problem]); codeq.tr.registerDictionary('python', problemDef.translations); codeq.tr.translateDom(jqScreen); jqBtnPlan.prop('disabled', !hinter.hasNextPlan()); 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.prop('disabled', true).blur(); } }); 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(); }); jqBtnRun.on('click', function () { var program = editor.getDoc().getValue(); activityHandler.queueTrace({'typ': 'python_run', 'program': program}); codeq.comms.sendPythonStop({}) .then(function () { codeq.comms.sendPythonExec({ 'program': program, 'problem_id': problemDef.id }); }) .fail(alert) .done(); // focus the terminal jqTerminal.click(); }); jqBtnStop.on('click', function () { activityHandler.queueTrace({'typ': 'python_stop'}); codeq.comms.sendPythonStop({}) .fail(alert) .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-python .title').text(''); //empty the title text jqAllButtons.off(); editor.off('change'); codeq.comms.off('terminal_output'); // stop listening for the terminal events from server 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('python', codeq.tr.emptyDictionary); } }; }; })();