summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--js/codemirror/python.js358
-rw-r--r--js/python.js331
-rw-r--r--python.html102
3 files changed, 791 insertions, 0 deletions
diff --git a/js/codemirror/python.js b/js/codemirror/python.js
new file mode 100644
index 0000000..e5a0971
--- /dev/null
+++ b/js/codemirror/python.js
@@ -0,0 +1,358 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function wordRegexp(words) {
+ return new RegExp("^((" + words.join(")|(") + "))\\b");
+ }
+
+ var wordOperators = wordRegexp(["and", "or", "not", "is"]);
+ var commonKeywords = ["as", "assert", "break", "class", "continue",
+ "def", "del", "elif", "else", "except", "finally",
+ "for", "from", "global", "if", "import",
+ "lambda", "pass", "raise", "return",
+ "try", "while", "with", "yield", "in"];
+ var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
+ "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
+ "enumerate", "eval", "filter", "float", "format", "frozenset",
+ "getattr", "globals", "hasattr", "hash", "help", "hex", "id",
+ "input", "int", "isinstance", "issubclass", "iter", "len",
+ "list", "locals", "map", "max", "memoryview", "min", "next",
+ "object", "oct", "open", "ord", "pow", "property", "range",
+ "repr", "reversed", "round", "set", "setattr", "slice",
+ "sorted", "staticmethod", "str", "sum", "super", "tuple",
+ "type", "vars", "zip", "__import__", "NotImplemented",
+ "Ellipsis", "__debug__"];
+ var py2 = {builtins: ["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
+ "file", "intern", "long", "raw_input", "reduce", "reload",
+ "unichr", "unicode", "xrange", "False", "True", "None"],
+ keywords: ["exec", "print"]};
+ var py3 = {builtins: ["ascii", "bytes", "exec", "print"],
+ keywords: ["nonlocal", "False", "True", "None", "async", "await"]};
+
+ CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins));
+
+ function top(state) {
+ return state.scopes[state.scopes.length - 1];
+ }
+
+ CodeMirror.defineMode("python", function(conf, parserConf) {
+ var ERRORCLASS = "error";
+
+ var singleDelimiters = parserConf.singleDelimiters || new RegExp("^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]");
+ var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
+ var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
+ var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
+
+ if (parserConf.version && parseInt(parserConf.version, 10) == 3){
+ // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
+ var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!@]");
+ var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*");
+ } else {
+ var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
+ var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
+ }
+
+ var hangingIndent = parserConf.hangingIndent || conf.indentUnit;
+
+ var myKeywords = commonKeywords, myBuiltins = commonBuiltins;
+ if(parserConf.extra_keywords != undefined){
+ myKeywords = myKeywords.concat(parserConf.extra_keywords);
+ }
+ if(parserConf.extra_builtins != undefined){
+ myBuiltins = myBuiltins.concat(parserConf.extra_builtins);
+ }
+ if (parserConf.version && parseInt(parserConf.version, 10) == 3) {
+ myKeywords = myKeywords.concat(py3.keywords);
+ myBuiltins = myBuiltins.concat(py3.builtins);
+ var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
+ } else {
+ myKeywords = myKeywords.concat(py2.keywords);
+ myBuiltins = myBuiltins.concat(py2.builtins);
+ var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
+ }
+ var keywords = wordRegexp(myKeywords);
+ var builtins = wordRegexp(myBuiltins);
+
+ // tokenizers
+ function tokenBase(stream, state) {
+ // Handle scope changes
+ if (stream.sol() && top(state).type == "py") {
+ var scopeOffset = top(state).offset;
+ if (stream.eatSpace()) {
+ var lineOffset = stream.indentation();
+ if (lineOffset > scopeOffset)
+ pushScope(stream, state, "py");
+ else if (lineOffset < scopeOffset && dedent(stream, state))
+ state.errorToken = true;
+ return null;
+ } else {
+ var style = tokenBaseInner(stream, state);
+ if (scopeOffset > 0 && dedent(stream, state))
+ style += " " + ERRORCLASS;
+ return style;
+ }
+ }
+ return tokenBaseInner(stream, state);
+ }
+
+ function tokenBaseInner(stream, state) {
+ if (stream.eatSpace()) return null;
+
+ var ch = stream.peek();
+
+ // Handle Comments
+ if (ch == "#") {
+ stream.skipToEnd();
+ return "comment";
+ }
+
+ // Handle Number Literals
+ if (stream.match(/^[0-9\.]/, false)) {
+ var floatLiteral = false;
+ // Floats
+ if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
+ if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
+ if (stream.match(/^\.\d+/)) { floatLiteral = true; }
+ if (floatLiteral) {
+ // Float literals may be "imaginary"
+ stream.eat(/J/i);
+ return "number";
+ }
+ // Integers
+ var intLiteral = false;
+ // Hex
+ if (stream.match(/^0x[0-9a-f]+/i)) intLiteral = true;
+ // Binary
+ if (stream.match(/^0b[01]+/i)) intLiteral = true;
+ // Octal
+ if (stream.match(/^0o[0-7]+/i)) intLiteral = true;
+ // Decimal
+ if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
+ // Decimal literals may be "imaginary"
+ stream.eat(/J/i);
+ // TODO - Can you have imaginary longs?
+ intLiteral = true;
+ }
+ // Zero by itself with no other piece of number.
+ if (stream.match(/^0(?![\dx])/i)) intLiteral = true;
+ if (intLiteral) {
+ // Integer literals may be "long"
+ stream.eat(/L/i);
+ return "number";
+ }
+ }
+
+ // Handle Strings
+ if (stream.match(stringPrefixes)) {
+ state.tokenize = tokenStringFactory(stream.current());
+ return state.tokenize(stream, state);
+ }
+
+ // Handle operators and Delimiters
+ if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters))
+ return null;
+
+ if (stream.match(doubleOperators) || stream.match(singleOperators))
+ return "operator";
+
+ if (stream.match(singleDelimiters))
+ return null;
+
+ if (stream.match(keywords) || stream.match(wordOperators))
+ return "keyword";
+
+ if (stream.match(builtins))
+ return "builtin";
+
+ if (stream.match(/^(self|cls)\b/))
+ return "variable-2";
+
+ if (stream.match(identifiers)) {
+ if (state.lastToken == "def" || state.lastToken == "class")
+ return "def";
+ return "variable";
+ }
+
+ // Handle non-detected items
+ stream.next();
+ return ERRORCLASS;
+ }
+
+ function tokenStringFactory(delimiter) {
+ while ("rub".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)
+ delimiter = delimiter.substr(1);
+
+ var singleline = delimiter.length == 1;
+ var OUTCLASS = "string";
+
+ function tokenString(stream, state) {
+ while (!stream.eol()) {
+ stream.eatWhile(/[^'"\\]/);
+ if (stream.eat("\\")) {
+ stream.next();
+ if (singleline && stream.eol())
+ return OUTCLASS;
+ } else if (stream.match(delimiter)) {
+ state.tokenize = tokenBase;
+ return OUTCLASS;
+ } else {
+ stream.eat(/['"]/);
+ }
+ }
+ if (singleline) {
+ if (parserConf.singleLineStringErrors)
+ return ERRORCLASS;
+ else
+ state.tokenize = tokenBase;
+ }
+ return OUTCLASS;
+ }
+ tokenString.isString = true;
+ return tokenString;
+ }
+
+ function pushScope(stream, state, type) {
+ var offset = 0, align = null;
+ if (type == "py") {
+ while (top(state).type != "py")
+ state.scopes.pop();
+ }
+ offset = top(state).offset + (type == "py" ? conf.indentUnit : hangingIndent);
+ if (type != "py" && !stream.match(/^(\s|#.*)*$/, false))
+ align = stream.column() + 1;
+ state.scopes.push({offset: offset, type: type, align: align});
+ }
+
+ function dedent(stream, state) {
+ var indented = stream.indentation();
+ while (top(state).offset > indented) {
+ if (top(state).type != "py") return true;
+ state.scopes.pop();
+ }
+ return top(state).offset != indented;
+ }
+
+ function tokenLexer(stream, state) {
+ var style = state.tokenize(stream, state);
+ var current = stream.current();
+
+ // Handle '.' connected identifiers
+ if (current == ".") {
+ style = stream.match(identifiers, false) ? null : ERRORCLASS;
+ if (style == null && state.lastStyle == "meta") {
+ // Apply 'meta' style to '.' connected identifiers when
+ // appropriate.
+ style = "meta";
+ }
+ return style;
+ }
+
+ // Handle decorators
+ if (current == "@"){
+ if(parserConf.version && parseInt(parserConf.version, 10) == 3){
+ return stream.match(identifiers, false) ? "meta" : "operator";
+ } else {
+ return stream.match(identifiers, false) ? "meta" : ERRORCLASS;
+ }
+ }
+
+ if ((style == "variable" || style == "builtin")
+ && state.lastStyle == "meta")
+ style = "meta";
+
+ // Handle scope changes.
+ if (current == "pass" || current == "return")
+ state.dedent += 1;
+
+ if (current == "lambda") state.lambda = true;
+ if (current == ":" && !state.lambda && top(state).type == "py")
+ pushScope(stream, state, "py");
+
+ var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1;
+ if (delimiter_index != -1)
+ pushScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
+
+ delimiter_index = "])}".indexOf(current);
+ if (delimiter_index != -1) {
+ if (top(state).type == current) state.scopes.pop();
+ else return ERRORCLASS;
+ }
+ if (state.dedent > 0 && stream.eol() && top(state).type == "py") {
+ if (state.scopes.length > 1) state.scopes.pop();
+ state.dedent -= 1;
+ }
+
+ return style;
+ }
+
+ var external = {
+ startState: function(basecolumn) {
+ return {
+ tokenize: tokenBase,
+ scopes: [{offset: basecolumn || 0, type: "py", align: null}],
+ lastStyle: null,
+ lastToken: null,
+ lambda: false,
+ dedent: 0
+ };
+ },
+
+ token: function(stream, state) {
+ var addErr = state.errorToken;
+ if (addErr) state.errorToken = false;
+ var style = tokenLexer(stream, state);
+
+ state.lastStyle = style;
+
+ var current = stream.current();
+ if (current && style)
+ state.lastToken = current;
+
+ if (stream.eol() && state.lambda)
+ state.lambda = false;
+ return addErr ? style + " " + ERRORCLASS : style;
+ },
+
+ indent: function(state, textAfter) {
+ if (state.tokenize != tokenBase)
+ return state.tokenize.isString ? CodeMirror.Pass : 0;
+
+ var scope = top(state);
+ var closing = textAfter && textAfter.charAt(0) == scope.type;
+ if (scope.align != null)
+ return scope.align - (closing ? 1 : 0);
+ else if (closing && state.scopes.length > 1)
+ return state.scopes[state.scopes.length - 2].offset;
+ else
+ return scope.offset;
+ },
+
+ closeBrackets: {triples: "'\""},
+ lineComment: "#",
+ fold: "indent"
+ };
+ return external;
+ });
+
+ CodeMirror.defineMIME("text/x-python", "python");
+
+ var words = function(str) { return str.split(" "); };
+
+ CodeMirror.defineMIME("text/x-cython", {
+ name: "python",
+ extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+
+ "extern gil include nogil property public"+
+ "readonly struct union DEF IF ELIF ELSE")
+ });
+
+});
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;
+ };
+})();
diff --git a/python.html b/python.html
new file mode 100644
index 0000000..4e3b788
--- /dev/null
+++ b/python.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, target-densitydpi=device-dpi">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+ <meta name="format-detection" content="telephone=no">
+ <link rel="stylesheet" href="css/jquery/jquery.terminal.css" type="text/css">
+ <!-- CodeMirror -->
+ <link rel="stylesheet" href="css/codemirror/codemirror.css" type="text/css">
+ <link rel="stylesheet" href="css/codemirror/show-hint.css" type="text/css">
+ <!-- Bootstrap -->
+ <link href="css/bootstrap.min.css" rel="stylesheet">
+ <link href="css/bootstrap-theme.min.css" rel="stylesheet">
+ <!-- App -->
+ <link rel="stylesheet" href="css/codeq.css" type="text/css">
+ </head>
+ <body>
+
+ <div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="container-fluid">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#" title="Intelligent tutor system">CodeQ</a>
+ <a class="navbar-brand" id="title"></a>
+ </div>
+ <div class="collapse navbar-collapse">
+ <ul class="nav navbar-nav navbar-right">
+ <p class="navbar-text">Signed in as Franc Jožef</p>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">
+ <span class="glyphicon glyphicon glyphicon-user"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Profile</a></li>
+ <li><a href="#">Login or Signup</a></li>
+ <li><a href="#">Logout</a></li>
+ </ul>
+ </li>
+ <li><a href="#"><span class="glyphicon glyphicon glyphicon-wrench" aria-hidden="true"></span></a></li>
+ </ul>
+ </div><!--/.nav-collapse -->
+
+ </div>
+ </div>
+
+ <div class="container-fluid" id="gui">
+ <div class="row" id="block-row">
+ <div class="col-lg-3 col-md-6 col-sm-12 block">
+ <div id="description"></div>
+ <div class="block-label">Instructions</div>
+ </div>
+ <div class="col-lg-3 col-md-6 col-sm-12 block">
+ <nav class="navbar navbar-default" id="block-toolbar">
+ <div class="container-fluid">
+ <button type="button" class="btn btn-default navbar-btn" id="btn_code_run">Run</button>
+ <button type="button" class="btn btn-default navbar-btn" id="btn_code_break">Break</button>
+ <button type="button" class="btn btn-default navbar-btn" id="btn_code_hint">Hint</button>
+ <button type="button" class="btn btn-default navbar-btn" id="btn_code_test">Test</button>
+ </div>
+ </nav>
+ <div id="code_editor"></div>
+ <div class="block-label">Code</div>
+ </div>
+ <div class="col-lg-3 col-md-6 col-sm-12 block">
+ <div id="console"></div>
+ <div class="block-label">Console</div>
+ </div>
+ <div class="col-lg-3 col-md-6 col-sm-12 block">
+ <div id="info"></div>
+ <div class="block-label">Hints</div>
+ </div>
+ </div><!--/row-->
+ </div><!--container-->
+
+ <!-- jQuery stuff -->
+ <script src="js/jquery/jquery-1.11.3.js"></script>
+ <script src="js/jquery/jquery.terminal-0.8.8.js"></script>
+ <!--script src="js/jquery/jquery.console.js"></script>
+ <!-- Q promise library -->
+ <script src="js/q.js"></script>
+ <!-- Bootstrap -->
+ <script src="js/bootstrap/bootstrap.min.js"></script>
+ <!-- Cordova/PhoneGap -->
+ <script type="text/javascript" src="cordova.js"></script>
+ <!-- CodeMirror stuff -->
+ <script src="js/codemirror/codemirror.js"></script>
+ <script src="js/codemirror/matchbrackets.js"></script>
+ <script src="js/codemirror/python.js"></script>
+ <script src="js/codemirror/show-hint.js"></script>
+ <!-- codeq app -->
+ <script src="js/codeq.js"></script>
+ <script src="js/codeq/comms.js"></script>
+ <script src="js/def_parser.js"></script>
+ <script src="js/python.js"></script>
+ </body>
+</html>