diff options
-rw-r--r-- | css/codeq/console.css | 2 | ||||
-rw-r--r-- | js/codeq/console.js | 237 | ||||
-rw-r--r-- | 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 = $('<span class="cq-con-text ' + span.cssClass + '"></span>'); + 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 = $('<pre class="cq-con-line ' + lineDescriptor.className + '"></pre>'); + jqLine = $('<pre class="cq-con-line"></pre>'); 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 = $('<span class="' + className + '"></span>'); + // cursor after the last character + renderSpan(lineDescriptor, 0, content.length); jqCursor = $('<span class="cq-con-cursor"> </span>'); - jqLine.append(jq1); jqLine.append(jqCursor); - jq1.text(content); } else if (currentCol <= 0) { - jq2 = $('<span class="' + className + '"></span>'); - jqCursor = $('<span class="cq-con-cursor"></span>'); + // cursor at the first character + classDescriptor = lineDescriptor.classNames[0] || makeClassDescriptor('', 0, content.length); + jqCursor = $('<span class="cq-con-cursor ' + classDescriptor.name + '"></span>'); jqLine.append(jqCursor); - jqLine.append(jq2); jqCursor.text(content.charAt(0)); - jq2.text(content.substr(1)); + renderSpan(lineDescriptor, 1, content.length - 1); } else { - jq1 = $('<span class="' + className + '"></span>'); - jq2 = $('<span class="' + className + '"></span>'); - jqCursor = $('<span class="cq-con-cursor"></span>'); - jqLine.append(jq1); + // cursor somewhere in between + classDescriptor = renderSpan(lineDescriptor, 0, currentCol); + jqCursor = $('<span class="cq-con-cursor ' + classDescriptor.name + '"></span>'); 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 = $('<span class="' + className + '"></span>'); - 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(); |