summaryrefslogtreecommitdiff
path: root/js/python.js
diff options
context:
space:
mode:
authorTimotej Lazar <timotej.lazar@araneo.org>2015-09-01 16:36:32 +0200
committerTimotej Lazar <timotej.lazar@araneo.org>2015-09-01 16:36:32 +0200
commit5c4f726e7dc79735376fde9514768d8b7c9c049a (patch)
treea762e71c4d50c675f9c7208d05ab86d04e56849d /js/python.js
parent52d9ebc1b0d7b46d19cf1a59b39c52f80a3285d7 (diff)
Add a webpage for Python problems
Diffstat (limited to 'js/python.js')
-rw-r--r--js/python.js331
1 files changed, 331 insertions, 0 deletions
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.<string, HintDefinition> */
+ 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('<div class="hint-static">' + template + '</div>'); // 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 = $('<div style="position: absolute;" class="editor-popup ' + mark.className + '"></div>').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;
+ };
+})();