From 8ae11f070aafea81bc91bdaf6918f396777f88df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 17 Sep 2015 11:45:03 +0200 Subject: Console fix: a line can now have multiple CSS classes, so text can e.g. now be in multiple colors. --- css/codeq/console.css | 2 +- js/codeq/console.js | 237 +++++++++++++++++++++++++++++++++++++++++++------- js/codeq/startup.js | 1 + 3 files changed, 208 insertions(+), 32 deletions(-) diff --git a/css/codeq/console.css b/css/codeq/console.css index 2f5e9cf..c8dd9fd 100644 --- a/css/codeq/console.css +++ b/css/codeq/console.css @@ -8,7 +8,7 @@ background-color: transparent; border: none; color: inherit; - line-height: inherit; + line-height: normal; margin: 0; padding: 0; } diff --git a/js/codeq/console.js b/js/codeq/console.js index 99c1550..9331bc1 100644 --- a/js/codeq/console.js +++ b/js/codeq/console.js @@ -160,7 +160,7 @@ }; codeq.makeConsole = function (jqElt, options) { - var lines = [], // console content + var lines = [],// console content, line descriptors of the form {'content': 'string', 'classNames': [], 'jqLine': jQuery object, 'row': number} maxLines = (options && options.maxLines) || 10000, // how many lines to display at most currentRow = 0, // cursor position currentCol = 0, @@ -169,14 +169,65 @@ inputDisabled = false, // whether to ignore input lineBuffered = true, // whether we are line-buffered or not buffered + renderSpan = function (lineDescriptor, startCol, length) { + var jqLine = lineDescriptor.jqLine, + content = lineDescriptor.content, + classes = lineDescriptor.classNames, + endCol = startCol + length, + N = classes ? classes.length : 0, + i = 0, + spans = [], // what to render, items are of the form {'content': string, 'cssClass': string} + entry, classSpan, jq, span, s; + // seek out the first css class + while (i < N) { + entry = classes[i]; + if (((entry.start + entry.length) > startCol) && (entry.start < endCol)) break; + i++; + } + if (i < N) { + // render the first span + classSpan = entry.length - startCol + entry.start; + span = {'content': content.substr(startCol, classSpan), 'cssClass': entry.name}; + spans.push(span); + startCol += classSpan; + i++; + // render the rest + while ((startCol < endCol) && (i < N)) { + entry = classes[i]; + s = content.substr(startCol, entry.length); + if (entry.name === span.cssClass) { + span.content += s; // join content where the class is the same + } + else { + span = {'content': s, 'cssClass': entry.name}; + spans.push(span); + } + startCol += entry.length; + i++; + } + } + // render any leftover + if (startCol < endCol) { + entry = makeClassDescriptor('', startCol, endCol - startCol); + spans.push({'content': content.substr(startCol), 'cssClass': ''}); + } + // render spans + for (i = 0; i < spans.length; i++) { + span = spans[i]; + jq = $(''); + jqLine.append(jq); + jq.text(span.content); + } + return entry; // return the last entry used + }, + renderLine = function (lineDescriptor) { var jqLine = lineDescriptor.jqLine, - className = 'cq-con-text ' + lineDescriptor.className, - content = lineDescriptor.content || '', - jq1, jq2, jqCursor; + content = lineDescriptor.content, + jqCursor, classDescriptor; if (!jqLine) { - jqLine = $('
');
+                    jqLine = $('
');
                     if (lineDescriptor.row == 0) {
                         jqContent.prepend(jqLine);
                     }
@@ -196,36 +247,30 @@
                 if (currentRow == lineDescriptor.row) {
                     // mark the cursor in the current line
                     if (currentCol >= content.length) {
-                        jq1 = $('');
+                        // cursor after the last character
+                        renderSpan(lineDescriptor, 0, content.length);
                         jqCursor = $(' ');
-                        jqLine.append(jq1);
                         jqLine.append(jqCursor);
-                        jq1.text(content);
                     }
                     else if (currentCol <= 0) {
-                        jq2 = $('');
-                        jqCursor = $('');
+                        // cursor at the first character
+                        classDescriptor = lineDescriptor.classNames[0] || makeClassDescriptor('', 0, content.length);
+                        jqCursor = $('');
                         jqLine.append(jqCursor);
-                        jqLine.append(jq2);
                         jqCursor.text(content.charAt(0));
-                        jq2.text(content.substr(1));
+                        renderSpan(lineDescriptor, 1, content.length - 1);
                     }
                     else {
-                        jq1 = $('');
-                        jq2 = $('');
-                        jqCursor = $('');
-                        jqLine.append(jq1);
+                        // cursor somewhere in between
+                        classDescriptor = renderSpan(lineDescriptor, 0, currentCol);
+                        jqCursor = $('');
                         jqLine.append(jqCursor);
-                        jqLine.append(jq2);
-                        jq1.text(content.substr(0, currentCol));
                         jqCursor.text(content.charAt(currentCol));
-                        jq2.text(content.substr(currentCol + 1));
+                        renderSpan(lineDescriptor, currentCol + 1, content.length - currentCol - 1);
                     }
                 }
                 else {
-                    jq1 = $('');
-                    jqLine.append(jq1);
-                    jq1.text(lineDescriptor.content);
+                    renderSpan(lineDescriptor, 0, content.length);
                 }
             },
 
@@ -259,6 +304,120 @@
                 return newLines;
             },
 
