diff --git a/lib/ace/apply_delta.js b/lib/ace/apply_delta.js index ed019ccb..98a1a857 100644 --- a/lib/ace/apply_delta.js +++ b/lib/ace/apply_delta.js @@ -42,7 +42,6 @@ function positionInDocument(docLines, position) { } function validateDelta(docLines, delta) { - // Validate action string. if (delta.action != "insert" && delta.action != "remove") throwDeltaError(delta, "delta.action must be 'insert' or 'remove'"); @@ -73,47 +72,37 @@ function validateDelta(docLines, delta) { } exports.applyDelta = function(docLines, delta, doNotValidate) { - - // Validate delta. if (!doNotValidate) validateDelta(docLines, delta); var row = delta.start.row; var startColumn = delta.start.column; var line = docLines[row]; - // Apply delta. - if (row == delta.end.row) { - // Apply single-line delta. - // Note: The multi-line code below correctly handle single-line - // deltas too, but we need to short-circuit for speed. - var endColumn = delta.end.column; - switch (delta.action) { - case "insert": + switch (delta.action) { + case "insert": + var lines = delta.lines; + if (lines.length === 1) { docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn); - break; - case "remove": - docLines[row] = line.substring(0, startColumn) + line.substring(endColumn); - break; - } - } else { - // Apply multi-line delta. - switch (delta.action) { - case "insert": + } else { var line = docLines[row]; var args = [row, 1].concat(delta.lines); docLines.splice.apply(docLines, args); docLines[row] = line.substring(0, startColumn) + docLines[row]; docLines[row + delta.lines.length - 1] += line.substring(startColumn); - break; - case "remove": - var endRow + } + break; + case "remove": + var endColumn = delta.end.column; + var endRow = delta.end.row; + if (row === endRow) { + docLines[row] = line.substring(0, startColumn) + line.substring(endColumn); + } else { docLines.splice( - row, // Where to start deleting - delta.end.row - delta.start.row + 1, // Num lines to delete. - line.substring(0, startColumn) + docLines[delta.end.row].substring(delta.end.column) + row, endRow - row + 1, + line.substring(0, startColumn) + docLines[endRow].substring(endColumn) ); - break; - } + } + break; } } }); diff --git a/lib/ace/document.js b/lib/ace/document.js index 43d1a73c..1abb95a2 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -39,13 +39,12 @@ var Anchor = require("./anchor").Anchor; /** * Contains the text of the document. Document can be attached to several [[EditSession `EditSession`]]s. - * * At its core, `Document`s are just an array of strings, with each row in the document matching up to the array index. * * @class Document **/ - /** +/** * * Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty. * @param {String | Array} text The starting text @@ -76,9 +75,9 @@ var Document = function(textOrLines) { * @param {String} text The text to use **/ this.setValue = function(text) { - var len = this.getLength(); - this.remove(new Range(0, 0, len, this.getLine(len-1).length)); - this.insert({row: 0, column:0}, text); + var len = this.getLength() - 1; + this.remove(new Range(0, 0, len, this.getLine(len).length)); + this.insert({row: 0, column: 0}, text); }; /** @@ -224,7 +223,7 @@ var Document = function(textOrLines) { **/ this.getLinesForRange = function(range) { var lines; - if (range.start.row == range.end.row) { + if (range.start.row === range.end.row) { // Handle a single-line range. lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)]; } else { @@ -238,29 +237,6 @@ var Document = function(textOrLines) { return lines; }; - this.$clipPosition = function(position) { - var length = this.getLength(); - if (position.row >= length) { - position.row = Math.max(0, length - 1); - position.column = this.getLine(length - 1).length; - } else { - position.row = Math.max(0, position.row); - position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length); - } - return position; - }; - - this.$getClippedRange = function(range) { - // Get Range object. - if (!range instanceof Range) - range = Range.fromPoints(range.start, range.end); - - // Return clipped range. - this.$clipPosition(range.start); - this.$clipPosition(range.end); - return range; - }; - // Deprecated methods retained for backwards compatibility. this.insertLines = function(row, lines) { console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."); @@ -305,25 +281,54 @@ var Document = function(textOrLines) { * ``` **/ this.insertInLine = function(position, text) { - // Calculate insertion range end point. - this.$clipPosition(position); - var endPoint = { - row : position.row, - column : position.column + text.length - }; + var start = this.clippedPos(position.row, position.column); + var end = this.pos(position.row, position.column + text.length); - var range = Range.fromPoints(position, endPoint); - // Apply delta (emits change). this.applyDelta({ action: "insert", - start: range.start, - end: range.end, + start: start, + end: end, lines: [text] - }, true /*doNotValidate*/); + }, true); - return endPoint; + return this.clonePos(end); }; + this.clippedPos = function(row, column) { + var length = this.getLength(); + if (row === undefined) { + row = length; + } else if (row < 0) { + row = 0; + } else if (row >= length) { + row = length - 1; + column = undefined + } + var line = this.getLine(row); + column = Math.min(Math.max(column, 0), line.length); + return {row: row, column: column}; + }; + + this.clonePos = function(pos) { + return {row: pos.row, column: pos.column}; + }; + + this.pos = function(row, column) { + return {row: row, column: column}; + }; + + this.$clipPosition = function(position) { + var length = this.getLength(); + if (position.row >= length) { + position.row = Math.max(0, length - 1); + position.column = this.getLine(length - 1).length; + } else { + position.row = Math.max(0, position.row); + position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length); + } + return position; + }; + /** * Fires whenever the document changes. * @@ -392,14 +397,12 @@ var Document = function(textOrLines) { * **/ this.insertMergedLines = function(position, lines) { - // Calculate insertion range end point. this.$clipPosition(position); var endPoint = { row : position.row + lines.length - 1, column : (lines.length == 1 ? position.column : 0) + lines[lines.length - 1].length }; - // Apply delta (emits change). this.applyDelta({ action: "insert", start: position, @@ -417,15 +420,14 @@ var Document = function(textOrLines) { * **/ this.remove = function(range) { - // Apply delta (emits change). - range = this.$getClippedRange(range); + var start = this.clippedPos(range.start.row, range.start.column); this.applyDelta({ action: "remove", - start: range.start, - end: range.end, + start: start, + end: this.clippedPos(range.end.row, range.end.column), lines: this.getLinesForRange(range), }); - return range.start; + return this.clonePos(start); }; /** @@ -437,19 +439,16 @@ var Document = function(textOrLines) { * **/ this.removeInLine = function(row, startColumn, endColumn) { - // Calculate deleteion range. - var range = new Range(row, startColumn, row, endColumn); - range = this.$getClippedRange(range); + var start = this.clippedPos(row, startColumn); - // Apply delta (emits change). this.applyDelta({ action: "remove", - start: range.start, - end: range.end, + start: start, + end: this.clippedPos(row, endColumn), lines: this.getLinesForRange(range), - }, true /*doNotValidate*/); + }, true); - return range.start; + return this.clonePos(start); }; /** @@ -478,7 +477,6 @@ var Document = function(textOrLines) { // Store delelted lines with bounding newlines ommitted (maintains previous behavior). var deletedLines = this.$lines.slice(firstRow, lastRow + 1); - // Apply delta (emits change). this.applyDelta({ action: "remove", start: range.start, @@ -496,13 +494,11 @@ var Document = function(textOrLines) { * **/ this.removeNewLine = function(row) { - if (row < this.getLength() - 1 && row >= 0) { - var range = new Range(row, this.getLine(row).length, row + 1, 0); - // Apply delta (emits change). + if (row < this.getLength() - 1 && row >= 0) { this.applyDelta({ action: "remove", - start: range.start, - end: range.end, + start: this.pos(row, this.getLine(row).length), + end: this.pos(row + 1, 0), lines: ["", ""] }); } @@ -566,10 +562,21 @@ var Document = function(textOrLines) { * @param {Object} delta A delta object (can include "insert" and "remove" actions) **/ this.applyDelta = function(delta, doNotValidate) { + var isInsert = delta.action == "insert"; // An empty range is a NOOP. - if (!Range.comparePoints(delta.start, delta.end)) + if (isInsert ? !delta.lines.length + : !Range.comparePoints(delta.start, delta.end)) return; + if (isInsert && delta.lines.length > 0xFFFF) + this.$splitAndapplyLargeDelta(delta); + + // Apply. + applyDelta(this.$lines, delta, doNotValidate); + this._signal("change", {data: delta}); + }; + + this.$splitAndapplyLargeDelta = function(delta) { // Split large insert deltas. This is necessary because: // 1. We need to support splicing delta lines into the document via $lines.splice.apply(...) // 2. fn.apply() doesn't work for a large number of params. The mallest threshold is on safari 0xFFFF. @@ -578,28 +585,18 @@ var Document = function(textOrLines) { // delta handling is too slow. If we make delete delta handling faster we can split all large deltas // as shown in https://gist.github.com/aldendaniels/8367109#file-document-snippet-js // If we do this, update validateDelta() to limit the number of lines in a delete delta. - var bIsInsert = delta.action == "insert"; - while (bIsInsert && delta.lines.length > 65001) { + while (delta.lines.length > 0xFFFF) { // Get split deltas. - var lines = delta.lines.splice(0, 65000); + var lines = delta.lines.splice(0, 0xFFFF); lines.push(""); - var range = new Range(delta.start.row, delta.start.column, - delta.start.row + 65000, 0) + var start = delta.start; this.applyDelta({ action: delta.action, lines: lines, - start: range.start, - end: range.end, - }); - - // Update remaining delta. - delta.start.row += 65000; - delta.start.column = 0; + start: this.pos(start.row, start.column), + end: this.pos(start.row += 0xFFFF, start.column = 0) // Updates remaining delta. + }, true); } - - // Apply. - applyDelta(this.$lines, delta, doNotValidate); - this._emit("change", { data: delta }); }; /** diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 4baa525f..c9759fb1 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1394,25 +1394,25 @@ var EditSession = function(text, mode) { **/ this.outdentRows = function (range) { var rowRange = range.collapseRows(); + var deleteRange = new Range(0, 0, 0, 0); var size = this.getTabSize(); - + for (var i = rowRange.start.row; i <= rowRange.end.row; ++i) { var line = this.getLine(i); - var row = i; - var startCol = 0; - var endCol = 0; - + + deleteRange.start.row = i; + deleteRange.end.row = i; for (var j = 0; j < size; ++j) if (line.charAt(j) != ' ') break; if (j < size && line.charAt(j) == '\t') { - startCol = j; - endCol = j + 1; + deleteRange.start.column = j; + deleteRange.end.column = j + 1; } else { - startCol = 0; - endCol = j; + deleteRange.start.column = 0; + deleteRange.end.column = j; } - this.doc.removeInLine(row, startCol, endCol); + this.remove(deleteRange); } }; @@ -1684,7 +1684,7 @@ var EditSession = function(text, mode) { this.$updating = true; if (len != 0) { - if (action == "remove") { + if (action === "remove") { this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len); var foldLines = this.$foldData; @@ -1759,7 +1759,7 @@ var EditSession = function(text, mode) { // Realign folds. E.g. if you add some new chars before a fold, the // fold should "move" to the right. len = Math.abs(e.data.start.column - e.data.end.column); - if (action == "remove") { + if (action === "remove") { // Get all the folds in the change range and remove them. removedFolds = this.getFoldsInRange(e.data); this.removeFolds(removedFolds); diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index 0cbcc394..a112139a 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -181,13 +181,13 @@ var RangeList = function() { }; this.$onChange = function(e) { - var changeRange = e.data.range; - if (e.data.action[0] == "i"){ - var start = changeRange.start; - var end = changeRange.end; + var delta = e.data; + if (delta.action == "insert"){ + var start = delta.start; + var end = delta.end; } else { - var end = changeRange.start; - var start = changeRange.end; + var end = delta.start; + var start = delta.end; } var startRow = start.row; var endRow = end.row; diff --git a/lib/ace/undomanager.js b/lib/ace/undomanager.js index 71b4ba65..6da50a8b 100644 --- a/lib/ace/undomanager.js +++ b/lib/ace/undomanager.js @@ -61,10 +61,9 @@ var UndoManager = function() { * **/ this.execute = function(options) { - // Normalize deltas for storage. - var deltaSets = this.$serializeDeltas(options.args[0]); - + // var deltaSets = this.$serializeDeltas(options.args[0]); + var deltaSets = options.args[0]; // Add deltas to undo stack. this.$doc = options.args[1]; if (options.merge && this.hasUndo()){ @@ -207,7 +206,7 @@ var UndoManager = function() { deltaSets_new[i] = deltaSet_new; } - return deltaSets_new; + return deltaSets_new; } }).call(UndoManager.prototype); diff --git a/lib/ace/worker/mirror.js b/lib/ace/worker/mirror.js index 8254f205..514d377f 100644 --- a/lib/ace/worker/mirror.js +++ b/lib/ace/worker/mirror.js @@ -13,7 +13,19 @@ var Mirror = exports.Mirror = function(sender) { var _self = this; sender.on("change", function(e) { - doc.applyDeltas(e.data); + var data = e.data; + if (data[0].start) { + doc.applyDeltas(data); + } else { + for (var i = 0; i < data.length; i += 2) { + if (Array.isArray(data[i+1])) + var d = {action: "insert", start: data[i], lines: data[i+1]}; + else + var d = {action: "remove", start: data[i],end: data[i+1]}; + + doc.applyDelta(d, true); + } + } if (_self.$timeout) return deferredUpdate.schedule(_self.$timeout); _self.onUpdate(); diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index cb18998f..f5902f90 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -162,17 +162,21 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { this.changeListener = function(e) { if (!this.deltaQueue) { - this.deltaQueue = [e.data]; + this.deltaQueue = []; setTimeout(this.$sendDeltaQueue, 0); - } else - this.deltaQueue.push(e.data); + } + var delta = e.data; + if (delta.action == "insert") + this.deltaQueue.push(delta.start, delta.lines); + else + this.deltaQueue.push(delta.start, delta.end); }; this.$sendDeltaQueue = function() { var q = this.deltaQueue; if (!q) return; this.deltaQueue = null; - if (q.length > 20 && q.length > this.$doc.getLength() >> 1) { + if (q.length > 50 && q.length > this.$doc.getLength() >> 1) { this.call("setValue", [this.$doc.getValue()]); } else this.emit("change", {data: q});