From acad5db74baf202e26cb2ba2e8f32c3d6ae0ef63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 17 Sep 2015 18:13:06 +0200 Subject: Refactored hint handling into a separate module. --- js/codeq/hint.js | 149 ++++++++++++++++++++++++++++++++++++++++ js/prolog.js | 199 +++++------------------------------------------------ js/python.js | 203 +++++-------------------------------------------------- 3 files changed, 183 insertions(+), 368 deletions(-) create mode 100644 js/codeq/hint.js (limited to 'js') diff --git a/js/codeq/hint.js b/js/codeq/hint.js new file mode 100644 index 0000000..ffae638 --- /dev/null +++ b/js/codeq/hint.js @@ -0,0 +1,149 @@ +/** + * Creates a hint handler, displaying hints inside the provided jqHints
. + */ + +(function () { + // constants + var firstCharacterPos = {'line': 0, 'ch': 0}, + sel_no_scroll = {'scroll': false}; + + codeq.makeHinter = function (jqHints, jqEditor, editor, hintDefs) { + var 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; + }, + processTemplate = function (template, args) { + if (!args) + return template; + return template.replace(/\[%=(\w+)%\]/g, function(match, name) { + return args[name]; + }); + }, + typeHandlers = { + 'static': function (type, template, serverHint) { + codeq.log.debug('Processing static hint'); + var message = processTemplate(template, serverHint.args); + jqHints.append('
' + message + '
'); + // no hint cleaner here, a static hint remains on the screen + }, + + 'popup': function (type, template, serverHint) { + codeq.log.debug('Processing popup hint'); + var message = processTemplate(template, serverHint.args), + mark = addMark(serverHint.start, serverHint.end), // add the mark + jqMark = jqEditor.find('.' + mark.className); + + jqMark.popover({'content': message, '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.setSelection(firstCharacterPos, firstCharacterPos, sel_no_scroll); // deselect anything + } + + 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); + } + }; + + return { + /** + * 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 + */ + 'handle': function (serverHints) { + var n = serverHints.length, + /** number */ i, + /** ServerHint */ serverHint, + /** HintDefinition */ hintDef, + hintType, hintTemplate, t, fn, indices; + clearHints(); + for (i = 0; i < n; i++) { + serverHint = serverHints[i]; + hintDef = hintDefs[serverHint.id]; + if (serverHint.indices) { + indices = serverHint.indices + for (i = 0; i < indices.length; i++) { + hintDef = hintDef[indices[i]]; + if (!hintDef) + break; + } + } + if (!hintDef) { + codeq.log.error('Undefined hint ' + serverHint.id + ' with indices ' + serverHint.indices); + 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 = typeHandlers[hintType]; + if (!fn) codeq.log.error('Unsupported hint type: ' + hintType); + else fn(hintType, hintTemplate, serverHint); + } + }, + + 'destroy': function () { + clearHints(); + jqHints.empty(); + jqHints = null; + jqEditor = null; + editor = null; + } + }; + }; +})(); \ No newline at end of file diff --git a/js/prolog.js b/js/prolog.js index 085860c..1c811ed 100644 --- a/js/prolog.js +++ b/js/prolog.js @@ -149,119 +149,7 @@ editor = CodeMirror(jqEditor[0], { cursorHeight: 0.85, lineNumbers: true, matchBrackets: true }), activityHandler = makeActivityHandler(editor, problem.id), terminal = makePrologTerminalHandler(jqConsole, editor, problem.id, activityHandler), - 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; - }, - processTemplate = function (template, args) { - if (!args) - return template; - return template.replace(/\[%=(\w+)%\]/g, function(match, name) { - return args[name]; - }); - }, - hintHandlers = { - 'static': function (type, template, serverHint) { - codeq.log.debug('Processing static hint'); - var message = processTemplate(template, serverHint.args); - jqHints.append('
' + message + '
'); - // no hint cleaner here, a static hint remains on the screen - }, - 'popup': function (type, template, serverHint) { - codeq.log.debug('Processing popup hint'); - var message = processTemplate(template, serverHint.args), - 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 = $('
').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: message, 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); - } - }; + hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint); editor.setValue(info.solution); $('#title').text(problem.slug); @@ -280,73 +168,6 @@ } }); - 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 - 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 (serverHint.indices) { - indices = serverHint.indices - for (i = 0; i < indices.length; i++) { - hintDef = hintDef[indices[i]]; - if (!hintDef) - break; - } - } - if (!hintDef) { - codeq.log.error('Undefined hint ' + serverHint.id + ' with indices ' + serverHint.indices); - 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_hint').on('click', function () { terminal.append('hint.\n', 'input'); terminal.inputDisable(); @@ -359,7 +180,7 @@ .then( function hintSuccess(data) { if (data.code === 0) - handler.processServerHints(data.hints); + hinter.handle(data.hints); else terminal.append(data.message + '\n', 'error'); }, @@ -385,7 +206,7 @@ .then( function testSuccess(data) { if (data.code === 0) - handler.processServerHints(data.hints); + hinter.handle(data.hints); else terminal.append(data.message + '\n', 'error'); }, @@ -400,6 +221,18 @@ .done(); }); - return handler; + return { + destroy: function () { + 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; + } + }; }; })(); diff --git a/js/python.js b/js/python.js index 7d7458e..9d051fe 100644 --- a/js/python.js +++ b/js/python.js @@ -64,7 +64,8 @@ 'queueTrace': function (trace) { trace['dt'] = deltaActivityMillis(); queue.push(trace); - if (ts === null) setTimeout(timer, 10000); // flush every 10 seconds + if (ts === null) ts = setTimeout(timer, 10000); // flush every 10 seconds + return this; }, 'flush': flush, 'addAndPurge': function (trace) { @@ -96,119 +97,7 @@ 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), - 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; - }, - processTemplate = function (template, args) { - if (!args) - return template; - return template.replace(/\[%=(\w+)%\]/g, function(match, name) { - return args[name]; - }); - }, - hintHandlers = { - 'static': function (type, template, serverHint) { - codeq.log.debug('Processing static hint'); - var message = processTemplate(template, serverHint.args); - jqHints.append('
' + message + '
'); - // no hint cleaner here, a static hint remains on the screen - }, - 'popup': function (type, template, serverHint) { - codeq.log.debug('Processing popup hint'); - var message = processTemplate(template, serverHint.args), - 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 = $('
').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: message, 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); - } - }; + hinter = codeq.makeHinter(jqHints, jqEditor, editor, problem.hint); editor.setValue(info.solution); $('#title').text(problem.slug); @@ -227,75 +116,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 - 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 (serverHint.indices) { - indices = serverHint.indices - for (i = 0; i < indices.length; i++) { - hintDef = hintDef[indices[i]]; - if (!hintDef) - break; - } - } - if (!hintDef) { - codeq.log.error('Undefined hint ' + serverHint.id + ' with indices ' + serverHint.indices); - 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_hint').on('click', function () { -// handler.processServerHints([{id:'drop_down', start: 20, end: 26, choices:['ena', 'dva', 'tri']}]); var doc = editor.getDoc(); codeq.comms.sendHint({ 'language': 'python', @@ -304,7 +125,7 @@ }).then( function hintSuccess(data) { if (data.code === 0) - handler.processServerHints(data.hints); + hinter.handle(data.hints); else terminal.append('error: ' + data.message); }, @@ -322,7 +143,7 @@ }).then( function testSuccess(data) { if (data.code === 0) - handler.processServerHints(data.hints); + hinter.handle(data.hints); else terminal.append('error: ' + data.message); }, @@ -337,6 +158,18 @@ 'text': '' }); - return handler; + return { + destroy: function () { + 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; + } + }; }; })(); -- cgit v1.2.1