From c2f7944a753f599d14097adfa33ef5d86f324b82 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 18 Jan 2011 11:40:24 +0100 Subject: [PATCH] split off the document from the edit session --- lib/ace/document.js | 796 +++++----------------------- lib/ace/edit_session.js | 845 ++++++++++++++++++++++++++++++ lib/ace/test/document_test.js | 217 +------- lib/ace/test/edit_session_test.js | 295 +++++++++++ 4 files changed, 1284 insertions(+), 869 deletions(-) create mode 100644 lib/ace/edit_session.js create mode 100644 lib/ace/test/edit_session_test.js diff --git a/lib/ace/document.js b/lib/ace/document.js index dd8ad7d9..99a76fc4 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -40,38 +40,31 @@ define(function(require, exports, module) { var oop = require("pilot/oop"); var lang = require("pilot/lang"); var EventEmitter = require("pilot/event_emitter").EventEmitter; -var Selection = require("ace/selection").Selection; -var TextMode = require("ace/mode/text").Mode; var Range = require("ace/range").Range; -var NO_CHANGE_DELTAS = {}; - -var Document = function(text, mode) { - - this.modified = true; +var Document = function(text) { this.$lines = []; - this.selection = new Selection(this); - this.$breakpoints = []; - this.listeners = []; - if (mode) { - this.setMode(mode); - } - if (Array.isArray(text)) { - this.$insertLines(0, text); + this.insertLines(0, text); } else { - this.$insert({row: 0, column: 0}, text); + this.insert({row: 0, column:0}, text); } }; - (function() { oop.implement(this, EventEmitter); - this.$undoManager = null; - + this.setValue = function(text) { + this.remove(new Range(0, 0, this.$lines.length, this.$lines[this.$lines.length-1].length)); + this.insertLines(0, this.$split(text)); + }; + + this.getValue = function() { + return this.$lines.join(this.getNewLineCharacter()); + }; + // check for IE split bug if ("aaa".split(/a/).length == 0) this.$split = function(text) { @@ -82,125 +75,7 @@ var Document = function(text, mode) { return text.split(/\r\n|\r|\n/); }; - this.setValue = function(text) { - var args = [0, this.$lines.length]; - args.push.apply(args, this.$split(text)); - this.$lines.splice.apply(this.$lines, args); - this.modified = true; - this.fireChangeEvent(0); - }; - - this.toString = function() { - return this.$lines.join(this.$getNewLineCharacter()); - }; - this.getValue = this.toString; - - this.getSelection = function() { - return this.selection; - }; - - this.fireChangeEvent = function(firstRow, lastRow) { - var data = { - firstRow: firstRow, - lastRow: lastRow - }; - this._dispatchEvent("change", { data: data}); - }; - - this.setUndoManager = function(undoManager) { - this.$undoManager = undoManager; - this.$deltas = []; - - if (this.$informUndoManager) { - this.$informUndoManager.cancel(); - } - - if (undoManager) { - var self = this; - this.$informUndoManager = lang.deferredCall(function() { - if (self.$deltas.length > 0) - undoManager.execute({ - action : "aceupdate", - args : [self.$deltas, self] - }); - self.$deltas = []; - }); - } - }; - - this.$defaultUndoManager = { - undo: function() {}, - redo: function() {} - }; - - this.getUndoManager = function() { - return this.$undoManager || this.$defaultUndoManager; - }, - - this.getTabString = function() { - if (this.getUseSoftTabs()) { - return lang.stringRepeat(" ", this.getTabSize()); - } else { - return "\t"; - } - }; - - this.$useSoftTabs = true; - this.setUseSoftTabs = function(useSoftTabs) { - if (this.$useSoftTabs === useSoftTabs) return; - - this.$useSoftTabs = useSoftTabs; - }; - - this.getUseSoftTabs = function() { - return this.$useSoftTabs; - }; - - this.$tabSize = 4; - this.setTabSize = function(tabSize) { - if (isNaN(tabSize) || this.$tabSize === tabSize) return; - - this.modified = true; - this.$tabSize = tabSize; - this._dispatchEvent("changeTabSize"); - }; - - this.getTabSize = function() { - return this.$tabSize; - }; - - this.isTabStop = function(position) { - return this.$useSoftTabs && (position.column % this.$tabSize == 0); - }; - - this.getBreakpoints = function() { - return this.$breakpoints; - }; - - this.setBreakpoints = function(rows) { - this.$breakpoints = []; - for (var i=0; i 0) { - inToken = !!line.charAt(column - 1).match(this.tokenRe); - } - - if (!inToken) { - inToken = !!line.charAt(column).match(this.tokenRe); - } - - var re = inToken ? this.tokenRe : this.nonTokenRe; - - var start = column; - if (start > 0) { - do { - start--; - } - while (start >= 0 && line.charAt(start).match(re)); - start++; - } - - var end = column; - while (end < line.length && line.charAt(end).match(re)) { - end++; - } - - return new Range(row, start, row, end); - }; - - this.$getNewLineCharacter = function() { + this.getNewLineCharacter = function() { switch (this.$newLineMode) { case "windows": return "\r\n"; @@ -268,66 +109,9 @@ var Document = function(text, mode) { this.getNewLineMode = function() { return this.$newLineMode; }; - - this.$mode = null; - this.setMode = function(mode) { - if (this.$mode === mode) return; - - this.$mode = mode; - this._dispatchEvent("changeMode"); - }; - - this.getMode = function() { - if (!this.$mode) { - this.$mode = new TextMode(); - } - return this.$mode; - }; - - this.$scrollTop = 0; - this.setScrollTopRow = function(scrollTopRow) { - if (this.$scrollTop === scrollTopRow) return; - - this.$scrollTop = scrollTopRow; - this._dispatchEvent("changeScrollTop"); - }; - - this.getScrollTopRow = function() { - return this.$scrollTop; - }; - - this.getWidth = function() { - this.$computeWidth(); - return this.width; - }; - - this.getScreenWidth = function() { - this.$computeWidth(); - return this.screenWidth; - }; - - this.$computeWidth = function() { - if (this.modified) { - this.modified = false; - - var lines = this.$lines; - var longestLine = 0; - var longestScreenLine = 0; - var tabSize = this.getTabSize(); - - for ( var i = 0; i < lines.length; i++) { - var len = lines[i].length; - longestLine = Math.max(longestLine, len); - - lines[i].replace("\t", function(m) { - len += tabSize-1; - return m; - }); - longestScreenLine = Math.max(longestScreenLine, len); - } - this.width = longestLine; - this.screenWidth = longestScreenLine; - } + + this.isNewLine = function(text) { + return (text == "\r\n" || text == "\r" || text == "\n"); }; /** @@ -337,14 +121,6 @@ var Document = function(text, mode) { return this.$lines[row] || ""; }; - /** - * Get a line as it is displayed on screen. Tabs are replaced by spaces. - */ - this.getDisplayLine = function(row) { - var tab = new Array(this.getTabSize()+1).join(" "); - return this.$lines[row].replace(/\t/g, tab); - }; - this.getLines = function(firstRow, lastRow) { return this.$lines.slice(firstRow, lastRow+1); }; @@ -363,482 +139,184 @@ var Document = function(text, mode) { lines.push(this.$lines[range.start.row].substring(range.start.column)); lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); lines.push(this.$lines[range.end.row].substring(0, range.end.column)); - return lines.join(this.$getNewLineCharacter()); + return lines.join(this.getNewLineCharacter()); } }; - this.findMatchingBracket = function(position) { - if (position.column == 0) return null; - - var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); - if (charBeforeCursor == "") return null; - - var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); - if (!match) { - return null; - } - - if (match[1]) { - return this.$findClosingBracket(match[1], position); - } else { - return this.$findOpeningBracket(match[2], position); - } - }; - - this.$brackets = { - ")": "(", - "(": ")", - "]": "[", - "[": "]", - "{": "}", - "}": "{" - }; - - this.$findOpeningBracket = function(bracket, position) { - var openBracket = this.$brackets[bracket]; - - var column = position.column - 2; - var row = position.row; - var depth = 1; - - var line = this.getLine(row); - - while (true) { - while(column >= 0) { - var ch = line.charAt(column); - if (ch == openBracket) { - depth -= 1; - if (depth == 0) { - return {row: row, column: column}; - } - } - else if (ch == bracket) { - depth +=1; - } - column -= 1; - } - row -=1; - if (row < 0) break; - - var line = this.getLine(row); - var column = line.length-1; - } - return null; - }; - - this.$findClosingBracket = function(bracket, position) { - var closingBracket = this.$brackets[bracket]; - - var column = position.column; - var row = position.row; - var depth = 1; - - var line = this.getLine(row); - var lineCount = this.getLength(); - - while (true) { - while(column < line.length) { - var ch = line.charAt(column); - if (ch == closingBracket) { - depth -= 1; - if (depth == 0) { - return {row: row, column: column}; - } - } - else if (ch == bracket) { - depth +=1; - } - column += 1; - } - row +=1; - if (row >= lineCount) break; - - var line = this.getLine(row); - var column = 0; - } - return null; - }; - - this.insert = function(position, text, fromUndo) { - var end = this.$insert(position, text, fromUndo); - this.fireChangeEvent(position.row, position.row == end.row ? position.row - : undefined); - return end; - }; - - /** - * @param rows Array[Integer] sorted list of rows - */ - this.multiRowInsert = function(rows, column, text) { - var lines = this.$lines; - - for (var i=rows.length-1; i>=0; i--) { - var row = rows[i]; - if (row >= lines.length) - continue; - - var diff = column - lines[row].length; - if ( diff > 0) { - var padded = lang.stringRepeat(" ", diff) + text; - var offset = -diff; - } - else { - padded = text; - offset = 0; - } - - var end = this.$insert({row: row, column: column+offset}, padded, false); - } - - if (end) { - this.fireChangeEvent(rows[0], rows[rows.length-1] + end.row - rows[0]); - return { - rows: end.row - rows[0], - columns: end.column - column - } - } - else { - return { - rows: 0, - columns: 0 - } - } - }; - - this.$insertLines = function(row, lines, fromUndo) { - if (lines.length == 0) - return; - - var args = [row, 0]; - args.push.apply(args, lines); - this.$lines.splice.apply(this.$lines, args); - - var nl = this.$getNewLineCharacter(); - var delta = { - action: "insertText", - range: new Range(row, 0, row + lines.length, 0), - text: lines.join(nl) + nl - }; - if (!fromUndo && this.$undoManager) { - this.$deltas.push(delta); - this.$informUndoManager.schedule(); - } - - if (fromUndo !== NO_CHANGE_DELTAS) { - this._dispatchEvent("changeDelta", { data: delta }); - } - }, - - this.$insert = function(position, text, fromUndo) { + this.insert = function(position, text) { if (text.length == 0) return position; - this.modified = true; if (this.$lines.length <= 1) { this.$detectNewLine(text); } var newLines = this.$split(text); - if (this.$isNewLine(text)) { - 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)); - - var end = { - row : position.row + 1, - column : 0 - }; + if (this.isNewLine(text)) { + var end = this.insertNewLine(position); } else if (newLines.length == 1) { - 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 end = this.insertInLine(position, text); } else { - var line = this.$lines[position.row] || ""; - var firstLine = line.substring(0, position.column) + newLines[0]; - var lastLine = newLines[newLines.length - 1] + line.substring(position.column); - - this.$lines[position.row] = firstLine; - this.$insertLines(position.row + 1, [lastLine], NO_CHANGE_DELTAS); - - if (newLines.length > 2) { - this.$insertLines(position.row + 1, newLines.slice(1, -1), NO_CHANGE_DELTAS); - } - - var end = { - row : position.row + newLines.length - 1, - column : newLines[newLines.length - 1].length - }; + this.insertInLine(position, newLines[0]); + if (newLines.length > 2) + this.insertLines(position.row+1, newLines.slice(1, newLines.length-1)); + + var end = this.insertInLine({row: position.row + newLines.length - 1, column: 0}, newLines[newLines.length-1]); } + return end; + }; + + this.insertLines = function(row, lines) { + if (lines.length == 0) + return {row: row, column: 0}; + + var args = [row, 0]; + args.push.apply(args, lines); + this.$lines.splice.apply(this.$lines, args); + + var range = new Range(row, 0, row + lines.length, 0); + var delta = { + action: "insertLines", + nl: this.getNewLineCharacter(), + range: range, + lines: lines + }; + this._dispatchEvent("changeDelta", { data: delta }); + return range.end; + }, + + this.insertNewLine = function(position) { + console.log(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._dispatchEvent("changeDelta", { data: delta }); + + return end; + }; + + this.insertInLine = function(position, text) { + 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 }; - if (!fromUndo && this.$undoManager) { - this.$deltas.push(delta); - this.$informUndoManager.schedule(); - } this._dispatchEvent("changeDelta", { data: delta }); - + return end; }; - this.$isNewLine = function(text) { - return (text == "\r\n" || text == "\r" || text == "\n"); - }; - - this.remove = function(range, fromUndo) { + this.remove = function(range) { if (range.isEmpty()) return range.start; - this.$remove(range, fromUndo); - - this.fireChangeEvent(range.start.row, range.isMultiLine() ? undefined : range.start.row); - - return range.start; - }; - - this.multiRowRemove = function(rows, range) { - if (range.start.row !== rows[0]) - throw new TypeError("range must start in the first row!"); + if (range.isMultiLine()) { + var firstRow = range.start.row; + var lastRow = range.end.row; - var height = range.end.row - rows[0]; - for (var i=rows.length-1; i>=0; i--) { - var row = rows[i]; - if (row >= this.$lines.length) - continue; - - var end = this.$remove(new Range(row, range.start.column, row+height, range.end.column), false); + // TODO removeInLine can be optimized away! + this.removeInLine(lastRow, 0, range.end.column); + if (lastRow - firstRow >= 2) + this.removeLines(firstRow + 1, lastRow - 1); + this.removeInLine(firstRow, range.start.column, this.$lines[firstRow].length); + this.removeNewLine(range.start.row); } - - if (end) { - if (height < 0) - this.fireChangeEvent(rows[0]+height, undefined); - else - this.fireChangeEvent(rows[0], height == 0 ? rows[rows.length-1] : undefined); + else { + this.removeInLine(range) } }; - this.$remove = function(range, fromUndo) { - if (range.isEmpty()) - return; - + this.removeInLine = function(row, startColumn, endColumn) { + var range = new Range(row, startColumn, row, endColumn); + var line = this.$lines[row]; + var removed = line.substring(startColumn, endColumn); + var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length); + this.$lines.splice(row, 1, newLine); + var delta = { action: "removeText", - range: range.clone(), - text: this.getTextRange(range) + range: range, + text: removed }; - if (!fromUndo && this.$undoManager) { - this.$deltas.push(delta); - this.$informUndoManager.schedule(); - } - - this.modified = true; - - var firstRow = range.start.row; - var lastRow = range.end.row; - - var row = this.getLine(firstRow).substring(0, range.start.column) - + this.getLine(lastRow).substring(range.end.column); - - if (row != "") - this.$lines.splice(firstRow, lastRow - firstRow + 1, row); - else - this.$lines.splice(firstRow, lastRow - firstRow + 1, ""); - - this._dispatchEvent("changeDelta", { data: delta }); return range.start; }; - this.undoChanges = function(deltas) { - this.selection.clearSelection(); - for (var i=deltas.length-1; i>=0; i--) { - var delta = deltas[i]; - if (delta.action == "insertText") { - this.remove(delta.range, true); - this.selection.moveCursorToPosition(delta.range.start); - } else { - this.insert(delta.range.start, delta.text, true); - this.selection.clearSelection(); - } - } - }, - - this.redoChanges = function(deltas) { - this.selection.clearSelection(); - for (var i=0; i= this.$lines.length-1) return 0; - - var removed = this.$lines.slice(firstRow, lastRow + 1); - this.$remove(new Range(firstRow, 0, lastRow + 1, 0)); - this.$insertLines(firstRow+1, removed); - - this.fireChangeEvent(firstRow, lastRow + 1); - return 1; - }; - - this.duplicateLines = function(firstRow, lastRow) { - var firstRow = this.$clipRowToDocument(firstRow); - var lastRow = this.$clipRowToDocument(lastRow); - - var lines = this.getLines(firstRow, lastRow); - this.$insertLines(firstRow, lines); - - var addedRows = lastRow - firstRow + 1; - this.fireChangeEvent(firstRow); - - return addedRows; - }; - - this.$clipRowToDocument = function(row) { - return Math.max(0, Math.min(row, this.$lines.length-1)); - }; - - this.documentToScreenColumn = function(row, docColumn) { - var tabSize = this.getTabSize(); - - var screenColumn = 0; - var remaining = docColumn; - - var line = this.getLine(row).split("\t"); - for (var i=0; i len) { - remaining -= (len + 1); - screenColumn += len + tabSize; - } - else { - screenColumn += remaining; - break; - } - } - - return screenColumn; - }; - - this.screenToDocumentColumn = function(row, screenColumn) { - var tabSize = this.getTabSize(); - - var docColumn = 0; - var remaining = screenColumn; - - var line = this.getLine(row).split("\t"); - for (var i=0; i= len + tabSize) { - remaining -= (len + tabSize); - docColumn += (len + 1); - } - else if (remaining > len){ - docColumn += len; - break; - } - else { - docColumn += remaining; - break; - } - } - return docColumn; - }; - }).call(Document.prototype); exports.Document = Document; diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js new file mode 100644 index 00000000..1a5dd67c --- /dev/null +++ b/lib/ace/edit_session.js @@ -0,0 +1,845 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var oop = require("pilot/oop"); +var lang = require("pilot/lang"); +var EventEmitter = require("pilot/event_emitter").EventEmitter; +var Selection = require("ace/selection").Selection; +var TextMode = require("ace/mode/text").Mode; +var Range = require("ace/range").Range; + +var NO_CHANGE_DELTAS = {}; + +var EditSession = function(text, mode) { + + this.modified = true; + this.$lines = []; + this.selection = new Selection(this); + this.$breakpoints = []; + + this.listeners = []; + if (mode) { + this.setMode(mode); + } + + if (Array.isArray(text)) { + this.$insertLines(0, text); + } else { + this.$insert({row: 0, column: 0}, text); + } +}; + + +(function() { + + oop.implement(this, EventEmitter); + + this.$undoManager = null; + + // check for IE split bug + if ("aaa".split(/a/).length == 0) + this.$split = function(text) { + return text.replace(/\r\n|\r/g, "\n").split("\n"); + } + else + this.$split = function(text) { + return text.split(/\r\n|\r|\n/); + }; + + this.setValue = function(text) { + var args = [0, this.$lines.length]; + args.push.apply(args, this.$split(text)); + this.$lines.splice.apply(this.$lines, args); + this.modified = true; + this.fireChangeEvent(0); + }; + + this.toString = function() { + return this.$lines.join(this.$getNewLineCharacter()); + }; + + this.getValue = this.toString; + + this.getSelection = function() { + return this.selection; + }; + + this.fireChangeEvent = function(firstRow, lastRow) { + var data = { + firstRow: firstRow, + lastRow: lastRow + }; + this._dispatchEvent("change", { data: data}); + }; + + this.setUndoManager = function(undoManager) { + this.$undoManager = undoManager; + this.$deltas = []; + + if (this.$informUndoManager) { + this.$informUndoManager.cancel(); + } + + if (undoManager) { + var self = this; + this.$informUndoManager = lang.deferredCall(function() { + if (self.$deltas.length > 0) + undoManager.execute({ + action : "aceupdate", + args : [self.$deltas, self] + }); + self.$deltas = []; + }); + } + }; + + this.$defaultUndoManager = { + undo: function() {}, + redo: function() {} + }; + + this.getUndoManager = function() { + return this.$undoManager || this.$defaultUndoManager; + }, + + this.getTabString = function() { + if (this.getUseSoftTabs()) { + return lang.stringRepeat(" ", this.getTabSize()); + } else { + return "\t"; + } + }; + + this.$useSoftTabs = true; + this.setUseSoftTabs = function(useSoftTabs) { + if (this.$useSoftTabs === useSoftTabs) return; + + this.$useSoftTabs = useSoftTabs; + }; + + this.getUseSoftTabs = function() { + return this.$useSoftTabs; + }; + + this.$tabSize = 4; + this.setTabSize = function(tabSize) { + if (isNaN(tabSize) || this.$tabSize === tabSize) return; + + this.modified = true; + this.$tabSize = tabSize; + this._dispatchEvent("changeTabSize"); + }; + + this.getTabSize = function() { + return this.$tabSize; + }; + + this.isTabStop = function(position) { + return this.$useSoftTabs && (position.column % this.$tabSize == 0); + }; + + this.getBreakpoints = function() { + return this.$breakpoints; + }; + + this.setBreakpoints = function(rows) { + this.$breakpoints = []; + for (var i=0; i 0) { + inToken = !!line.charAt(column - 1).match(this.tokenRe); + } + + if (!inToken) { + inToken = !!line.charAt(column).match(this.tokenRe); + } + + var re = inToken ? this.tokenRe : this.nonTokenRe; + + var start = column; + if (start > 0) { + do { + start--; + } + while (start >= 0 && line.charAt(start).match(re)); + start++; + } + + var end = column; + while (end < line.length && line.charAt(end).match(re)) { + end++; + } + + return new Range(row, start, row, end); + }; + + this.$getNewLineCharacter = function() { + switch (this.$newLineMode) { + case "windows": + return "\r\n"; + + case "unix": + return "\n"; + + case "auto": + return this.$autoNewLine; + } + }, + + this.$autoNewLine = "\n"; + this.$newLineMode = "auto"; + this.setNewLineMode = function(newLineMode) { + if (this.$newLineMode === newLineMode) return; + + this.$newLineMode = newLineMode; + }; + + this.getNewLineMode = function() { + return this.$newLineMode; + }; + + this.$mode = null; + this.setMode = function(mode) { + if (this.$mode === mode) return; + + this.$mode = mode; + this._dispatchEvent("changeMode"); + }; + + this.getMode = function() { + if (!this.$mode) { + this.$mode = new TextMode(); + } + return this.$mode; + }; + + this.$scrollTop = 0; + this.setScrollTopRow = function(scrollTopRow) { + if (this.$scrollTop === scrollTopRow) return; + + this.$scrollTop = scrollTopRow; + this._dispatchEvent("changeScrollTop"); + }; + + this.getScrollTopRow = function() { + return this.$scrollTop; + }; + + this.getWidth = function() { + this.$computeWidth(); + return this.width; + }; + + this.getScreenWidth = function() { + this.$computeWidth(); + return this.screenWidth; + }; + + this.$computeWidth = function() { + if (this.modified) { + this.modified = false; + + var lines = this.$lines; + var longestLine = 0; + var longestScreenLine = 0; + var tabSize = this.getTabSize(); + + for ( var i = 0; i < lines.length; i++) { + var len = lines[i].length; + longestLine = Math.max(longestLine, len); + + lines[i].replace("\t", function(m) { + len += tabSize-1; + return m; + }); + longestScreenLine = Math.max(longestScreenLine, len); + } + this.width = longestLine; + this.screenWidth = longestScreenLine; + } + }; + + /** + * Get a verbatim copy of the given line as it is in the document + */ + this.getLine = function(row) { + return this.$lines[row] || ""; + }; + + /** + * Get a line as it is displayed on screen. Tabs are replaced by spaces. + */ + this.getDisplayLine = function(row) { + var tab = new Array(this.getTabSize()+1).join(" "); + return this.$lines[row].replace(/\t/g, tab); + }; + + this.getLines = function(firstRow, lastRow) { + return this.$lines.slice(firstRow, lastRow+1); + }; + + this.getLength = function() { + return this.$lines.length; + }; + + this.getTextRange = function(range) { + if (range.start.row == range.end.row) { + return this.$lines[range.start.row].substring(range.start.column, + range.end.column); + } + else { + var lines = []; + lines.push(this.$lines[range.start.row].substring(range.start.column)); + lines.push.apply(lines, this.getLines(range.start.row+1, range.end.row-1)); + lines.push(this.$lines[range.end.row].substring(0, range.end.column)); + return lines.join(this.$getNewLineCharacter()); + } + }; + + this.findMatchingBracket = function(position) { + if (position.column == 0) return null; + + var charBeforeCursor = this.getLine(position.row).charAt(position.column-1); + if (charBeforeCursor == "") return null; + + var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/); + if (!match) { + return null; + } + + if (match[1]) { + return this.$findClosingBracket(match[1], position); + } else { + return this.$findOpeningBracket(match[2], position); + } + }; + + this.$brackets = { + ")": "(", + "(": ")", + "]": "[", + "[": "]", + "{": "}", + "}": "{" + }; + + this.$findOpeningBracket = function(bracket, position) { + var openBracket = this.$brackets[bracket]; + + var column = position.column - 2; + var row = position.row; + var depth = 1; + + var line = this.getLine(row); + + while (true) { + while(column >= 0) { + var ch = line.charAt(column); + if (ch == openBracket) { + depth -= 1; + if (depth == 0) { + return {row: row, column: column}; + } + } + else if (ch == bracket) { + depth +=1; + } + column -= 1; + } + row -=1; + if (row < 0) break; + + var line = this.getLine(row); + var column = line.length-1; + } + return null; + }; + + this.$findClosingBracket = function(bracket, position) { + var closingBracket = this.$brackets[bracket]; + + var column = position.column; + var row = position.row; + var depth = 1; + + var line = this.getLine(row); + var lineCount = this.getLength(); + + while (true) { + while(column < line.length) { + var ch = line.charAt(column); + if (ch == closingBracket) { + depth -= 1; + if (depth == 0) { + return {row: row, column: column}; + } + } + else if (ch == bracket) { + depth +=1; + } + column += 1; + } + row +=1; + if (row >= lineCount) break; + + var line = this.getLine(row); + var column = 0; + } + return null; + }; + + this.insert = function(position, text, fromUndo) { + var end = this.$insert(position, text, fromUndo); + this.fireChangeEvent(position.row, position.row == end.row ? position.row + : undefined); + return end; + }; + + /** + * @param rows Array[Integer] sorted list of rows + */ + this.multiRowInsert = function(rows, column, text) { + var lines = this.$lines; + + for (var i=rows.length-1; i>=0; i--) { + var row = rows[i]; + if (row >= lines.length) + continue; + + var diff = column - lines[row].length; + if ( diff > 0) { + var padded = lang.stringRepeat(" ", diff) + text; + var offset = -diff; + } + else { + padded = text; + offset = 0; + } + + var end = this.$insert({row: row, column: column+offset}, padded, false); + } + + if (end) { + this.fireChangeEvent(rows[0], rows[rows.length-1] + end.row - rows[0]); + return { + rows: end.row - rows[0], + columns: end.column - column + } + } + else { + return { + rows: 0, + columns: 0 + } + } + }; + + this.$insertLines = function(row, lines, fromUndo) { + if (lines.length == 0) + return; + + var args = [row, 0]; + args.push.apply(args, lines); + this.$lines.splice.apply(this.$lines, args); + + var nl = this.$getNewLineCharacter(); + var delta = { + action: "insertText", + range: new Range(row, 0, row + lines.length, 0), + text: lines.join(nl) + nl + }; + if (!fromUndo && this.$undoManager) { + this.$deltas.push(delta); + this.$informUndoManager.schedule(); + } + + if (fromUndo !== NO_CHANGE_DELTAS) { + this._dispatchEvent("changeDelta", { data: delta }); + } + }, + + this.$insert = function(position, text, fromUndo) { + if (text.length == 0) + return position; + + this.modified = true; + if (this.$lines.length <= 1) { + this.$detectNewLine(text); + } + + var newLines = this.$split(text); + + if (this.$isNewLine(text)) { + 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)); + + var end = { + row : position.row + 1, + column : 0 + }; + } + else if (newLines.length == 1) { + 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 + }; + } + else { + var line = this.$lines[position.row] || ""; + var firstLine = line.substring(0, position.column) + newLines[0]; + var lastLine = newLines[newLines.length - 1] + line.substring(position.column); + + this.$lines[position.row] = firstLine; + this.$insertLines(position.row + 1, [lastLine], NO_CHANGE_DELTAS); + + if (newLines.length > 2) { + this.$insertLines(position.row + 1, newLines.slice(1, -1), NO_CHANGE_DELTAS); + } + + var end = { + row : position.row + newLines.length - 1, + column : newLines[newLines.length - 1].length + }; + } + + var delta = { + action: "insertText", + range: Range.fromPoints(position, end), + text: text + }; + if (!fromUndo && this.$undoManager) { + this.$deltas.push(delta); + this.$informUndoManager.schedule(); + } + this._dispatchEvent("changeDelta", { data: delta }); + + return end; + }; + + this.$isNewLine = function(text) { + return (text == "\r\n" || text == "\r" || text == "\n"); + }; + + this.remove = function(range, fromUndo) { + if (range.isEmpty()) + return range.start; + + this.$remove(range, fromUndo); + + this.fireChangeEvent(range.start.row, range.isMultiLine() ? undefined : range.start.row); + + return range.start; + }; + + this.multiRowRemove = function(rows, range) { + if (range.start.row !== rows[0]) + throw new TypeError("range must start in the first row!"); + + var height = range.end.row - rows[0]; + for (var i=rows.length-1; i>=0; i--) { + var row = rows[i]; + if (row >= this.$lines.length) + continue; + + var end = this.$remove(new Range(row, range.start.column, row+height, range.end.column), false); + } + + if (end) { + if (height < 0) + this.fireChangeEvent(rows[0]+height, undefined); + else + this.fireChangeEvent(rows[0], height == 0 ? rows[rows.length-1] : undefined); + } + }; + + this.$remove = function(range, fromUndo) { + if (range.isEmpty()) + return; + + var delta = { + action: "removeText", + range: range.clone(), + text: this.getTextRange(range) + }; + if (!fromUndo && this.$undoManager) { + this.$deltas.push(delta); + this.$informUndoManager.schedule(); + } + + this.modified = true; + + var firstRow = range.start.row; + var lastRow = range.end.row; + + var row = this.getLine(firstRow).substring(0, range.start.column) + + this.getLine(lastRow).substring(range.end.column); + + if (row != "") + this.$lines.splice(firstRow, lastRow - firstRow + 1, row); + else + this.$lines.splice(firstRow, lastRow - firstRow + 1, ""); + + this._dispatchEvent("changeDelta", { data: delta }); + return range.start; + }; + + this.undoChanges = function(deltas) { + this.selection.clearSelection(); + for (var i=deltas.length-1; i>=0; i--) { + var delta = deltas[i]; + if (delta.action == "insertText") { + this.remove(delta.range, true); + this.selection.moveCursorToPosition(delta.range.start); + } else { + this.insert(delta.range.start, delta.text, true); + this.selection.clearSelection(); + } + } + }, + + this.redoChanges = function(deltas) { + this.selection.clearSelection(); + for (var i=0; i= this.$lines.length-1) return 0; + + var removed = this.$lines.slice(firstRow, lastRow + 1); + this.$remove(new Range(firstRow, 0, lastRow + 1, 0)); + this.$insertLines(firstRow+1, removed); + + this.fireChangeEvent(firstRow, lastRow + 1); + return 1; + }; + + this.duplicateLines = function(firstRow, lastRow) { + var firstRow = this.$clipRowToDocument(firstRow); + var lastRow = this.$clipRowToDocument(lastRow); + + var lines = this.getLines(firstRow, lastRow); + this.$insertLines(firstRow, lines); + + var addedRows = lastRow - firstRow + 1; + this.fireChangeEvent(firstRow); + + return addedRows; + }; + + this.$clipRowToDocument = function(row) { + return Math.max(0, Math.min(row, this.$lines.length-1)); + }; + + this.documentToScreenColumn = function(row, docColumn) { + var tabSize = this.getTabSize(); + + var screenColumn = 0; + var remaining = docColumn; + + var line = this.getLine(row).split("\t"); + for (var i=0; i len) { + remaining -= (len + 1); + screenColumn += len + tabSize; + } + else { + screenColumn += remaining; + break; + } + } + + return screenColumn; + }; + + this.screenToDocumentColumn = function(row, screenColumn) { + var tabSize = this.getTabSize(); + + var docColumn = 0; + var remaining = screenColumn; + + var line = this.getLine(row).split("\t"); + for (var i=0; i= len + tabSize) { + remaining -= (len + tabSize); + docColumn += (len + 1); + } + else if (remaining > len){ + docColumn += len; + break; + } + else { + docColumn += remaining; + break; + } + } + return docColumn; + }; + +}).call(EditSession.prototype); + +exports.EditSession = EditSession; +}); diff --git a/lib/ace/test/document_test.js b/lib/ace/test/document_test.js index 1df5f511..9076a7fd 100644 --- a/lib/ace/test/document_test.js +++ b/lib/ace/test/document_test.js @@ -35,259 +35,56 @@ * * ***** END LICENSE BLOCK ***** */ -define(function(require, exports, module) { - var Document = require("../document").Document, - UndoManager = require("../undomanager").UndoManager, - MockRenderer = require("./mockrenderer"), Range = require("../range").Range, assert = require("./assertions"), async = require("async"); var Test = { - "test: find matching opening bracket" : function() { - var doc = new Document(["(()(", "())))"]); - - assert.position(doc.findMatchingBracket({row: 0, column: 3}), 0, 1); - assert.position(doc.findMatchingBracket({row: 1, column: 2}), 1, 0); - assert.position(doc.findMatchingBracket({row: 1, column: 3}), 0, 3); - assert.position(doc.findMatchingBracket({row: 1, column: 4}), 0, 0); - assert.equal(doc.findMatchingBracket({row: 1, column: 5}), null); - }, - - "test: find matching closing bracket" : function() { - var doc = new Document(["(()(", "())))"]); - - assert.position(doc.findMatchingBracket({row: 1, column: 1}), 1, 1); - assert.position(doc.findMatchingBracket({row: 1, column: 1}), 1, 1); - assert.position(doc.findMatchingBracket({row: 0, column: 4}), 1, 2); - assert.position(doc.findMatchingBracket({row: 0, column: 2}), 0, 2); - assert.position(doc.findMatchingBracket({row: 0, column: 1}), 1, 3); - assert.equal(doc.findMatchingBracket({row: 0, column: 0}), null); - }, - - "test: match different bracket types" : function() { - var doc = new Document(["({[", ")]}"]); - - assert.position(doc.findMatchingBracket({row: 0, column: 1}), 1, 0); - assert.position(doc.findMatchingBracket({row: 0, column: 2}), 1, 2); - assert.position(doc.findMatchingBracket({row: 0, column: 3}), 1, 1); - - assert.position(doc.findMatchingBracket({row: 1, column: 1}), 0, 0); - assert.position(doc.findMatchingBracket({row: 1, column: 2}), 0, 2); - assert.position(doc.findMatchingBracket({row: 1, column: 3}), 0, 1); - }, - - "test: move lines down" : function() { - var doc = new Document(["a1", "a2", "a3", "a4"]); - - doc.moveLinesDown(0, 1); - assert.equal(doc.toString(), ["a3", "a1", "a2", "a4"].join("\n")); - - doc.moveLinesDown(1, 2); - assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); - - doc.moveLinesDown(2, 3); - assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); - - doc.moveLinesDown(2, 2); - assert.equal(doc.toString(), ["a3", "a4", "a2", "a1"].join("\n")); - }, - - "test: move lines up" : function() { - var doc = new Document(["a1", "a2", "a3", "a4"]); - - doc.moveLinesUp(2, 3); - assert.equal(doc.toString(), ["a1", "a3", "a4", "a2"].join("\n")); - - doc.moveLinesUp(1, 2); - assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); - - doc.moveLinesUp(0, 1); - assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); - - doc.moveLinesUp(2, 2); - assert.equal(doc.toString(), ["a3", "a1", "a4", "a2"].join("\n")); - }, - - "test: duplicate lines" : function() { - var doc = new Document(["1", "2", "3", "4"]); - - doc.duplicateLines(1, 2); - assert.equal(doc.toString(), ["1", "2", "3", "2", "3", "4"].join("\n")); - }, - - "test: duplicate last line" : function() { - var doc = new Document(["1", "2", "3"]); - - doc.duplicateLines(2, 2); - assert.equal(doc.toString(), ["1", "2", "3", "3"].join("\n")); - }, - - "test: duplicate first line" : function() { - var doc = new Document(["1", "2", "3"]); - - doc.duplicateLines(0, 0); - assert.equal(doc.toString(), ["1", "1", "2", "3"].join("\n")); - }, - "test: should handle unix style new lines" : function() { var doc = new Document(["1", "2", "3"]); - assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); }, "test: should handle windows style new lines" : function() { var doc = new Document(["1", "2", "3"].join("\r\n")); doc.setNewLineMode("unix"); - assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); }, "test: set new line mode to 'windows' should use '\r\n' as new lines": function() { var doc = new Document(["1", "2", "3"].join("\n")); doc.setNewLineMode("windows"); - assert.equal(doc.toString(), ["1", "2", "3"].join("\r\n")); + assert.equal(doc.getValue(), ["1", "2", "3"].join("\r\n")); }, "test: set new line mode to 'unix' should use '\n' as new lines": function() { var doc = new Document(["1", "2", "3"].join("\r\n")); doc.setNewLineMode("unix"); - assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); }, "test: set new line mode to 'auto' should detect the incoming nl type": function() { var doc = new Document(["1", "2", "3"].join("\n")); doc.setNewLineMode("auto"); - assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); var doc = new Document(["1", "2", "3"].join("\r\n")); doc.setNewLineMode("auto"); - assert.equal(doc.toString(), ["1", "2", "3"].join("\r\n")); + assert.equal(doc.getValue(), ["1", "2", "3"].join("\r\n")); doc.replace(new Range(0, 0, 2, 1), ["4", "5", "6"].join("\n")); - assert.equal(["4", "5", "6"].join("\n"), doc.toString()); - }, - - "test: convert document to screen coordinates" : function() { - var doc = new Document("01234\t567890\t1234"); - doc.setTabSize(4); - - assert.equal(doc.documentToScreenColumn(0, 0), 0); - assert.equal(doc.documentToScreenColumn(0, 4), 4); - assert.equal(doc.documentToScreenColumn(0, 5), 5); - assert.equal(doc.documentToScreenColumn(0, 6), 9); - assert.equal(doc.documentToScreenColumn(0, 12), 15); - assert.equal(doc.documentToScreenColumn(0, 13), 19); - - doc.setTabSize(2); - - assert.equal(doc.documentToScreenColumn(0, 0), 0); - assert.equal(doc.documentToScreenColumn(0, 4), 4); - assert.equal(doc.documentToScreenColumn(0, 5), 5); - assert.equal(doc.documentToScreenColumn(0, 6), 7); - assert.equal(doc.documentToScreenColumn(0, 12), 13); - assert.equal(doc.documentToScreenColumn(0, 13), 15); - }, - - "test: convert document to scrren coordinates with leading tabs": function() { - var doc = new Document("\t\t123"); - doc.setTabSize(4); - - assert.equal(doc.documentToScreenColumn(0, 0), 0); - assert.equal(doc.documentToScreenColumn(0, 1), 4); - assert.equal(doc.documentToScreenColumn(0, 2), 8); - assert.equal(doc.documentToScreenColumn(0, 3), 9); - }, - - "test: convert screen to document coordinates" : function() { - var doc = new Document("01234\t567890\t1234"); - doc.setTabSize(4); - - assert.equal(doc.screenToDocumentColumn(0, 0), 0); - assert.equal(doc.screenToDocumentColumn(0, 4), 4); - assert.equal(doc.screenToDocumentColumn(0, 5), 5); - assert.equal(doc.screenToDocumentColumn(0, 6), 5); - assert.equal(doc.screenToDocumentColumn(0, 7), 5); - assert.equal(doc.screenToDocumentColumn(0, 8), 5); - assert.equal(doc.screenToDocumentColumn(0, 9), 6); - assert.equal(doc.screenToDocumentColumn(0, 15), 12); - assert.equal(doc.screenToDocumentColumn(0, 19), 13); - }, - - "test: insert text in multiple rows": function() { - var doc = new Document(["12", "", "abcd"]); - - var inserted = doc.multiRowInsert([0, 1, 2], 2, "juhu 1"); - assert.equal(inserted.rows, 0); - assert.equal(inserted.columns, 6); - - assert.equal(doc.toString(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); - }, - - "test: undo insert text in multiple rows": function() { - var doc = new Document(["12", "", "abcd"]); - - var undoManager = new UndoManager(); - doc.setUndoManager(undoManager); - - doc.multiRowInsert([0, 1, 2], 2, "juhu 1"); - doc.$informUndoManager.call(); - assert.equal(doc.toString(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); - - undoManager.undo(); - assert.equal(doc.toString(), ["12", "", "abcd"].join("\n")); - - undoManager.redo(); - assert.equal(doc.toString(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); - }, - - "test: insert new line in multiple rows": function() { - var doc = new Document(["12", "", "abcd"]); - - var inserted = doc.multiRowInsert([0, 1, 2], 2, "\n"); - assert.equal(inserted.rows, 1); - assert.equal(doc.toString(), ["12\n", " \n", "ab\ncd"].join("\n")); - }, - - "test: insert multi line text in multiple rows": function() { - var doc = new Document(["12", "", "abcd"]); - - var inserted = doc.multiRowInsert([0, 1, 2], 2, "juhu\n12"); - assert.equal(inserted.rows, 1); - assert.equal(doc.toString(), ["12juhu\n12", " juhu\n12", "abjuhu\n12cd"].join("\n")); - }, - - "test: remove right in multiple rows" : function() { - var doc = new Document(["12", "", "abcd"]); - - doc.multiRowRemove([0, 1, 2], new Range(0, 2, 0, 3)); - assert.equal(doc.toString(), ["12", "", "abd"].join("\n")); - }, - - "test: undo remove right in multiple rows" : function() { - var doc = new Document(["12", "", "abcd"]); - var undoManager = new UndoManager(); - doc.setUndoManager(undoManager); - - doc.multiRowRemove([0, 1, 2], new Range(0, 1, 0, 3)); - doc.$informUndoManager.call(); - assert.equal(doc.toString(), ["1", "", "ad"].join("\n")); - - undoManager.undo(); - assert.equal(doc.toString(), ["12", "", "abcd"].join("\n")); - - undoManager.redo(); - assert.equal(doc.toString(), ["1", "", "ad"].join("\n")); + assert.equal(["4", "5", "6"].join("\n"), doc.getValue()); } }; module.exports = require("async/test").testcase(Test); -}); if (module === require.main) { require("../../../support/paths"); diff --git a/lib/ace/test/edit_session_test.js b/lib/ace/test/edit_session_test.js new file mode 100644 index 00000000..1df5f511 --- /dev/null +++ b/lib/ace/test/edit_session_test.js @@ -0,0 +1,295 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var Document = require("../document").Document, + UndoManager = require("../undomanager").UndoManager, + MockRenderer = require("./mockrenderer"), + Range = require("../range").Range, + assert = require("./assertions"), + async = require("async"); + +var Test = { + + "test: find matching opening bracket" : function() { + var doc = new Document(["(()(", "())))"]); + + assert.position(doc.findMatchingBracket({row: 0, column: 3}), 0, 1); + assert.position(doc.findMatchingBracket({row: 1, column: 2}), 1, 0); + assert.position(doc.findMatchingBracket({row: 1, column: 3}), 0, 3); + assert.position(doc.findMatchingBracket({row: 1, column: 4}), 0, 0); + assert.equal(doc.findMatchingBracket({row: 1, column: 5}), null); + }, + + "test: find matching closing bracket" : function() { + var doc = new Document(["(()(", "())))"]); + + assert.position(doc.findMatchingBracket({row: 1, column: 1}), 1, 1); + assert.position(doc.findMatchingBracket({row: 1, column: 1}), 1, 1); + assert.position(doc.findMatchingBracket({row: 0, column: 4}), 1, 2); + assert.position(doc.findMatchingBracket({row: 0, column: 2}), 0, 2); + assert.position(doc.findMatchingBracket({row: 0, column: 1}), 1, 3); + assert.equal(doc.findMatchingBracket({row: 0, column: 0}), null); + }, + + "test: match different bracket types" : function() { + var doc = new Document(["({[", ")]}"]); + + assert.position(doc.findMatchingBracket({row: 0, column: 1}), 1, 0); + assert.position(doc.findMatchingBracket({row: 0, column: 2}), 1, 2); + assert.position(doc.findMatchingBracket({row: 0, column: 3}), 1, 1); + + assert.position(doc.findMatchingBracket({row: 1, column: 1}), 0, 0); + assert.position(doc.findMatchingBracket({row: 1, column: 2}), 0, 2); + assert.position(doc.findMatchingBracket({row: 1, column: 3}), 0, 1); + }, + + "test: move lines down" : function() { + var doc = new Document(["a1", "a2", "a3", "a4"]); + + doc.moveLinesDown(0, 1); + assert.equal(doc.toString(), ["a3", "a1", "a2", "a4"].join("\n")); + + doc.moveLinesDown(1, 2); + assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); + + doc.moveLinesDown(2, 3); + assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); + + doc.moveLinesDown(2, 2); + assert.equal(doc.toString(), ["a3", "a4", "a2", "a1"].join("\n")); + }, + + "test: move lines up" : function() { + var doc = new Document(["a1", "a2", "a3", "a4"]); + + doc.moveLinesUp(2, 3); + assert.equal(doc.toString(), ["a1", "a3", "a4", "a2"].join("\n")); + + doc.moveLinesUp(1, 2); + assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); + + doc.moveLinesUp(0, 1); + assert.equal(doc.toString(), ["a3", "a4", "a1", "a2"].join("\n")); + + doc.moveLinesUp(2, 2); + assert.equal(doc.toString(), ["a3", "a1", "a4", "a2"].join("\n")); + }, + + "test: duplicate lines" : function() { + var doc = new Document(["1", "2", "3", "4"]); + + doc.duplicateLines(1, 2); + assert.equal(doc.toString(), ["1", "2", "3", "2", "3", "4"].join("\n")); + }, + + "test: duplicate last line" : function() { + var doc = new Document(["1", "2", "3"]); + + doc.duplicateLines(2, 2); + assert.equal(doc.toString(), ["1", "2", "3", "3"].join("\n")); + }, + + "test: duplicate first line" : function() { + var doc = new Document(["1", "2", "3"]); + + doc.duplicateLines(0, 0); + assert.equal(doc.toString(), ["1", "1", "2", "3"].join("\n")); + }, + + "test: should handle unix style new lines" : function() { + var doc = new Document(["1", "2", "3"]); + + assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + }, + + "test: should handle windows style new lines" : function() { + var doc = new Document(["1", "2", "3"].join("\r\n")); + + doc.setNewLineMode("unix"); + assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + }, + + "test: set new line mode to 'windows' should use '\r\n' as new lines": function() { + var doc = new Document(["1", "2", "3"].join("\n")); + doc.setNewLineMode("windows"); + assert.equal(doc.toString(), ["1", "2", "3"].join("\r\n")); + }, + + "test: set new line mode to 'unix' should use '\n' as new lines": function() { + var doc = new Document(["1", "2", "3"].join("\r\n")); + + doc.setNewLineMode("unix"); + assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + }, + + "test: set new line mode to 'auto' should detect the incoming nl type": function() { + var doc = new Document(["1", "2", "3"].join("\n")); + + doc.setNewLineMode("auto"); + assert.equal(doc.toString(), ["1", "2", "3"].join("\n")); + + var doc = new Document(["1", "2", "3"].join("\r\n")); + + doc.setNewLineMode("auto"); + assert.equal(doc.toString(), ["1", "2", "3"].join("\r\n")); + + doc.replace(new Range(0, 0, 2, 1), ["4", "5", "6"].join("\n")); + assert.equal(["4", "5", "6"].join("\n"), doc.toString()); + }, + + "test: convert document to screen coordinates" : function() { + var doc = new Document("01234\t567890\t1234"); + doc.setTabSize(4); + + assert.equal(doc.documentToScreenColumn(0, 0), 0); + assert.equal(doc.documentToScreenColumn(0, 4), 4); + assert.equal(doc.documentToScreenColumn(0, 5), 5); + assert.equal(doc.documentToScreenColumn(0, 6), 9); + assert.equal(doc.documentToScreenColumn(0, 12), 15); + assert.equal(doc.documentToScreenColumn(0, 13), 19); + + doc.setTabSize(2); + + assert.equal(doc.documentToScreenColumn(0, 0), 0); + assert.equal(doc.documentToScreenColumn(0, 4), 4); + assert.equal(doc.documentToScreenColumn(0, 5), 5); + assert.equal(doc.documentToScreenColumn(0, 6), 7); + assert.equal(doc.documentToScreenColumn(0, 12), 13); + assert.equal(doc.documentToScreenColumn(0, 13), 15); + }, + + "test: convert document to scrren coordinates with leading tabs": function() { + var doc = new Document("\t\t123"); + doc.setTabSize(4); + + assert.equal(doc.documentToScreenColumn(0, 0), 0); + assert.equal(doc.documentToScreenColumn(0, 1), 4); + assert.equal(doc.documentToScreenColumn(0, 2), 8); + assert.equal(doc.documentToScreenColumn(0, 3), 9); + }, + + "test: convert screen to document coordinates" : function() { + var doc = new Document("01234\t567890\t1234"); + doc.setTabSize(4); + + assert.equal(doc.screenToDocumentColumn(0, 0), 0); + assert.equal(doc.screenToDocumentColumn(0, 4), 4); + assert.equal(doc.screenToDocumentColumn(0, 5), 5); + assert.equal(doc.screenToDocumentColumn(0, 6), 5); + assert.equal(doc.screenToDocumentColumn(0, 7), 5); + assert.equal(doc.screenToDocumentColumn(0, 8), 5); + assert.equal(doc.screenToDocumentColumn(0, 9), 6); + assert.equal(doc.screenToDocumentColumn(0, 15), 12); + assert.equal(doc.screenToDocumentColumn(0, 19), 13); + }, + + "test: insert text in multiple rows": function() { + var doc = new Document(["12", "", "abcd"]); + + var inserted = doc.multiRowInsert([0, 1, 2], 2, "juhu 1"); + assert.equal(inserted.rows, 0); + assert.equal(inserted.columns, 6); + + assert.equal(doc.toString(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); + }, + + "test: undo insert text in multiple rows": function() { + var doc = new Document(["12", "", "abcd"]); + + var undoManager = new UndoManager(); + doc.setUndoManager(undoManager); + + doc.multiRowInsert([0, 1, 2], 2, "juhu 1"); + doc.$informUndoManager.call(); + assert.equal(doc.toString(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); + + undoManager.undo(); + assert.equal(doc.toString(), ["12", "", "abcd"].join("\n")); + + undoManager.redo(); + assert.equal(doc.toString(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); + }, + + "test: insert new line in multiple rows": function() { + var doc = new Document(["12", "", "abcd"]); + + var inserted = doc.multiRowInsert([0, 1, 2], 2, "\n"); + assert.equal(inserted.rows, 1); + assert.equal(doc.toString(), ["12\n", " \n", "ab\ncd"].join("\n")); + }, + + "test: insert multi line text in multiple rows": function() { + var doc = new Document(["12", "", "abcd"]); + + var inserted = doc.multiRowInsert([0, 1, 2], 2, "juhu\n12"); + assert.equal(inserted.rows, 1); + assert.equal(doc.toString(), ["12juhu\n12", " juhu\n12", "abjuhu\n12cd"].join("\n")); + }, + + "test: remove right in multiple rows" : function() { + var doc = new Document(["12", "", "abcd"]); + + doc.multiRowRemove([0, 1, 2], new Range(0, 2, 0, 3)); + assert.equal(doc.toString(), ["12", "", "abd"].join("\n")); + }, + + "test: undo remove right in multiple rows" : function() { + var doc = new Document(["12", "", "abcd"]); + var undoManager = new UndoManager(); + doc.setUndoManager(undoManager); + + doc.multiRowRemove([0, 1, 2], new Range(0, 1, 0, 3)); + doc.$informUndoManager.call(); + assert.equal(doc.toString(), ["1", "", "ad"].join("\n")); + + undoManager.undo(); + assert.equal(doc.toString(), ["12", "", "abcd"].join("\n")); + + undoManager.redo(); + assert.equal(doc.toString(), ["1", "", "ad"].join("\n")); + } +}; + +module.exports = require("async/test").testcase(Test); +}); + +if (module === require.main) { + require("../../../support/paths"); + exports.exec() +} \ No newline at end of file