+            makeClassDescriptor = function (name, start, length) {
+                return {'name': name, 'start': start, 'length': length};
+            },
+
+            indexOfClassAtOrAfterColumn = function (classes, col) {
+                var N = classes.length,
+                    i;
+                for (i = 0; i < N; i++) {
+                    if (classes[i].start >= col) return i;
+                }
+                return N;
+            },
+
+            insertClass = function (classes, newClassName, startingCol, length) {
+                var newEntry = makeClassDescriptor(newClassName, startingCol, length),
+                    endCol = startingCol + length,
+                    i, N, prevClass, pos, auxEntry, delta;
+                // seek out the position where to insert the new class
+                i = indexOfClassAtOrAfterColumn(classes, startingCol);
+                prevClass = i > 0 ? classes[i-1] : null;
+                // insert the new class
+                classes.splice(i, 0, newEntry);
+                // if needed, adjust the length of the previous class
+                if (prevClass) {
+                    pos = prevClass.start + prevClass.length;
+                    if (pos > startingCol) {
+                        // split the previous class in two, the new class gets inserted in between
+                        delta = pos - startingCol;
+                        auxEntry = makeClassDescriptor(prevClass.name, endCol, delta);
+                        classes.splice(i+1, 0, auxEntry);
+                        prevClass.length = prevClass.length - delta;
+                    }
+                }
+                // readjust positions of the classes after the new one
+                N = classes.length;
+                for (i++; i < N; i++) {
+                    auxEntry = classes[i];
+                    auxEntry.start = endCol;
+                    endCol += auxEntry.length;
+                }
+            },
+
+            removeClasses = function (classes, startCol, length) {
+                var i, entry, endCol, e, prefixDelta, suffixDelta, N;
+                if (!length) return;
+                endCol = startCol + length;
+                for (i = classes.length - 1; i >= 0; i--) {
+                    entry = classes[i];
+                    e = entry.start + entry.length;
+                    if ((e > startCol) && (entry.start < endCol)) {
+                        prefixDelta = entry.start < startCol ? startCol - entry.start : 0;
+                        suffixDelta = e > endCol ? e - endCol : 0;
+                        if ((prefixDelta == 0) && (suffixDelta == 0)) classes.splice(i, 1);
+                        else entry.length = prefixDelta + suffixDelta;
+                    }
+                }
+                // readjust positions
+                N = classes.length;
+                endCol = 0;
+                for (i = 0; i < N; i++) {
+                    entry = classes[i];
+                    entry.start = endCol;
+                    endCol += entry.length;
+                }
+            },
+
+            removeClassesBefore = function (classes, col) {
+                var i = indexOfClassAtOrAfterColumn(classes, col);
+                if (i == 0) return; // nothing to remove
+                classes.splice(0, i);
+            },
+
+            replaceClassesBeforeColumn = function (classes, col, newClassName, newLength) {
+                var i = indexOfClassAtOrAfterColumn(classes, col),
+                    newEntry = makeClassDescriptor(newClassName, 0, newLength);
+                classes.splice(0, i, newEntry);
+            },
+
+            classesBetween = function (classes, startCol, endCol) {
+                var N = classes.length,
+                    result = [],
+                    i, e, entry, prefixDelta, suffixDelta;
+                for (i = 0; i < N; i++) {
+                    entry = classes[i];
+                    e = entry.start + entry.length; // end column of this css class
+                    if ((e > startCol) && (entry.start < endCol)) {
+                        prefixDelta = entry.start < startCol ? startCol - entry.start : 0;
+                        suffixDelta = e > endCol ? e - endCol : 0;
+                        result.push(makeClassDescriptor(entry.name, entry.start + prefixDelta, entry.length - prefixDelta - suffixDelta));
+                    }
+                }
+                return result;
+            },
+
+            mergeClasses = function () {
+                var result = [],
+                    i, arg, j, entry, endCol;
+                for (i = 0; i < arguments.length; i++) {
+                    arg = arguments[i];
+                    if (arg instanceof Array) {
+                        for (j = 0; j < arg.length; j++) result.push(arg[j]);
+                    }
+                    else result.push(arg);
+                }
+                // adjust positions of css classes
+                endCol = 0;
+                for (i = 0; i < result.length; i++) {
+                    entry = result[i];
+                    entry.start = endCol;
+                    endCol += entry.length;
+                }
+                return result;
+            },
+
             // the handler object that is returned
             handler = {
                 'setLineBuffered': function () { lineBuffered = true; },
@@ -309,14 +468,15 @@
 
                 'insertRow': function (row, content, className, deferReflow) {
                     // inserts a new row at the position specified
-                    var lineDescriptor, i, jqLine;
+                    var lineDescriptor, i;
+                    if (!className) className = 'output';
                     for (i = lines.length; i < row; i++) {
                         // insert any empty rows
-                        lineDescriptor = {content: '', className: className, row: i};
+                        lineDescriptor = {content: '', classNames: [], row: i};
                         lines.push(lineDescriptor);
                         if (!deferReflow) renderLine(lineDescriptor);
                     }
-                    lineDescriptor = {content: content, className: className, row: row};
+                    lineDescriptor = {content: content, classNames: content.length > 0 ? [makeClassDescriptor(className, 0, content.length)] : [], row: row};
                     if (row >= lines.length) lines.push(lineDescriptor);
                     else lines.splice(row, 0, lineDescriptor);
                     if (!deferReflow) this.reflow(row);
@@ -324,7 +484,7 @@
                 },
 
                 'splice': function (startRow, startCol, endRow, endCol, newText, className) {
-                    var i, j, n, startLine, endLine, newLines, newTextLines, part1, part2;
+                    var i, j, n, startLine, endLine, newLines, part1, part2, s, auxLine;
 
                     if (!className) className = 'output';
                     // pre-process newText, convert it to newLines: an array of strings without newlines
@@ -370,19 +530,25 @@
                         if (newLines.length > 1) {
                             // the first and the last new lines are separate existing lines, modify them
                             startLine.content = part1 + newLines[0];
+                            startLine.classNames = mergeClasses(classesBetween(startLine.classNames, 0, part1.length), makeClassDescriptor(className, part1.length, newLines[0].length));
                             if (startRow == endRow) {
                                 // we need to create one additional line to hold the compositum of the last line and part2
-                                this.insertRow(j, newLines[newLines.length - 1] + part2, className, true);
+                                auxLine = this.insertRow(j, newLines[newLines.length - 1], className, true);
+                                insertClass(auxLine.classNames, auxLine.content.length, part2.length);
+                                auxLine.content = auxLine.content + part2;
                                 j++;
                             }
                             else {
-                                endLine.content = newLines[newLines.length - 1] + part2;
+                                s = newLines[newLines.length - 1];
+                                endLine.content = s + part2;
+                                replaceClassesBeforeColumn(endLine.classNames, endCol, className, s.length);
                                 n--; // retain the last line
                             }
                         }
                         else {
                             // the first and the last new lines are the same existing line
                             startLine.content = part1 + newLines[0] + part2;
+                            startLine.classNames = mergeClasses(classesBetween(startLine.classNames, 0, part1.length), makeClassDescriptor(className, part1.length, newLines[0].length), classesBetween(endLine.classNames, endCol, endCol + part2.length));
                         }
                         // remove the rest of the old lines
                         while (n > 0) {
@@ -422,7 +588,9 @@
                         startRow = lines.length - 1,
                         i, lineDescriptor;
                     if (newLines.length == 0) return; // nothing to do
+                    if (typeof className !== 'string') className = 'output';
                     lineDescriptor = lines[startRow];
+                    insertClass(lineDescriptor.classNames, className, lineDescriptor.content.length, newLines[0].length);
                     lineDescriptor.content = lineDescriptor.content + newLines[0];
                     for (i = 1; i < newLines.length; i++) {
                         lineDescriptor = this.insertRow(startRow + i, newLines[i], className, true);
@@ -441,20 +609,25 @@
                         part1 = currentContent.substring(0, currentCol),
                         part2 = currentContent.substring(currentCol),
                         newLines = formNewLines(text),
-                        i, n;
+                        i, n, auxLine;
+                    if (typeof className !== 'string') className = 'output';
                     if (newLines.length == 0) return; // nothing to do
                     else if (newLines.length == 1) {
+                        insertClass(currentLine.classNames, className, part1.length, newLines[0].length);
                         currentLine.content = part1 + newLines[0] + part2;
                         if (!dontMoveCursor) currentCol += newLines[0].length;
                         renderLine(currentLine);
                     }
                     else {
+                        currentLine.classNames = mergeClasses(classesBetween(currentLine.classNames, 0, part1.length), makeClassDescriptor(className, part1.length, newLines[0].length));
                         currentLine.content = part1 + newLines[0];
                         n = newLines.length - 1;
                         for (i = 1; i < n; i++) {
                             this.insertRow(startRow + i, newLines[i], className, true);
                         }
-                        this.insertRow(startRow + n, newLines[n] + part2, className, true);
+                        auxLine = this.insertRow(startRow + n, newLines[n], className, true);
+                        insertClass(auxLine.classNames, auxLine.content.length, part2.length);
+                        auxLine.content = auxLine.content + part2;
                         if (!dontMoveCursor) {
                             currentRow = startRow + n;
                             currentCol = newLines[n].length;
@@ -485,6 +658,7 @@
                         leftmost = typeof this.leftmostCol === 'number' && this.leftmostCol || 0;
                     if ((currentCol > leftmost) && (currentCol <= content.length)) {
                         currentCol--;
+                        removeClasses(lineDescriptor.classNames, currentCol, 1);
                         lineDescriptor.content = content.substring(0, currentCol) + content.substring(currentCol + 1);
                         renderLine(lineDescriptor);
                     }
@@ -494,6 +668,7 @@
                     var lineDescriptor = lines[currentRow],
                         content = lineDescriptor.content;
                     if ((currentCol >= 0) && (currentCol < content.length)) {
+                        removeClasses(lineDescriptor.classNames, currentCol, 1);
                         lineDescriptor.content = content.substring(0, currentCol) + content.substring(currentCol + 1);
                         renderLine(lineDescriptor);
                     }
@@ -739,7 +914,7 @@
             lines.push({content: options.greeting, className: 'greeting'});
             currentRow++;
         }
-        lines.push({content: '', className: 'input'}); // initial input line
+        lines.push({content: '', classNames: []}); // initial input line
         render();
 
         return handler;
diff --git a/js/codeq/startup.js b/js/codeq/startup.js
index 980d3d7..730a9f8 100644
--- a/js/codeq/startup.js
+++ b/js/codeq/startup.js
@@ -77,6 +77,7 @@ $(document).ready(function () {
                             })
                             .fail(function (reason) {
                                 $('#disabled').css('display', 'none');
+                                codeq.log.error('Login request failed: ' + reason, reason);
                                 alert('Login request failed: ' + reason);
                             })
                             .done();
-- 
cgit v1.2.1


From 6ac071053a9c8c77d796875c0e84871d6e71f75b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= 
Date: Thu, 17 Sep 2015 14:44:22 +0200
Subject: Fix handling of "enter" in the console: the current line must be
 processed in its entirety, not split at the cursor.

---
 js/codeq/console.js | 107 +++++++++++++++++++++++++++-------------------------
 1 file changed, 56 insertions(+), 51 deletions(-)

diff --git a/js/codeq/console.js b/js/codeq/console.js
index 9331bc1..d49cd19 100644
--- a/js/codeq/console.js
+++ b/js/codeq/console.js
@@ -418,6 +418,12 @@
                 return result;
             },
 
+            scrollCursorIntoView = function () {
+                var dh = jqContent.height() - jqElt.height();
+                if (dh > 0)
+                    jqElt.scrollTop(dh);
+            },
+
             // the handler object that is returned
             handler = {
                 'setLineBuffered': function () { lineBuffered = true; },
@@ -729,10 +735,8 @@
 
             handleInput = function (chars) {
                 var isPastedFromClipboard = clipboardPasteInProgress || !aKeyIsDown,
-                    newLines = chars.split('\n'),
-                    N = newLines.length,
-                    lastLineIndex = N - 1,
-                    i, promise;
+                    cookedChars = chars, // the default
+                    cookedPromise, cookedLines, lastCookedLineIndex, i;
 
                 if (inputDisabled || internallyDisabled) {
                     // flatly ignore
@@ -741,69 +745,70 @@
                 }
 
                 internallyDisabled = true;
-                promise = Q();
+                cookedPromise = Q();
 
-                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;
-
-                            // first cook the input
-                            if (typeof handler.onKeypress === 'function') {
-                                // only invoke the handler if the newLine is not an empty string, otherwise cookedChars = ''
-                                try {
-                                    cookedChars = cookedChars && handler.onKeypress(cookedChars, isPastedFromClipboard);
-                                }
-                                catch (e) {
-                                    codeq.log.error('Error during invocation of terminal onKeypress: ' + e, e);
-                                }
-                            }
+                // first cook the input
+                if (typeof handler.onKeypress === 'function') {
+                    // only invoke the handler if the newLine is not an empty string, otherwise cookedChars = ''
+                    try {
+                        cookedChars = cookedChars && handler.onKeypress(cookedChars, isPastedFromClipboard);
+                    }
+                    catch (e) {
+                        codeq.log.error('Error during invocation of terminal onKeypress: ' + e, e);
+                    }
+                }
 
-                            if (!cookedChars) {
-                                // nothing to append
-                                return;
-                            }
+                if (typeof cookedChars !== 'string') cookedLines = [''];
+                else cookedLines = cookedChars.split('\n');
+                lastCookedLineIndex = cookedLines.length - 1;
+
+                // now serve the cooking
+                for (i = 0; i <= lastCookedLineIndex; i++) {
+                    cookedPromise = cookedPromise
+                        .then(scrollCursorIntoView) // scroll to bottom on input
+                        .then((function (cookedLine, cookedLineIndex) {return function () {
+                            var thisRow = currentRow,
+                                leftmost = typeof handler.leftmostCol === 'number' && handler.leftmostCol || 0;
+
+                                if (cookedLine) handler.insertAtCursor(cookedLine, 'input'); // append what we have to the display
+                                if (cookedLineIndex < lastCookedLineIndex) {
+                                    // press on "enter" must be simulated -> jump to the start of the next line, which is first added
+                                    currentRow++;
+                                    currentCol = 0;
+                                    handler.insertRow(currentRow, '', 'input', true);
+                                    handler.reflow(currentRow - 1); // the previous line must be rendered without a cursor, and the new line must be rendered completely
+                                }
 
-                            // 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
+                                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 (cookedLine) {
                                         try {
-                                            return handler.onInput(lines[thisRow].content.substring(leftmost));
+                                            return handler.onInput(cookedLine);
                                         }
                                         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))
+                        };})(cookedLines[i], i))
                         .fail(function (e) {
                             codeq.log.error('Error in handleInput loop: ' + e, e);
                         });
                 }
 
-                promise.fin(function () {
+                cookedPromise.fin(function () {
                     internallyDisabled = false;
+                    scrollCursorIntoView();
                 }).done();
 
                 clipboardPasteInProgress = false;
-- 
cgit v1.2.1


From ebd7224135e2b283ff6cc58c5a54c4aaca378a0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= 
Date: Thu, 17 Sep 2015 14:57:42 +0200
Subject: Minor bugfix in prolog mode terminal: don't add an additional
 full-stop on manual stop of browsing through answers.

---
 js/prolog.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/js/prolog.js b/js/prolog.js
index 2868830..085860c 100644
--- a/js/prolog.js
+++ b/js/prolog.js
@@ -8,6 +8,7 @@
 
     var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
         var promptMode = true, // default: query composition; alternative: query result browsing
+            manualStop = false, // if the user stopped showing next answers (false) or if there are no more answers (true)
             terminal = codeq.makeConsole(jqConsole, {
                 'greeting': 'CodeQ Prolog terminal proxy'
             }),
@@ -24,7 +25,7 @@
                 }
                 if (promptMode) {
                     terminal.setLineBuffered();
-                    terminal.append('.\n?- ', 'output');
+                    terminal.append(manualStop ? '?- ' : '.\n?- ', 'output');
                 }
             },
             tcf = function terminalCommandFailed (error) {
@@ -49,6 +50,7 @@
         terminal.onInput = function (command) {
             if (promptMode) {
                 promptMode = false;
+                manualStop = false;
                 terminal.setNotBuffered();
                 return codeq.comms.sendQuery({
                     'problem_id': problem_id,
@@ -70,6 +72,7 @@
                 }
                 else {
                     // stop searching for answers
+                    manualStop = true;
                     return codeq.comms.sendQuery({
                         'problem_id': problem_id,
                         'step': 'end',
-- 
cgit v1.2.1


From f32e5966ab598d0a7cad20e74a8d9f29edb1bdf5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= 
Date: Thu, 17 Sep 2015 15:34:44 +0200
Subject: Console bugfix: moving inside pasted string did not work correctly.

---
 js/codeq/console.js | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/js/codeq/console.js b/js/codeq/console.js
index d49cd19..57d5a07 100644
--- a/js/codeq/console.js
+++ b/js/codeq/console.js
@@ -177,7 +177,7 @@
                     N = classes ? classes.length : 0,
                     i = 0,
                     spans = [], // what to render, items are of the form {'content': string, 'cssClass': string}
-                    entry, classSpan, jq, span, s;
+                    entry, classSpan, jq, span, s, prefixDelta, suffixDelta, e;
                 // seek out the first css class
                 while (i < N) {
                     entry = classes[i];
@@ -186,7 +186,10 @@
                 }
                 if (i < N) {
                     // render the first span
-                    classSpan = entry.length - startCol + entry.start;
+                    e = entry.start + entry.length; // end column of this css class
+                    prefixDelta = entry.start < startCol ? startCol - entry.start : 0;
+                    suffixDelta = e > endCol ? e - endCol : 0;
+                    classSpan = entry.length - prefixDelta - suffixDelta;
                     span = {'content': content.substr(startCol, classSpan), 'cssClass': entry.name};
                     spans.push(span);
                     startCol += classSpan;
@@ -194,7 +197,11 @@
                     // render the rest
                     while ((startCol < endCol) && (i < N)) {
                         entry = classes[i];
-                        s = content.substr(startCol, entry.length);
+                        e = entry.start + entry.length; // end column of this css class
+                        prefixDelta = entry.start < startCol ? startCol - entry.start : 0;
+                        suffixDelta = e > endCol ? e - endCol : 0;
+                        classSpan = entry.length - prefixDelta - suffixDelta;
+                        s = content.substr(startCol, classSpan);
                         if (entry.name === span.cssClass) {
                             span.content += s; // join content where the class is the same
                         }
@@ -202,14 +209,14 @@
                             span = {'content': s, 'cssClass': entry.name};
                             spans.push(span);
                         }
-                        startCol += entry.length;
+                        startCol += classSpan;
                         i++;
                     }
                 }
                 // render any leftover
                 if (startCol < endCol) {
                     entry = makeClassDescriptor('', startCol, endCol - startCol);
-                    spans.push({'content': content.substr(startCol), 'cssClass': ''});
+                    spans.push({'content': content.substring(startCol, endCol), 'cssClass': ''});
                 }
                 // render spans
                 for (i = 0; i < spans.length; i++) {
-- 
cgit v1.2.1


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.

---
 index.html       |   1 +
 js/codeq/hint.js | 149 ++++++++++++++++++++++++++++++++++++++++
 js/prolog.js     | 199 +++++------------------------------------------------
 js/python.js     | 203 +++++--------------------------------------------------
 4 files changed, 184 insertions(+), 368 deletions(-)
 create mode 100644 js/codeq/hint.js

diff --git a/index.html b/index.html
index 0f5105d..6f109f3 100644
--- a/index.html
+++ b/index.html
@@ -131,6 +131,7 @@
         
         
         
+        
         
         
         
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 From 31cc69df82abce2250505c1e088d3f787a521a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 17 Sep 2015 19:46:34 +0200 Subject: Implemented support for an array of static hints, which are displayed in sequence after pressing the "more" button. --- js/codeq/hint.js | 64 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/js/codeq/hint.js b/js/codeq/hint.js index ffae638..c22f39f 100644 --- a/js/codeq/hint.js +++ b/js/codeq/hint.js @@ -10,6 +10,7 @@ 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--) { @@ -18,6 +19,7 @@ hintCleaners.length = 0; hintCounter = 0; }, + addMark = function (start, end) { var posStart = editor.posFromIndex(start), posEnd = editor.posFromIndex(end), @@ -28,6 +30,7 @@ hintCounter++; return result; }, + processTemplate = function (template, args) { if (!args) return template; @@ -35,11 +38,49 @@ return args[name]; }); }, + typeHandlers = { 'static': function (type, template, serverHint) { - codeq.log.debug('Processing static hint'); - var message = processTemplate(template, serverHint.args); - jqHints.append('
' + message + '
'); + var jqContainer, jqButton, promise, i, lastIndex; + if (template instanceof Array) { // unwrap the template if there's only one + if (template.length == 0) template = ''; + else if (template.length == 1) template = template[0] + ''; + } + if (template instanceof Array) { + codeq.log.debug('Processing an array of static hints'); + jqContainer = $('
'); + jqButton = $(''); // TODO: translate "more" + jqHints.append(jqContainer); + lastIndex = template.length - 1; + promise = Q(); + for (i = 0; i <= lastIndex; i++) { + promise = promise.then((function (tmpl, index) { + return Q.Promise(function (resolve, reject) { + var message = processTemplate(tmpl, serverHint.args), + onClick; + jqContainer.append('
' + message + '
'); + if (index < lastIndex) { + onClick = function () { + jqButton.off('click', onClick); + jqButton.remove(); + resolve(); + }; + jqContainer.append(jqButton); + jqButton.on('click', onClick); + } + else { + resolve(); + } + }); + })(template[i], i)); + } + promise.done(); + } + else { + codeq.log.debug('Processing a single static hint'); + var message = processTemplate(template, serverHint.args); + jqHints.append('
' + message + '
'); + } // no hint cleaner here, a static hint remains on the screen }, @@ -102,21 +143,24 @@ /** HintDefinition */ hintDef, hintType, hintTemplate, t, fn, indices; clearHints(); + mainLoop: for (i = 0; i < n; i++) { serverHint = serverHints[i]; hintDef = hintDefs[serverHint.id]; + if (!hintDef) { + codeq.log.error('Undefined hint ' + serverHint.id); + continue; + } if (serverHint.indices) { - indices = 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 mainLoop; + } } } - if (!hintDef) { - codeq.log.error('Undefined hint ' + serverHint.id + ' with indices ' + serverHint.indices); - continue; - } t = typeof hintDef; if (t === 'object') { hintType = hintDef.type; -- cgit v1.2.1