/** * Created by robert on 9/17/15. * * The python state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen. * * Currentyl it is mostly a copy of the prolog state (the exception is of course the makePythonTerminalHandler instead of makePrologTerminalHandler) */ (function() { var subScreens, //this will be the actual (sub)state machine stateNameTag = 'stateName', //a tag for data which is added to some html elements jqScreen = $('#screen_python'), // the screen container element //quadrants jqDescription = jqScreen.find('.block1'), jqCode = jqScreen.find('.block2'), jqConsole = jqScreen.find('.block3'), jqInfo = jqScreen.find('.block4'), jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants // buttons jqBtnPlan = jqScreen.find('.btn-plan'), jqBtnHint = jqScreen.find('.btn-hint'), jqBtnTest = jqScreen.find('.btn-test'), jqBtnRun = jqScreen.find('.btn-run'), jqBtnStop = jqScreen.find('.btn-stop'), jqInfoButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnTest), // all info-focusing buttons jqAllButtons = jqInfoButtons.add(jqBtnRun).add(jqBtnStop), // all buttons // misc currentSubState = null, transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below) substates = { 'description': { 'enter': function () { currentSubState = 'block1'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } }, 'code': { 'enter': function () { currentSubState = 'block2'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } }, 'info': { 'enter': function () { currentSubState = 'block4'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } }, 'console': { 'enter': function () { currentSubState = 'block3'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } } }; var pythonHandler; //created when we enter the python state and destroyed once we leave it codeq.globalStateMachine.register('python', { 'enter': function (problemDef, commonHints, currentSolution) { $('#navigation-language').css('display', ''); $('#navigation-problem').css('display', ''); $("#navigation-python").addClass("active"); $('#navigation-python').css('display', ''); jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly pythonHandler = createPythonHandler(problemDef, commonHints, currentSolution); subScreens = codeq.makeStateMachine(substates); subScreens.transition(jqDescription.data(stateNameTag)); /* Q.delay(100).then(function(){ jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading }).done();*/ jqInfoButtons.on(transitionEventName, function (event) { subScreens.transition('info'); // set focus on the hints quadrant event.stopPropagation(); // don't allow the event to go on and trigger further transition }); jqBtnRun.on(transitionEventName, function (event) { subScreens.transition('console'); // set focus on the hints quadrant event.stopPropagation(); // don't allow the event to go on and trigger further transition }); jqAllQuadrants.on(transitionEventName, function () { subScreens.transition($(this).data(stateNameTag)); }); }, 'exit': function () { jqAllButtons.off(); // unregister all event handlers jqAllQuadrants.off(); jqScreen.css('display', 'none'); // jqAllQuadrants.removeClass('transition'); pythonHandler.destroy(); pythonHandler = null; subScreens.destroy(); subScreens = null; jqScreen.addClass('block1'); $('#navigation-language').css('display', 'none'); $('#navigation-problem').css('display', 'none'); $("#navigation-python").removeClass("active"); $('#navigation-python').css('display', 'none'); } }); jqDescription.data(stateNameTag, 'description'); jqCode.data(stateNameTag, 'code'); jqConsole.data(stateNameTag, 'console'); jqInfo.data(stateNameTag, 'info'); // 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) { terminal.append(data.message, 'error'); } }, tcf = function terminalCommandFailed (error) { terminal.append(error + '\n', 'error'); }; terminal.onInput = function (text) { terminal.leftmostCol = 0; 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; }; var makeActivityHandler = function (editor, problem_id) { var lastActivityMillis = Date.now(), deltaActivityMillis = function deltaActivityMillisFunc () { var now = Date.now(), dt = now - lastActivityMillis; lastActivityMillis = now; return dt; }, queue = [], ts = null, timer = function () { var promise; ts = null; if (queue.length === 0) return Q(true); promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); queue.length = 0; return promise; }, flush = function () { clearTimeout(ts); return timer(); }; return { 'queueTrace': function (trace) { trace['dt'] = deltaActivityMillis(); queue.push(trace); if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds return this; }, 'flush': flush, 'addAndPurge': function (trace) { var accumulatedTrace = queue; queue = []; trace['dt'] = deltaActivityMillis(); accumulatedTrace.push(trace); if (ts !== null) { clearTimeout(ts); ts = null; } return accumulatedTrace; } }; }; codeq.on('init', function (args) { codeq.tr.registerDictionary('python', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active }); /** * Creates a new handler for the given Prolog assignment definition. * * @param {PrologTaskDef} info * @returns {{destroy: Function, processServerHints: Function}} */ var createPythonHandler = function (problemDef, commonHints, currentSolution) { var //problem = info.problem, jqDescriptionContent = jqDescription.find('.description'), jqEditor = jqCode.find('.code_editor'), jqTerminal = jqConsole.find('.console'), jqHints = jqInfo.find('.hints'), editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true, mode: 'python', indentUnit: 4, extraKeys: { // replace tabs with spaces Tab: function (cm) { var spaces = Array(cm.getOption("indentUnit") + 1).join(" "); cm.replaceSelection(spaces); } } }), activityHandler = makeActivityHandler(editor, problemDef.id), terminal = makePythonTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler), hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'python_hints', problemDef.hint, commonHints, problemDef.plan), commError = function (error) { alert(error); }; codeq.tr.registerDictionary('python', problemDef.translations); codeq.tr.translateDom(jqScreen); if (currentSolution) editor.setValue(currentSolution); // $('#screen_python .title').text(problem.slug); // jqDescriptionContent.html(problem.description); jqBtnPlan.prop('disabled', ((problemDef.plan && problemDef.plan.sl) || []).length == 0); editor.on('change', function (instance, changeObj) { var doc = editor.getDoc(), pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)); if (changeObj.removed) { activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'len': codeq.codePointCount(changeObj.removed.join(''))}); } if (changeObj.text) { activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); } }); jqBtnPlan.on('click', function () { if (!hinter.planNext()) { jqBtnPlan.prop('disabled', true).blur(); } }); jqBtnHint.on('click', function () { var doc = editor.getDoc(); codeq.comms.sendHint({ 'language': 'python', 'program': editor.getDoc().getValue(), 'problem_id': problemDef.id }) .then(function (data) { if (data.code === 0) { hinter.handle(data.hints); } else { terminal.append('error: ' + data.message); } }) .fail(commError) .done(); }); jqBtnTest.on('click', function () { var doc = editor.getDoc(); codeq.comms.sendTest({ 'language': 'python', 'program': editor.getDoc().getValue(), 'problem_id': problemDef.id }) .then(function (data) { if (data.code === 0) { hinter.handle(data.hints); } else { terminal.append('error: ' + data.message); } }) .fail(commError) .done(); }); jqBtnRun.on('click', function () { var program = editor.getDoc().getValue(); codeq.comms.sendPythonStop({}) .then(function () { codeq.comms.sendPythonExec({ 'program': program }); }) .fail(commError) .done(); // focus the terminal jqTerminal.click(); }); jqBtnStop.on('click', function () { codeq.comms.sendPythonStop({}) .fail(commError) .done(); }); // TODO first line of interpreter output is buffered without this, why? codeq.comms.sendPythonPush({ 'text': '' }); return { destroy: function () { $('#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 hinter.destroy(); terminal.destroy(); jqDescriptionContent.empty(); jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it jqTerminal.empty(); // TODO: the same with the console jqDescriptionContent = null; jqEditor = null; jqTerminal = null; jqHints = null; codeq.tr.registerDictionary('python', codeq.tr.emptyDictionary); } }; }; })();