From 6bb35042ef6fb19d9af5ee874de9da2816cbc5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Tue, 15 Sep 2015 18:47:26 +0200 Subject: Augmenting the console: - implemented the support of the leftmostCol, the column from which the editing is allowed, - the onInput() handler can return a Promise, which is then waited on to be resolved before further input is allowed, - pasted multi-line content is split into lines, and processed line-by-line as if a single line was pasted multiple times. --- js/codeq/comms.js | 4 +- js/codeq/console.js | 120 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 75 insertions(+), 49 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..41aae40 100644 --- a/js/codeq/console.js +++ b/js/codeq/console.js @@ -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 this.leftmostCol === 'number' && this.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]; -- cgit v1.2.1 From 3c02044761366fe474c7bf8cb5ba7307960c788c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Tue, 15 Sep 2015 20:06:01 +0200 Subject: Prolog handler now uses CodeQ terminal. --- css/codeq/console.css | 7 +++ js/prolog.js | 140 ++++++++++++++++++++++++++++++++++++++++++++++++-- js/python.js | 9 ++-- 3 files changed, 147 insertions(+), 9 deletions(-) diff --git a/css/codeq/console.css b/css/codeq/console.css index 4290eea..7d1f6ae 100644 --- a/css/codeq/console.css +++ b/css/codeq/console.css @@ -15,3 +15,10 @@ color: greenyellow; background: black; } + +.cq-con-text.input { + color: #a6e1ec; +} +.cq-con-text.error { + color: #d9534f; +} \ No newline at end of file diff --git a/js/prolog.js b/js/prolog.js index 7ea8ce3..cc46736 100644 --- a/js/prolog.js +++ b/js/prolog.js @@ -6,7 +6,7 @@ // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; - var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { +/* var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var promptMode = true, // default: query composition; alternative: query result browsing tcs = function terminalCommandSuccess (data) { var t, lines, i; @@ -82,6 +82,85 @@ }); return {}; + };*/ + + 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; + if (data.code === 0) { + t = data.terminal; + terminal.append(t.messages.join('\n') + '\n', 'output'); + promptMode = !t.have_more; + } + else { + terminal.error(data.message + '\n', 'error'); + promptMode = true; + } + if (promptMode) { + terminal.setLineBuffered(); + terminal.append('?- ', 'output'); + } + }, + tcf = function terminalCommandFailed (error) { + promptMode = true; + 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 { + if (command == ';') { + // show next answer + return codeq.comms.sendQuery({ + 'problem_id': problem_id, + 'step': 'next', + 'trace': activityHandler.addAndPurge({'typ': 'nxt'}) + }, problem_id).then(tcs, tcf); + } + else { + // stop searching for answers + return codeq.comms.sendQuery({ + 'problem_id': problem_id, + 'step': 'end', + 'trace': activityHandler.addAndPurge({'typ': 'stp'}) + }, problem_id).then(tcs, tcf); + } + + } + }; + + terminal.leftmostCol = 3; + terminal.append('?- ', 'output'); + + return terminal; }; var makeActivityHandler = function (editor, problem_id) { @@ -296,6 +375,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 @@ -370,7 +450,7 @@ }); $('#btn_code_hint').on('click', function () { // handler.processServerHints([{id:'drop_down', start: 20, end: 26, choices:['ena', 'dva', 'tri']}]); - jqConsole.echo('?- hint.'); +/* jqConsole.echo('?- hint.'); jqConsole.pause(); var doc = editor.getDoc(); codeq.comms.sendHint({ @@ -389,10 +469,35 @@ jqConsole.resume(); jqConsole.exception(error); } - ).done(); + ).done();*/ + + 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) { + 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.echo('?- test.'); jqConsole.pause(); var doc = editor.getDoc(); codeq.comms.sendTest({ @@ -411,7 +516,32 @@ jqConsole.resume(); jqConsole.exception(error); } - ).done(); + ).done();*/ + + 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) { + 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..82423c6 100644 --- a/js/python.js +++ b/js/python.js @@ -48,11 +48,11 @@ 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 +62,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; }, @@ -111,7 +111,7 @@ 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. */ @@ -248,6 +248,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 -- cgit v1.2.1 From e481326ffd15c7dd7f9878cc9f4f94b41021ac1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Tue, 15 Sep 2015 20:19:59 +0200 Subject: Bugfix: leftmostCol was not correctly referenced. --- js/codeq/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/codeq/console.js b/js/codeq/console.js index 41aae40..90b66cd 100644 --- a/js/codeq/console.js +++ b/js/codeq/console.js @@ -573,7 +573,7 @@ .then((function (newLine, lineIndex) {return function () { var cookedChars = lineIndex < lastLineIndex ? newLine + '\n' : newLine, // default thisRow = currentRow, - leftmost = typeof this.leftmostCol === 'number' && this.leftmostCol || 0, + leftmost = typeof handler.leftmostCol === 'number' && handler.leftmostCol || 0, dh; // first cook the input -- cgit v1.2.1 From fc502942d67e9d9854a2ef5a67dcadd831a75b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Tue, 15 Sep 2015 20:20:33 +0200 Subject: A few terminal newline fixes for Prolog. --- js/prolog.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/prolog.js b/js/prolog.js index cc46736..730e740 100644 --- a/js/prolog.js +++ b/js/prolog.js @@ -93,16 +93,16 @@ var t, lines, i; if (data.code === 0) { t = data.terminal; - terminal.append(t.messages.join('\n') + '\n', 'output'); + terminal.append(t.messages.join('\n'), 'output'); promptMode = !t.have_more; } else { - terminal.error(data.message + '\n', 'error'); + terminal.error(data.message, 'error'); promptMode = true; } if (promptMode) { terminal.setLineBuffered(); - terminal.append('?- ', 'output'); + terminal.append('\n?- ', 'output'); } }, tcf = function terminalCommandFailed (error) { @@ -137,6 +137,7 @@ }, problem_id).then(tcs, tcf); } else { + terminal.append('\n'); if (command == ';') { // show next answer return codeq.comms.sendQuery({ -- cgit v1.2.1 From d10866be04d45fc73eddce0ec7005161e69e97a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Wed, 16 Sep 2015 11:37:52 +0200 Subject: Removed the "run" and "break" buttons. Removed commented-out code from prolog and python handlers. --- index.html | 2 - js/prolog.js | 154 ++--------------------------------------------------------- js/python.js | 18 +------ 3 files changed, 4 insertions(+), 170 deletions(-) diff --git a/index.html b/index.html index fb8342a..0f5105d 100644 --- a/index.html +++ b/index.html @@ -89,8 +89,6 @@