/* * Copyright (C) 2009 Google Inc. 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 Google Inc. 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 THE COPYRIGHT * OWNER OR CONTRIBUTORS 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. */ WebInspector.TextRange = function(startLine, startColumn, endLine, endColumn) { this.startLine = startLine; this.startColumn = startColumn; this.endLine = endLine; this.endColumn = endColumn; } WebInspector.TextRange.prototype = { isEmpty: function() { return this.startLine === this.endLine && this.startColumn === this.endColumn; }, get linesCount() { return this.endLine - this.startLine; }, clone: function() { return new WebInspector.TextRange(this.startLine, this.startColumn, this.endLine, this.endColumn); } } WebInspector.TextEditorModel = function() { this._lines = [""]; this._attributes = []; this._undoStack = []; this._noPunctuationRegex = /[^ !%&()*+,-.:;<=>?\[\]\^{|}~]+/; this._lineBreak = "\n"; } WebInspector.TextEditorModel.prototype = { set changeListener(changeListener) { this._changeListener = changeListener; }, get linesCount() { return this._lines.length; }, get text() { return this._lines.join(this._lineBreak); }, line: function(lineNumber) { if (lineNumber >= this._lines.length) throw "Out of bounds:" + lineNumber; return this._lines[lineNumber]; }, lineLength: function(lineNumber) { return this._lines[lineNumber].length; }, setText: function(range, text) { text = text || ""; if (!range) { range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length); this._lineBreak = /\r\n/.test(text) ? "\r\n" : "\n"; } var command = this._pushUndoableCommand(range); var newRange = this._innerSetText(range, text); command.range = newRange.clone(); if (this._changeListener) this._changeListener(range, newRange, command.text, text); return newRange; }, set replaceTabsWithSpaces(replaceTabsWithSpaces) { this._replaceTabsWithSpaces = replaceTabsWithSpaces; }, _innerSetText: function(range, text) { this._eraseRange(range); if (text === "") return new WebInspector.TextRange(range.startLine, range.startColumn, range.startLine, range.startColumn); var newLines = text.split(/\r?\n/); this._replaceTabsIfNeeded(newLines); var prefix = this._lines[range.startLine].substring(0, range.startColumn); var suffix = this._lines[range.startLine].substring(range.startColumn); var postCaret = prefix.length; // Insert text. if (newLines.length === 1) { this._setLine(range.startLine, prefix + newLines[0] + suffix); postCaret += newLines[0].length; } else { this._setLine(range.startLine, prefix + newLines[0]); for (var i = 1; i < newLines.length; ++i) this._insertLine(range.startLine + i, newLines[i]); this._setLine(range.startLine + newLines.length - 1, newLines[newLines.length - 1] + suffix); postCaret = newLines[newLines.length - 1].length; } return new WebInspector.TextRange(range.startLine, range.startColumn, range.startLine + newLines.length - 1, postCaret); }, _replaceTabsIfNeeded: function(lines) { if (!this._replaceTabsWithSpaces) return; var spaces = [ " ", " ", " ", " "]; for (var i = 0; i < lines.length; ++i) { var line = lines[i]; var index = line.indexOf("\t"); while (index !== -1) { line = line.substring(0, index) + spaces[index % 4] + line.substring(index + 1); index = line.indexOf("\t", index + 1); } lines[i] = line; } }, _eraseRange: function(range) { if (range.isEmpty()) return; var prefix = this._lines[range.startLine].substring(0, range.startColumn); var suffix = this._lines[range.endLine].substring(range.endColumn); if (range.endLine > range.startLine) this._removeLines(range.startLine + 1, range.endLine - range.startLine); this._setLine(range.startLine, prefix + suffix); }, _setLine: function(lineNumber, text) { this._lines[lineNumber] = text; }, _removeLines: function(fromLine, count) { this._lines.splice(fromLine, count); this._attributes.splice(fromLine, count); }, _insertLine: function(lineNumber, text) { this._lines.splice(lineNumber, 0, text); this._attributes.splice(lineNumber, 0, {}); }, wordRange: function(lineNumber, column) { return new WebInspector.TextRange(lineNumber, this.wordStart(lineNumber, column, true), lineNumber, this.wordEnd(lineNumber, column, true)); }, wordStart: function(lineNumber, column, gapless) { var line = this._lines[lineNumber]; var prefix = line.substring(0, column).split("").reverse().join(""); var prefixMatch = this._noPunctuationRegex.exec(prefix); return prefixMatch && (!gapless || prefixMatch.index === 0) ? column - prefixMatch.index - prefixMatch[0].length : column; }, wordEnd: function(lineNumber, column, gapless) { var line = this._lines[lineNumber]; var suffix = line.substring(column); var suffixMatch = this._noPunctuationRegex.exec(suffix); return suffixMatch && (!gapless || suffixMatch.index === 0) ? column + suffixMatch.index + suffixMatch[0].length : column; }, copyRange: function(range) { if (!range) range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length); var clip = []; if (range.startLine === range.endLine) { clip.push(this._lines[range.startLine].substring(range.startColumn, range.endColumn)); return clip.join(this._lineBreak); } clip.push(this._lines[range.startLine].substring(range.startColumn)); for (var i = range.startLine + 1; i < range.endLine; ++i) clip.push(this._lines[i]); clip.push(this._lines[range.endLine].substring(0, range.endColumn)); return clip.join(this._lineBreak); }, setAttribute: function(line, name, value) { var attrs = this._attributes[line]; if (!attrs) { attrs = {}; this._attributes[line] = attrs; } attrs[name] = value; }, getAttribute: function(line, name) { var attrs = this._attributes[line]; return attrs ? attrs[name] : null; }, removeAttribute: function(line, name) { var attrs = this._attributes[line]; if (attrs) delete attrs[name]; }, _pushUndoableCommand: function(range) { var command = { text: this.copyRange(range), startLine: range.startLine, startColumn: range.startColumn, endLine: range.startLine, endColumn: range.startColumn }; if (this._inUndo) this._redoStack.push(command); else { if (!this._inRedo) this._redoStack = []; this._undoStack.push(command); } return command; }, undo: function(callback) { this._markRedoableState(); this._inUndo = true; var range = this._doUndo(this._undoStack, callback); delete this._inUndo; return range; }, redo: function(callback) { this.markUndoableState(); this._inRedo = true; var range = this._doUndo(this._redoStack, callback); delete this._inRedo; return range; }, _doUndo: function(stack, callback) { var range = null; for (var i = stack.length - 1; i >= 0; --i) { var command = stack[i]; stack.length = i; range = this.setText(command.range, command.text); if (callback) callback(command.range, range); if (i > 0 && stack[i - 1].explicit) return range; } return range; }, markUndoableState: function() { if (this._undoStack.length) this._undoStack[this._undoStack.length - 1].explicit = true; }, _markRedoableState: function() { if (this._redoStack.length) this._redoStack[this._redoStack.length - 1].explicit = true; }, resetUndoStack: function() { this._undoStack = []; } }