From 5c4f726e7dc79735376fde9514768d8b7c9c049a Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Tue, 1 Sep 2015 16:36:32 +0200 Subject: Add a webpage for Python problems --- js/python.js | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 js/python.js (limited to 'js/python.js') diff --git a/js/python.js b/js/python.js new file mode 100644 index 0000000..f4703b2 --- /dev/null +++ b/js/python.js @@ -0,0 +1,331 @@ +/** + * Handler for a Python assignment. Works with the corresponding python.html content. + */ + +(function () { + // a constant + var firstCharacterPos = {'line': 0, 'ch': 0}; + + var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { + var terminal = jqConsole.terminal(function (command, terminal) { + terminal.echo('Not implemented yet.'); + }, { + 'history': true, + 'prompt': '>>> ', + 'greetings': 'CodeQ python terminal proxy', + 'exit': false, + 'clear': false + }); + + return {}; + }; + + var makeActivityHandler = function (editor) { + var lastActivityMillis = Date.now(), + deltaActivityMillis = function deltaActivityMillisFunc () { + var now = Date.now(), + dt = Math.max(0, Math.min(30000, now - lastActivityMillis)); // 0 sec <= dt <= 30 sec + 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()); + queue.length = 0; + return promise; + }, + flush = function () { + clearTimeout(ts); + return timer(); + }; + + return { + "trace": function (trace) { + trace['dt'] = deltaActivityMillis(); + return trace; + }, + 'queue': function (trace) { + queue.push(trace); + if (ts === null) setTimeout(timer, 10000); // flush every 10 seconds + }, + 'queueTrace': function (trace) { + this.queue(this.trace(trace)); + }, + 'flush': flush, + 'addAndPurge': function (trace) { + var accumulatedTrace = queue; + queue = []; + trace['dt'] = deltaActivityMillis(); + accumulatedTrace.push(trace); + if (ts !== null) { + clearTimeout(ts); + ts = null; + } + return accumulatedTrace; + } + }; + }; + + /** + * Creates a new handler for the given Prolog assignment definition. + * + * @param {PrologTaskDef} info + * @returns {{destroy: Function, processServerHints: Function}} + */ + codeq.createPythonHandler = function (info) { + var problem = info.problem, + jqDescription = $('#description'), + jqEditor = $('#code_editor'), + jqConsole = $('#console'), + jqHints = $('#info'), + editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true, mode: 'python' }), + activityHandler = makeActivityHandler(editor), + terminal = makePythonTerminalHandler(jqConsole, editor, problem.id, activityHandler), + + /** Object. */ + hintDefs = problem.hint, + hintCounter = 0, // for generating unique class-names + hintCleaners = [], + clearHints = function () { + var i; + for (i = hintCleaners.length - 1; i >= 0; i--) { + hintCleaners[i](); + } + hintCleaners.length = 0; + hintCounter = 0; + }, + addMark = function (start, end) { + var posStart = editor.posFromIndex(start), + posEnd = editor.posFromIndex(end), + doc = editor.getDoc(), + mark = doc.markText(posStart, posEnd, {className: 'editor-mark _emark_' + hintCounter}), + result = {start: posStart, end: posEnd, mark: mark, className: '_emark_' + hintCounter}; + hintCleaners.push(function () { mark.clear(); mark = null; doc = null; result.mark = null; }); + hintCounter++; + return result; + }, + hintHandlers = { + 'static': function (type, template, serverHint) { + codeq.log.debug('Processing static hint'); + if (serverHint.args) { + template = template.replace(/\[%=(\w+)%\]/g, function(match, name) { + return serverHint.args[name]; + }); + } + jqHints.append('
' + template + '
'); // TODO: incorporate any serverHint.args + // no hint cleaner here, a static hint remains on the screen + }, + 'popup': function (type, template, serverHint) { + codeq.log.debug('Processing popup hint'); + var mark = addMark(serverHint.start, serverHint.end), // add the mark + jqMark = jqEditor.find('.' + mark.className); +/* jqPopup = null, + onBlur = function () { + codeq.log.debug('Removing popup'); + if (jqPopup) { + jqPopup.off('blur', onBlur); + jqPopup.remove(); + jqPopup = null; + } + }; + + window.jqMark = jqMark; // TODO: DEBUG + + jqMark.on('click', function () { + if (jqPopup) return; + codeq.log.debug('Showing popup'); + var pos = mark.mark.find(), // results in {from: {line: number, ch: number}, to: {line: number, ch: number}} + left = pos.from.ch < pos.to.ch ? pos.from.ch : pos.to.ch, + down = pos.from.line < pos.to.line ? pos.to.line : pos.from.line; + jqPopup = $('
').html(template); + editor.addWidget({line: down, ch: left}, jqPopup[0], true); + jqPopup = jqEditor.find('.editor-popup.' + mark.className); + setTimeout(function () {jqPopup.trigger('focus'); jqPopup.on('click', onBlur);}, 50); // event handlers can be registered only when the DOM elements is part of the document + window.jqPopup = jqPopup; // TODO: DEBUG + }); + + hintCleaners.push(function () { + if (jqPopup) { + jqPopup.off('blur', onBlur); + jqPopup.remove(); + jqPopup = null; + } + if (jqMark) { + jqMark = null; + } + mark = null; + });*/ + + jqMark.popover({content: template, html: true, placement: 'auto bottom', trigger: 'hover focus click', container: 'body'}); + hintCleaners.push(function () { if (jqMark) { jqMark.popover('destroy'); jqMark = null; } }); + + mark.mark.on('', function () {}); + }, + 'dropdown': function (type, template, serverHint) { + codeq.log.debug('Processing dropdown hint'); + var completion = null, // the completion object, created in showHint() + close = function () { + if (completion) { + completion.close(); + completion = null; + } + }; + + if ((editor.listSelections().length > 1) || editor.somethingSelected()) { + // showHint() doesn't work if a selection is activeparts + } + + editor.showHint({ + hint: function () { + var hints = { + list: serverHint.choices, + from: editor.posFromIndex(serverHint.start), + to: editor.posFromIndex(serverHint.end) + }; + completion = editor.state.completionActive; + return hints; + }, + completeOnSingleClick: true, + completeSingle: false + }); + + hintCleaners.push(close); + } + }; + + editor.setValue(info.solution); + $('#title').text(problem.slug); + jqDescription.html(problem.description); + + 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)}); + } + + if (changeObj.text) { + activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': changeObj.text}); + } + }); + + var handler = { + destroy: function () { + jqDescription.empty(); + jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it + jqConsole.empty(); // TODO: the same with the console + jqHints.empty(); // TODO: just make static references to these + jqDescription = null; + jqEditor = null; + jqConsole = null; + jqHints = null; + }, + + /** + * Processes and display appropriately the server hints. + * TODO: sort hints so static and popup hints come first, and a (single) drop-down hint last + * + * @param {ServerHint[]} serverHints an array of hints from the server + */ + processServerHints: function (serverHints) { + var n = serverHints.length, + /** number */ i, + /** ServerHint */ serverHint, + /** HintDefinition */ hintDef, + hintType, hintTemplate, t, fn; + clearHints(); + for (i = 0; i < n; i++) { + serverHint = serverHints[i]; + hintDef = hintDefs[serverHint.id]; + if (!hintDef) { + codeq.log.error('Undefined hint: ' + serverHint.id); + continue; + } + t = typeof hintDef; + if (t === 'object') { + hintType = hintDef.type; + hintTemplate = hintDef.message; + } + else if (t === 'string') { + hintType = 'static'; + hintTemplate = hintDef; + } + else { + codeq.log.error('Unsupported hint definition: ' + t); + continue; + } + + fn = hintHandlers[hintType]; + if (!fn) codeq.log.error('Unsupported hint type: ' + hintType); + else fn(hintType, hintTemplate, serverHint); + } + } + }; + + var jqButtons = $('#block-toolbar button'); +// $(jqButtons.get(0)).on('click', function () { handler.processServerHints([{id:'x_must_be_female'}]); }); +// $(jqButtons.get(1)).on('click', function () { handler.processServerHints([{id:'popup_unknown', start: 20, end: 26}]); }); +// $(jqButtons.get(2)).on('click', function () { handler.processServerHints([{id:'drop_down', start: 20, end: 26, choices:['ena', 'dva', 'tri']}]); }); + + $('#btn_code_run').on('click', function () { + var doc = editor.getDoc(); +// codeq.comms.sendActivity({'typ': 'slv', 'dt': dt, 'qry': }); +// handler.processServerHints([{id:'list_empty'}]); + }); + $('#btn_code_break').on('click', function () { +// handler.processServerHints([{id:'popup_unknown', start: 20, end: 26}]); + }); + $('#btn_code_hint').on('click', function () { +// handler.processServerHints([{id:'drop_down', start: 20, end: 26, choices:['ena', 'dva', 'tri']}]); + jqConsole.echo('?- hint.'); + jqConsole.pause(); + var doc = editor.getDoc(); + codeq.comms.sendHint({ + 'language': 'python', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }).then( + function hintSuccess(data) { + jqConsole.resume(); + if (data.code === 0) + handler.processServerHints(data.hints); + else + jqConsole.error(data.message); + }, + function hintFailed (error) { + jqConsole.resume(); + jqConsole.exception(error); + } + ).done(); + }); + $('#btn_code_test').on('click', function () { + jqConsole.echo('?- test.'); + jqConsole.pause(); + var doc = editor.getDoc(); + codeq.comms.sendTest({ + 'language': 'python', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }).then( + function testSuccess(data) { + jqConsole.resume(); + if (data.code === 0) + handler.processServerHints(data.hints); + else + jqConsole.error(data.message); + }, + function testFailed (error) { + jqConsole.resume(); + jqConsole.exception(error); + } + ).done(); + }); + + return handler; + }; +})(); -- cgit v1.2.1