summaryrefslogtreecommitdiff
path: root/js/codeq/python.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/codeq/python.js')
-rw-r--r--js/codeq/python.js393
1 files changed, 393 insertions, 0 deletions
diff --git a/js/codeq/python.js b/js/codeq/python.js
new file mode 100644
index 0000000..868055c
--- /dev/null
+++ b/js/codeq/python.js
@@ -0,0 +1,393 @@
+/**
+ * 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 the actual (sub)state machine
+ stateNameTag = 'stateName';//a tag for data which is added to some html elements
+
+ //get the divs which are the main elements being change on transitions between states in the sub-state machine
+ var divs = {};
+ divs['description'] = $('#description_outer_div');
+ divs['code'] = $('#code_editor_outer_div');
+ divs['console'] = $('#console_outer_div');
+ divs['info'] = $('#info_outer_div');
+
+ //these tags connect the div to their respective state in the sub-state machine
+ divs['description'].data(stateNameTag, 'description');
+ divs['code'].data(stateNameTag, 'code');
+ divs['console'].data(stateNameTag, 'console');
+ divs['info'].data(stateNameTag, 'info');
+
+ var 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)
+ /**
+ * the general function which should be called on the mousedown event to trigger the transition between states
+ */
+ mouseDownEventFunction = function () {
+ subScreens.transition($(this).data(stateNameTag));
+ },
+ /**
+ * removes the above function from the divs
+ */
+ removeListenersFromDivs = function () {
+ $.each(divs, function (i, value) {
+ value.off(transitionEventName, mouseDownEventFunction);
+ });
+ },
+ /**
+ * removes the 'block' class from all divs we need to change when we change the current state
+ */
+ removeBlockClassesFromDivs = function () {
+ $.each(divs, function (i, value) {
+ value.removeClass('block');
+ });
+ },
+ /**
+ * removes all those special classes from the divs, which are set when a new state is entered and it also adds the 'block' class back
+ */
+ removeChangedClassesFromDivs = function () {
+ $.each(divs, function (i, value) {
+ value.removeClass('block-focus block-less-width block-less-height block-less-everything').addClass('block');//these class names were chosen for the view where the screen is partitioned in 4 quarters (2 by 2)
+ });
+ },
+ /**
+ *
+ * @param current the key of the state ('description','code','hint','console'), which is currently active, because it must not have a transition set
+ */
+ setTransitionsBetweenDivs = function (current) {
+ $.each(divs, function (key, value) {
+ if (current !== key) value.on(transitionEventName, mouseDownEventFunction);
+ });
+ },
+ /**
+ *
+ * @param divsWithClasses a object where we have the keys of the divs object paired with the class name we want to assign to them e.g. :
+ * {
+ 'description': 'block-focus',
+ 'code': 'block-less-width',
+ 'console': 'block-less-height',
+ 'info': 'block-less-everything'
+ }
+ */
+ addClassesToDivs = function (divsWithClasses) {
+ $.each(divsWithClasses, function (divName, className) {
+ divs[divName].addClass(className);
+ });
+ },
+ /**
+ * the buttons (hint and test) will have to 'eat' the mousedown event to prevent some weird transitions.
+ * Because a click on them won't trigger the transition to the code part, but will transition to the hints part.
+ *
+ * @param event
+ */
+ mouseDownEventIgnoreFun = function(event){
+ event.stopPropagation();
+ },
+ /**
+ * The transition from the buttons to the hints screen will be triggered with this function
+ */
+ clickListenerTransitionFun = function(){
+ subScreens.transition(divs['info'].data(stateNameTag));
+ },
+ /**
+ * add the above function to the buttons
+ */
+ addClickListenerTranstions = function(){
+ $('#btn_code_hint').on('click',clickListenerTransitionFun);
+ $('#btn_code_test').on('click',clickListenerTransitionFun);
+
+ $('#btn_code_hint').on(transitionEventName,mouseDownEventIgnoreFun);
+ $('#btn_code_test').on(transitionEventName,mouseDownEventIgnoreFun);
+ },
+ /**
+ * and a function to remove it
+ */
+ removeClickListenerTransition = function(){
+ $('#btn_code_hint').off('click',clickListenerTransitionFun);
+ $('#btn_code_test').off('click',clickListenerTransitionFun);
+
+ $('#btn_code_hint').off(transitionEventName,mouseDownEventIgnoreFun);
+ $('#btn_code_test').off(transitionEventName,mouseDownEventIgnoreFun);
+ },
+ substates = {
+ 'description': {
+ 'enter': function () {
+ removeBlockClassesFromDivs();
+ addClassesToDivs({
+ 'description': 'block-focus',
+ 'code': 'block-less-width',
+ 'console': 'block-less-height',
+ 'info': 'block-less-everything'
+ });
+ setTransitionsBetweenDivs('description');
+
+ //and set up the buttons
+ addClickListenerTranstions();
+ },
+ 'exit': function () {
+ removeChangedClassesFromDivs();
+ removeListenersFromDivs();
+ removeClickListenerTransition();
+ }
+ },
+ 'code': {
+ 'enter': function () {
+ removeBlockClassesFromDivs();
+ addClassesToDivs({
+ 'description': 'block-less-width',
+ 'code': 'block-focus',
+ 'console': 'block-less-everything',
+ 'info': 'block-less-height'
+ });
+ setTransitionsBetweenDivs('code');
+
+ //and set up the buttons
+ addClickListenerTranstions();
+ },
+ 'exit': function () {
+ removeChangedClassesFromDivs();
+ removeListenersFromDivs();
+ removeClickListenerTransition();
+ }
+ },
+ 'info': {
+ 'enter': function () {
+ removeBlockClassesFromDivs();
+ addClassesToDivs({
+ 'description': 'block-less-everything',
+ 'code': 'block-less-height',
+ 'console': 'block-less-width',
+ 'info': 'block-focus'
+ });
+ setTransitionsBetweenDivs('info');
+
+ //and set up the buttons
+ addClickListenerTranstions();
+ },
+ 'exit': function () {
+ removeChangedClassesFromDivs();
+ removeListenersFromDivs();
+ removeClickListenerTransition();
+ }
+ },
+ 'console': {
+ 'enter': function () {
+ removeBlockClassesFromDivs();
+ addClassesToDivs({
+ 'description': 'block-less-height',
+ 'code': 'block-less-everything',
+ 'console': 'block-focus',
+ 'info': 'block-less-width'
+ });
+ setTransitionsBetweenDivs('console');
+
+ //and set up the buttons
+ addClickListenerTranstions();
+ },
+ 'exit': function () {
+ removeChangedClassesFromDivs();
+ removeListenersFromDivs();
+ removeClickListenerTransition();
+ }
+ }
+ };
+ var pythonHandler;
+ codeq.globalStateMachine.register('python', {
+ 'enter': function (data) {
+ $('#disabled').css('display', '');
+ $('#screen_prolog').css('display', '');//we have to show the screen now so the code editor shopws its initial values correctly
+ pythonHandler = createPythonHandler(data.data);//codeq.createProgrammingLanguageHandler('prolog',data.data,codeq.makePythonTerminalHandler);
+ subScreens = codeq.makeStateMachine(substates);
+ subScreens.transition(divs['description'].data(stateNameTag));
+ Q.delay(100).then(function(){
+ $('div.col-lg-3.col-md-6.col-sm-12').addClass('transition');
+ }).done();
+ $('#disabled').css('display', 'none');
+ },
+ 'exit': function () {
+ $('#screen_prolog').css('display', 'none');
+ $('div.col-lg-3.col-md-6.col-sm-12').removeClass('transition');
+ pythonHandler.destroy();
+ subScreens.destroy();
+ }
+ });
+
+ // 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'
+ }),
+ 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) {
+ return codeq.comms.sendPush({
+ 'text': text + '\n'
+ }).then(tcs, tcf);
+ };
+
+ codeq.comms.on('terminal_output', function (data) {
+ var text = data.text;
+ terminal.append(text, 'output');
+ lines = text.split('\n');
+ 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;
+ }
+ };
+ };
+
+
+
+ /**
+ * Creates a new handler for the given Prolog assignment definition.
+ *
+ * @param {PrologTaskDef} info
+ * @returns {{destroy: Function, processServerHints: Function}}
+ */
+ var 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, problem.id),
+ terminal = makePythonTerminalHandler(jqConsole, editor, problem.id, activityHandler),
+ hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint);
+
+ 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});
+ }
+ });
+
+ $('#btn_code_hint').on('click', function () {
+ var doc = editor.getDoc();
+ codeq.comms.sendHint({
+ 'language': 'python',
+ 'program': editor.getDoc().getValue(),
+ 'problem_id': problem.id
+ }).then(
+ function hintSuccess(data) {
+ if (data.code === 0)
+ hinter.handle(data.hints);
+ else
+ terminal.append('error: ' + data.message);
+ },
+ function hintFailed (error) {
+ terminal.append('exception: ' + error);
+ }
+ ).done();
+ });
+ $('#btn_code_test').on('click', function () {
+ var doc = editor.getDoc();
+ codeq.comms.sendTest({
+ 'language': 'python',
+ 'program': editor.getDoc().getValue(),
+ 'problem_id': problem.id
+ }).then(
+ function testSuccess(data) {
+ if (data.code === 0)
+ hinter.handle(data.hints);
+ else
+ terminal.append('error: ' + data.message);
+ },
+ function testFailed (error) {
+ terminal.append('exception: ' + error);
+ }
+ ).done();
+ });
+
+ // TODO first line of interpreter output is buffered without this, why?
+ codeq.comms.sendPush({
+ 'text': ''
+ });
+
+ return {
+ destroy: function () {
+ $('#btn_code_hint').off('click');
+ $('#btn_code_test').off('click');
+ editor.off('change');
+ hinter.destroy();
+ terminal.destroy();
+ 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
+ jqDescription = null;
+ jqEditor = null;
+ jqConsole = null;
+ jqHints = null;
+ }
+ };
+ };
+})();