diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/codeq/comms.js | 4 | ||||
-rw-r--r-- | js/codeq/console.js | 122 | ||||
-rw-r--r-- | js/prolog.js | 225 | ||||
-rw-r--r-- | js/python.js | 87 |
4 files changed, 212 insertions, 226 deletions
diff --git a/js/codeq/comms.js b/js/codeq/comms.js index de6f918..db9b7b5 100644 --- a/js/codeq/comms.js +++ b/js/codeq/comms.js @@ -86,13 +86,13 @@ event = m.event; if (typeof event !== 'string') { - codeq.log.warn('Incoming message without a TID and with no event name, dropping it on the floor: ' + data); + codeq.log.info('Incoming message without a TID and with no event name, dropping it on the floor: ' + data); return; } handlers = requestHandlers[event]; if (!handlers) { - codeq.log.warn('Incoming event message cannot be handled: no handler registered for ' + event); + codeq.log.info('Incoming event message cannot be handled: no handler registered for ' + event); return; } diff --git a/js/codeq/console.js b/js/codeq/console.js index ddf4ce6..99c1550 100644 --- a/js/codeq/console.js +++ b/js/codeq/console.js @@ -176,7 +176,7 @@ jq1, jq2, jqCursor; if (!jqLine) { - jqLine = $('<div class="' + lineDescriptor.className + '"></div>'); + jqLine = $('<pre class="cq-con-line ' + lineDescriptor.className + '"></pre>'); if (lineDescriptor.row == 0) { jqContent.prepend(jqLine); } @@ -263,11 +263,12 @@ handler = { 'setLineBuffered': function () { lineBuffered = true; }, 'setNotBuffered': function () { lineBuffered = false; }, - 'onInput': false, // the caller should assign a function here, that takes input as parameter + 'onInput': false, // the caller should assign a function here, that takes input as parameter, and optionally returns a promise which is then waited to be resolved before further input is accepted 'onKeypress': false, // same, but it takes a keycode, and returns a keycode 'inputEnable': function () { inputDisabled = false; }, 'inputDisable': function () { inputDisabled = true; }, 'keyMaps': {}, // will be filled in later + 'leftmostCol': 0, // the column from where editing is possible; it is not possible to delete or change a character to the left of this column 'reflow': function () { var startRow, endRow, i, lineDescriptor; @@ -463,7 +464,8 @@ }, 'moveLeft': function () { - if (currentCol > 0) { + var leftmost = typeof this.leftmostCol === 'number' && this.leftmostCol || 0; + if (currentCol > leftmost) { currentCol--; renderLine(lines[currentRow]); } @@ -479,8 +481,9 @@ 'deleteCharacterLeft': function () { var lineDescriptor = lines[currentRow], - content = lineDescriptor.content; - if ((currentCol > 0) && (currentCol <= content.length)) { + content = lineDescriptor.content, + leftmost = typeof this.leftmostCol === 'number' && this.leftmostCol || 0; + if ((currentCol > leftmost) && (currentCol <= content.length)) { currentCol--; lineDescriptor.content = content.substring(0, currentCol) + content.substring(currentCol + 1); renderLine(lineDescriptor); @@ -547,65 +550,88 @@ } }, + internallyDisabled = false, + handleInput = function (chars) { - var cookedChars = chars, // default - startingRow = currentRow, - dh, i; + var isPastedFromClipboard = clipboardPasteInProgress || !aKeyIsDown, + newLines = chars.split('\n'), + N = newLines.length, + lastLineIndex = N - 1, + i, promise; - if (inputDisabled) { + if (inputDisabled || internallyDisabled) { // flatly ignore clipboardPasteInProgress = false; return; } - // first cook the input - if (typeof handler.onKeypress === 'function') { - try { - cookedChars = handler.onKeypress(chars, clipboardPasteInProgress || !aKeyIsDown); - } - catch (e) { - // TODO: log error - } - } + internallyDisabled = true; + promise = Q(); - if (!cookedChars) { - // nothing to append - clipboardPasteInProgress = false; - return; - } + for (i = 0; i < N; i++) { + promise = promise + .then((function (newLine, lineIndex) {return function () { + var cookedChars = lineIndex < lastLineIndex ? newLine + '\n' : newLine, // default + thisRow = currentRow, + leftmost = typeof handler.leftmostCol === 'number' && handler.leftmostCol || 0, + dh; - // now serve the cooking - handler.insertAtCursor(cookedChars, 'input'); // append what we have to the display - if (typeof handler.onInput === 'function') { // now tell the owner what we've done - if (lineBuffered) { - if (currentRow !== startingRow) { - // in line-buffered mode emit each line separately, except for the last (current) line - for (i = startingRow; i < lines.length; i++) { + // first cook the input + if (typeof handler.onKeypress === 'function') { + // only invoke the handler if the newLine is not an empty string, otherwise cookedChars = '' try { - handler.onInput(lines[i].content); + cookedChars = cookedChars && handler.onKeypress(cookedChars, isPastedFromClipboard); } catch (e) { - // TODO: log error + codeq.log.error('Error during invocation of terminal onKeypress: ' + e, e); } } - } - } - else { - try { - handler.onInput(cookedChars); - } - catch (e) { - // TODO: log error - } - } + + if (!cookedChars) { + // nothing to append + return; + } + + // now serve the cooking + handler.insertAtCursor(cookedChars, 'input'); // append what we have to the display + // scroll to bottom on input + dh = jqContent.height() - jqElt.height(); + if (dh > 0) + jqElt.scrollTop(dh); + + if (typeof handler.onInput === 'function') { // now tell the owner what we've done + if (lineBuffered) { + if (thisRow < currentRow) { + // in line-buffered mode emit each line separately, except for the last (current) line + try { + return handler.onInput(lines[thisRow].content.substring(leftmost)); + } + catch (e) { + codeq.log.error('Error while invoking terminal onInput: ' + e, e); + } + } + } + else if (cookedChars) { + try { + return handler.onInput(cookedChars); + } + catch (e) { + codeq.log.error('Error while invoking terminal onInput: ' + e, e); + } + } + } + + };})(newLines[i], i)) + .fail(function (e) { + codeq.log.error('Error in handleInput loop: ' + e, e); + }); } - clipboardPasteInProgress = false; + promise.fin(function () { + internallyDisabled = false; + }).done(); - // scroll to bottom on input - dh = jqContent.height() - jqElt.height(); - if (dh > 0) - jqElt.scrollTop(dh); + clipboardPasteInProgress = false; }, clipboardPasteInProgress = false, // whether the previous keydown was a CTRL+V or shift+insert @@ -646,7 +672,7 @@ jqInput.on('keydown', function (evt) { var modifiers = ['' + (0 + evt.shiftKey), '' + (0 + (evt.ctrlKey || evt.metaKey)), '' + (0 + evt.altKey)].join(''), handlersForModifiers = handler.keyMaps[modifiers], - acceptKeydown = !inputDisabled, // the default value + acceptKeydown = !(inputDisabled || internallyDisabled), // the default value eventInfo = processKeyboardEvent(evt), handlerForKeydown = handlersForModifiers && handlersForModifiers[eventInfo.code]; diff --git a/js/prolog.js b/js/prolog.js index 7ea8ce3..2868830 100644 --- a/js/prolog.js +++ b/js/prolog.js @@ -8,80 +8,82 @@ var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var promptMode = true, // default: query composition; alternative: query result browsing + terminal = codeq.makeConsole(jqConsole, { + 'greeting': 'CodeQ Prolog terminal proxy' + }), tcs = function terminalCommandSuccess (data) { var t, lines, i; - terminal.resume(); if (data.code === 0) { t = data.terminal; - lines = t.messages; - for (i = 0; i < lines.length; i++) { - terminal.echo(lines[i]); - } + terminal.append(t.messages.join('\n'), 'output'); promptMode = !t.have_more; } else { - terminal.error(data.message); + terminal.append(data.message, 'error'); promptMode = true; } + if (promptMode) { + terminal.setLineBuffered(); + terminal.append('.\n?- ', 'output'); + } }, tcf = function terminalCommandFailed (error) { - terminal.resume(); - terminal.exception(error); promptMode = true; - }, - terminal = jqConsole.terminal(function (command, terminal) { - if (promptMode) { - terminal.pause(); - codeq.comms.sendQuery({ + terminal.setLineBuffered(); + terminal.append(error + '\n', 'error'); + terminal.append('?- ', 'output'); + }; + + terminal.onKeypress = function (c) { + if (promptMode) return c; // query composition: return the character unchanged + switch (c) { + case ' ': + case ';': + case '\n': + return ';'; // show next answer on space, semicolon or enter + default: + return '.'; // everything else: stop searching for answers + } + }; + + terminal.onInput = function (command) { + if (promptMode) { + promptMode = false; + terminal.setNotBuffered(); + return codeq.comms.sendQuery({ + 'problem_id': problem_id, + 'step': 'run', + 'program': editor.getDoc().getValue(), + 'query': command, + 'trace': activityHandler.addAndPurge({'typ': 'slv', 'qry': command}) + }, problem_id).then(tcs, tcf); + } + else { + terminal.append('\n', 'input'); + if (command == ';') { + // show next answer + return codeq.comms.sendQuery({ 'problem_id': problem_id, - 'step': 'run', - 'program': editor.getDoc().getValue(), - 'query': command, - 'trace': activityHandler.addAndPurge({'typ': 'slv', 'qry': command}) - }, problem_id).then(tcs, tcf).done(); + 'step': 'next', + 'trace': activityHandler.addAndPurge({'typ': 'nxt'}) + }, problem_id).then(tcs, tcf); } else { - // not in prompt mode -- we should never land here, but handle it anyway - codeq.comms.sendQuery({ + // stop searching for answers + return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'end', 'trace': activityHandler.addAndPurge({'typ': 'stp'}) - }, problem_id).then(tcs, tcf).done(); - } - }, { - 'history': true, - 'prompt': '?- ', - 'greetings': 'CodeQ prolog terminal proxy', - 'exit': false, - 'clear': false, - 'keypress': function (event, terminal) { - if (promptMode) return true; - setTimeout(function () { - terminal.echo(''); // send newline after semicolon or full-stop - terminal.pause(); - }, 0); - if ((event.which == 32) || (event.which == 59) || (event.which == 110)) { - // space or semicolon or n -> show next answer - event.which = 59; // semicolon - codeq.comms.sendQuery({ - 'problem_id': problem_id, - 'step': 'next', - 'trace': activityHandler.addAndPurge({'typ': 'nxt'}) - }, problem_id).then(tcs, tcf).done(); - } - else { - // everything else: stop searching for answers - event.which = 46; // full stop - codeq.comms.sendQuery({ - 'problem_id': problem_id, - 'step': 'end', - 'trace': activityHandler.addAndPurge({'typ': 'stp'}) - }, problem_id).then(tcs, tcf).done(); - } + }, problem_id).then(tcs, tcf); } - }); - return {}; + } + }; + + terminal.leftmostCol = 3; + terminal.append('?- ', 'output'); + + return terminal; }; var makeActivityHandler = function (editor, problem_id) { @@ -108,16 +110,11 @@ }; return { - "trace": function (trace) { + 'queueTrace': 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)); + if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds + return this; }, 'flush': flush, 'addAndPurge': function (trace) { @@ -148,21 +145,7 @@ jqHints = $('#info'), editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }), activityHandler = makeActivityHandler(editor, problem.id), -/* controller = jqConsole.console({ - promptLabel: '?- ', - commandValidate: function (line) { - return !!line; - }, - commandHandle: function (line) { - return [{msg:'Not implemented.', className:'console-response'}]; - }, - autofocus: false, - animateScroll: false, - promptHistory: false, - welcomeMessage: 'Prolog REPL.' - }),*/ terminal = makePrologTerminalHandler(jqConsole, editor, problem.id, activityHandler), - /** Object.<string, HintDefinition> */ hintDefs = problem.hint, hintCounter = 0, // for generating unique class-names hintCleaners = [], @@ -296,6 +279,7 @@ var handler = { destroy: function () { + 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 @@ -360,58 +344,57 @@ // $(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(); + terminal.append('hint.\n', 'input'); + terminal.inputDisable(); var doc = editor.getDoc(); codeq.comms.sendHint({ - 'language': 'prolog', - '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(); + 'language': 'prolog', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }) + .then( + function hintSuccess(data) { + if (data.code === 0) + handler.processServerHints(data.hints); + else + terminal.append(data.message + '\n', 'error'); + }, + function hintFailed (error) { + terminal.append(error + '\n', 'error'); + } + ) + .fin(function () { + terminal.inputEnable(); + terminal.append('?- ', 'output'); + }) + .done(); }); $('#btn_code_test').on('click', function () { - jqConsole.echo('?- test.'); - jqConsole.pause(); + terminal.append('test.\n', 'input'); + terminal.inputDisable(); var doc = editor.getDoc(); codeq.comms.sendTest({ - 'language': 'prolog', - '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(); + 'language': 'prolog', + 'program': editor.getDoc().getValue(), + 'problem_id': problem.id + }) + .then( + function testSuccess(data) { + if (data.code === 0) + handler.processServerHints(data.hints); + else + terminal.append(data.message + '\n', 'error'); + }, + function testFailed (error) { + terminal.append(error + '\n', 'error'); + } + ) + .fin(function () { + terminal.inputEnable(); + terminal.append('?- ', 'output'); + }) + .done(); }); return handler; diff --git a/js/python.js b/js/python.js index d16cc72..7d7458e 100644 --- a/js/python.js +++ b/js/python.js @@ -8,51 +8,40 @@ var makePythonTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var terminal = codeq.makeConsole(jqConsole, { - 'greeting': 'CodeQ Python terminal proxy' - }); + '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) { - if (!text) - return; - codeq.comms.sendPush({ + return codeq.comms.sendPush({ 'text': text + '\n' - }).then( - function pushSuccess (data) { - if (data.code !== 0) - terminal.append('error: ' + data.message); - }, - function pushFailed (error) { - terminal.append('exception: '+ error); - } - ).done(); + }).then(tcs, tcf); }; - var interval_id = setInterval(function () { - codeq.comms.sendPull({}).then( - function pullSuccess (data) { - if (data.code === 0) { - t = data.terminal; - if (!t.text) - return; - terminal.append(t.text); - } - else { - terminal.append('error: ' + data.message); - } - }, - function pullFailed (error) { - terminal.append('exception: '+ error); - } - ).done(); - }, 200); + 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) { + var makeActivityHandler = function (editor, problem_id) { 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 + dt = now - lastActivityMillis; lastActivityMillis = now; return dt; }, @@ -62,7 +51,7 @@ var promise; ts = null; if (queue.length === 0) return Q(true); - promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue()); + promise = codeq.comms.sendActivity(queue, editor.getDoc().getValue(), problem_id); queue.length = 0; return promise; }, @@ -72,17 +61,11 @@ }; return { - "trace": function (trace) { + 'queueTrace': 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; @@ -111,10 +94,8 @@ jqConsole = $('#console'), jqHints = $('#info'), editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true, mode: 'python' }), - activityHandler = makeActivityHandler(editor), + activityHandler = makeActivityHandler(editor, problem.id), terminal = makePythonTerminalHandler(jqConsole, editor, problem.id, activityHandler), - - /** Object.<string, HintDefinition> */ hintDefs = problem.hint, hintCounter = 0, // for generating unique class-names hintCleaners = [], @@ -248,6 +229,7 @@ var handler = { destroy: function () { + 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 @@ -312,17 +294,8 @@ // $(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']}]); - terminal.append('>>> hint\n'); var doc = editor.getDoc(); codeq.comms.sendHint({ 'language': 'python', @@ -341,7 +314,6 @@ ).done(); }); $('#btn_code_test').on('click', function () { - terminal.append('>>> test\n'); var doc = editor.getDoc(); codeq.comms.sendTest({ 'language': 'python', @@ -360,6 +332,11 @@ ).done(); }); + // TODO first line of interpreter output is buffered without this, why? + codeq.comms.sendPush({ + 'text': '' + }); + return handler; }; })(); |