Merge pull request #1819 from ajaxorg/v-1.2
Refactor document delta handling for v1.2
This commit is contained in:
commit
0a3b002e28
26 changed files with 723 additions and 535 deletions
|
|
@ -1,5 +1,16 @@
|
||||||
|
Version 1.2.0-pre
|
||||||
|
|
||||||
|
* New Features
|
||||||
|
- Indented soft wrap (danyaPostfactum)
|
||||||
|
|
||||||
|
* API Changes
|
||||||
|
- unified delta types `{start, end, action, lines}` (Alden Daniels https://github.com/ajaxorg/ace/pull/1745)
|
||||||
|
- "change" event listeners on session and editor get delta objects directly
|
||||||
|
|
||||||
2015.04.03 Version 1.1.9
|
2015.04.03 Version 1.1.9
|
||||||
|
|
||||||
|
- Small Enhancements and Bugfixes
|
||||||
|
|
||||||
2014.11.08 Version 1.1.8
|
2014.11.08 Version 1.1.8
|
||||||
|
|
||||||
* API Changes
|
* API Changes
|
||||||
|
|
|
||||||
2
build
2
build
|
|
@ -1 +1 @@
|
||||||
Subproject commit e3ccd2c654cf45ee41ffb09d0e7fa5b40cf91a8f
|
Subproject commit a4e495d8901876c6bafe3870a35cb8e32c827e97
|
||||||
|
|
@ -99,72 +99,54 @@ var Anchor = exports.Anchor = function(doc, row, column) {
|
||||||
* - `value`: An object describing the new Anchor position
|
* - `value`: An object describing the new Anchor position
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.onChange = function(e) {
|
this.onChange = function(delta) {
|
||||||
var delta = e.data;
|
if (delta.start.row == delta.end.row && delta.start.row != this.row)
|
||||||
var range = delta.range;
|
|
||||||
|
|
||||||
if (range.start.row == range.end.row && range.start.row != this.row)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (range.start.row > this.row)
|
if (delta.start.row > this.row)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (range.start.row == this.row && range.start.column > this.column)
|
var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight);
|
||||||
return;
|
this.setPosition(point.row, point.column, true);
|
||||||
|
|
||||||
var row = this.row;
|
|
||||||
var column = this.column;
|
|
||||||
var start = range.start;
|
|
||||||
var end = range.end;
|
|
||||||
|
|
||||||
if (delta.action === "insertText") {
|
|
||||||
if (start.row === row && start.column <= column) {
|
|
||||||
if (start.column === column && this.$insertRight) {
|
|
||||||
// do nothing
|
|
||||||
} else if (start.row === end.row) {
|
|
||||||
column += end.column - start.column;
|
|
||||||
} else {
|
|
||||||
column -= start.column;
|
|
||||||
row += end.row - start.row;
|
|
||||||
}
|
|
||||||
} else if (start.row !== end.row && start.row < row) {
|
|
||||||
row += end.row - start.row;
|
|
||||||
}
|
|
||||||
} else if (delta.action === "insertLines") {
|
|
||||||
if (start.row === row && column === 0 && this.$insertRight) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
else if (start.row <= row) {
|
|
||||||
row += end.row - start.row;
|
|
||||||
}
|
|
||||||
} else if (delta.action === "removeText") {
|
|
||||||
if (start.row === row && start.column < column) {
|
|
||||||
if (end.column >= column)
|
|
||||||
column = start.column;
|
|
||||||
else
|
|
||||||
column = Math.max(0, column - (end.column - start.column));
|
|
||||||
|
|
||||||
} else if (start.row !== end.row && start.row < row) {
|
|
||||||
if (end.row === row)
|
|
||||||
column = Math.max(0, column - end.column) + start.column;
|
|
||||||
row -= (end.row - start.row);
|
|
||||||
} else if (end.row === row) {
|
|
||||||
row -= end.row - start.row;
|
|
||||||
column = Math.max(0, column - end.column) + start.column;
|
|
||||||
}
|
|
||||||
} else if (delta.action == "removeLines") {
|
|
||||||
if (start.row <= row) {
|
|
||||||
if (end.row <= row)
|
|
||||||
row -= end.row - start.row;
|
|
||||||
else {
|
|
||||||
row = start.row;
|
|
||||||
column = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setPosition(row, column, true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function $pointsInOrder(point1, point2, equalPointsInOrder) {
|
||||||
|
var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column;
|
||||||
|
return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function $getTransformedPoint(delta, point, moveIfEqual) {
|
||||||
|
// Get delta info.
|
||||||
|
var deltaIsInsert = delta.action == "insert";
|
||||||
|
var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row);
|
||||||
|
var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column);
|
||||||
|
var deltaStart = delta.start;
|
||||||
|
var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range.
|
||||||
|
|
||||||
|
// DELTA AFTER POINT: No change needed.
|
||||||
|
if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
|
||||||
|
return {
|
||||||
|
row: point.row,
|
||||||
|
column: point.column
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELTA BEFORE POINT: Move point by delta shift.
|
||||||
|
if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
|
||||||
|
return {
|
||||||
|
row: point.row + deltaRowShift,
|
||||||
|
column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELTA ENVELOPS POINT (delete only): Move point to delta start.
|
||||||
|
// TODO warn if delta.action != "remove" ?
|
||||||
|
|
||||||
|
return {
|
||||||
|
row: deltaStart.row,
|
||||||
|
column: deltaStart.column
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped.
|
* Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped.
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ module.exports = {
|
||||||
var doc = new Document("juhu\nkinners");
|
var doc = new Document("juhu\nkinners");
|
||||||
var anchor = new Anchor(doc, 1, 4);
|
var anchor = new Anchor(doc, 1, 4);
|
||||||
|
|
||||||
doc.insertLines(1, ["123", "456"]);
|
doc.insertFullLines(1, ["123", "456"]);
|
||||||
assert.position(anchor.getPosition(), 3, 4);
|
assert.position(anchor.getPosition(), 3, 4);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ module.exports = {
|
||||||
var doc = new Document("juhu\nkinners");
|
var doc = new Document("juhu\nkinners");
|
||||||
var anchor = new Anchor(doc, 1, 4);
|
var anchor = new Anchor(doc, 1, 4);
|
||||||
|
|
||||||
doc.insertNewLine({row: 0, column: 0});
|
doc.insertMergedLines({row: 0, column: 0}, ['', '']);
|
||||||
assert.position(anchor.getPosition(), 2, 4);
|
assert.position(anchor.getPosition(), 2, 4);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ module.exports = {
|
||||||
var doc = new Document("juhu\nkinners");
|
var doc = new Document("juhu\nkinners");
|
||||||
var anchor = new Anchor(doc, 1, 4);
|
var anchor = new Anchor(doc, 1, 4);
|
||||||
|
|
||||||
doc.insertNewLine({row: 1, column: 2});
|
doc.insertMergedLines({row: 1, column: 2}, ['', '']);
|
||||||
assert.position(anchor.getPosition(), 2, 2);
|
assert.position(anchor.getPosition(), 2, 2);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -145,7 +145,7 @@ module.exports = {
|
||||||
var doc = new Document("juhu\n1\n2\nkinners");
|
var doc = new Document("juhu\n1\n2\nkinners");
|
||||||
var anchor = new Anchor(doc, 3, 4);
|
var anchor = new Anchor(doc, 3, 4);
|
||||||
|
|
||||||
doc.removeLines(1, 2);
|
doc.removeFullLines(1, 2);
|
||||||
assert.position(anchor.getPosition(), 1, 4);
|
assert.position(anchor.getPosition(), 1, 4);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ module.exports = {
|
||||||
var doc = new Document("juhu\nkinners\n123");
|
var doc = new Document("juhu\nkinners\n123");
|
||||||
var anchor = new Anchor(doc, 1, 5);
|
var anchor = new Anchor(doc, 1, 5);
|
||||||
|
|
||||||
doc.removeLines(1, 1);
|
doc.removeFullLines(1, 1);
|
||||||
assert.position(anchor.getPosition(), 1, 0);
|
assert.position(anchor.getPosition(), 1, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -208,9 +208,9 @@ module.exports = {
|
||||||
var doc = new Document("juhu\nkinners\n123");
|
var doc = new Document("juhu\nkinners\n123");
|
||||||
var anchor = new Anchor(doc, 2, 4);
|
var anchor = new Anchor(doc, 2, 4);
|
||||||
|
|
||||||
doc.removeLines(0, 3);
|
doc.removeFullLines(0, 3);
|
||||||
assert.position(anchor.getPosition(), 0, 0);
|
assert.position(anchor.getPosition(), 0, 0);
|
||||||
doc.insertLines(0, ["a", "b", "c"]);
|
doc.insertFullLines(0, ["a", "b", "c"]);
|
||||||
assert.position(anchor.getPosition(), 3, 0);
|
assert.position(anchor.getPosition(), 3, 0);
|
||||||
assert.equal(doc.getValue(), "a\nb\nc\n");
|
assert.equal(doc.getValue(), "a\nb\nc\n");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
lib/ace/apply_delta.js
Normal file
108
lib/ace/apply_delta.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
|
* Distributed under the BSD license:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2010, Ajax.org B.V.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Ajax.org B.V. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
define(function(require, exports, module) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function throwDeltaError(delta, errorText){
|
||||||
|
console.log("Invalid Delta:", delta);
|
||||||
|
throw "Invalid Delta: " + errorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionInDocument(docLines, position) {
|
||||||
|
return position.row >= 0 && position.row < docLines.length &&
|
||||||
|
position.column >= 0 && position.column <= docLines[position.row].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDelta(docLines, delta) {
|
||||||
|
// Validate action string.
|
||||||
|
if (delta.action != "insert" && delta.action != "remove")
|
||||||
|
throwDeltaError(delta, "delta.action must be 'insert' or 'remove'");
|
||||||
|
|
||||||
|
// Validate lines type.
|
||||||
|
if (!(delta.lines instanceof Array))
|
||||||
|
throwDeltaError(delta, "delta.lines must be an Array");
|
||||||
|
|
||||||
|
// Validate range type.
|
||||||
|
if (!delta.start || !delta.end)
|
||||||
|
throwDeltaError(delta, "delta.start/end must be an present");
|
||||||
|
|
||||||
|
// Validate that the start point is contained in the document.
|
||||||
|
var start = delta.start;
|
||||||
|
if (!positionInDocument(docLines, delta.start))
|
||||||
|
throwDeltaError(delta, "delta.start must be contained in document");
|
||||||
|
|
||||||
|
// Validate that the end point is contained in the document (remove deltas only).
|
||||||
|
var end = delta.end;
|
||||||
|
if (delta.action == "remove" && !positionInDocument(docLines, end))
|
||||||
|
throwDeltaError(delta, "delta.end must contained in document for 'remove' actions");
|
||||||
|
|
||||||
|
// Validate that the .range size matches the .lines size.
|
||||||
|
var numRangeRows = end.row - start.row;
|
||||||
|
var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0));
|
||||||
|
if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars)
|
||||||
|
throwDeltaError(delta, "delta.range must match delta lines");
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.applyDelta = function(docLines, delta, doNotValidate) {
|
||||||
|
// disabled validation since it breaks autocompletion popup
|
||||||
|
// if (!doNotValidate)
|
||||||
|
// validateDelta(docLines, delta);
|
||||||
|
|
||||||
|
var row = delta.start.row;
|
||||||
|
var startColumn = delta.start.column;
|
||||||
|
var line = docLines[row] || "";
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
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 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, endRow - row + 1,
|
||||||
|
line.substring(0, startColumn) + docLines[endRow].substring(endColumn)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -220,8 +220,8 @@ var AcePopup = function(parentNode) {
|
||||||
|
|
||||||
popup.data = [];
|
popup.data = [];
|
||||||
popup.setData = function(list) {
|
popup.setData = function(list) {
|
||||||
popup.data = list || [];
|
|
||||||
popup.setValue(lang.stringRepeat("\n", list.length), -1);
|
popup.setValue(lang.stringRepeat("\n", list.length), -1);
|
||||||
|
popup.data = list || [];
|
||||||
popup.setRow(0);
|
popup.setRow(0);
|
||||||
};
|
};
|
||||||
popup.getData = function(row) {
|
popup.getData = function(row) {
|
||||||
|
|
|
||||||
|
|
@ -171,13 +171,12 @@ var BackgroundTokenizer = function(tokenizer, editor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$updateOnChange = function(delta) {
|
this.$updateOnChange = function(delta) {
|
||||||
var range = delta.range;
|
var startRow = delta.start.row;
|
||||||
var startRow = range.start.row;
|
var len = delta.end.row - startRow;
|
||||||
var len = range.end.row - startRow;
|
|
||||||
|
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
this.lines[startRow] = null;
|
this.lines[startRow] = null;
|
||||||
} else if (delta.action == "removeText" || delta.action == "removeLines") {
|
} else if (delta.action == "remove") {
|
||||||
this.lines.splice(startRow, len + 1, null);
|
this.lines.splice(startRow, len + 1, null);
|
||||||
this.states.splice(startRow, len + 1, null);
|
this.states.splice(startRow, len + 1, null);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -32,36 +32,36 @@ define(function(require, exports, module) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var oop = require("./lib/oop");
|
var oop = require("./lib/oop");
|
||||||
|
var applyDelta = require("./apply_delta").applyDelta;
|
||||||
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
||||||
var Range = require("./range").Range;
|
var Range = require("./range").Range;
|
||||||
var Anchor = require("./anchor").Anchor;
|
var Anchor = require("./anchor").Anchor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the text of the document. Document can be attached to several [[EditSession `EditSession`]]s.
|
* 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.
|
* 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
|
* @class Document
|
||||||
**/
|
**/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty.
|
* Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty.
|
||||||
* @param {String | Array} text The starting text
|
* @param {String | Array} text The starting text
|
||||||
* @constructor
|
* @constructor
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var Document = function(text) {
|
var Document = function(textOrLines) {
|
||||||
this.$lines = [];
|
this.$lines = [""];
|
||||||
|
|
||||||
// There has to be one line at least in the document. If you pass an empty
|
// There has to be one line at least in the document. If you pass an empty
|
||||||
// string to the insert function, nothing will happen. Workaround.
|
// string to the insert function, nothing will happen. Workaround.
|
||||||
if (text.length === 0) {
|
if (textOrLines.length === 0) {
|
||||||
this.$lines = [""];
|
this.$lines = [""];
|
||||||
} else if (Array.isArray(text)) {
|
} else if (Array.isArray(textOrLines)) {
|
||||||
this._insertLines(0, text);
|
this.insertMergedLines({row: 0, column: 0}, textOrLines);
|
||||||
} else {
|
} else {
|
||||||
this.insert({row: 0, column:0}, text);
|
this.insert({row: 0, column:0}, textOrLines);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -75,9 +75,9 @@ var Document = function(text) {
|
||||||
* @param {String} text The text to use
|
* @param {String} text The text to use
|
||||||
**/
|
**/
|
||||||
this.setValue = function(text) {
|
this.setValue = function(text) {
|
||||||
var len = this.getLength();
|
var len = this.getLength() - 1;
|
||||||
this.remove(new Range(0, 0, len, this.getLine(len-1).length));
|
this.remove(new Range(0, 0, len, this.getLine(len).length));
|
||||||
this.insert({row: 0, column:0}, text);
|
this.insert({row: 0, column: 0}, text);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,7 +98,7 @@ var Document = function(text) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits a string of text on any newline (`\n`) or carriage-return ('\r') characters.
|
* Splits a string of text on any newline (`\n`) or carriage-return (`\r`) characters.
|
||||||
*
|
*
|
||||||
* @method $split
|
* @method $split
|
||||||
* @param {String} text The text to work with
|
* @param {String} text The text to work with
|
||||||
|
|
@ -107,14 +107,15 @@ var Document = function(text) {
|
||||||
**/
|
**/
|
||||||
|
|
||||||
// check for IE split bug
|
// check for IE split bug
|
||||||
if ("aaa".split(/a/).length === 0)
|
if ("aaa".split(/a/).length === 0) {
|
||||||
this.$split = function(text) {
|
this.$split = function(text) {
|
||||||
return text.replace(/\r\n|\r/g, "\n").split("\n");
|
return text.replace(/\r\n|\r/g, "\n").split("\n");
|
||||||
};
|
};
|
||||||
else
|
} else {
|
||||||
this.$split = function(text) {
|
this.$split = function(text) {
|
||||||
return text.split(/\r\n|\r|\n/);
|
return text.split(/\r\n|\r|\n/);
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.$detectNewLine = function(text) {
|
this.$detectNewLine = function(text) {
|
||||||
|
|
@ -207,32 +208,49 @@ var Document = function(text) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Given a range within the document, this function returns all the text within that range as a single string.]{: #Document.getTextRange.desc}
|
* Returns all the text within `range` as a single string.
|
||||||
* @param {Range} range The range to work with
|
* @param {Range} range The range to work with.
|
||||||
*
|
*
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
**/
|
**/
|
||||||
this.getTextRange = function(range) {
|
this.getTextRange = function(range) {
|
||||||
if (range.start.row == range.end.row) {
|
return this.getLinesForRange(range).join(this.getNewLineCharacter());
|
||||||
return this.getLine(range.start.row)
|
};
|
||||||
.substring(range.start.column, range.end.column);
|
|
||||||
|
/**
|
||||||
|
* Returns all the text within `range` as an array of lines.
|
||||||
|
* @param {Range} range The range to work with.
|
||||||
|
*
|
||||||
|
* @returns {Array}
|
||||||
|
**/
|
||||||
|
this.getLinesForRange = function(range) {
|
||||||
|
var lines;
|
||||||
|
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 {
|
||||||
|
// Handle a multi-line range.
|
||||||
|
lines = this.getLines(range.start.row, range.end.row);
|
||||||
|
lines[0] = (lines[0] || "").substring(range.start.column);
|
||||||
|
var l = lines.length - 1;
|
||||||
|
if (range.end.row - range.start.row == l)
|
||||||
|
lines[l] = lines[l].substring(0, range.end.column);
|
||||||
}
|
}
|
||||||
var lines = this.getLines(range.start.row, range.end.row);
|
return lines;
|
||||||
lines[0] = (lines[0] || "").substring(range.start.column);
|
|
||||||
var l = lines.length - 1;
|
|
||||||
if (range.end.row - range.start.row == l)
|
|
||||||
lines[l] = lines[l].substring(0, range.end.column);
|
|
||||||
return lines.join(this.getNewLineCharacter());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$clipPosition = function(position) {
|
// Deprecated methods retained for backwards compatibility.
|
||||||
var length = this.getLength();
|
this.insertLines = function(row, lines) {
|
||||||
if (position.row >= length) {
|
console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead.");
|
||||||
position.row = Math.max(0, length - 1);
|
return this.insertFullLines(row, lines);
|
||||||
position.column = this.getLine(length-1).length;
|
};
|
||||||
} else if (position.row < 0)
|
this.removeLines = function(firstRow, lastRow) {
|
||||||
position.row = 0;
|
console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead.");
|
||||||
return position;
|
return this.removeFullLines(firstRow, lastRow);
|
||||||
|
};
|
||||||
|
this.insertNewLine = function(position) {
|
||||||
|
console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, [\'\', \'\']) instead.");
|
||||||
|
return this.insertMergedLines(position, ["", ""]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -243,53 +261,97 @@ var Document = function(text) {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.insert = function(position, text) {
|
this.insert = function(position, text) {
|
||||||
if (!text || text.length === 0)
|
// Only detect new lines if the document has no line break yet.
|
||||||
return position;
|
|
||||||
|
|
||||||
position = this.$clipPosition(position);
|
|
||||||
|
|
||||||
// only detect new lines if the document has no line break yet
|
|
||||||
if (this.getLength() <= 1)
|
if (this.getLength() <= 1)
|
||||||
this.$detectNewLine(text);
|
this.$detectNewLine(text);
|
||||||
|
|
||||||
var lines = this.$split(text);
|
return this.insertMergedLines(position, this.$split(text));
|
||||||
var firstLine = lines.splice(0, 1)[0];
|
};
|
||||||
var lastLine = lines.length == 0 ? null : lines.splice(lines.length - 1, 1)[0];
|
|
||||||
|
/**
|
||||||
position = this.insertInLine(position, firstLine);
|
* Inserts `text` into the `position` at the current row. This method also triggers the `"change"` event.
|
||||||
if (lastLine !== null) {
|
*
|
||||||
position = this.insertNewLine(position); // terminate first line
|
* This differs from the `insert` method in two ways:
|
||||||
position = this._insertLines(position.row, lines);
|
* 1. This does NOT handle newline characters (single-line text only).
|
||||||
position = this.insertInLine(position, lastLine || "");
|
* 2. This is faster than the `insert` method for single-line text insertions.
|
||||||
|
*
|
||||||
|
* @param {Object} position The position to insert at; it's an object that looks like `{ row: row, column: column}`
|
||||||
|
* @param {String} text A chunk of text
|
||||||
|
* @returns {Object} Returns an object containing the final row and column, like this:
|
||||||
|
* ```
|
||||||
|
* {row: endRow, column: 0}
|
||||||
|
* ```
|
||||||
|
**/
|
||||||
|
this.insertInLine = function(position, text) {
|
||||||
|
var start = this.clippedPos(position.row, position.column);
|
||||||
|
var end = this.pos(position.row, position.column + text.length);
|
||||||
|
|
||||||
|
this.applyDelta({
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
action: "insert",
|
||||||
|
lines: [text]
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (column == undefined)
|
||||||
|
column = line.length;
|
||||||
|
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;
|
return position;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires whenever the document changes.
|
* Fires whenever the document changes.
|
||||||
*
|
*
|
||||||
* Several methods trigger different `"change"` events. Below is a list of each action type, followed by each property that's also available:
|
* Several methods trigger different `"change"` events. Below is a list of each action type, followed by each property that's also available:
|
||||||
*
|
*
|
||||||
* * `"insertLines"` (emitted by [[Document.insertLines]])
|
* * `"insert"`
|
||||||
* * `range`: the [[Range]] of the change within the document
|
* * `range`: the [[Range]] of the change within the document
|
||||||
* * `lines`: the lines in the document that are changing
|
* * `lines`: the lines being added
|
||||||
* * `"insertText"` (emitted by [[Document.insertNewLine]])
|
* * `"remove"`
|
||||||
* * `range`: the [[Range]] of the change within the document
|
* * `range`: the [[Range]] of the change within the document
|
||||||
* * `text`: the text that's being added
|
* * `lines`: the lines being removed
|
||||||
* * `"removeLines"` (emitted by [[Document.insertLines]])
|
*
|
||||||
* * `range`: the [[Range]] of the change within the document
|
* @event change
|
||||||
* * `lines`: the lines in the document that were removed
|
* @param {Object} e Contains at least one property called `"action"`. `"action"` indicates the action that triggered the change. Each action also has a set of additional properties.
|
||||||
* * `nl`: the new line character (as defined by [[Document.getNewLineCharacter]])
|
*
|
||||||
* * `"removeText"` (emitted by [[Document.removeInLine]] and [[Document.removeNewLine]])
|
**/
|
||||||
* * `range`: the [[Range]] of the change within the document
|
|
||||||
* * `text`: the text that's being removed
|
|
||||||
*
|
|
||||||
* @event change
|
|
||||||
* @param {Object} e Contains at least one property called `"action"`. `"action"` indicates the action that triggered the change. Each action also has a set of additional properties.
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
/**
|
/**
|
||||||
* Inserts the elements in `lines` into the document, starting at the row index given by `row`. This method also triggers the `'change'` event.
|
* Inserts the elements in `lines` into the document as full lines (does not merge with existing line), starting at the row index given by `row`. This method also triggers the `"change"` event.
|
||||||
* @param {Number} row The index of the row to insert at
|
* @param {Number} row The index of the row to insert at
|
||||||
* @param {Array} lines An array of strings
|
* @param {Array} lines An array of strings
|
||||||
* @returns {Object} Contains the final row and column, like this:
|
* @returns {Object} Contains the final row and column, like this:
|
||||||
|
|
@ -302,101 +364,57 @@ var Document = function(text) {
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.insertLines = function(row, lines) {
|
this.insertFullLines = function(row, lines) {
|
||||||
if (row >= this.getLength())
|
// Clip to document.
|
||||||
return this.insert({row: row, column: 0}, "\n" + lines.join("\n"));
|
// Allow one past the document end.
|
||||||
return this._insertLines(Math.max(row, 0), lines);
|
row = Math.min(Math.max(row, 0), this.getLength());
|
||||||
};
|
|
||||||
this._insertLines = function(row, lines) {
|
// Calculate insertion point.
|
||||||
if (lines.length == 0)
|
var column = 0;
|
||||||
return {row: row, column: 0};
|
if (row < this.getLength()) {
|
||||||
|
// Insert before the specified row.
|
||||||
// Apply doesn't work for big arrays due to max call stack detection.
|
lines = lines.concat([""]);
|
||||||
// Chrome 64 bit requires threshold smaller than 32k, using a safe value of 20k.
|
column = 0;
|
||||||
// To circumvent that we have to break huge inserts into smaller chunks here.
|
} else {
|
||||||
while (lines.length > 20000) {
|
// Insert after the last row in the document.
|
||||||
var end = this._insertLines(row, lines.slice(0, 20000));
|
lines = [""].concat(lines);
|
||||||
lines = lines.slice(20000);
|
row--;
|
||||||
row = end.row;
|
column = this.$lines[row].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert.
|
||||||
|
this.insertMergedLines({row: row, column: column}, lines);
|
||||||
|
};
|
||||||
|
|
||||||
var args = [row, 0];
|
/**
|
||||||
args.push.apply(args, lines);
|
* Inserts the elements in `lines` into the document, starting at the position index given by `row`. This method also triggers the `"change"` event.
|
||||||
this.$lines.splice.apply(this.$lines, args);
|
* @param {Number} row The index of the row to insert at
|
||||||
|
* @param {Array} lines An array of strings
|
||||||
var range = new Range(row, 0, row + lines.length, 0);
|
* @returns {Object} Contains the final row and column, like this:
|
||||||
var delta = {
|
* ```
|
||||||
action: "insertLines",
|
* {row: endRow, column: 0}
|
||||||
range: range,
|
* ```
|
||||||
|
* If `lines` is empty, this function returns an object containing the current row, and column, like this:
|
||||||
|
* ```
|
||||||
|
* {row: row, column: 0}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
this.insertMergedLines = function(position, lines) {
|
||||||
|
var start = this.clippedPos(position.row, position.column);
|
||||||
|
var end = {
|
||||||
|
row: start.row + lines.length - 1,
|
||||||
|
column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length
|
||||||
|
};
|
||||||
|
|
||||||
|
this.applyDelta({
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
action: "insert",
|
||||||
lines: lines
|
lines: lines
|
||||||
};
|
});
|
||||||
this._signal("change", { data: delta });
|
|
||||||
return range.end;
|
return this.clonePos(end);
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a new line into the document at the current row's `position`. This method also triggers the `'change'` event.
|
|
||||||
* @param {Object} position The position to insert at
|
|
||||||
* @returns {Object} Returns an object containing the final row and column, like this:<br/>
|
|
||||||
* ```
|
|
||||||
* {row: endRow, column: 0}
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
this.insertNewLine = function(position) {
|
|
||||||
position = this.$clipPosition(position);
|
|
||||||
var line = this.$lines[position.row] || "";
|
|
||||||
|
|
||||||
this.$lines[position.row] = line.substring(0, position.column);
|
|
||||||
this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length));
|
|
||||||
|
|
||||||
var end = {
|
|
||||||
row : position.row + 1,
|
|
||||||
column : 0
|
|
||||||
};
|
|
||||||
|
|
||||||
var delta = {
|
|
||||||
action: "insertText",
|
|
||||||
range: Range.fromPoints(position, end),
|
|
||||||
text: this.getNewLineCharacter()
|
|
||||||
};
|
|
||||||
this._signal("change", { data: delta });
|
|
||||||
|
|
||||||
return end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts `text` into the `position` at the current row. This method also triggers the `'change'` event.
|
|
||||||
* @param {Object} position The position to insert at; it's an object that looks like `{ row: row, column: column}`
|
|
||||||
* @param {String} text A chunk of text
|
|
||||||
* @returns {Object} Returns an object containing the final row and column, like this:
|
|
||||||
* ```
|
|
||||||
* {row: endRow, column: 0}
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
this.insertInLine = function(position, text) {
|
|
||||||
if (text.length == 0)
|
|
||||||
return position;
|
|
||||||
|
|
||||||
var line = this.$lines[position.row] || "";
|
|
||||||
|
|
||||||
this.$lines[position.row] = line.substring(0, position.column) + text
|
|
||||||
+ line.substring(position.column);
|
|
||||||
|
|
||||||
var end = {
|
|
||||||
row : position.row,
|
|
||||||
column : position.column + text.length
|
|
||||||
};
|
|
||||||
|
|
||||||
var delta = {
|
|
||||||
action: "insertText",
|
|
||||||
range: Range.fromPoints(position, end),
|
|
||||||
text: text
|
|
||||||
};
|
|
||||||
this._signal("change", { data: delta });
|
|
||||||
|
|
||||||
return end;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -406,113 +424,90 @@ var Document = function(text) {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.remove = function(range) {
|
this.remove = function(range) {
|
||||||
if (!(range instanceof Range))
|
var start = this.clippedPos(range.start.row, range.start.column);
|
||||||
range = Range.fromPoints(range.start, range.end);
|
var end = this.clippedPos(range.end.row, range.end.column);
|
||||||
// clip to document
|
this.applyDelta({
|
||||||
range.start = this.$clipPosition(range.start);
|
start: start,
|
||||||
range.end = this.$clipPosition(range.end);
|
end: end,
|
||||||
|
action: "remove",
|
||||||
if (range.isEmpty())
|
lines: this.getLinesForRange({start: start, end: end})
|
||||||
return range.start;
|
});
|
||||||
|
return this.clonePos(start);
|
||||||
var firstRow = range.start.row;
|
|
||||||
var lastRow = range.end.row;
|
|
||||||
|
|
||||||
if (range.isMultiLine()) {
|
|
||||||
var firstFullRow = range.start.column == 0 ? firstRow : firstRow + 1;
|
|
||||||
var lastFullRow = lastRow - 1;
|
|
||||||
|
|
||||||
if (range.end.column > 0)
|
|
||||||
this.removeInLine(lastRow, 0, range.end.column);
|
|
||||||
|
|
||||||
if (lastFullRow >= firstFullRow)
|
|
||||||
this._removeLines(firstFullRow, lastFullRow);
|
|
||||||
|
|
||||||
if (firstFullRow != firstRow) {
|
|
||||||
this.removeInLine(firstRow, range.start.column, this.getLine(firstRow).length);
|
|
||||||
this.removeNewLine(range.start.row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.removeInLine(firstRow, range.start.column, range.end.column);
|
|
||||||
}
|
|
||||||
return range.start;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the specified columns from the `row`. This method also triggers the `'change'` event.
|
* Removes the specified columns from the `row`. This method also triggers a `"change"` event.
|
||||||
* @param {Number} row The row to remove from
|
* @param {Number} row The row to remove from
|
||||||
* @param {Number} startColumn The column to start removing at
|
* @param {Number} startColumn The column to start removing at
|
||||||
* @param {Number} endColumn The column to stop removing at
|
* @param {Number} endColumn The column to stop removing at
|
||||||
* @returns {Object} Returns an object containing `startRow` and `startColumn`, indicating the new row and column values.<br/>If `startColumn` is equal to `endColumn`, this function returns nothing.
|
* @returns {Object} Returns an object containing `startRow` and `startColumn`, indicating the new row and column values.<br/>If `startColumn` is equal to `endColumn`, this function returns nothing.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.removeInLine = function(row, startColumn, endColumn) {
|
this.removeInLine = function(row, startColumn, endColumn) {
|
||||||
if (startColumn == endColumn)
|
var start = this.clippedPos(row, startColumn);
|
||||||
return;
|
var end = this.clippedPos(row, endColumn);
|
||||||
|
|
||||||
var range = new Range(row, startColumn, row, endColumn);
|
this.applyDelta({
|
||||||
var line = this.getLine(row);
|
start: start,
|
||||||
var removed = line.substring(startColumn, endColumn);
|
end: end,
|
||||||
var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length);
|
action: "remove",
|
||||||
this.$lines.splice(row, 1, newLine);
|
lines: this.getLinesForRange({start: start, end: end})
|
||||||
|
}, true);
|
||||||
var delta = {
|
|
||||||
action: "removeText",
|
return this.clonePos(start);
|
||||||
range: range,
|
|
||||||
text: removed
|
|
||||||
};
|
|
||||||
this._signal("change", { data: delta });
|
|
||||||
return range.start;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a range of full lines. This method also triggers the `'change'` event.
|
* Removes a range of full lines. This method also triggers the `"change"` event.
|
||||||
* @param {Number} firstRow The first row to be removed
|
* @param {Number} firstRow The first row to be removed
|
||||||
* @param {Number} lastRow The last row to be removed
|
* @param {Number} lastRow The last row to be removed
|
||||||
* @returns {[String]} Returns all the removed lines.
|
* @returns {[String]} Returns all the removed lines.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.removeLines = function(firstRow, lastRow) {
|
this.removeFullLines = function(firstRow, lastRow) {
|
||||||
if (firstRow < 0 || lastRow >= this.getLength())
|
// Clip to document.
|
||||||
return this.remove(new Range(firstRow, 0, lastRow + 1, 0));
|
firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1);
|
||||||
return this._removeLines(firstRow, lastRow);
|
lastRow = Math.min(Math.max(0, lastRow ), this.getLength() - 1);
|
||||||
};
|
|
||||||
|
// Calculate deletion range.
|
||||||
this._removeLines = function(firstRow, lastRow) {
|
// Delete the ending new line unless we're at the end of the document.
|
||||||
var range = new Range(firstRow, 0, lastRow + 1, 0);
|
// If we're at the end of the document, delete the starting new line.
|
||||||
var removed = this.$lines.splice(firstRow, lastRow - firstRow + 1);
|
var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0;
|
||||||
|
var deleteLastNewLine = lastRow < this.getLength() - 1;
|
||||||
var delta = {
|
var startRow = ( deleteFirstNewLine ? firstRow - 1 : firstRow );
|
||||||
action: "removeLines",
|
var startCol = ( deleteFirstNewLine ? this.getLine(startRow).length : 0 );
|
||||||
range: range,
|
var endRow = ( deleteLastNewLine ? lastRow + 1 : lastRow );
|
||||||
nl: this.getNewLineCharacter(),
|
var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length );
|
||||||
lines: removed
|
var range = new Range(startRow, startCol, endRow, endCol);
|
||||||
};
|
|
||||||
this._signal("change", { data: delta });
|
// Store delelted lines with bounding newlines ommitted (maintains previous behavior).
|
||||||
return removed;
|
var deletedLines = this.$lines.slice(firstRow, lastRow + 1);
|
||||||
|
|
||||||
|
this.applyDelta({
|
||||||
|
start: range.start,
|
||||||
|
end: range.end,
|
||||||
|
action: "remove",
|
||||||
|
lines: this.getLinesForRange(range)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the deleted lines.
|
||||||
|
return deletedLines;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the new line between `row` and the row immediately following it. This method also triggers the `'change'` event.
|
* Removes the new line between `row` and the row immediately following it. This method also triggers the `"change"` event.
|
||||||
* @param {Number} row The row to check
|
* @param {Number} row The row to check
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.removeNewLine = function(row) {
|
this.removeNewLine = function(row) {
|
||||||
var firstLine = this.getLine(row);
|
if (row < this.getLength() - 1 && row >= 0) {
|
||||||
var secondLine = this.getLine(row+1);
|
this.applyDelta({
|
||||||
|
start: this.pos(row, this.getLine(row).length),
|
||||||
var range = new Range(row, firstLine.length, row+1, 0);
|
end: this.pos(row + 1, 0),
|
||||||
var line = firstLine + secondLine;
|
action: "remove",
|
||||||
|
lines: ["", ""]
|
||||||
this.$lines.splice(row, 2, line);
|
});
|
||||||
|
}
|
||||||
var delta = {
|
|
||||||
action: "removeText",
|
|
||||||
range: range,
|
|
||||||
text: this.getNewLineCharacter()
|
|
||||||
};
|
|
||||||
this._signal("change", { data: delta });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -526,9 +521,9 @@ var Document = function(text) {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.replace = function(range, text) {
|
this.replace = function(range, text) {
|
||||||
if (!(range instanceof Range))
|
if (!range instanceof Range)
|
||||||
range = Range.fromPoints(range.start, range.end);
|
range = Range.fromPoints(range.start, range.end);
|
||||||
if (text.length == 0 && range.isEmpty())
|
if (text.length === 0 && range.isEmpty())
|
||||||
return range.start;
|
return range.start;
|
||||||
|
|
||||||
// Shortcut: If the text we want to insert is the same as it is already
|
// Shortcut: If the text we want to insert is the same as it is already
|
||||||
|
|
@ -537,55 +532,106 @@ var Document = function(text) {
|
||||||
return range.end;
|
return range.end;
|
||||||
|
|
||||||
this.remove(range);
|
this.remove(range);
|
||||||
|
var end;
|
||||||
if (text) {
|
if (text) {
|
||||||
var end = this.insert(range.start, text);
|
end = this.insert(range.start, text);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
end = range.start;
|
end = range.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
return end;
|
return end;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies all the changes previously accumulated. These can be either `'insertText'`, `'insertLines'`, `'removeText'`, and `'removeLines'`.
|
* Applies all changes in `deltas` to the document.
|
||||||
|
* @param {Array} deltas An array of delta objects (can include "insert" and "remove" actions)
|
||||||
**/
|
**/
|
||||||
this.applyDeltas = function(deltas) {
|
this.applyDeltas = function(deltas) {
|
||||||
for (var i=0; i<deltas.length; i++) {
|
for (var i=0; i<deltas.length; i++) {
|
||||||
var delta = deltas[i];
|
this.applyDelta(deltas[i]);
|
||||||
var range = Range.fromPoints(delta.range.start, delta.range.end);
|
|
||||||
|
|
||||||
if (delta.action == "insertLines")
|
|
||||||
this.insertLines(range.start.row, delta.lines);
|
|
||||||
else if (delta.action == "insertText")
|
|
||||||
this.insert(range.start, delta.text);
|
|
||||||
else if (delta.action == "removeLines")
|
|
||||||
this._removeLines(range.start.row, range.end.row - 1);
|
|
||||||
else if (delta.action == "removeText")
|
|
||||||
this.remove(range);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverts any changes previously applied. These can be either `'insertText'`, `'insertLines'`, `'removeText'`, and `'removeLines'`.
|
* Reverts all changes in `deltas` from the document.
|
||||||
|
* @param {Array} deltas An array of delta objects (can include "insert" and "remove" actions)
|
||||||
**/
|
**/
|
||||||
this.revertDeltas = function(deltas) {
|
this.revertDeltas = function(deltas) {
|
||||||
for (var i=deltas.length-1; i>=0; i--) {
|
for (var i=deltas.length-1; i>=0; i--) {
|
||||||
var delta = deltas[i];
|
this.revertDelta(deltas[i]);
|
||||||
|
|
||||||
var range = Range.fromPoints(delta.range.start, delta.range.end);
|
|
||||||
|
|
||||||
if (delta.action == "insertLines")
|
|
||||||
this._removeLines(range.start.row, range.end.row - 1);
|
|
||||||
else if (delta.action == "insertText")
|
|
||||||
this.remove(range);
|
|
||||||
else if (delta.action == "removeLines")
|
|
||||||
this._insertLines(range.start.row, delta.lines);
|
|
||||||
else if (delta.action == "removeText")
|
|
||||||
this.insert(range.start, delta.text);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies `delta` to the document.
|
||||||
|
* @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 (isInsert ? delta.lines.length <= 1 && !delta.lines[0]
|
||||||
|
: !Range.comparePoints(delta.start, delta.end)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInsert && delta.lines.length > 20000)
|
||||||
|
this.$splitAndapplyLargeDelta(delta, 20000);
|
||||||
|
|
||||||
|
// Apply.
|
||||||
|
applyDelta(this.$lines, delta, doNotValidate);
|
||||||
|
this._signal("change", delta);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$splitAndapplyLargeDelta = function(delta, MAX) {
|
||||||
|
// 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 smallest threshold is on chrome 40 ~42000.
|
||||||
|
// we use 20000 to leave some space for actual stack
|
||||||
|
//
|
||||||
|
// To Do: Ideally we'd be consistent and also split 'delete' deltas. We don't do this now, because delete
|
||||||
|
// 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 lines = delta.lines;
|
||||||
|
var l = lines.length;
|
||||||
|
var row = delta.start.row;
|
||||||
|
var column = delta.start.column;
|
||||||
|
var from = 0, to = 0;
|
||||||
|
do {
|
||||||
|
from = to;
|
||||||
|
to += MAX - 1;
|
||||||
|
var chunk = lines.slice(from, to);
|
||||||
|
if (to > l) {
|
||||||
|
// Update remaining delta.
|
||||||
|
delta.lines = chunk;
|
||||||
|
delta.start.row = row + from;
|
||||||
|
delta.start.column = column;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chunk.push("");
|
||||||
|
this.applyDelta({
|
||||||
|
start: this.pos(row + from, column),
|
||||||
|
end: this.pos(row + to, column = 0),
|
||||||
|
action: delta.action,
|
||||||
|
lines: chunk
|
||||||
|
}, true);
|
||||||
|
} while(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts `delta` from the document.
|
||||||
|
* @param {Object} delta A delta object (can include "insert" and "remove" actions)
|
||||||
|
**/
|
||||||
|
this.revertDelta = function(delta) {
|
||||||
|
this.applyDelta({
|
||||||
|
start: this.clonePos(delta.start),
|
||||||
|
end: this.clonePos(delta.end),
|
||||||
|
action: (delta.action == "insert" ? "remove" : "insert"),
|
||||||
|
lines: delta.lines.slice()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an index position in a document to a `{row, column}` object.
|
* Converts an index position in a document to a `{row, column}` object.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insert({row: 0, column: 1}, "juhu");
|
doc.insert({row: 0, column: 1}, "juhu");
|
||||||
assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n"));
|
assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n"));
|
||||||
|
|
@ -63,9 +63,9 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insertNewLine({row: 0, column: 1});
|
doc.insertMergedLines({row: 0, column: 1}, ['', '']);
|
||||||
assert.equal(doc.getValue(), ["1", "2", "34"].join("\n"));
|
assert.equal(doc.getValue(), ["1", "2", "34"].join("\n"));
|
||||||
|
|
||||||
var d = deltas.concat();
|
var d = deltas.concat();
|
||||||
|
|
@ -80,9 +80,9 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insertLines(0, ["aa", "bb"]);
|
doc.insertFullLines(0, ["aa", "bb"]);
|
||||||
assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n"));
|
assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n"));
|
||||||
|
|
||||||
var d = deltas.concat();
|
var d = deltas.concat();
|
||||||
|
|
@ -97,9 +97,9 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insertLines(2, ["aa", "bb"]);
|
doc.insertFullLines(2, ["aa", "bb"]);
|
||||||
assert.equal(doc.getValue(), ["12", "34", "aa", "bb"].join("\n"));
|
assert.equal(doc.getValue(), ["12", "34", "aa", "bb"].join("\n"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -107,9 +107,9 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insertLines(1, ["aa", "bb"]);
|
doc.insertFullLines(1, ["aa", "bb"]);
|
||||||
assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n"));
|
assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n"));
|
||||||
|
|
||||||
var d = deltas.concat();
|
var d = deltas.concat();
|
||||||
|
|
@ -124,7 +124,7 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insert({row: 0, column: 0}, "aa\nbb\ncc");
|
doc.insert({row: 0, column: 0}, "aa\nbb\ncc");
|
||||||
assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n"));
|
assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n"));
|
||||||
|
|
@ -141,9 +141,9 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insert({row: 2, column: 0}, "aa\nbb\ncc");
|
doc.insert({row: 1, column: 2}, "aa\nbb\ncc");
|
||||||
assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n"));
|
assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n"));
|
||||||
|
|
||||||
var d = deltas.concat();
|
var d = deltas.concat();
|
||||||
|
|
@ -158,7 +158,7 @@ module.exports = {
|
||||||
var doc = new Document(["12", "34"]);
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.insert({row: 0, column: 1}, "aa\nbb\ncc");
|
doc.insert({row: 0, column: 1}, "aa\nbb\ncc");
|
||||||
assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n"));
|
assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n"));
|
||||||
|
|
@ -175,7 +175,7 @@ module.exports = {
|
||||||
var doc = new Document(["1234", "5678"]);
|
var doc = new Document(["1234", "5678"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.remove(new Range(0, 1, 0, 3));
|
doc.remove(new Range(0, 1, 0, 3));
|
||||||
assert.equal(doc.getValue(), ["14", "5678"].join("\n"));
|
assert.equal(doc.getValue(), ["14", "5678"].join("\n"));
|
||||||
|
|
@ -192,7 +192,7 @@ module.exports = {
|
||||||
var doc = new Document(["1234", "5678"]);
|
var doc = new Document(["1234", "5678"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.remove(new Range(0, 4, 1, 0));
|
doc.remove(new Range(0, 4, 1, 0));
|
||||||
assert.equal(doc.getValue(), ["12345678"].join("\n"));
|
assert.equal(doc.getValue(), ["12345678"].join("\n"));
|
||||||
|
|
@ -209,7 +209,7 @@ module.exports = {
|
||||||
var doc = new Document(["1234", "5678", "abcd"]);
|
var doc = new Document(["1234", "5678", "abcd"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.remove(new Range(0, 2, 2, 2));
|
doc.remove(new Range(0, 2, 2, 2));
|
||||||
assert.equal(doc.getValue(), ["12cd"].join("\n"));
|
assert.equal(doc.getValue(), ["12cd"].join("\n"));
|
||||||
|
|
@ -226,7 +226,7 @@ module.exports = {
|
||||||
var doc = new Document(["1234", "5678", "abcd"]);
|
var doc = new Document(["1234", "5678", "abcd"]);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
doc.on("change", function(e) { deltas.push(e.data); });
|
doc.on("change", function(e) { deltas.push(e); });
|
||||||
|
|
||||||
doc.remove(new Range(1, 0, 3, 0));
|
doc.remove(new Range(1, 0, 3, 0));
|
||||||
assert.equal(doc.getValue(), ["1234", ""].join("\n"));
|
assert.equal(doc.getValue(), ["1234", ""].join("\n"));
|
||||||
|
|
@ -235,7 +235,7 @@ module.exports = {
|
||||||
"test: remove lines should return the removed lines" : function() {
|
"test: remove lines should return the removed lines" : function() {
|
||||||
var doc = new Document(["1234", "5678", "abcd"]);
|
var doc = new Document(["1234", "5678", "abcd"]);
|
||||||
|
|
||||||
var removed = doc.removeLines(1, 2);
|
var removed = doc.removeFullLines(1, 2);
|
||||||
assert.equal(removed.join("\n"), ["5678", "abcd"].join("\n"));
|
assert.equal(removed.join("\n"), ["5678", "abcd"].join("\n"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,13 +249,12 @@ var EditSession = function(text, mode) {
|
||||||
this.$resetRowCache(fold.start.row);
|
this.$resetRowCache(fold.start.row);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onChange = function(e) {
|
this.onChange = function(delta) {
|
||||||
var delta = e.data;
|
|
||||||
this.$modified = true;
|
this.$modified = true;
|
||||||
|
|
||||||
this.$resetRowCache(delta.range.start.row);
|
this.$resetRowCache(delta.start.row);
|
||||||
|
|
||||||
var removedFolds = this.$updateInternalDataOnChange(e);
|
var removedFolds = this.$updateInternalDataOnChange(delta);
|
||||||
if (!this.$fromUndo && this.$undoManager && !delta.ignore) {
|
if (!this.$fromUndo && this.$undoManager && !delta.ignore) {
|
||||||
this.$deltasDoc.push(delta);
|
this.$deltasDoc.push(delta);
|
||||||
if (removedFolds && removedFolds.length != 0) {
|
if (removedFolds && removedFolds.length != 0) {
|
||||||
|
|
@ -269,7 +268,7 @@ var EditSession = function(text, mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bgTokenizer && this.bgTokenizer.$updateOnChange(delta);
|
this.bgTokenizer && this.bgTokenizer.$updateOnChange(delta);
|
||||||
this._signal("change", e);
|
this._signal("change", delta);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1146,6 +1145,19 @@ var EditSession = function(text, mode) {
|
||||||
this.remove = function(range) {
|
this.remove = function(range) {
|
||||||
return this.doc.remove(range);
|
return this.doc.remove(range);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a range of full lines. This method also triggers the `'change'` event.
|
||||||
|
* @param {Number} firstRow The first row to be removed
|
||||||
|
* @param {Number} lastRow The last row to be removed
|
||||||
|
* @returns {[String]} Returns all the removed lines.
|
||||||
|
*
|
||||||
|
* @related Document.removeFullLines
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
this.removeFullLines = function(firstRow, lastRow){
|
||||||
|
return this.doc.removeFullLines(firstRow, lastRow);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverts previous changes to your document.
|
* Reverts previous changes to your document.
|
||||||
|
|
@ -1222,39 +1234,36 @@ var EditSession = function(text, mode) {
|
||||||
|
|
||||||
this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) {
|
this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) {
|
||||||
function isInsert(delta) {
|
function isInsert(delta) {
|
||||||
var insert =
|
return isUndo ? delta.action !== "insert" : delta.action === "insert";
|
||||||
delta.action === "insertText" || delta.action === "insertLines";
|
|
||||||
return isUndo ? !insert : insert;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var delta = deltas[0];
|
var delta = deltas[0];
|
||||||
var range, point;
|
var range, point;
|
||||||
var lastDeltaIsInsert = false;
|
var lastDeltaIsInsert = false;
|
||||||
if (isInsert(delta)) {
|
if (isInsert(delta)) {
|
||||||
range = Range.fromPoints(delta.range.start, delta.range.end);
|
range = Range.fromPoints(delta.start, delta.end);
|
||||||
lastDeltaIsInsert = true;
|
lastDeltaIsInsert = true;
|
||||||
} else {
|
} else {
|
||||||
range = Range.fromPoints(delta.range.start, delta.range.start);
|
range = Range.fromPoints(delta.start, delta.start);
|
||||||
lastDeltaIsInsert = false;
|
lastDeltaIsInsert = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 1; i < deltas.length; i++) {
|
for (var i = 1; i < deltas.length; i++) {
|
||||||
delta = deltas[i];
|
delta = deltas[i];
|
||||||
if (isInsert(delta)) {
|
if (isInsert(delta)) {
|
||||||
point = delta.range.start;
|
point = delta.start;
|
||||||
if (range.compare(point.row, point.column) == -1) {
|
if (range.compare(point.row, point.column) == -1) {
|
||||||
range.setStart(delta.range.start);
|
range.setStart(point);
|
||||||
}
|
}
|
||||||
point = delta.range.end;
|
point = delta.end;
|
||||||
if (range.compare(point.row, point.column) == 1) {
|
if (range.compare(point.row, point.column) == 1) {
|
||||||
range.setEnd(delta.range.end);
|
range.setEnd(point);
|
||||||
}
|
}
|
||||||
lastDeltaIsInsert = true;
|
lastDeltaIsInsert = true;
|
||||||
} else {
|
} else {
|
||||||
point = delta.range.start;
|
point = delta.start;
|
||||||
if (range.compare(point.row, point.column) == -1) {
|
if (range.compare(point.row, point.column) == -1) {
|
||||||
range =
|
range = Range.fromPoints(delta.start, delta.start);
|
||||||
Range.fromPoints(delta.range.start, delta.range.start);
|
|
||||||
}
|
}
|
||||||
lastDeltaIsInsert = false;
|
lastDeltaIsInsert = false;
|
||||||
}
|
}
|
||||||
|
|
@ -1368,7 +1377,7 @@ var EditSession = function(text, mode) {
|
||||||
this.indentRows = function(startRow, endRow, indentString) {
|
this.indentRows = function(startRow, endRow, indentString) {
|
||||||
indentString = indentString.replace(/\t/g, this.getTabString());
|
indentString = indentString.replace(/\t/g, this.getTabString());
|
||||||
for (var row=startRow; row<=endRow; row++)
|
for (var row=startRow; row<=endRow; row++)
|
||||||
this.insert({row: row, column:0}, indentString);
|
this.doc.insertInLine({row: row, column: 0}, indentString);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1425,11 +1434,11 @@ var EditSession = function(text, mode) {
|
||||||
x.end.row += diff;
|
x.end.row += diff;
|
||||||
return x;
|
return x;
|
||||||
});
|
});
|
||||||
|
|
||||||
var lines = dir == 0
|
var lines = dir == 0
|
||||||
? this.doc.getLines(firstRow, lastRow)
|
? this.doc.getLines(firstRow, lastRow)
|
||||||
: this.doc.removeLines(firstRow, lastRow);
|
: this.doc.removeFullLines(firstRow, lastRow);
|
||||||
this.doc.insertLines(firstRow+diff, lines);
|
this.doc.insertFullLines(firstRow+diff, lines);
|
||||||
folds.length && this.addFolds(folds);
|
folds.length && this.addFolds(folds);
|
||||||
return diff;
|
return diff;
|
||||||
};
|
};
|
||||||
|
|
@ -1439,8 +1448,6 @@ var EditSession = function(text, mode) {
|
||||||
* @param {Number} lastRow The final row to move up
|
* @param {Number} lastRow The final row to move up
|
||||||
* @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
|
* @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
|
||||||
*
|
*
|
||||||
* @related Document.insertLines
|
|
||||||
*
|
|
||||||
**/
|
**/
|
||||||
this.moveLinesUp = function(firstRow, lastRow) {
|
this.moveLinesUp = function(firstRow, lastRow) {
|
||||||
return this.$moveLines(firstRow, lastRow, -1);
|
return this.$moveLines(firstRow, lastRow, -1);
|
||||||
|
|
@ -1451,8 +1458,6 @@ var EditSession = function(text, mode) {
|
||||||
* @param {Number} firstRow The starting row to move down
|
* @param {Number} firstRow The starting row to move down
|
||||||
* @param {Number} lastRow The final row to move down
|
* @param {Number} lastRow The final row to move down
|
||||||
* @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
|
* @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
|
||||||
*
|
|
||||||
* @related Document.insertLines
|
|
||||||
**/
|
**/
|
||||||
this.moveLinesDown = function(firstRow, lastRow) {
|
this.moveLinesDown = function(firstRow, lastRow) {
|
||||||
return this.$moveLines(firstRow, lastRow, 1);
|
return this.$moveLines(firstRow, lastRow, 1);
|
||||||
|
|
@ -1656,34 +1661,23 @@ var EditSession = function(text, mode) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$updateInternalDataOnChange = function(e) {
|
this.$updateInternalDataOnChange = function(delta) {
|
||||||
var useWrapMode = this.$useWrapMode;
|
var useWrapMode = this.$useWrapMode;
|
||||||
var len;
|
var action = delta.action;
|
||||||
var action = e.data.action;
|
var start = delta.start;
|
||||||
var firstRow = e.data.range.start.row;
|
var end = delta.end;
|
||||||
var lastRow = e.data.range.end.row;
|
var firstRow = start.row;
|
||||||
var start = e.data.range.start;
|
var lastRow = end.row;
|
||||||
var end = e.data.range.end;
|
var len = lastRow - firstRow;
|
||||||
var removedFolds = null;
|
var removedFolds = null;
|
||||||
|
|
||||||
if (action.indexOf("Lines") != -1) {
|
|
||||||
if (action == "insertLines") {
|
|
||||||
lastRow = firstRow + (e.data.lines.length);
|
|
||||||
} else {
|
|
||||||
lastRow = firstRow;
|
|
||||||
}
|
|
||||||
len = e.data.lines ? e.data.lines.length : lastRow - firstRow;
|
|
||||||
} else {
|
|
||||||
len = lastRow - firstRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$updating = true;
|
this.$updating = true;
|
||||||
if (len != 0) {
|
if (len != 0) {
|
||||||
if (action.indexOf("remove") != -1) {
|
if (action === "remove") {
|
||||||
this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len);
|
this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len);
|
||||||
|
|
||||||
var foldLines = this.$foldData;
|
var foldLines = this.$foldData;
|
||||||
removedFolds = this.getFoldsInRange(e.data.range);
|
removedFolds = this.getFoldsInRange(delta);
|
||||||
this.removeFolds(removedFolds);
|
this.removeFolds(removedFolds);
|
||||||
|
|
||||||
var foldLine = this.getFoldLine(end.row);
|
var foldLine = this.getFoldLine(end.row);
|
||||||
|
|
@ -1748,10 +1742,10 @@ var EditSession = function(text, mode) {
|
||||||
} else {
|
} else {
|
||||||
// Realign folds. E.g. if you add some new chars before a fold, the
|
// Realign folds. E.g. if you add some new chars before a fold, the
|
||||||
// fold should "move" to the right.
|
// fold should "move" to the right.
|
||||||
len = Math.abs(e.data.range.start.column - e.data.range.end.column);
|
len = Math.abs(delta.start.column - delta.end.column);
|
||||||
if (action.indexOf("remove") != -1) {
|
if (action === "remove") {
|
||||||
// Get all the folds in the change range and remove them.
|
// Get all the folds in the change range and remove them.
|
||||||
removedFolds = this.getFoldsInRange(e.data.range);
|
removedFolds = this.getFoldsInRange(delta);
|
||||||
this.removeFolds(removedFolds);
|
this.removeFolds(removedFolds);
|
||||||
|
|
||||||
len = -len;
|
len = -len;
|
||||||
|
|
|
||||||
|
|
@ -829,15 +829,13 @@ function Folding() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateFoldWidgets = function(e) {
|
this.updateFoldWidgets = function(delta) {
|
||||||
var delta = e.data;
|
var firstRow = delta.start.row;
|
||||||
var range = delta.range;
|
var len = delta.end.row - firstRow;
|
||||||
var firstRow = range.start.row;
|
|
||||||
var len = range.end.row - firstRow;
|
|
||||||
|
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
this.foldWidgets[firstRow] = null;
|
this.foldWidgets[firstRow] = null;
|
||||||
} else if (delta.action == "removeText" || delta.action == "removeLines") {
|
} else if (delta.action == 'remove') {
|
||||||
this.foldWidgets.splice(firstRow, len + 1, null);
|
this.foldWidgets.splice(firstRow, len + 1, null);
|
||||||
} else {
|
} else {
|
||||||
var args = Array(len + 1);
|
var args = Array(len + 1);
|
||||||
|
|
|
||||||
|
|
@ -442,12 +442,12 @@ module.exports = {
|
||||||
session.setTabSize(4);
|
session.setTabSize(4);
|
||||||
assert.equal(session.getScreenWidth(), 2);
|
assert.equal(session.getScreenWidth(), 2);
|
||||||
|
|
||||||
session.doc.insertNewLine({row: 0, column: Infinity});
|
session.doc.insertMergedLines({row: 0, column: Infinity}, ['', '']);
|
||||||
session.doc.insertLines(1, ["123"]);
|
session.doc.insertFullLines(1, ["123"]);
|
||||||
assert.equal(session.getScreenWidth(), 3);
|
assert.equal(session.getScreenWidth(), 3);
|
||||||
|
|
||||||
session.doc.insertNewLine({row: 0, column: Infinity});
|
session.doc.insertMergedLines({row: 0, column: Infinity}, ['', '']);
|
||||||
session.doc.insertLines(1, ["\t\t"]);
|
session.doc.insertFullLines(1, ["\t\t"]);
|
||||||
|
|
||||||
assert.equal(session.getScreenWidth(), 8);
|
assert.equal(session.getScreenWidth(), 8);
|
||||||
|
|
||||||
|
|
@ -471,9 +471,9 @@ module.exports = {
|
||||||
|
|
||||||
session.setUseWrapMode(true);
|
session.setUseWrapMode(true);
|
||||||
|
|
||||||
document.insertLines(0, ["a", "b"]);
|
document.insertFullLines(0, ["a", "b"]);
|
||||||
document.insertLines(2, ["c", "d"]);
|
document.insertFullLines(2, ["c", "d"]);
|
||||||
document.removeLines(1, 2);
|
document.removeFullLines(1, 2);
|
||||||
},
|
},
|
||||||
|
|
||||||
"test wrapMode init has to create wrapData array": function() {
|
"test wrapMode init has to create wrapData array": function() {
|
||||||
|
|
|
||||||
|
|
@ -692,20 +692,15 @@ var Editor = function(renderer, session) {
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.onDocumentChange = function(e) {
|
this.onDocumentChange = function(delta) {
|
||||||
var delta = e.data;
|
// Rerender and emit "change" event.
|
||||||
var range = delta.range;
|
var wrap = this.session.$useWrapMode;
|
||||||
var lastRow;
|
var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity);
|
||||||
|
this.renderer.updateLines(delta.start.row, lastRow, wrap);
|
||||||
|
|
||||||
if (range.start.row == range.end.row && delta.action != "insertLines" && delta.action != "removeLines")
|
this._signal("change", delta);
|
||||||
lastRow = range.end.row;
|
|
||||||
else
|
// Update cursor because tab characters can influence the cursor position.
|
||||||
lastRow = Infinity;
|
|
||||||
this.renderer.updateLines(range.start.row, lastRow, this.session.$useWrapMode);
|
|
||||||
|
|
||||||
this._signal("change", e);
|
|
||||||
|
|
||||||
// update cursor because tab characters can influence the cursor position
|
|
||||||
this.$cursorChange();
|
this.$cursorChange();
|
||||||
this.$updateHighlightActiveLine();
|
this.$updateHighlightActiveLine();
|
||||||
};
|
};
|
||||||
|
|
@ -1633,15 +1628,7 @@ var Editor = function(renderer, session) {
|
||||||
**/
|
**/
|
||||||
this.removeLines = function() {
|
this.removeLines = function() {
|
||||||
var rows = this.$getSelectedRows();
|
var rows = this.$getSelectedRows();
|
||||||
var range;
|
this.session.removeFullLines(rows.first, rows.last);
|
||||||
if (rows.first === 0 || rows.last+1 < this.session.getLength())
|
|
||||||
range = new Range(rows.first, 0, rows.last+1, 0);
|
|
||||||
else
|
|
||||||
range = new Range(
|
|
||||||
rows.first-1, this.session.getLine(rows.first-1).length,
|
|
||||||
rows.last, this.session.getLine(rows.last).length
|
|
||||||
);
|
|
||||||
this.session.remove(range);
|
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -578,15 +578,14 @@ var onSelectionChange = function(evt) {
|
||||||
* and deleting text.
|
* and deleting text.
|
||||||
* @param {!Event} evt The event.
|
* @param {!Event} evt The event.
|
||||||
*/
|
*/
|
||||||
var onChange = function(evt) {
|
var onChange = function(delta) {
|
||||||
var data = evt.data;
|
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case 'removeText':
|
case 'remove':
|
||||||
cvox.Api.speak(data.text, 0, DELETED_PROP);
|
cvox.Api.speak(data.text, 0, DELETED_PROP);
|
||||||
/* Let the future cursor change event know it's from text change. */
|
/* Let the future cursor change event know it's from text change. */
|
||||||
changed = true;
|
changed = true;
|
||||||
break;
|
break;
|
||||||
case 'insertText':
|
case 'insert':
|
||||||
cvox.Api.speak(data.text, 0);
|
cvox.Api.speak(data.text, 0);
|
||||||
/* Let the future cursor change event know it's from text change. */
|
/* Let the future cursor change event know it's from text change. */
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|
|
||||||
|
|
@ -44,13 +44,12 @@ var ElasticTabstopsLite = function(editor) {
|
||||||
this.onExec = function() {
|
this.onExec = function() {
|
||||||
recordChanges = true;
|
recordChanges = true;
|
||||||
};
|
};
|
||||||
this.onChange = function(e) {
|
this.onChange = function(delta) {
|
||||||
var range = e.data.range
|
|
||||||
if (recordChanges) {
|
if (recordChanges) {
|
||||||
if (changedRows.indexOf(range.start.row) == -1)
|
if (changedRows.indexOf(delta.start.row) == -1)
|
||||||
changedRows.push(range.start.row);
|
changedRows.push(delta.start.row);
|
||||||
if (range.end.row != range.start.row)
|
if (delta.end.row != delta.start.row)
|
||||||
changedRows.push(range.end.row);
|
changedRows.push(delta.end.row);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -178,13 +178,6 @@ define(function(require, exports, module) {
|
||||||
return this.ace.inVirtualSelectionMode && this.ace.selection.index;
|
return this.ace.inVirtualSelectionMode && this.ace.selection.index;
|
||||||
};
|
};
|
||||||
this.onChange = function(delta) {
|
this.onChange = function(delta) {
|
||||||
var oldDelta = delta.data;
|
|
||||||
delta = {
|
|
||||||
start: oldDelta.range.start,
|
|
||||||
end: oldDelta.range.end,
|
|
||||||
action: oldDelta.action,
|
|
||||||
lines: oldDelta.lines || [oldDelta.text]
|
|
||||||
};// v1.2 api compatibility
|
|
||||||
if (delta.action[0] == 'i') {
|
if (delta.action[0] == 'i') {
|
||||||
var change = { text: delta.lines };
|
var change = { text: delta.lines };
|
||||||
var curOp = this.curOp = this.curOp || {};
|
var curOp = this.curOp = this.curOp || {};
|
||||||
|
|
@ -578,7 +571,6 @@ define(function(require, exports, module) {
|
||||||
highlight.session = null;
|
highlight.session = null;
|
||||||
};
|
};
|
||||||
highlight.updateOnChange = function(delta) {
|
highlight.updateOnChange = function(delta) {
|
||||||
delta = delta.data.range;// v1.2 api compatibility
|
|
||||||
var row = delta.start.row;
|
var row = delta.start.row;
|
||||||
if (row == delta.end.row) highlight.cache[row] = undefined;
|
if (row == delta.end.row) highlight.cache[row] = undefined;
|
||||||
else highlight.cache.splice(row, highlight.cache.length);
|
else highlight.cache.splice(row, highlight.cache.length);
|
||||||
|
|
|
||||||
|
|
@ -100,16 +100,14 @@ var Gutter = function(parentEl) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$updateAnnotations = function (e) {
|
this.$updateAnnotations = function (delta) {
|
||||||
if (!this.$annotations.length)
|
if (!this.$annotations.length)
|
||||||
return;
|
return;
|
||||||
var delta = e.data;
|
var firstRow = delta.start.row;
|
||||||
var range = delta.range;
|
var len = delta.end.row - firstRow;
|
||||||
var firstRow = range.start.row;
|
|
||||||
var len = range.end.row - firstRow;
|
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else if (delta.action == "removeText" || delta.action == "removeLines") {
|
} else if (delta.action == 'remove') {
|
||||||
this.$annotations.splice(firstRow, len + 1, null);
|
this.$annotations.splice(firstRow, len + 1, null);
|
||||||
} else {
|
} else {
|
||||||
var args = new Array(len + 1);
|
var args = new Array(len + 1);
|
||||||
|
|
|
||||||
|
|
@ -113,18 +113,16 @@ function LineWidgets(session) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateOnChange = function(e) {
|
this.updateOnChange = function(delta) {
|
||||||
var lineWidgets = this.session.lineWidgets;
|
var lineWidgets = this.session.lineWidgets;
|
||||||
if (!lineWidgets) return;
|
if (!lineWidgets) return;
|
||||||
|
|
||||||
var delta = e.data;
|
var startRow = delta.start.row;
|
||||||
var range = delta.range;
|
var len = delta.end.row - startRow;
|
||||||
var startRow = range.start.row;
|
|
||||||
var len = range.end.row - startRow;
|
|
||||||
|
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
// return
|
// return
|
||||||
} else if (delta.action == "removeText" || delta.action == "removeLines") {
|
} else if (delta.action == 'remove') {
|
||||||
var removed = lineWidgets.splice(startRow + 1, len);
|
var removed = lineWidgets.splice(startRow + 1, len);
|
||||||
removed.forEach(function(w) {
|
removed.forEach(function(w) {
|
||||||
w && this.removeLineWidget(w);
|
w && this.removeLineWidget(w);
|
||||||
|
|
|
||||||
|
|
@ -754,9 +754,9 @@ var Editor = require("./editor").Editor;
|
||||||
if (fr < 0) fr = 0;
|
if (fr < 0) fr = 0;
|
||||||
if (lr >= max) lr = max - 1;
|
if (lr >= max) lr = max - 1;
|
||||||
}
|
}
|
||||||
var lines = this.session.doc.removeLines(fr, lr);
|
var lines = this.session.removeFullLines(fr, lr);
|
||||||
lines = this.$reAlignText(lines, guessRange);
|
lines = this.$reAlignText(lines, guessRange);
|
||||||
this.session.doc.insert({row: fr, column: 0}, lines.join("\n") + "\n");
|
this.session.insert({row: fr, column: 0}, lines.join("\n") + "\n");
|
||||||
if (!guessRange) {
|
if (!guessRange) {
|
||||||
range.start.column = 0;
|
range.start.column = 0;
|
||||||
range.end.column = lines[lines.length - 1].length;
|
range.end.column = lines[lines.length - 1].length;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
if (typeof process !== "undefined") {
|
if (typeof process !== "undefined") {
|
||||||
require("amd-loader");
|
require("amd-loader");
|
||||||
|
require("./test/mockdom");
|
||||||
}
|
}
|
||||||
|
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
|
|
@ -41,6 +42,7 @@ var Range = require("./range").Range;
|
||||||
var Editor = require("./editor").Editor;
|
var Editor = require("./editor").Editor;
|
||||||
var EditSession = require("./edit_session").EditSession;
|
var EditSession = require("./edit_session").EditSession;
|
||||||
var MockRenderer = require("./test/mockrenderer").MockRenderer;
|
var MockRenderer = require("./test/mockrenderer").MockRenderer;
|
||||||
|
var UndoManager = require("./undomanager").UndoManager;
|
||||||
|
|
||||||
var editor;
|
var editor;
|
||||||
var exec = function(name, times, args) {
|
var exec = function(name, times, args) {
|
||||||
|
|
@ -259,6 +261,20 @@ module.exports = {
|
||||||
selection.fromJSON(after);
|
selection.fromJSON(after);
|
||||||
assert.ok(!selection.isEqual(before));
|
assert.ok(!selection.isEqual(before));
|
||||||
assert.ok(selection.isEqual(after));
|
assert.ok(selection.isEqual(after));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test multiselect align": function() {
|
||||||
|
var doc = new EditSession(["l1", "l2", "l3"]);
|
||||||
|
doc.setUndoManager(new UndoManager());
|
||||||
|
editor = new Editor(new MockRenderer(), doc);
|
||||||
|
var selection = editor.selection;
|
||||||
|
selection.addRange(new Range(1,0,1,0))
|
||||||
|
selection.addRange(new Range(2,2,2,2))
|
||||||
|
editor.execCommand("alignCursors");
|
||||||
|
assert.equal(' l1\n l2\nl3', editor.getValue());
|
||||||
|
doc.markUndoGroup();
|
||||||
|
editor.execCommand("undo");
|
||||||
|
assert.equal('l1\nl2\nl3', editor.getValue());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,28 +150,27 @@ var PlaceHolder = function(session, length, pos, others, mainClass, othersClass)
|
||||||
* Emitted when the place holder updates.
|
* Emitted when the place holder updates.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.onUpdate = function(event) {
|
this.onUpdate = function(delta) {
|
||||||
var delta = event.data;
|
var range = delta;
|
||||||
var range = delta.range;
|
|
||||||
if(range.start.row !== range.end.row) return;
|
if(range.start.row !== range.end.row) return;
|
||||||
if(range.start.row !== this.pos.row) return;
|
if(range.start.row !== this.pos.row) return;
|
||||||
if (this.$updating) return;
|
if (this.$updating) return;
|
||||||
this.$updating = true;
|
this.$updating = true;
|
||||||
var lengthDiff = delta.action === "insertText" ? range.end.column - range.start.column : range.start.column - range.end.column;
|
var lengthDiff = delta.action === "insert" ? range.end.column - range.start.column : range.start.column - range.end.column;
|
||||||
|
|
||||||
if(range.start.column >= this.pos.column && range.start.column <= this.pos.column + this.length + 1) {
|
if(range.start.column >= this.pos.column && range.start.column <= this.pos.column + this.length + 1) {
|
||||||
var distanceFromStart = range.start.column - this.pos.column;
|
var distanceFromStart = range.start.column - this.pos.column;
|
||||||
this.length += lengthDiff;
|
this.length += lengthDiff;
|
||||||
if(!this.session.$fromUndo) {
|
if(!this.session.$fromUndo) {
|
||||||
if(delta.action === "insertText") {
|
if(delta.action === 'insert') {
|
||||||
for (var i = this.others.length - 1; i >= 0; i--) {
|
for (var i = this.others.length - 1; i >= 0; i--) {
|
||||||
var otherPos = this.others[i];
|
var otherPos = this.others[i];
|
||||||
var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
|
var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
|
||||||
if(otherPos.row === range.start.row && range.start.column < otherPos.column)
|
if(otherPos.row === range.start.row && range.start.column < otherPos.column)
|
||||||
newPos.column += lengthDiff;
|
newPos.column += lengthDiff;
|
||||||
this.doc.insert(newPos, delta.text);
|
this.doc.insertMergedLines(newPos, delta.lines);
|
||||||
}
|
}
|
||||||
} else if(delta.action === "removeText") {
|
} else if(delta.action === 'remove') {
|
||||||
for (var i = this.others.length - 1; i >= 0; i--) {
|
for (var i = this.others.length - 1; i >= 0; i--) {
|
||||||
var otherPos = this.others[i];
|
var otherPos = this.others[i];
|
||||||
var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
|
var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
|
||||||
|
|
@ -181,7 +180,7 @@ var PlaceHolder = function(session, length, pos, others, mainClass, othersClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Special case: insert in beginning
|
// Special case: insert in beginning
|
||||||
if(range.start.column === this.pos.column && delta.action === "insertText") {
|
if(range.start.column === this.pos.column && delta.action === 'insert') {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
this.pos.setPosition(this.pos.row, this.pos.column - lengthDiff);
|
this.pos.setPosition(this.pos.row, this.pos.column - lengthDiff);
|
||||||
for (var i = 0; i < this.others.length; i++) {
|
for (var i = 0; i < this.others.length; i++) {
|
||||||
|
|
@ -193,7 +192,7 @@ var PlaceHolder = function(session, length, pos, others, mainClass, othersClass)
|
||||||
}
|
}
|
||||||
}.bind(this), 0);
|
}.bind(this), 0);
|
||||||
}
|
}
|
||||||
else if(range.start.column === this.pos.column && delta.action === "removeText") {
|
else if(range.start.column === this.pos.column && delta.action === 'remove') {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
for (var i = 0; i < this.others.length; i++) {
|
for (var i = 0; i < this.others.length; i++) {
|
||||||
var other = this.others[i];
|
var other = this.others[i];
|
||||||
|
|
|
||||||
|
|
@ -180,14 +180,13 @@ var RangeList = function() {
|
||||||
this.session = null;
|
this.session = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$onChange = function(e) {
|
this.$onChange = function(delta) {
|
||||||
var changeRange = e.data.range;
|
if (delta.action == "insert"){
|
||||||
if (e.data.action[0] == "i"){
|
var start = delta.start;
|
||||||
var start = changeRange.start;
|
var end = delta.end;
|
||||||
var end = changeRange.end;
|
|
||||||
} else {
|
} else {
|
||||||
var end = changeRange.start;
|
var end = delta.start;
|
||||||
var start = changeRange.end;
|
var start = delta.end;
|
||||||
}
|
}
|
||||||
var startRow = start.row;
|
var startRow = start.row;
|
||||||
var endRow = end.row;
|
var endRow = end.row;
|
||||||
|
|
|
||||||
|
|
@ -667,11 +667,11 @@ var TabstopManager = function(editor) {
|
||||||
this.editor = null;
|
this.editor = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onChange = function(e) {
|
this.onChange = function(delta) {
|
||||||
var changeRange = e.data.range;
|
var changeRange = delta;
|
||||||
var isRemove = e.data.action[0] == "r";
|
var isRemove = delta.action[0] == "r";
|
||||||
var start = changeRange.start;
|
var start = delta.start;
|
||||||
var end = changeRange.end;
|
var end = delta.end;
|
||||||
var startRow = start.row;
|
var startRow = start.row;
|
||||||
var endRow = end.row;
|
var endRow = end.row;
|
||||||
var lineDif = endRow - startRow;
|
var lineDif = endRow - startRow;
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,19 @@ var UndoManager = function() {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.execute = function(options) {
|
this.execute = function(options) {
|
||||||
var deltas = options.args[0];
|
// Normalize deltas for storage.
|
||||||
|
// var deltaSets = this.$serializeDeltas(options.args[0]);
|
||||||
|
var deltaSets = options.args[0];
|
||||||
|
// Add deltas to undo stack.
|
||||||
this.$doc = options.args[1];
|
this.$doc = options.args[1];
|
||||||
if (options.merge && this.hasUndo()){
|
if (options.merge && this.hasUndo()){
|
||||||
this.dirtyCounter--;
|
this.dirtyCounter--;
|
||||||
deltas = this.$undoStack.pop().concat(deltas);
|
deltaSets = this.$undoStack.pop().concat(deltaSets);
|
||||||
}
|
}
|
||||||
this.$undoStack.push(deltas);
|
this.$undoStack.push(deltaSets);
|
||||||
|
|
||||||
|
// Reset redo stack.
|
||||||
this.$redoStack = [];
|
this.$redoStack = [];
|
||||||
|
|
||||||
if (this.dirtyCounter < 0) {
|
if (this.dirtyCounter < 0) {
|
||||||
// The user has made a change after undoing past the last clean state.
|
// The user has made a change after undoing past the last clean state.
|
||||||
// We can never get back to a clean state now until markClean() is called.
|
// We can never get back to a clean state now until markClean() is called.
|
||||||
|
|
@ -86,12 +90,11 @@ var UndoManager = function() {
|
||||||
* @returns {Range} The range of the undo.
|
* @returns {Range} The range of the undo.
|
||||||
**/
|
**/
|
||||||
this.undo = function(dontSelect) {
|
this.undo = function(dontSelect) {
|
||||||
var deltas = this.$undoStack.pop();
|
var deltaSets = this.$undoStack.pop();
|
||||||
var undoSelectionRange = null;
|
var undoSelectionRange = null;
|
||||||
if (deltas) {
|
if (deltaSets) {
|
||||||
undoSelectionRange =
|
undoSelectionRange = this.$doc.undoChanges(this.$deserializeDeltas(deltaSets), dontSelect);
|
||||||
this.$doc.undoChanges(deltas, dontSelect);
|
this.$redoStack.push(deltaSets);
|
||||||
this.$redoStack.push(deltas);
|
|
||||||
this.dirtyCounter--;
|
this.dirtyCounter--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,15 +108,14 @@ var UndoManager = function() {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.redo = function(dontSelect) {
|
this.redo = function(dontSelect) {
|
||||||
var deltas = this.$redoStack.pop();
|
var deltaSets = this.$redoStack.pop();
|
||||||
var redoSelectionRange = null;
|
var redoSelectionRange = null;
|
||||||
if (deltas) {
|
if (deltaSets) {
|
||||||
redoSelectionRange =
|
redoSelectionRange =
|
||||||
this.$doc.redoChanges(deltas, dontSelect);
|
this.$doc.redoChanges(this.$deserializeDeltas(deltaSets), dontSelect);
|
||||||
this.$undoStack.push(deltas);
|
this.$undoStack.push(deltaSets);
|
||||||
this.dirtyCounter++;
|
this.dirtyCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return redoSelectionRange;
|
return redoSelectionRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -161,7 +163,52 @@ var UndoManager = function() {
|
||||||
this.isClean = function() {
|
this.isClean = function() {
|
||||||
return this.dirtyCounter === 0;
|
return this.dirtyCounter === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Serializes deltaSets to reduce memory usage.
|
||||||
|
this.$serializeDeltas = function(deltaSets) {
|
||||||
|
return cloneDeltaSetsObj(deltaSets, $serializeDelta);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deserializes deltaSets to allow application to the document.
|
||||||
|
this.$deserializeDeltas = function(deltaSets) {
|
||||||
|
return cloneDeltaSetsObj(deltaSets, $deserializeDelta);
|
||||||
|
};
|
||||||
|
|
||||||
|
function $serializeDelta(delta){
|
||||||
|
return {
|
||||||
|
action: delta.action,
|
||||||
|
start: delta.start,
|
||||||
|
end: delta.end,
|
||||||
|
lines: delta.lines.length == 1 ? null : delta.lines,
|
||||||
|
text: delta.lines.length == 1 ? delta.lines[0] : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function $deserializeDelta(delta) {
|
||||||
|
return {
|
||||||
|
action: delta.action,
|
||||||
|
start: delta.start,
|
||||||
|
end: delta.end,
|
||||||
|
lines: delta.lines || [delta.text]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneDeltaSetsObj(deltaSets_old, fnGetModifiedDelta) {
|
||||||
|
var deltaSets_new = new Array(deltaSets_old.length);
|
||||||
|
for (var i = 0; i < deltaSets_old.length; i++) {
|
||||||
|
var deltaSet_old = deltaSets_old[i];
|
||||||
|
var deltaSet_new = { group: deltaSet_old.group, deltas: new Array(deltaSet_old.length)};
|
||||||
|
|
||||||
|
for (var j = 0; j < deltaSet_old.deltas.length; j++) {
|
||||||
|
var delta_old = deltaSet_old.deltas[j];
|
||||||
|
deltaSet_new.deltas[j] = fnGetModifiedDelta(delta_old);
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaSets_new[i] = deltaSet_new;
|
||||||
|
}
|
||||||
|
return deltaSets_new;
|
||||||
|
}
|
||||||
|
|
||||||
}).call(UndoManager.prototype);
|
}).call(UndoManager.prototype);
|
||||||
|
|
||||||
exports.UndoManager = UndoManager;
|
exports.UndoManager = UndoManager;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var Range = require("../range").Range;
|
||||||
var Document = require("../document").Document;
|
var Document = require("../document").Document;
|
||||||
var lang = require("../lib/lang");
|
var lang = require("../lib/lang");
|
||||||
|
|
||||||
|
|
@ -12,7 +13,19 @@ var Mirror = exports.Mirror = function(sender) {
|
||||||
|
|
||||||
var _self = this;
|
var _self = this;
|
||||||
sender.on("change", function(e) {
|
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)
|
if (_self.$timeout)
|
||||||
return deferredUpdate.schedule(_self.$timeout);
|
return deferredUpdate.schedule(_self.$timeout);
|
||||||
_self.onUpdate();
|
_self.onUpdate();
|
||||||
|
|
|
||||||
|
|
@ -162,19 +162,22 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) {
|
||||||
doc.on("change", this.changeListener);
|
doc.on("change", this.changeListener);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.changeListener = function(e) {
|
this.changeListener = function(delta) {
|
||||||
if (!this.deltaQueue) {
|
if (!this.deltaQueue) {
|
||||||
this.deltaQueue = [e.data];
|
this.deltaQueue = [];
|
||||||
setTimeout(this.$sendDeltaQueue, 0);
|
setTimeout(this.$sendDeltaQueue, 0);
|
||||||
} else
|
}
|
||||||
this.deltaQueue.push(e.data);
|
if (delta.action == "insert")
|
||||||
|
this.deltaQueue.push(delta.start, delta.lines);
|
||||||
|
else
|
||||||
|
this.deltaQueue.push(delta.start, delta.end);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$sendDeltaQueue = function() {
|
this.$sendDeltaQueue = function() {
|
||||||
var q = this.deltaQueue;
|
var q = this.deltaQueue;
|
||||||
if (!q) return;
|
if (!q) return;
|
||||||
this.deltaQueue = null;
|
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()]);
|
this.call("setValue", [this.$doc.getValue()]);
|
||||||
} else
|
} else
|
||||||
this.emit("change", {data: q});
|
this.emit("change", {data: q});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue