/* vim:ts=4:sts=4:sw=4: * ***** 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 * Irakli Gozalishvili (http://jeditoolkit.com) * Julian Viereck * * 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) { require("pilot/fixoldbrowsers"); var oop = require("pilot/oop"); var event = require("pilot/event"); var lang = require("pilot/lang"); var useragent = require("pilot/useragent"); var TextInput = require("ace/keyboard/textinput").TextInput; var MouseHandler = require("ace/mouse/mouse_handler").MouseHandler; //var TouchHandler = require("ace/touch_handler").TouchHandler; var KeyBinding = require("ace/keyboard/keybinding").KeyBinding; var EditSession = require("ace/edit_session").EditSession; var Search = require("ace/search").Search; var Range = require("ace/range").Range; var EventEmitter = require("pilot/event_emitter").EventEmitter; var Editor =function(renderer, session) { var container = renderer.getContainerElement(); this.container = container; this.renderer = renderer; this.textInput = new TextInput(renderer.getTextAreaContainer(), this); this.keyBinding = new KeyBinding(this); // TODO detect touch event support if (useragent.isIPad) { //this.$mouseHandler = new TouchHandler(this); } else { this.$mouseHandler = new MouseHandler(this); } this.$blockScrolling = 0; this.$search = new Search().set({ wrap: true }); this.setSession(session || new EditSession("")); }; (function(){ oop.implement(this, EventEmitter); this.$forwardEvents = { gutterclick: 1, gutterdblclick: 1 }; this.$originalAddEventListener = this.addEventListener; this.$originalRemoveEventListener = this.removeEventListener; this.addEventListener = function(eventName, callback) { if (this.$forwardEvents[eventName]) { return this.renderer.addEventListener(eventName, callback); } else { return this.$originalAddEventListener(eventName, callback); } }; this.removeEventListener = function(eventName, callback) { if (this.$forwardEvents[eventName]) { return this.renderer.removeEventListener(eventName, callback); } else { return this.$originalRemoveEventListener(eventName, callback); } }; this.setKeyboardHandler = function(keyboardHandler) { this.keyBinding.setKeyboardHandler(keyboardHandler); }; this.getKeyboardHandler = function() { return this.keyBinding.getKeyboardHandler(); }; this.setSession = function(session) { if (this.session == session) return; if (this.session) { var oldSession = this.session; this.session.removeEventListener("change", this.$onDocumentChange); this.session.removeEventListener("changeMode", this.$onChangeMode); this.session.removeEventListener("tokenizerUpdate", this.$onTokenizerUpdate); this.session.removeEventListener("changeTabSize", this.$onChangeTabSize); this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit); this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode); this.session.removeEventListener("onChangeFold", this.$onChangeFold); this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker); this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint); this.session.removeEventListener("changeAnnotation", this.$onChangeAnnotation); this.session.removeEventListener("changeOverwrite", this.$onCursorChange); var selection = this.session.getSelection(); selection.removeEventListener("changeCursor", this.$onCursorChange); selection.removeEventListener("changeSelection", this.$onSelectionChange); this.session.setScrollTopRow(this.renderer.getScrollTopRow()); } this.session = session; this.$onDocumentChange = this.onDocumentChange.bind(this); session.addEventListener("change", this.$onDocumentChange); this.renderer.setSession(session); this.$onChangeMode = this.onChangeMode.bind(this); session.addEventListener("changeMode", this.$onChangeMode); this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this); session.addEventListener("tokenizerUpdate", this.$onTokenizerUpdate); this.$onChangeTabSize = this.renderer.updateText.bind(this.renderer); session.addEventListener("changeTabSize", this.$onChangeTabSize); this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this); session.addEventListener("changeWrapLimit", this.$onChangeWrapLimit); this.$onChangeWrapMode = this.onChangeWrapMode.bind(this); session.addEventListener("changeWrapMode", this.$onChangeWrapMode); this.$onChangeFold = this.onChangeFold.bind(this); session.addEventListener("changeFold", this.$onChangeFold); this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this); this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.$onChangeBackMarker = this.onChangeBackMarker.bind(this); this.session.addEventListener("changeBackMarker", this.$onChangeBackMarker); this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this); this.session.addEventListener("changeBreakpoint", this.$onChangeBreakpoint); this.$onChangeAnnotation = this.onChangeAnnotation.bind(this); this.session.addEventListener("changeAnnotation", this.$onChangeAnnotation); this.$onCursorChange = this.onCursorChange.bind(this); this.session.addEventListener("changeOverwrite", this.$onCursorChange); this.selection = session.getSelection(); this.selection.addEventListener("changeCursor", this.$onCursorChange); this.$onSelectionChange = this.onSelectionChange.bind(this); this.selection.addEventListener("changeSelection", this.$onSelectionChange); this.onChangeMode(); this.onCursorChange(); this.onSelectionChange(); this.onChangeFrontMarker(); this.onChangeBackMarker(); this.onChangeBreakpoint(); this.onChangeAnnotation(); this.session.getUseWrapMode() && this.renderer.adjustWrapLimit(); this.renderer.scrollToRow(session.getScrollTopRow()); this.renderer.updateFull(); this._dispatchEvent("changeSession", { session: session, oldSession: oldSession }); }; this.getSession = function() { return this.session; }; this.getSelection = function() { return this.selection; }; this.resize = function() { this.renderer.onResize(); }; this.setTheme = function(theme) { this.renderer.setTheme(theme); }; this.getTheme = function() { return this.renderer.getTheme(); }; this.setStyle = function(style) { this.renderer.setStyle(style); }; this.unsetStyle = function(style) { this.renderer.unsetStyle(style); }; this.setFontSize = function(size) { this.container.style.fontSize = size; }; this.$highlightBrackets = function() { if (this.session.$bracketHighlight) { this.session.removeMarker(this.session.$bracketHighlight); this.session.$bracketHighlight = null; } if (this.$highlightPending) { return; } // perform highlight async to not block the browser during navigation var self = this; this.$highlightPending = true; setTimeout(function() { self.$highlightPending = false; var pos = self.session.findMatchingBracket(self.getCursorPosition()); if (pos) { var range = new Range(pos.row, pos.column, pos.row, pos.column+1); self.session.$bracketHighlight = self.session.addMarker(range, "ace_bracket", "text"); } }, 10); }; this.focus = function() { // Safari needs the timeout // iOS and Firefox need it called immediately // to be on the save side we do both var _self = this; setTimeout(function() { _self.textInput.focus(); }); this.textInput.focus(); }; this.isFocused = function() { return this.textInput.isFocused(); }; this.blur = function() { this.textInput.blur(); }; this.onFocus = function() { this.renderer.showCursor(); this.renderer.visualizeFocus(); this._dispatchEvent("focus"); }; this.onBlur = function() { this.renderer.hideCursor(); this.renderer.visualizeBlur(); this._dispatchEvent("blur"); }; this.onDocumentChange = function(e) { var delta = e.data; var range = delta.range; if (range.start.row == range.end.row && delta.action != "insertLines" && delta.action != "removeLines") var lastRow = range.end.row; else lastRow = Infinity; this.renderer.updateLines(range.start.row, lastRow); this._dispatchEvent("change", e); // update cursor because tab characters can influence the cursor position this.renderer.updateCursor(); }; this.onTokenizerUpdate = function(e) { var rows = e.data; this.renderer.updateLines(rows.first, rows.last); }; this.onCursorChange = function(e) { this.renderer.updateCursor(); if (!this.$blockScrolling) { this.renderer.scrollCursorIntoView(); } // move text input over the cursor // this is required for iOS and IME this.renderer.moveTextAreaToCursor(this.textInput.getElement()); this.$highlightBrackets(); this.$updateHighlightActiveLine(); }; this.$updateHighlightActiveLine = function() { var session = this.getSession(); if (session.$highlightLineMarker) { session.removeMarker(session.$highlightLineMarker); } session.$highlightLineMarker = null; if (this.getHighlightActiveLine() && (this.getSelectionStyle() != "line" || !this.selection.isMultiLine())) { var cursor = this.getCursorPosition(), foldLine = this.session.getFoldLine(cursor.row); var range; if (foldLine) { range = new Range(foldLine.start.row, 0, foldLine.end.row + 1, 0); } else { range = new Range(cursor.row, 0, cursor.row+1, 0); } session.$highlightLineMarker = session.addMarker(range, "ace_active_line", "background"); } }; this.onSelectionChange = function(e) { var session = this.getSession(); if (session.$selectionMarker) { session.removeMarker(session.$selectionMarker); } session.$selectionMarker = null; if (!this.selection.isEmpty()) { var range = this.selection.getRange(); var style = this.getSelectionStyle(); session.$selectionMarker = session.addMarker(range, "ace_selection", style); } else { this.$updateHighlightActiveLine(); } if (this.$highlightSelectedWord) this.session.getMode().highlightSelection(this); }; this.onChangeFrontMarker = function() { this.renderer.updateFrontMarkers(); }; this.onChangeBackMarker = function() { this.renderer.updateBackMarkers(); }; this.onChangeBreakpoint = function() { this.renderer.setBreakpoints(this.session.getBreakpoints()); }; this.onChangeAnnotation = function() { this.renderer.setAnnotations(this.session.getAnnotations()); }; this.onChangeMode = function() { this.renderer.updateText(); }; this.onChangeWrapLimit = function() { this.renderer.updateFull(); }; this.onChangeWrapMode = function() { this.renderer.onResize(true); }; this.onChangeFold = function() { // Update the active line marker as due to folding changes the current // line range on the screen might have changed. this.$updateHighlightActiveLine(); // TODO: This might be too much updating. Okay for now. this.renderer.updateFull(); }; this.getCopyText = function() { var text = ""; if (!this.selection.isEmpty()) text = this.session.getTextRange(this.getSelectionRange()); this._emit("copy", text); return text; }; this.onCut = function() { if (this.$readOnly) return; var range = this.getSelectionRange(); this._emit("cut", range); if (!this.selection.isEmpty()) { this.session.remove(range); this.clearSelection(); } }; this.insert = function(text) { if (this.$readOnly) return; var session = this.session; var mode = session.getMode(); var cursor = this.getCursorPosition(); if (this.getBehavioursEnabled()) { // Get a transform if the current mode wants one. var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text); if (transform) text = transform.text; } text = text.replace("\t", this.session.getTabString()); // remove selected text if (!this.selection.isEmpty()) { var cursor = this.session.remove(this.getSelectionRange()); this.clearSelection(); } else if (this.session.getOverwrite()) { var range = new Range.fromPoints(cursor, cursor); range.end.column += text.length; this.session.remove(range); } this.clearSelection(); var start = cursor.column; var lineState = session.getState(cursor.row); var shouldOutdent = mode.checkOutdent(lineState, session.getLine(cursor.row), text); var line = session.getLine(cursor.row); var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString()); var end = session.insert(cursor, text); if (transform && transform.selection) { if (transform.selection.length == 2) { // Transform relative to the current column this.selection.setSelectionRange( new Range(cursor.row, start + transform.selection[0], cursor.row, start + transform.selection[1])); } else { // Transform relative to the current row. this.selection.setSelectionRange( new Range(cursor.row + transform.selection[0], transform.selection[1], cursor.row + transform.selection[2], transform.selection[3])); } } var lineState = session.getState(cursor.row); // TODO disabled multiline auto indent // possibly doing the indent before inserting the text // if (cursor.row !== end.row) { if (session.getDocument().isNewLine(text)) { this.moveCursorTo(cursor.row+1, 0); var size = session.getTabSize(); var minIndent = Number.MAX_VALUE; for (var row = cursor.row + 1; row <= end.row; ++row) { var indent = 0; line = session.getLine(row); for (var i = 0; i < line.length; ++i) if (line.charAt(i) == '\t') indent += size; else if (line.charAt(i) == ' ') indent += 1; else break; if (/[^\s]/.test(line)) minIndent = Math.min(indent, minIndent); } for (var row = cursor.row + 1; row <= end.row; ++row) { var outdent = minIndent; line = session.getLine(row); for (var i = 0; i < line.length && outdent > 0; ++i) if (line.charAt(i) == '\t') outdent -= size; else if (line.charAt(i) == ' ') outdent -= 1; session.remove(new Range(row, 0, row, i)); } session.indentRows(cursor.row + 1, end.row, lineIndent); } if (shouldOutdent) mode.autoOutdent(lineState, session, cursor.row); }; this.onTextInput = function(text, notPasted) { if (!notPasted) this._emit("paste", text); // In case the text was not pasted and we got only one character, then // handel it as a command key stroke. if (notPasted && text.length == 1) { // Note: The `null` as `keyCode` is important here, as there are // some checks in the code for `keyCode == 0` meaning the text comes // from the keyBinding.onTextInput code path. var handled = this.keyBinding.onCommandKey({}, 0, null, text); // Check if the text was handled. If not, then handled it as "normal" // text and insert it to the editor directly. This shouldn't be done // using the this.keyBinding.onTextInput(text) function, as it would // make the `text` get sent to the keyboardHandler twice, which might // turn out to be a bad thing in case there is a custome keyboard // handler like the StateHandler. if (!handled) { this.insert(text); } } else { this.keyBinding.onTextInput(text); } }; this.onCommandKey = function(e, hashId, keyCode) { this.keyBinding.onCommandKey(e, hashId, keyCode); }; this.setOverwrite = function(overwrite) { this.session.setOverwrite(overwrite); }; this.getOverwrite = function() { return this.session.getOverwrite(); }; this.toggleOverwrite = function() { this.session.toggleOverwrite(); }; this.setScrollSpeed = function(speed) { this.$mouseHandler.setScrollSpeed(speed); }; this.getScrollSpeed = function() { return this.$mouseHandler.getScrollSpeed() }; this.$selectionStyle = "line"; this.setSelectionStyle = function(style) { if (this.$selectionStyle == style) return; this.$selectionStyle = style; this.onSelectionChange(); this._dispatchEvent("changeSelectionStyle", {data: style}); }; this.getSelectionStyle = function() { return this.$selectionStyle; }; this.$highlightActiveLine = true; this.setHighlightActiveLine = function(shouldHighlight) { if (this.$highlightActiveLine == shouldHighlight) return; this.$highlightActiveLine = shouldHighlight; this.$updateHighlightActiveLine(); }; this.getHighlightActiveLine = function() { return this.$highlightActiveLine; }; this.$highlightSelectedWord = true; this.setHighlightSelectedWord = function(shouldHighlight) { if (this.$highlightSelectedWord == shouldHighlight) return; this.$highlightSelectedWord = shouldHighlight; if (shouldHighlight) this.session.getMode().highlightSelection(this); else this.session.getMode().clearSelectionHighlight(this); }; this.getHighlightSelectedWord = function() { return this.$highlightSelectedWord; }; this.setShowInvisibles = function(showInvisibles) { if (this.getShowInvisibles() == showInvisibles) return; this.renderer.setShowInvisibles(showInvisibles); }; this.getShowInvisibles = function() { return this.renderer.getShowInvisibles(); }; this.setShowPrintMargin = function(showPrintMargin) { this.renderer.setShowPrintMargin(showPrintMargin); }; this.getShowPrintMargin = function() { return this.renderer.getShowPrintMargin(); }; this.setPrintMarginColumn = function(showPrintMargin) { this.renderer.setPrintMarginColumn(showPrintMargin); }; this.getPrintMarginColumn = function() { return this.renderer.getPrintMarginColumn(); }; this.$readOnly = false; this.setReadOnly = function(readOnly) { this.$readOnly = readOnly; }; this.getReadOnly = function() { return this.$readOnly; }; this.$modeBehaviours = true; this.setBehavioursEnabled = function (enabled) { this.$modeBehaviours = enabled; }; this.getBehavioursEnabled = function () { return this.$modeBehaviours; }; this.remove = function(dir) { if (this.$readOnly) return; if (this.selection.isEmpty()){ if (dir == "left") this.selection.selectLeft(); else this.selection.selectRight(); } var range = this.getSelectionRange(); if (this.getBehavioursEnabled()) { var session = this.session; var state = session.getState(range.start.row); var new_range = session.getMode().transformAction(state, 'deletion', this, session, range); if (new_range) range = new_range; } this.session.remove(range); this.clearSelection(); }; this.removeWordRight = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectWordRight(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeWordLeft = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectWordLeft(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeToLineStart = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectLineStart(); this.session.remove(this.getSelectionRange()); this.clearSelection(); }; this.removeToLineEnd = function() { if (this.$readOnly) return; if (this.selection.isEmpty()) this.selection.selectLineEnd(); var range = this.getSelectionRange(); if (range.start.column == range.end.column && range.start.row == range.end.row) { range.end.column = 0; range.end.row++; } this.session.remove(range); this.clearSelection(); }; this.splitLine = function() { if (this.$readOnly) return; if (!this.selection.isEmpty()) { this.session.remove(this.getSelectionRange()); this.clearSelection(); } var cursor = this.getCursorPosition(); this.insert("\n"); this.moveCursorToPosition(cursor); }; this.transposeLetters = function() { if (this.$readOnly) return; if (!this.selection.isEmpty()) { return; } var cursor = this.getCursorPosition(); var column = cursor.column; if (column === 0) return; var line = this.session.getLine(cursor.row); var swap, range; if (column < line.length) { swap = line.charAt(column) + line.charAt(column-1); range = new Range(cursor.row, column-1, cursor.row, column+1); } else { swap = line.charAt(column-1) + line.charAt(column-2); range = new Range(cursor.row, column-2, cursor.row, column); } this.session.replace(range, swap); }; this.indent = function() { if (this.$readOnly) return; var session = this.session; var range = this.getSelectionRange(); if (range.start.row < range.end.row || range.start.column < range.end.column) { var rows = this.$getSelectedRows(); session.indentRows(rows.first, rows.last, "\t"); } else { var indentString; if (this.session.getUseSoftTabs()) { var size = session.getTabSize(), position = this.getCursorPosition(), column = session.documentToScreenColumn(position.row, position.column), count = (size - column % size); indentString = lang.stringRepeat(" ", count); } else indentString = "\t"; return this.onTextInput(indentString); } }; this.blockOutdent = function() { if (this.$readOnly) return; var selection = this.session.getSelection(); this.session.outdentRows(selection.getRange()); }; this.toggleCommentLines = function() { if (this.$readOnly) return; var state = this.session.getState(this.getCursorPosition().row); var rows = this.$getSelectedRows(); this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last); }; this.removeLines = function() { if (this.$readOnly) return; var rows = this.$getSelectedRows(); var range; if (rows.last == 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.moveLinesDown = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { return this.session.moveLinesDown(firstRow, lastRow); }); }; this.moveLinesUp = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { return this.session.moveLinesUp(firstRow, lastRow); }); }; this.moveText = function(range, toPosition) { if (this.$readOnly) return null; return this.session.moveText(range, toPosition); }; this.copyLinesUp = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { this.session.duplicateLines(firstRow, lastRow); return 0; }); }; this.copyLinesDown = function() { if (this.$readOnly) return; this.$moveLines(function(firstRow, lastRow) { return this.session.duplicateLines(firstRow, lastRow); }); }; this.$moveLines = function(mover) { var rows = this.$getSelectedRows(); var linesMoved = mover.call(this, rows.first, rows.last); var selection = this.selection; selection.setSelectionAnchor(rows.last+linesMoved+1, 0); selection.$moveSelection(function() { selection.moveCursorTo(rows.first+linesMoved, 0); }); }; this.$getSelectedRows = function() { var range = this.getSelectionRange().collapseRows(); return { first: range.start.row, last: range.end.row }; }; this.onCompositionStart = function(text) { this.renderer.showComposition(this.getCursorPosition()); }; this.onCompositionUpdate = function(text) { this.renderer.setCompositionText(text); }; this.onCompositionEnd = function() { this.renderer.hideComposition(); }; this.getFirstVisibleRow = function() { return this.renderer.getFirstVisibleRow(); }; this.getLastVisibleRow = function() { return this.renderer.getLastVisibleRow(); }; this.isRowVisible = function(row) { return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow()); }; this.$getVisibleRowCount = function() { return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1; }; this.$getTabDownRow = function() { return this.renderer.getScrollBottomRow(); }; this.$getTabUpRow = function() { var firstRow = this.renderer.getScrollTopRow(); var lastRow = this.renderer.getScrollBottomRow(); return firstRow - (lastRow - firstRow); }; this.selectTabDown = function() { var row = this.$getTabDownRow() + Math.floor(this.$getVisibleRowCount() / 2); this.scrollTabDown(); var selection = this.getSelection(); var leadScreenPos = this.session.documentToScreenPosition(selection.getSelectionLead()); var dest = this.session.screenToDocumentPosition(row, leadScreenPos.column); selection.selectTo(dest.row, dest.column); }; this.selectTabUp = function() { var visibleRows = this.renderer.getScrollTopRow() - this.renderer.getScrollBottomRow(); var row = this.$getTabUpRow() + Math.round(visibleRows / 2); this.scrollTabUp(); var selection = this.getSelection(); var leadScreenPos = this.session.documentToScreenPosition(selection.getSelectionLead()); var dest = this.session.screenToDocumentPosition(row, leadScreenPos.column); selection.selectTo(dest.row, dest.column); }; this.gotoTabDown = function() { var row = this.$getTabDownRow(); var column = this.getCursorPositionScreen().column; this.scrollToRow(row); this.getSelection().moveCursorToScreen(row, column); }; this.gotoTabUp = function() { var row = this.$getTabUpRow(); var column = this.getCursorPositionScreen().column; this.scrollToRow(row); this.getSelection().moveCursorToScreen(row, column); }; this.scrollTabDown = function() { this.scrollToRow(this.$getTabDownRow()); }; this.scrollTabUp = function() { this.renderer.scrollToRow(this.$getTabUpRow()); }; this.scrollToRow = function(row) { this.renderer.scrollToRow(row); }; this.scrollToLine = function(line, center) { this.renderer.scrollToLine(line, center); }; this.centerSelection = function() { var range = this.getSelectionRange(); var line = Math.floor(range.start.row + (range.end.row - range.start.row) / 2); this.renderer.scrollToLine(line, true); }; this.getCursorPosition = function() { return this.selection.getCursor(); }; this.getCursorPositionScreen = function() { return this.session.documentToScreenPosition(this.getCursorPosition()); }; this.getSelectionRange = function() { return this.selection.getRange(); }; this.selectAll = function() { this.$blockScrolling += 1; this.selection.selectAll(); this.$blockScrolling -= 1; }; this.clearSelection = function() { this.selection.clearSelection(); }; this.moveCursorTo = function(row, column) { this.selection.moveCursorTo(row, column); }; this.moveCursorToPosition = function(pos) { this.selection.moveCursorToPosition(pos); }; this.gotoLine = function(lineNumber, column) { this.selection.clearSelection(); this.$blockScrolling += 1; this.moveCursorTo(lineNumber-1, column || 0); this.$blockScrolling -= 1; if (!this.isRowVisible(this.getCursorPosition().row)) { this.scrollToLine(lineNumber, true); } }; this.navigateTo = function(row, column) { this.clearSelection(); this.moveCursorTo(row, column); }; this.navigateUp = function(times) { this.selection.clearSelection(); times = times || 1; this.selection.moveCursorBy(-times, 0); }; this.navigateDown = function(times) { this.selection.clearSelection(); times = times || 1; this.selection.moveCursorBy(times, 0); }; this.navigateLeft = function(times) { if (!this.selection.isEmpty()) { var selectionStart = this.getSelectionRange().start; this.moveCursorToPosition(selectionStart); } else { times = times || 1; while (times--) { this.selection.moveCursorLeft(); } } this.clearSelection(); }; this.navigateRight = function(times) { if (!this.selection.isEmpty()) { var selectionEnd = this.getSelectionRange().end; this.moveCursorToPosition(selectionEnd); } else { times = times || 1; while (times--) { this.selection.moveCursorRight(); } } this.clearSelection(); }; this.navigateLineStart = function() { this.selection.moveCursorLineStart(); this.clearSelection(); }; this.navigateLineEnd = function() { this.selection.moveCursorLineEnd(); this.clearSelection(); }; this.navigateFileEnd = function() { this.selection.moveCursorFileEnd(); this.clearSelection(); }; this.navigateFileStart = function() { this.selection.moveCursorFileStart(); this.clearSelection(); }; this.navigateWordRight = function() { this.selection.moveCursorWordRight(); this.clearSelection(); }; this.navigateWordLeft = function() { this.selection.moveCursorWordLeft(); this.clearSelection(); }; this.replace = function(replacement, options) { if (options) this.$search.set(options); var range = this.$search.find(this.session); if (!range) return; this.$tryReplace(range, replacement); if (range !== null) this.selection.setSelectionRange(range); }; this.replaceAll = function(replacement, options) { if (options) { this.$search.set(options); } var ranges = this.$search.findAll(this.session); if (!ranges.length) return; var selection = this.getSelectionRange(); this.clearSelection(); this.selection.moveCursorTo(0, 0); this.$blockScrolling += 1; for (var i = ranges.length - 1; i >= 0; --i) this.$tryReplace(ranges[i], replacement); this.selection.setSelectionRange(selection); this.$blockScrolling -= 1; }; this.$tryReplace = function(range, replacement) { var input = this.session.getTextRange(range); replacement = this.$search.replace(input, replacement); if (replacement !== null) { range.end = this.session.replace(range, replacement); return range; } else { return null; } }; this.getLastSearchOptions = function() { return this.$search.getOptions(); }; this.find = function(needle, options) { this.clearSelection(); options = options || {}; options.needle = needle; this.$search.set(options); this.$find(); }; this.findNext = function(options) { options = options || {}; if (typeof options.backwards == "undefined") options.backwards = false; this.$search.set(options); this.$find(); }; this.findPrevious = function(options) { options = options || {}; if (typeof options.backwards == "undefined") options.backwards = true; this.$search.set(options); this.$find(); }; this.$find = function(backwards) { if (!this.selection.isEmpty()) { this.$search.set({needle: this.session.getTextRange(this.getSelectionRange())}); } if (typeof backwards != "undefined") this.$search.set({backwards: backwards}); var range = this.$search.find(this.session); if (range) { this.gotoLine(range.end.row+1, range.end.column); this.selection.setSelectionRange(range); } }; this.undo = function() { this.session.getUndoManager().undo(); }; this.redo = function() { this.session.getUndoManager().redo(); }; this.destroy = function() { this.renderer.destroy(); }; }).call(Editor.prototype); exports.Editor = Editor; });