summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/codeq/comms.js4
-rw-r--r--js/codeq/console.js122
-rw-r--r--js/prolog.js225
-rw-r--r--js/python.js87
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;
};
})();