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. --- js/codeq/console.js | 237 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 206 insertions(+), 31 deletions(-) (limited to 'js/codeq/console.js') 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;
-- 
cgit v1.2.1