2905 lines
95 KiB
JavaScript
Executable File
2905 lines
95 KiB
JavaScript
Executable File
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Distributed under the BSD license:
|
|
*
|
|
* Copyright (c) 2010, Ajax.org B.V.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Ajax.org B.V. nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
define(function(require, exports, module) {
|
|
"use strict";
|
|
|
|
require("./lib/fixoldbrowsers");
|
|
|
|
var oop = require("./lib/oop");
|
|
var dom = require("./lib/dom");
|
|
var lang = require("./lib/lang");
|
|
var useragent = require("./lib/useragent");
|
|
var TextInput = require("./keyboard/textinput").TextInput;
|
|
var MouseHandler = require("./mouse/mouse_handler").MouseHandler;
|
|
var FoldHandler = require("./mouse/fold_handler").FoldHandler;
|
|
var KeyBinding = require("./keyboard/keybinding").KeyBinding;
|
|
var EditSession = require("./edit_session").EditSession;
|
|
var Search = require("./search").Search;
|
|
var Range = require("./range").Range;
|
|
var EventEmitter = require("./lib/event_emitter").EventEmitter;
|
|
var CommandManager = require("./commands/command_manager").CommandManager;
|
|
var defaultCommands = require("./commands/default_commands").commands;
|
|
var config = require("./config");
|
|
var TokenIterator = require("./token_iterator").TokenIterator;
|
|
|
|
var clipboard = require("./clipboard");
|
|
|
|
/**
|
|
* The main entry point into the Ace functionality.
|
|
*
|
|
* The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen.
|
|
*
|
|
* Event sessions dealing with the mouse and keyboard are bubbled up from `Document` to the `Editor`, which decides what to do with them.
|
|
* @class Editor
|
|
**/
|
|
|
|
/**
|
|
* Creates a new `Editor` object.
|
|
*
|
|
* @param {VirtualRenderer} renderer Associated `VirtualRenderer` that draws everything
|
|
* @param {EditSession} session The `EditSession` to refer to
|
|
*
|
|
*
|
|
* @constructor
|
|
**/
|
|
var Editor = function(renderer, session, options) {
|
|
var container = renderer.getContainerElement();
|
|
this.container = container;
|
|
this.renderer = renderer;
|
|
this.id = "editor" + (++Editor.$uid);
|
|
|
|
this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands);
|
|
if (typeof document == "object") {
|
|
this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
|
|
this.renderer.textarea = this.textInput.getElement();
|
|
// TODO detect touch event support
|
|
this.$mouseHandler = new MouseHandler(this);
|
|
new FoldHandler(this);
|
|
}
|
|
|
|
this.keyBinding = new KeyBinding(this);
|
|
|
|
this.$search = new Search().set({
|
|
wrap: true
|
|
});
|
|
|
|
this.$historyTracker = this.$historyTracker.bind(this);
|
|
this.commands.on("exec", this.$historyTracker);
|
|
|
|
this.$initOperationListeners();
|
|
|
|
this._$emitInputEvent = lang.delayedCall(function() {
|
|
this._signal("input", {});
|
|
if (this.session && this.session.bgTokenizer)
|
|
this.session.bgTokenizer.scheduleStart();
|
|
}.bind(this));
|
|
|
|
this.on("change", function(_, _self) {
|
|
_self._$emitInputEvent.schedule(31);
|
|
});
|
|
|
|
this.setSession(session || options && options.session || new EditSession(""));
|
|
config.resetOptions(this);
|
|
if (options)
|
|
this.setOptions(options);
|
|
config._signal("editor", this);
|
|
};
|
|
|
|
Editor.$uid = 0;
|
|
|
|
(function(){
|
|
|
|
oop.implement(this, EventEmitter);
|
|
|
|
this.$initOperationListeners = function() {
|
|
this.commands.on("exec", this.startOperation.bind(this), true);
|
|
this.commands.on("afterExec", this.endOperation.bind(this), true);
|
|
|
|
this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this, true));
|
|
|
|
// todo: add before change events?
|
|
this.on("change", function() {
|
|
if (!this.curOp) {
|
|
this.startOperation();
|
|
this.curOp.selectionBefore = this.$lastSel;
|
|
}
|
|
this.curOp.docChanged = true;
|
|
}.bind(this), true);
|
|
|
|
this.on("changeSelection", function() {
|
|
if (!this.curOp) {
|
|
this.startOperation();
|
|
this.curOp.selectionBefore = this.$lastSel;
|
|
}
|
|
this.curOp.selectionChanged = true;
|
|
}.bind(this), true);
|
|
};
|
|
|
|
this.curOp = null;
|
|
this.prevOp = {};
|
|
this.startOperation = function(commandEvent) {
|
|
if (this.curOp) {
|
|
if (!commandEvent || this.curOp.command)
|
|
return;
|
|
this.prevOp = this.curOp;
|
|
}
|
|
if (!commandEvent) {
|
|
this.previousCommand = null;
|
|
commandEvent = {};
|
|
}
|
|
|
|
this.$opResetTimer.schedule();
|
|
this.curOp = this.session.curOp = {
|
|
command: commandEvent.command || {},
|
|
args: commandEvent.args,
|
|
scrollTop: this.renderer.scrollTop
|
|
};
|
|
this.curOp.selectionBefore = this.selection.toJSON();
|
|
};
|
|
|
|
this.endOperation = function(e) {
|
|
if (this.curOp) {
|
|
if (e && e.returnValue === false)
|
|
return (this.curOp = null);
|
|
if (e == true && this.curOp.command && this.curOp.command.name == "mouse")
|
|
return;
|
|
this._signal("beforeEndOperation");
|
|
if (!this.curOp) return;
|
|
var command = this.curOp.command;
|
|
var scrollIntoView = command && command.scrollIntoView;
|
|
if (scrollIntoView) {
|
|
switch (scrollIntoView) {
|
|
case "center-animate":
|
|
scrollIntoView = "animate";
|
|
/* fall through */
|
|
case "center":
|
|
this.renderer.scrollCursorIntoView(null, 0.5);
|
|
break;
|
|
case "animate":
|
|
case "cursor":
|
|
this.renderer.scrollCursorIntoView();
|
|
break;
|
|
case "selectionPart":
|
|
var range = this.selection.getRange();
|
|
var config = this.renderer.layerConfig;
|
|
if (range.start.row >= config.lastRow || range.end.row <= config.firstRow) {
|
|
this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (scrollIntoView == "animate")
|
|
this.renderer.animateScrolling(this.curOp.scrollTop);
|
|
}
|
|
var sel = this.selection.toJSON();
|
|
this.curOp.selectionAfter = sel;
|
|
this.$lastSel = this.selection.toJSON();
|
|
|
|
// console.log(this.$lastSel+" endOP")
|
|
this.session.getUndoManager().addSelection(sel);
|
|
this.prevOp = this.curOp;
|
|
this.curOp = null;
|
|
}
|
|
};
|
|
|
|
// TODO use property on commands instead of this
|
|
this.$mergeableCommands = ["backspace", "del", "insertstring"];
|
|
this.$historyTracker = function(e) {
|
|
if (!this.$mergeUndoDeltas)
|
|
return;
|
|
|
|
var prev = this.prevOp;
|
|
var mergeableCommands = this.$mergeableCommands;
|
|
// previous command was the same
|
|
var shouldMerge = prev.command && (e.command.name == prev.command.name);
|
|
if (e.command.name == "insertstring") {
|
|
var text = e.args;
|
|
if (this.mergeNextCommand === undefined)
|
|
this.mergeNextCommand = true;
|
|
|
|
shouldMerge = shouldMerge
|
|
&& this.mergeNextCommand // previous command allows to coalesce with
|
|
&& (!/\s/.test(text) || /\s/.test(prev.args)); // previous insertion was of same type
|
|
|
|
this.mergeNextCommand = true;
|
|
} else {
|
|
shouldMerge = shouldMerge
|
|
&& mergeableCommands.indexOf(e.command.name) !== -1; // the command is mergeable
|
|
}
|
|
|
|
if (
|
|
this.$mergeUndoDeltas != "always"
|
|
&& Date.now() - this.sequenceStartTime > 2000
|
|
) {
|
|
shouldMerge = false; // the sequence is too long
|
|
}
|
|
|
|
if (shouldMerge)
|
|
this.session.mergeUndoDeltas = true;
|
|
else if (mergeableCommands.indexOf(e.command.name) !== -1)
|
|
this.sequenceStartTime = Date.now();
|
|
};
|
|
|
|
/**
|
|
* Sets a new key handler, such as "vim" or "windows".
|
|
* @param {String} keyboardHandler The new key handler
|
|
*
|
|
**/
|
|
this.setKeyboardHandler = function(keyboardHandler, cb) {
|
|
if (keyboardHandler && typeof keyboardHandler === "string" && keyboardHandler != "ace") {
|
|
this.$keybindingId = keyboardHandler;
|
|
var _self = this;
|
|
config.loadModule(["keybinding", keyboardHandler], function(module) {
|
|
if (_self.$keybindingId == keyboardHandler)
|
|
_self.keyBinding.setKeyboardHandler(module && module.handler);
|
|
cb && cb();
|
|
});
|
|
} else {
|
|
this.$keybindingId = null;
|
|
this.keyBinding.setKeyboardHandler(keyboardHandler);
|
|
cb && cb();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the keyboard handler, such as "vim" or "windows".
|
|
*
|
|
* @returns {String}
|
|
*
|
|
**/
|
|
this.getKeyboardHandler = function() {
|
|
return this.keyBinding.getKeyboardHandler();
|
|
};
|
|
|
|
|
|
/**
|
|
* Emitted whenever the [[EditSession]] changes.
|
|
* @event changeSession
|
|
* @param {Object} e An object with two properties, `oldSession` and `session`, that represent the old and new [[EditSession]]s.
|
|
*
|
|
**/
|
|
/**
|
|
* Sets a new editsession to use. This method also emits the `'changeSession'` event.
|
|
* @param {EditSession} session The new session to use
|
|
*
|
|
**/
|
|
this.setSession = function(session) {
|
|
if (this.session == session)
|
|
return;
|
|
|
|
// make sure operationEnd events are not emitted to wrong session
|
|
if (this.curOp) this.endOperation();
|
|
this.curOp = {};
|
|
|
|
var oldSession = this.session;
|
|
if (oldSession) {
|
|
this.session.off("change", this.$onDocumentChange);
|
|
this.session.off("changeMode", this.$onChangeMode);
|
|
this.session.off("tokenizerUpdate", this.$onTokenizerUpdate);
|
|
this.session.off("changeTabSize", this.$onChangeTabSize);
|
|
this.session.off("changeWrapLimit", this.$onChangeWrapLimit);
|
|
this.session.off("changeWrapMode", this.$onChangeWrapMode);
|
|
this.session.off("changeFold", this.$onChangeFold);
|
|
this.session.off("changeFrontMarker", this.$onChangeFrontMarker);
|
|
this.session.off("changeBackMarker", this.$onChangeBackMarker);
|
|
this.session.off("changeBreakpoint", this.$onChangeBreakpoint);
|
|
this.session.off("changeAnnotation", this.$onChangeAnnotation);
|
|
this.session.off("changeOverwrite", this.$onCursorChange);
|
|
this.session.off("changeScrollTop", this.$onScrollTopChange);
|
|
this.session.off("changeScrollLeft", this.$onScrollLeftChange);
|
|
|
|
var selection = this.session.getSelection();
|
|
selection.off("changeCursor", this.$onCursorChange);
|
|
selection.off("changeSelection", this.$onSelectionChange);
|
|
}
|
|
|
|
this.session = session;
|
|
if (session) {
|
|
this.$onDocumentChange = this.onDocumentChange.bind(this);
|
|
session.on("change", this.$onDocumentChange);
|
|
this.renderer.setSession(session);
|
|
|
|
this.$onChangeMode = this.onChangeMode.bind(this);
|
|
session.on("changeMode", this.$onChangeMode);
|
|
|
|
this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
|
|
session.on("tokenizerUpdate", this.$onTokenizerUpdate);
|
|
|
|
this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer);
|
|
session.on("changeTabSize", this.$onChangeTabSize);
|
|
|
|
this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
|
|
session.on("changeWrapLimit", this.$onChangeWrapLimit);
|
|
|
|
this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
|
|
session.on("changeWrapMode", this.$onChangeWrapMode);
|
|
|
|
this.$onChangeFold = this.onChangeFold.bind(this);
|
|
session.on("changeFold", this.$onChangeFold);
|
|
|
|
this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
|
|
this.session.on("changeFrontMarker", this.$onChangeFrontMarker);
|
|
|
|
this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
|
|
this.session.on("changeBackMarker", this.$onChangeBackMarker);
|
|
|
|
this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
|
|
this.session.on("changeBreakpoint", this.$onChangeBreakpoint);
|
|
|
|
this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
|
|
this.session.on("changeAnnotation", this.$onChangeAnnotation);
|
|
|
|
this.$onCursorChange = this.onCursorChange.bind(this);
|
|
this.session.on("changeOverwrite", this.$onCursorChange);
|
|
|
|
this.$onScrollTopChange = this.onScrollTopChange.bind(this);
|
|
this.session.on("changeScrollTop", this.$onScrollTopChange);
|
|
|
|
this.$onScrollLeftChange = this.onScrollLeftChange.bind(this);
|
|
this.session.on("changeScrollLeft", this.$onScrollLeftChange);
|
|
|
|
this.selection = session.getSelection();
|
|
this.selection.on("changeCursor", this.$onCursorChange);
|
|
|
|
this.$onSelectionChange = this.onSelectionChange.bind(this);
|
|
this.selection.on("changeSelection", this.$onSelectionChange);
|
|
|
|
this.onChangeMode();
|
|
|
|
this.onCursorChange();
|
|
|
|
this.onScrollTopChange();
|
|
this.onScrollLeftChange();
|
|
this.onSelectionChange();
|
|
this.onChangeFrontMarker();
|
|
this.onChangeBackMarker();
|
|
this.onChangeBreakpoint();
|
|
this.onChangeAnnotation();
|
|
this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
|
|
this.renderer.updateFull();
|
|
} else {
|
|
this.selection = null;
|
|
this.renderer.setSession(session);
|
|
}
|
|
|
|
this._signal("changeSession", {
|
|
session: session,
|
|
oldSession: oldSession
|
|
});
|
|
|
|
this.curOp = null;
|
|
|
|
oldSession && oldSession._signal("changeEditor", {oldEditor: this});
|
|
session && session._signal("changeEditor", {editor: this});
|
|
|
|
if (session && session.bgTokenizer)
|
|
session.bgTokenizer.scheduleStart();
|
|
};
|
|
|
|
/**
|
|
* Returns the current session being used.
|
|
* @returns {EditSession}
|
|
**/
|
|
this.getSession = function() {
|
|
return this.session;
|
|
};
|
|
|
|
/**
|
|
* Sets the current document to `val`.
|
|
* @param {String} val The new value to set for the document
|
|
* @param {Number} cursorPos Where to set the new value. `undefined` or 0 is selectAll, -1 is at the document start, and 1 is at the end
|
|
*
|
|
* @returns {String} The current document value
|
|
* @related Document.setValue
|
|
**/
|
|
this.setValue = function(val, cursorPos) {
|
|
this.session.doc.setValue(val);
|
|
|
|
if (!cursorPos)
|
|
this.selectAll();
|
|
else if (cursorPos == 1)
|
|
this.navigateFileEnd();
|
|
else if (cursorPos == -1)
|
|
this.navigateFileStart();
|
|
|
|
return val;
|
|
};
|
|
|
|
/**
|
|
* Returns the current session's content.
|
|
*
|
|
* @returns {String}
|
|
* @related EditSession.getValue
|
|
**/
|
|
this.getValue = function() {
|
|
return this.session.getValue();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Returns the currently highlighted selection.
|
|
* @returns {Selection} The selection object
|
|
**/
|
|
this.getSelection = function() {
|
|
return this.selection;
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.onResize}
|
|
* @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed
|
|
*
|
|
*
|
|
* @related VirtualRenderer.onResize
|
|
**/
|
|
this.resize = function(force) {
|
|
this.renderer.onResize(force);
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.setTheme}
|
|
* @param {String} theme The path to a theme
|
|
* @param {Function} cb optional callback called when theme is loaded
|
|
**/
|
|
this.setTheme = function(theme, cb) {
|
|
this.renderer.setTheme(theme, cb);
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.getTheme}
|
|
*
|
|
* @returns {String} The set theme
|
|
* @related VirtualRenderer.getTheme
|
|
**/
|
|
this.getTheme = function() {
|
|
return this.renderer.getTheme();
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.setStyle}
|
|
* @param {String} style A class name
|
|
*
|
|
*
|
|
* @related VirtualRenderer.setStyle
|
|
**/
|
|
this.setStyle = function(style) {
|
|
this.renderer.setStyle(style);
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.unsetStyle}
|
|
* @related VirtualRenderer.unsetStyle
|
|
**/
|
|
this.unsetStyle = function(style) {
|
|
this.renderer.unsetStyle(style);
|
|
};
|
|
|
|
/**
|
|
* Gets the current font size of the editor text.
|
|
*/
|
|
this.getFontSize = function () {
|
|
return this.getOption("fontSize") ||
|
|
dom.computedStyle(this.container).fontSize;
|
|
};
|
|
|
|
/**
|
|
* Set a new font size (in pixels) for the editor text.
|
|
* @param {String} size A font size ( _e.g._ "12px")
|
|
*
|
|
*
|
|
**/
|
|
this.setFontSize = function(size) {
|
|
this.setOption("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 session = self.session;
|
|
if (!session || !session.bgTokenizer) return;
|
|
var pos = session.findMatchingBracket(self.getCursorPosition());
|
|
if (pos) {
|
|
var range = new Range(pos.row, pos.column, pos.row, pos.column + 1);
|
|
} else if (session.$mode.getMatching) {
|
|
var range = session.$mode.getMatching(self.session);
|
|
}
|
|
if (range)
|
|
session.$bracketHighlight = session.addMarker(range, "ace_bracket", "text");
|
|
}, 50);
|
|
};
|
|
|
|
// todo: move to mode.getMatching
|
|
this.$highlightTags = function() {
|
|
if (this.$highlightTagPending)
|
|
return;
|
|
|
|
// perform highlight async to not block the browser during navigation
|
|
var self = this;
|
|
this.$highlightTagPending = true;
|
|
setTimeout(function() {
|
|
self.$highlightTagPending = false;
|
|
|
|
var session = self.session;
|
|
if (!session || !session.bgTokenizer) return;
|
|
|
|
var pos = self.getCursorPosition();
|
|
var iterator = new TokenIterator(self.session, pos.row, pos.column);
|
|
var token = iterator.getCurrentToken();
|
|
|
|
if (!token || !/\b(?:tag-open|tag-name)/.test(token.type)) {
|
|
session.removeMarker(session.$tagHighlight);
|
|
session.$tagHighlight = null;
|
|
return;
|
|
}
|
|
|
|
if (token.type.indexOf("tag-open") != -1) {
|
|
token = iterator.stepForward();
|
|
if (!token)
|
|
return;
|
|
}
|
|
|
|
var tag = token.value;
|
|
var depth = 0;
|
|
var prevToken = iterator.stepBackward();
|
|
|
|
if (prevToken.value == '<'){
|
|
//find closing tag
|
|
do {
|
|
prevToken = token;
|
|
token = iterator.stepForward();
|
|
|
|
if (token && token.value === tag && token.type.indexOf('tag-name') !== -1) {
|
|
if (prevToken.value === '<'){
|
|
depth++;
|
|
} else if (prevToken.value === '</'){
|
|
depth--;
|
|
}
|
|
}
|
|
|
|
} while (token && depth >= 0);
|
|
} else {
|
|
//find opening tag
|
|
do {
|
|
token = prevToken;
|
|
prevToken = iterator.stepBackward();
|
|
|
|
if (token && token.value === tag && token.type.indexOf('tag-name') !== -1) {
|
|
if (prevToken.value === '<') {
|
|
depth++;
|
|
} else if (prevToken.value === '</') {
|
|
depth--;
|
|
}
|
|
}
|
|
} while (prevToken && depth <= 0);
|
|
|
|
//select tag again
|
|
iterator.stepForward();
|
|
}
|
|
|
|
if (!token) {
|
|
session.removeMarker(session.$tagHighlight);
|
|
session.$tagHighlight = null;
|
|
return;
|
|
}
|
|
|
|
var row = iterator.getCurrentTokenRow();
|
|
var column = iterator.getCurrentTokenColumn();
|
|
var range = new Range(row, column, row, column+token.value.length);
|
|
|
|
//remove range if different
|
|
var sbm = session.$backMarkers[session.$tagHighlight];
|
|
if (session.$tagHighlight && sbm != undefined && range.compareRange(sbm.range) !== 0) {
|
|
session.removeMarker(session.$tagHighlight);
|
|
session.$tagHighlight = null;
|
|
}
|
|
|
|
if (!session.$tagHighlight)
|
|
session.$tagHighlight = session.addMarker(range, "ace_bracket", "text");
|
|
}, 50);
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Brings the current `textInput` into focus.
|
|
**/
|
|
this.focus = function() {
|
|
// focusing after timeout is not needed now, but some code using ace
|
|
// depends on being able to call focus when textarea is not visible,
|
|
// so to keep backwards compatibility we keep this until the next major release
|
|
var _self = this;
|
|
setTimeout(function() {
|
|
if (!_self.isFocused())
|
|
_self.textInput.focus();
|
|
});
|
|
this.textInput.focus();
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if the current `textInput` is in focus.
|
|
* @return {Boolean}
|
|
**/
|
|
this.isFocused = function() {
|
|
return this.textInput.isFocused();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Blurs the current `textInput`.
|
|
**/
|
|
this.blur = function() {
|
|
this.textInput.blur();
|
|
};
|
|
|
|
/**
|
|
* Emitted once the editor comes into focus.
|
|
* @event focus
|
|
*
|
|
*
|
|
**/
|
|
this.onFocus = function(e) {
|
|
if (this.$isFocused)
|
|
return;
|
|
this.$isFocused = true;
|
|
this.renderer.showCursor();
|
|
this.renderer.visualizeFocus();
|
|
this._emit("focus", e);
|
|
};
|
|
|
|
/**
|
|
* Emitted once the editor has been blurred.
|
|
* @event blur
|
|
*
|
|
*
|
|
**/
|
|
this.onBlur = function(e) {
|
|
if (!this.$isFocused)
|
|
return;
|
|
this.$isFocused = false;
|
|
this.renderer.hideCursor();
|
|
this.renderer.visualizeBlur();
|
|
this._emit("blur", e);
|
|
};
|
|
|
|
this.$cursorChange = function() {
|
|
this.renderer.updateCursor();
|
|
};
|
|
|
|
/**
|
|
* Emitted whenever the document is changed.
|
|
* @event change
|
|
* @param {Object} e Contains a single property, `data`, which has the delta of changes
|
|
*
|
|
*
|
|
*
|
|
**/
|
|
this.onDocumentChange = function(delta) {
|
|
// Rerender and emit "change" event.
|
|
var wrap = this.session.$useWrapMode;
|
|
var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity);
|
|
this.renderer.updateLines(delta.start.row, lastRow, wrap);
|
|
|
|
this._signal("change", delta);
|
|
|
|
// Update cursor because tab characters can influence the cursor position.
|
|
this.$cursorChange();
|
|
this.$updateHighlightActiveLine();
|
|
};
|
|
|
|
this.onTokenizerUpdate = function(e) {
|
|
var rows = e.data;
|
|
this.renderer.updateLines(rows.first, rows.last);
|
|
};
|
|
|
|
|
|
this.onScrollTopChange = function() {
|
|
this.renderer.scrollToY(this.session.getScrollTop());
|
|
};
|
|
|
|
this.onScrollLeftChange = function() {
|
|
this.renderer.scrollToX(this.session.getScrollLeft());
|
|
};
|
|
|
|
/**
|
|
* Emitted when the selection changes.
|
|
*
|
|
**/
|
|
this.onCursorChange = function() {
|
|
this.$cursorChange();
|
|
|
|
this.$highlightBrackets();
|
|
this.$highlightTags();
|
|
this.$updateHighlightActiveLine();
|
|
this._signal("changeSelection");
|
|
};
|
|
|
|
this.$updateHighlightActiveLine = function() {
|
|
var session = this.getSession();
|
|
|
|
var highlight;
|
|
if (this.$highlightActiveLine) {
|
|
if (this.$selectionStyle != "line" || !this.selection.isMultiLine())
|
|
highlight = this.getCursorPosition();
|
|
if (this.renderer.theme && this.renderer.theme.$selectionColorConflict && !this.selection.isEmpty())
|
|
highlight = false;
|
|
if (this.renderer.$maxLines && this.session.getLength() === 1 && !(this.renderer.$minLines > 1))
|
|
highlight = false;
|
|
}
|
|
|
|
if (session.$highlightLineMarker && !highlight) {
|
|
session.removeMarker(session.$highlightLineMarker.id);
|
|
session.$highlightLineMarker = null;
|
|
} else if (!session.$highlightLineMarker && highlight) {
|
|
var range = new Range(highlight.row, highlight.column, highlight.row, Infinity);
|
|
range.id = session.addMarker(range, "ace_active-line", "screenLine");
|
|
session.$highlightLineMarker = range;
|
|
} else if (highlight) {
|
|
session.$highlightLineMarker.start.row = highlight.row;
|
|
session.$highlightLineMarker.end.row = highlight.row;
|
|
session.$highlightLineMarker.start.column = highlight.column;
|
|
session._signal("changeBackMarker");
|
|
}
|
|
};
|
|
|
|
this.onSelectionChange = function(e) {
|
|
var session = this.session;
|
|
|
|
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();
|
|
}
|
|
|
|
var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp();
|
|
this.session.highlight(re);
|
|
|
|
this._signal("changeSelection");
|
|
};
|
|
|
|
this.$getSelectionHighLightRegexp = function() {
|
|
var session = this.session;
|
|
|
|
var selection = this.getSelectionRange();
|
|
if (selection.isEmpty() || selection.isMultiLine())
|
|
return;
|
|
|
|
var startColumn = selection.start.column;
|
|
var endColumn = selection.end.column;
|
|
var line = session.getLine(selection.start.row);
|
|
|
|
var needle = line.substring(startColumn, endColumn);
|
|
// maximum allowed size for regular expressions in 32000,
|
|
// but getting close to it has significant impact on the performance
|
|
if (needle.length > 5000 || !/[\w\d]/.test(needle))
|
|
return;
|
|
|
|
var re = this.$search.$assembleRegExp({
|
|
wholeWord: true,
|
|
caseSensitive: true,
|
|
needle: needle
|
|
});
|
|
|
|
var wordWithBoundary = line.substring(startColumn - 1, endColumn + 1);
|
|
if (!re.test(wordWithBoundary))
|
|
return;
|
|
|
|
return re;
|
|
};
|
|
|
|
|
|
this.onChangeFrontMarker = function() {
|
|
this.renderer.updateFrontMarkers();
|
|
};
|
|
|
|
this.onChangeBackMarker = function() {
|
|
this.renderer.updateBackMarkers();
|
|
};
|
|
|
|
|
|
this.onChangeBreakpoint = function() {
|
|
this.renderer.updateBreakpoints();
|
|
};
|
|
|
|
this.onChangeAnnotation = function() {
|
|
this.renderer.setAnnotations(this.session.getAnnotations());
|
|
};
|
|
|
|
|
|
this.onChangeMode = function(e) {
|
|
this.renderer.updateText();
|
|
this._emit("changeMode", e);
|
|
};
|
|
|
|
|
|
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();
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns the string of text currently highlighted.
|
|
* @returns {String}
|
|
**/
|
|
this.getSelectedText = function() {
|
|
return this.session.getTextRange(this.getSelectionRange());
|
|
};
|
|
|
|
/**
|
|
* Emitted when text is copied.
|
|
* @event copy
|
|
* @param {String} text The copied text
|
|
*
|
|
**/
|
|
/**
|
|
* Returns the string of text currently highlighted.
|
|
* @returns {String}
|
|
**/
|
|
this.getCopyText = function() {
|
|
var text = this.getSelectedText();
|
|
var nl = this.session.doc.getNewLineCharacter();
|
|
var copyLine= false;
|
|
if (!text && this.$copyWithEmptySelection) {
|
|
copyLine = true;
|
|
var ranges = this.selection.getAllRanges();
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
var range = ranges[i];
|
|
if (i && ranges[i - 1].start.row == range.start.row)
|
|
continue;
|
|
text += this.session.getLine(range.start.row) + nl;
|
|
}
|
|
}
|
|
var e = {text: text};
|
|
this._signal("copy", e);
|
|
clipboard.lineMode = copyLine ? e.text : "";
|
|
return e.text;
|
|
};
|
|
|
|
/**
|
|
* Called whenever a text "copy" happens.
|
|
**/
|
|
this.onCopy = function() {
|
|
this.commands.exec("copy", this);
|
|
};
|
|
|
|
/**
|
|
* Called whenever a text "cut" happens.
|
|
**/
|
|
this.onCut = function() {
|
|
this.commands.exec("cut", this);
|
|
};
|
|
|
|
/**
|
|
* Emitted when text is pasted.
|
|
* @event paste
|
|
* @param {Object} an object which contains one property, `text`, that represents the text to be pasted. Editing this property will alter the text that is pasted.
|
|
*
|
|
*
|
|
**/
|
|
/**
|
|
* Called whenever a text "paste" happens.
|
|
* @param {String} text The pasted text
|
|
*
|
|
*
|
|
**/
|
|
this.onPaste = function(text, event) {
|
|
var e = {text: text, event: event};
|
|
this.commands.exec("paste", this, e);
|
|
};
|
|
|
|
this.$handlePaste = function(e) {
|
|
if (typeof e == "string")
|
|
e = {text: e};
|
|
this._signal("paste", e);
|
|
var text = e.text;
|
|
|
|
var lineMode = text == clipboard.lineMode;
|
|
var session = this.session;
|
|
if (!this.inMultiSelectMode || this.inVirtualSelectionMode) {
|
|
if (lineMode)
|
|
session.insert({ row: this.selection.lead.row, column: 0 }, text);
|
|
else
|
|
this.insert(text);
|
|
} else if (lineMode) {
|
|
this.selection.rangeList.ranges.forEach(function(range) {
|
|
session.insert({ row: range.start.row, column: 0 }, text);
|
|
});
|
|
} else {
|
|
var lines = text.split(/\r\n|\r|\n/);
|
|
var ranges = this.selection.rangeList.ranges;
|
|
|
|
if (lines.length > ranges.length || lines.length < 2 || !lines[1])
|
|
return this.commands.exec("insertstring", this, text);
|
|
|
|
for (var i = ranges.length; i--;) {
|
|
var range = ranges[i];
|
|
if (!range.isEmpty())
|
|
session.remove(range);
|
|
|
|
session.insert(range.start, lines[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.execCommand = function(command, args) {
|
|
return this.commands.exec(command, this, args);
|
|
};
|
|
|
|
/**
|
|
* Inserts `text` into wherever the cursor is pointing.
|
|
* @param {String} text The new text to add
|
|
*
|
|
**/
|
|
this.insert = function(text, pasted) {
|
|
var session = this.session;
|
|
var mode = session.getMode();
|
|
var cursor = this.getCursorPosition();
|
|
|
|
if (this.getBehavioursEnabled() && !pasted) {
|
|
// Get a transform if the current mode wants one.
|
|
var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
|
|
if (transform) {
|
|
if (text !== transform.text) {
|
|
// keep automatic insertion in a separate delta, unless it is in multiselect mode
|
|
if (!this.inVirtualSelectionMode) {
|
|
this.session.mergeUndoDeltas = false;
|
|
this.mergeNextCommand = false;
|
|
}
|
|
}
|
|
text = transform.text;
|
|
|
|
}
|
|
}
|
|
|
|
if (text == "\t")
|
|
text = this.session.getTabString();
|
|
|
|
// remove selected text
|
|
if (!this.selection.isEmpty()) {
|
|
var range = this.getSelectionRange();
|
|
cursor = this.session.remove(range);
|
|
this.clearSelection();
|
|
}
|
|
else if (this.session.getOverwrite() && text.indexOf("\n") == -1) {
|
|
var range = new Range.fromPoints(cursor, cursor);
|
|
range.end.column += text.length;
|
|
this.session.remove(range);
|
|
}
|
|
|
|
if (text == "\n" || text == "\r\n") {
|
|
var line = session.getLine(cursor.row);
|
|
if (cursor.column > line.search(/\S|$/)) {
|
|
var d = line.substr(cursor.column).search(/\S|$/);
|
|
session.doc.removeInLine(cursor.row, cursor.column, cursor.column + d);
|
|
}
|
|
}
|
|
this.clearSelection();
|
|
|
|
var start = cursor.column;
|
|
var lineState = session.getState(cursor.row);
|
|
var line = session.getLine(cursor.row);
|
|
var shouldOutdent = mode.checkOutdent(lineState, line, text);
|
|
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]));
|
|
}
|
|
}
|
|
|
|
if (session.getDocument().isNewLine(text)) {
|
|
var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
|
|
|
|
session.insert({row: cursor.row+1, column: 0}, lineIndent);
|
|
}
|
|
if (shouldOutdent)
|
|
mode.autoOutdent(lineState, session, cursor.row);
|
|
};
|
|
|
|
this.onTextInput = function(text, composition) {
|
|
if (!composition)
|
|
return this.keyBinding.onTextInput(text);
|
|
|
|
this.startOperation({command: { name: "insertstring" }});
|
|
var applyComposition = this.applyComposition.bind(this, text, composition);
|
|
if (this.selection.rangeCount)
|
|
this.forEachSelection(applyComposition);
|
|
else
|
|
applyComposition();
|
|
this.endOperation();
|
|
};
|
|
|
|
this.applyComposition = function(text, composition) {
|
|
if (composition.extendLeft || composition.extendRight) {
|
|
var r = this.selection.getRange();
|
|
r.start.column -= composition.extendLeft;
|
|
r.end.column += composition.extendRight;
|
|
this.selection.setRange(r);
|
|
if (!text && !r.isEmpty())
|
|
this.remove();
|
|
}
|
|
if (text || !this.selection.isEmpty())
|
|
this.insert(text, true);
|
|
if (composition.restoreStart || composition.restoreEnd) {
|
|
var r = this.selection.getRange();
|
|
r.start.column -= composition.restoreStart;
|
|
r.end.column -= composition.restoreEnd;
|
|
this.selection.setRange(r);
|
|
}
|
|
};
|
|
|
|
this.onCommandKey = function(e, hashId, keyCode) {
|
|
this.keyBinding.onCommandKey(e, hashId, keyCode);
|
|
};
|
|
|
|
/**
|
|
* Pass in `true` to enable overwrites in your session, or `false` to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of `overwrite` changes, this function also emits the `changeOverwrite` event.
|
|
* @param {Boolean} overwrite Defines whether or not to set overwrites
|
|
*
|
|
*
|
|
* @related EditSession.setOverwrite
|
|
**/
|
|
this.setOverwrite = function(overwrite) {
|
|
this.session.setOverwrite(overwrite);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if overwrites are enabled; `false` otherwise.
|
|
* @returns {Boolean}
|
|
* @related EditSession.getOverwrite
|
|
**/
|
|
this.getOverwrite = function() {
|
|
return this.session.getOverwrite();
|
|
};
|
|
|
|
/**
|
|
* Sets the value of overwrite to the opposite of whatever it currently is.
|
|
* @related EditSession.toggleOverwrite
|
|
**/
|
|
this.toggleOverwrite = function() {
|
|
this.session.toggleOverwrite();
|
|
};
|
|
|
|
/**
|
|
* Sets how fast the mouse scrolling should do.
|
|
* @param {Number} speed A value indicating the new speed (in milliseconds)
|
|
**/
|
|
this.setScrollSpeed = function(speed) {
|
|
this.setOption("scrollSpeed", speed);
|
|
};
|
|
|
|
/**
|
|
* Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
|
|
* @returns {Number}
|
|
**/
|
|
this.getScrollSpeed = function() {
|
|
return this.getOption("scrollSpeed");
|
|
};
|
|
|
|
/**
|
|
* Sets the delay (in milliseconds) of the mouse drag.
|
|
* @param {Number} dragDelay A value indicating the new delay
|
|
**/
|
|
this.setDragDelay = function(dragDelay) {
|
|
this.setOption("dragDelay", dragDelay);
|
|
};
|
|
|
|
/**
|
|
* Returns the current mouse drag delay.
|
|
* @returns {Number}
|
|
**/
|
|
this.getDragDelay = function() {
|
|
return this.getOption("dragDelay");
|
|
};
|
|
|
|
/**
|
|
* Emitted when the selection style changes, via [[Editor.setSelectionStyle]].
|
|
* @event changeSelectionStyle
|
|
* @param {Object} data Contains one property, `data`, which indicates the new selection style
|
|
**/
|
|
/**
|
|
* Draw selection markers spanning whole line, or only over selected text. Default value is "line"
|
|
* @param {String} style The new selection style "line"|"text"
|
|
*
|
|
**/
|
|
this.setSelectionStyle = function(val) {
|
|
this.setOption("selectionStyle", val);
|
|
};
|
|
|
|
/**
|
|
* Returns the current selection style.
|
|
* @returns {String}
|
|
**/
|
|
this.getSelectionStyle = function() {
|
|
return this.getOption("selectionStyle");
|
|
};
|
|
|
|
/**
|
|
* Determines whether or not the current line should be highlighted.
|
|
* @param {Boolean} shouldHighlight Set to `true` to highlight the current line
|
|
**/
|
|
this.setHighlightActiveLine = function(shouldHighlight) {
|
|
this.setOption("highlightActiveLine", shouldHighlight);
|
|
};
|
|
/**
|
|
* Returns `true` if current lines are always highlighted.
|
|
* @return {Boolean}
|
|
**/
|
|
this.getHighlightActiveLine = function() {
|
|
return this.getOption("highlightActiveLine");
|
|
};
|
|
this.setHighlightGutterLine = function(shouldHighlight) {
|
|
this.setOption("highlightGutterLine", shouldHighlight);
|
|
};
|
|
|
|
this.getHighlightGutterLine = function() {
|
|
return this.getOption("highlightGutterLine");
|
|
};
|
|
|
|
/**
|
|
* Determines if the currently selected word should be highlighted.
|
|
* @param {Boolean} shouldHighlight Set to `true` to highlight the currently selected word
|
|
*
|
|
**/
|
|
this.setHighlightSelectedWord = function(shouldHighlight) {
|
|
this.setOption("highlightSelectedWord", shouldHighlight);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if currently highlighted words are to be highlighted.
|
|
* @returns {Boolean}
|
|
**/
|
|
this.getHighlightSelectedWord = function() {
|
|
return this.$highlightSelectedWord;
|
|
};
|
|
|
|
this.setAnimatedScroll = function(shouldAnimate){
|
|
this.renderer.setAnimatedScroll(shouldAnimate);
|
|
};
|
|
|
|
this.getAnimatedScroll = function(){
|
|
return this.renderer.getAnimatedScroll();
|
|
};
|
|
|
|
/**
|
|
* If `showInvisibles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor.
|
|
* @param {Boolean} showInvisibles Specifies whether or not to show invisible characters
|
|
*
|
|
**/
|
|
this.setShowInvisibles = function(showInvisibles) {
|
|
this.renderer.setShowInvisibles(showInvisibles);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if invisible characters are being shown.
|
|
* @returns {Boolean}
|
|
**/
|
|
this.getShowInvisibles = function() {
|
|
return this.renderer.getShowInvisibles();
|
|
};
|
|
|
|
this.setDisplayIndentGuides = function(display) {
|
|
this.renderer.setDisplayIndentGuides(display);
|
|
};
|
|
|
|
this.getDisplayIndentGuides = function() {
|
|
return this.renderer.getDisplayIndentGuides();
|
|
};
|
|
|
|
/**
|
|
* If `showPrintMargin` is set to `true`, the print margin is shown in the editor.
|
|
* @param {Boolean} showPrintMargin Specifies whether or not to show the print margin
|
|
*
|
|
**/
|
|
this.setShowPrintMargin = function(showPrintMargin) {
|
|
this.renderer.setShowPrintMargin(showPrintMargin);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if the print margin is being shown.
|
|
* @returns {Boolean}
|
|
**/
|
|
this.getShowPrintMargin = function() {
|
|
return this.renderer.getShowPrintMargin();
|
|
};
|
|
|
|
/**
|
|
* Sets the column defining where the print margin should be.
|
|
* @param {Number} showPrintMargin Specifies the new print margin
|
|
*
|
|
**/
|
|
this.setPrintMarginColumn = function(showPrintMargin) {
|
|
this.renderer.setPrintMarginColumn(showPrintMargin);
|
|
};
|
|
|
|
/**
|
|
* Returns the column number of where the print margin is.
|
|
* @returns {Number}
|
|
**/
|
|
this.getPrintMarginColumn = function() {
|
|
return this.renderer.getPrintMarginColumn();
|
|
};
|
|
|
|
/**
|
|
* If `readOnly` is true, then the editor is set to read-only mode, and none of the content can change.
|
|
* @param {Boolean} readOnly Specifies whether the editor can be modified or not
|
|
*
|
|
**/
|
|
this.setReadOnly = function(readOnly) {
|
|
this.setOption("readOnly", readOnly);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if the editor is set to read-only mode.
|
|
* @returns {Boolean}
|
|
**/
|
|
this.getReadOnly = function() {
|
|
return this.getOption("readOnly");
|
|
};
|
|
|
|
/**
|
|
* Specifies whether to use behaviors or not. ["Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.]{: #BehaviorsDef}
|
|
* @param {Boolean} enabled Enables or disables behaviors
|
|
*
|
|
**/
|
|
this.setBehavioursEnabled = function (enabled) {
|
|
this.setOption("behavioursEnabled", enabled);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if the behaviors are currently enabled. {:BehaviorsDef}
|
|
*
|
|
* @returns {Boolean}
|
|
**/
|
|
this.getBehavioursEnabled = function () {
|
|
return this.getOption("behavioursEnabled");
|
|
};
|
|
|
|
/**
|
|
* Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets
|
|
* when such a character is typed in.
|
|
* @param {Boolean} enabled Enables or disables wrapping behaviors
|
|
*
|
|
**/
|
|
this.setWrapBehavioursEnabled = function (enabled) {
|
|
this.setOption("wrapBehavioursEnabled", enabled);
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if the wrapping behaviors are currently enabled.
|
|
**/
|
|
this.getWrapBehavioursEnabled = function () {
|
|
return this.getOption("wrapBehavioursEnabled");
|
|
};
|
|
|
|
/**
|
|
* Indicates whether the fold widgets should be shown or not.
|
|
* @param {Boolean} show Specifies whether the fold widgets are shown
|
|
**/
|
|
this.setShowFoldWidgets = function(show) {
|
|
this.setOption("showFoldWidgets", show);
|
|
|
|
};
|
|
/**
|
|
* Returns `true` if the fold widgets are shown.
|
|
* @return {Boolean}
|
|
**/
|
|
this.getShowFoldWidgets = function() {
|
|
return this.getOption("showFoldWidgets");
|
|
};
|
|
|
|
this.setFadeFoldWidgets = function(fade) {
|
|
this.setOption("fadeFoldWidgets", fade);
|
|
};
|
|
|
|
this.getFadeFoldWidgets = function() {
|
|
return this.getOption("fadeFoldWidgets");
|
|
};
|
|
|
|
/**
|
|
* Removes the current selection or one character.
|
|
* @param {String} dir The direction of the deletion to occur, either "left" or "right"
|
|
*
|
|
**/
|
|
this.remove = function(dir) {
|
|
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 (range.end.column === 0) {
|
|
var text = session.getTextRange(range);
|
|
if (text[text.length - 1] == "\n") {
|
|
var line = session.getLine(range.end.row);
|
|
if (/^\s+$/.test(line)) {
|
|
range.end.column = line.length;
|
|
}
|
|
}
|
|
}
|
|
if (new_range)
|
|
range = new_range;
|
|
}
|
|
|
|
this.session.remove(range);
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
* Removes the word directly to the right of the current selection.
|
|
**/
|
|
this.removeWordRight = function() {
|
|
if (this.selection.isEmpty())
|
|
this.selection.selectWordRight();
|
|
|
|
this.session.remove(this.getSelectionRange());
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
* Removes the word directly to the left of the current selection.
|
|
**/
|
|
this.removeWordLeft = function() {
|
|
if (this.selection.isEmpty())
|
|
this.selection.selectWordLeft();
|
|
|
|
this.session.remove(this.getSelectionRange());
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
* Removes all the words to the left of the current selection, until the start of the line.
|
|
**/
|
|
this.removeToLineStart = function() {
|
|
if (this.selection.isEmpty())
|
|
this.selection.selectLineStart();
|
|
if (this.selection.isEmpty())
|
|
this.selection.selectLeft();
|
|
this.session.remove(this.getSelectionRange());
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
* Removes all the words to the right of the current selection, until the end of the line.
|
|
**/
|
|
this.removeToLineEnd = function() {
|
|
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();
|
|
};
|
|
|
|
/**
|
|
* Splits the line at the current selection (by inserting an `'\n'`).
|
|
**/
|
|
this.splitLine = function() {
|
|
if (!this.selection.isEmpty()) {
|
|
this.session.remove(this.getSelectionRange());
|
|
this.clearSelection();
|
|
}
|
|
|
|
var cursor = this.getCursorPosition();
|
|
this.insert("\n");
|
|
this.moveCursorToPosition(cursor);
|
|
};
|
|
|
|
/**
|
|
* Transposes current line.
|
|
**/
|
|
this.transposeLetters = function() {
|
|
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.session.selection.moveToPosition(range.end);
|
|
};
|
|
|
|
/**
|
|
* Converts the current selection entirely into lowercase.
|
|
**/
|
|
this.toLowerCase = function() {
|
|
var originalRange = this.getSelectionRange();
|
|
if (this.selection.isEmpty()) {
|
|
this.selection.selectWord();
|
|
}
|
|
|
|
var range = this.getSelectionRange();
|
|
var text = this.session.getTextRange(range);
|
|
this.session.replace(range, text.toLowerCase());
|
|
this.selection.setSelectionRange(originalRange);
|
|
};
|
|
|
|
/**
|
|
* Converts the current selection entirely into uppercase.
|
|
**/
|
|
this.toUpperCase = function() {
|
|
var originalRange = this.getSelectionRange();
|
|
if (this.selection.isEmpty()) {
|
|
this.selection.selectWord();
|
|
}
|
|
|
|
var range = this.getSelectionRange();
|
|
var text = this.session.getTextRange(range);
|
|
this.session.replace(range, text.toUpperCase());
|
|
this.selection.setSelectionRange(originalRange);
|
|
};
|
|
|
|
/**
|
|
* Inserts an indentation into the current cursor position or indents the selected lines.
|
|
*
|
|
* @related EditSession.indentRows
|
|
**/
|
|
this.indent = function() {
|
|
var session = this.session;
|
|
var range = this.getSelectionRange();
|
|
|
|
if (range.start.row < range.end.row) {
|
|
var rows = this.$getSelectedRows();
|
|
session.indentRows(rows.first, rows.last, "\t");
|
|
return;
|
|
} else if (range.start.column < range.end.column) {
|
|
var text = session.getTextRange(range);
|
|
if (!/^\s+$/.test(text)) {
|
|
var rows = this.$getSelectedRows();
|
|
session.indentRows(rows.first, rows.last, "\t");
|
|
return;
|
|
}
|
|
}
|
|
|
|
var line = session.getLine(range.start.row);
|
|
var position = range.start;
|
|
var size = session.getTabSize();
|
|
var column = session.documentToScreenColumn(position.row, position.column);
|
|
|
|
if (this.session.getUseSoftTabs()) {
|
|
var count = (size - column % size);
|
|
var indentString = lang.stringRepeat(" ", count);
|
|
} else {
|
|
var count = column % size;
|
|
while (line[range.start.column - 1] == " " && count) {
|
|
range.start.column--;
|
|
count--;
|
|
}
|
|
this.selection.setSelectionRange(range);
|
|
indentString = "\t";
|
|
}
|
|
return this.insert(indentString);
|
|
};
|
|
|
|
/**
|
|
* Indents the current line.
|
|
* @related EditSession.indentRows
|
|
**/
|
|
this.blockIndent = function() {
|
|
var rows = this.$getSelectedRows();
|
|
this.session.indentRows(rows.first, rows.last, "\t");
|
|
};
|
|
|
|
/**
|
|
* Outdents the current line.
|
|
* @related EditSession.outdentRows
|
|
**/
|
|
this.blockOutdent = function() {
|
|
var selection = this.session.getSelection();
|
|
this.session.outdentRows(selection.getRange());
|
|
};
|
|
|
|
// TODO: move out of core when we have good mechanism for managing extensions
|
|
this.sortLines = function() {
|
|
var rows = this.$getSelectedRows();
|
|
var session = this.session;
|
|
|
|
var lines = [];
|
|
for (var i = rows.first; i <= rows.last; i++)
|
|
lines.push(session.getLine(i));
|
|
|
|
lines.sort(function(a, b) {
|
|
if (a.toLowerCase() < b.toLowerCase()) return -1;
|
|
if (a.toLowerCase() > b.toLowerCase()) return 1;
|
|
return 0;
|
|
});
|
|
|
|
var deleteRange = new Range(0, 0, 0, 0);
|
|
for (var i = rows.first; i <= rows.last; i++) {
|
|
var line = session.getLine(i);
|
|
deleteRange.start.row = i;
|
|
deleteRange.end.row = i;
|
|
deleteRange.end.column = line.length;
|
|
session.replace(deleteRange, lines[i-rows.first]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Given the currently selected range, this function either comments all the lines, or uncomments all of them.
|
|
**/
|
|
this.toggleCommentLines = function() {
|
|
var state = this.session.getState(this.getCursorPosition().row);
|
|
var rows = this.$getSelectedRows();
|
|
this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last);
|
|
};
|
|
|
|
this.toggleBlockComment = function() {
|
|
var cursor = this.getCursorPosition();
|
|
var state = this.session.getState(cursor.row);
|
|
var range = this.getSelectionRange();
|
|
this.session.getMode().toggleBlockComment(state, this.session, range, cursor);
|
|
};
|
|
|
|
/**
|
|
* Works like [[EditSession.getTokenAt]], except it returns a number.
|
|
* @returns {Number}
|
|
**/
|
|
this.getNumberAt = function(row, column) {
|
|
var _numberRx = /[\-]?[0-9]+(?:\.[0-9]+)?/g;
|
|
_numberRx.lastIndex = 0;
|
|
|
|
var s = this.session.getLine(row);
|
|
while (_numberRx.lastIndex < column) {
|
|
var m = _numberRx.exec(s);
|
|
if(m.index <= column && m.index+m[0].length >= column){
|
|
var number = {
|
|
value: m[0],
|
|
start: m.index,
|
|
end: m.index+m[0].length
|
|
};
|
|
return number;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* If the character before the cursor is a number, this functions changes its value by `amount`.
|
|
* @param {Number} amount The value to change the numeral by (can be negative to decrease value)
|
|
*
|
|
**/
|
|
this.modifyNumber = function(amount) {
|
|
var row = this.selection.getCursor().row;
|
|
var column = this.selection.getCursor().column;
|
|
|
|
// get the char before the cursor
|
|
var charRange = new Range(row, column-1, row, column);
|
|
|
|
var c = this.session.getTextRange(charRange);
|
|
// if the char is a digit
|
|
if (!isNaN(parseFloat(c)) && isFinite(c)) {
|
|
// get the whole number the digit is part of
|
|
var nr = this.getNumberAt(row, column);
|
|
// if number found
|
|
if (nr) {
|
|
var fp = nr.value.indexOf(".") >= 0 ? nr.start + nr.value.indexOf(".") + 1 : nr.end;
|
|
var decimals = nr.start + nr.value.length - fp;
|
|
|
|
var t = parseFloat(nr.value);
|
|
t *= Math.pow(10, decimals);
|
|
|
|
|
|
if(fp !== nr.end && column < fp){
|
|
amount *= Math.pow(10, nr.end - column - 1);
|
|
} else {
|
|
amount *= Math.pow(10, nr.end - column);
|
|
}
|
|
|
|
t += amount;
|
|
t /= Math.pow(10, decimals);
|
|
var nnr = t.toFixed(decimals);
|
|
|
|
//update number
|
|
var replaceRange = new Range(row, nr.start, row, nr.end);
|
|
this.session.replace(replaceRange, nnr);
|
|
|
|
//reposition the cursor
|
|
this.moveCursorTo(row, Math.max(nr.start +1, column + nnr.length - nr.value.length));
|
|
|
|
}
|
|
} else {
|
|
this.toggleWord();
|
|
}
|
|
};
|
|
|
|
this.$toggleWordPairs = [
|
|
["first", "last"],
|
|
["true", "false"],
|
|
["yes", "no"],
|
|
["width", "height"],
|
|
["top", "bottom"],
|
|
["right", "left"],
|
|
["on", "off"],
|
|
["x", "y"],
|
|
["get", "set"],
|
|
["max", "min"],
|
|
["horizontal", "vertical"],
|
|
["show", "hide"],
|
|
["add", "remove"],
|
|
["up", "down"],
|
|
["before", "after"],
|
|
["even", "odd"],
|
|
["inside", "outside"],
|
|
["next", "previous"],
|
|
["increase", "decrease"],
|
|
["attach", "detach"],
|
|
["&&", "||"],
|
|
["==", "!="]
|
|
];
|
|
|
|
this.toggleWord = function () {
|
|
var row = this.selection.getCursor().row;
|
|
var column = this.selection.getCursor().column;
|
|
this.selection.selectWord();
|
|
var currentState = this.getSelectedText();
|
|
var currWordStart = this.selection.getWordRange().start.column;
|
|
var wordParts = currentState.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, '$1 ').split(/\s/);
|
|
var delta = column - currWordStart - 1;
|
|
if (delta < 0) delta = 0;
|
|
var curLength = 0, itLength = 0;
|
|
var that = this;
|
|
if (currentState.match(/[A-Za-z0-9_]+/)) {
|
|
wordParts.forEach(function (item, i) {
|
|
itLength = curLength + item.length;
|
|
if (delta >= curLength && delta <= itLength) {
|
|
currentState = item;
|
|
that.selection.clearSelection();
|
|
that.moveCursorTo(row, curLength + currWordStart);
|
|
that.selection.selectTo(row, itLength + currWordStart);
|
|
}
|
|
curLength = itLength;
|
|
});
|
|
}
|
|
|
|
var wordPairs = this.$toggleWordPairs;
|
|
var reg;
|
|
for (var i = 0; i < wordPairs.length; i++) {
|
|
var item = wordPairs[i];
|
|
for (var j = 0; j <= 1; j++) {
|
|
var negate = +!j;
|
|
var firstCondition = currentState.match(new RegExp('^\\s?_?(' + lang.escapeRegExp(item[j]) + ')\\s?$', 'i'));
|
|
if (firstCondition) {
|
|
var secondCondition = currentState.match(new RegExp('([_]|^|\\s)(' + lang.escapeRegExp(firstCondition[1]) + ')($|\\s)', 'g'));
|
|
if (secondCondition) {
|
|
reg = currentState.replace(new RegExp(lang.escapeRegExp(item[j]), 'i'), function (result) {
|
|
var res = item[negate];
|
|
if (result.toUpperCase() == result) {
|
|
res = res.toUpperCase();
|
|
} else if (result.charAt(0).toUpperCase() == result.charAt(0)) {
|
|
res = res.substr(0, 0) + item[negate].charAt(0).toUpperCase() + res.substr(1);
|
|
}
|
|
return res;
|
|
});
|
|
this.insert(reg);
|
|
reg = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Removes all the lines in the current selection
|
|
* @related EditSession.remove
|
|
**/
|
|
this.removeLines = function() {
|
|
var rows = this.$getSelectedRows();
|
|
this.session.removeFullLines(rows.first, rows.last);
|
|
this.clearSelection();
|
|
};
|
|
|
|
this.duplicateSelection = function() {
|
|
var sel = this.selection;
|
|
var doc = this.session;
|
|
var range = sel.getRange();
|
|
var reverse = sel.isBackwards();
|
|
if (range.isEmpty()) {
|
|
var row = range.start.row;
|
|
doc.duplicateLines(row, row);
|
|
} else {
|
|
var point = reverse ? range.start : range.end;
|
|
var endPoint = doc.insert(point, doc.getTextRange(range), false);
|
|
range.start = point;
|
|
range.end = endPoint;
|
|
|
|
sel.setSelectionRange(range, reverse);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Shifts all the selected lines down one row.
|
|
*
|
|
* @returns {Number} On success, it returns -1.
|
|
* @related EditSession.moveLinesUp
|
|
**/
|
|
this.moveLinesDown = function() {
|
|
this.$moveLines(1, false);
|
|
};
|
|
|
|
/**
|
|
* Shifts all the selected lines up one row.
|
|
* @returns {Number} On success, it returns -1.
|
|
* @related EditSession.moveLinesDown
|
|
**/
|
|
this.moveLinesUp = function() {
|
|
this.$moveLines(-1, false);
|
|
};
|
|
|
|
/**
|
|
* Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this:
|
|
* ```json
|
|
* { row: newRowLocation, column: newColumnLocation }
|
|
* ```
|
|
* @param {Range} fromRange The range of text you want moved within the document
|
|
* @param {Object} toPosition The location (row and column) where you want to move the text to
|
|
*
|
|
* @returns {Range} The new range where the text was moved to.
|
|
* @related EditSession.moveText
|
|
**/
|
|
this.moveText = function(range, toPosition, copy) {
|
|
return this.session.moveText(range, toPosition, copy);
|
|
};
|
|
|
|
/**
|
|
* Copies all the selected lines up one row.
|
|
* @returns {Number} On success, returns 0.
|
|
*
|
|
**/
|
|
this.copyLinesUp = function() {
|
|
this.$moveLines(-1, true);
|
|
};
|
|
|
|
/**
|
|
* Copies all the selected lines down one row.
|
|
* @returns {Number} On success, returns the number of new rows added; in other words, `lastRow - firstRow + 1`.
|
|
* @related EditSession.duplicateLines
|
|
*
|
|
**/
|
|
this.copyLinesDown = function() {
|
|
this.$moveLines(1, true);
|
|
};
|
|
|
|
/**
|
|
* for internal use
|
|
* @ignore
|
|
*
|
|
**/
|
|
this.$moveLines = function(dir, copy) {
|
|
var rows, moved;
|
|
var selection = this.selection;
|
|
if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) {
|
|
var range = selection.toOrientedRange();
|
|
rows = this.$getSelectedRows(range);
|
|
moved = this.session.$moveLines(rows.first, rows.last, copy ? 0 : dir);
|
|
if (copy && dir == -1) moved = 0;
|
|
range.moveBy(moved, 0);
|
|
selection.fromOrientedRange(range);
|
|
} else {
|
|
var ranges = selection.rangeList.ranges;
|
|
selection.rangeList.detach(this.session);
|
|
this.inVirtualSelectionMode = true;
|
|
|
|
var diff = 0;
|
|
var totalDiff = 0;
|
|
var l = ranges.length;
|
|
for (var i = 0; i < l; i++) {
|
|
var rangeIndex = i;
|
|
ranges[i].moveBy(diff, 0);
|
|
rows = this.$getSelectedRows(ranges[i]);
|
|
var first = rows.first;
|
|
var last = rows.last;
|
|
while (++i < l) {
|
|
if (totalDiff) ranges[i].moveBy(totalDiff, 0);
|
|
var subRows = this.$getSelectedRows(ranges[i]);
|
|
if (copy && subRows.first != last)
|
|
break;
|
|
else if (!copy && subRows.first > last + 1)
|
|
break;
|
|
last = subRows.last;
|
|
}
|
|
i--;
|
|
diff = this.session.$moveLines(first, last, copy ? 0 : dir);
|
|
if (copy && dir == -1) rangeIndex = i + 1;
|
|
while (rangeIndex <= i) {
|
|
ranges[rangeIndex].moveBy(diff, 0);
|
|
rangeIndex++;
|
|
}
|
|
if (!copy) diff = 0;
|
|
totalDiff += diff;
|
|
}
|
|
|
|
selection.fromOrientedRange(selection.ranges[0]);
|
|
selection.rangeList.attach(this.session);
|
|
this.inVirtualSelectionMode = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns an object indicating the currently selected rows. The object looks like this:
|
|
*
|
|
* ```json
|
|
* { first: range.start.row, last: range.end.row }
|
|
* ```
|
|
*
|
|
* @returns {Object}
|
|
**/
|
|
this.$getSelectedRows = function(range) {
|
|
range = (range || this.getSelectionRange()).collapseRows();
|
|
|
|
return {
|
|
first: this.session.getRowFoldStart(range.start.row),
|
|
last: this.session.getRowFoldEnd(range.end.row)
|
|
};
|
|
};
|
|
|
|
this.onCompositionStart = function(compositionState) {
|
|
this.renderer.showComposition(compositionState);
|
|
};
|
|
|
|
this.onCompositionUpdate = function(text) {
|
|
this.renderer.setCompositionText(text);
|
|
};
|
|
|
|
this.onCompositionEnd = function() {
|
|
this.renderer.hideComposition();
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.getFirstVisibleRow}
|
|
*
|
|
* @returns {Number}
|
|
* @related VirtualRenderer.getFirstVisibleRow
|
|
**/
|
|
this.getFirstVisibleRow = function() {
|
|
return this.renderer.getFirstVisibleRow();
|
|
};
|
|
|
|
/**
|
|
* {:VirtualRenderer.getLastVisibleRow}
|
|
*
|
|
* @returns {Number}
|
|
* @related VirtualRenderer.getLastVisibleRow
|
|
**/
|
|
this.getLastVisibleRow = function() {
|
|
return this.renderer.getLastVisibleRow();
|
|
};
|
|
|
|
/**
|
|
* Indicates if the row is currently visible on the screen.
|
|
* @param {Number} row The row to check
|
|
*
|
|
* @returns {Boolean}
|
|
**/
|
|
this.isRowVisible = function(row) {
|
|
return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow());
|
|
};
|
|
|
|
/**
|
|
* Indicates if the entire row is currently visible on the screen.
|
|
* @param {Number} row The row to check
|
|
*
|
|
*
|
|
* @returns {Boolean}
|
|
**/
|
|
this.isRowFullyVisible = function(row) {
|
|
return (row >= this.renderer.getFirstFullyVisibleRow() && row <= this.renderer.getLastFullyVisibleRow());
|
|
};
|
|
|
|
/**
|
|
* Returns the number of currently visible rows.
|
|
* @returns {Number}
|
|
**/
|
|
this.$getVisibleRowCount = function() {
|
|
return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1;
|
|
};
|
|
|
|
this.$moveByPage = function(dir, select) {
|
|
var renderer = this.renderer;
|
|
var config = this.renderer.layerConfig;
|
|
var rows = dir * Math.floor(config.height / config.lineHeight);
|
|
|
|
if (select === true) {
|
|
this.selection.$moveSelection(function(){
|
|
this.moveCursorBy(rows, 0);
|
|
});
|
|
} else if (select === false) {
|
|
this.selection.moveCursorBy(rows, 0);
|
|
this.selection.clearSelection();
|
|
}
|
|
|
|
var scrollTop = renderer.scrollTop;
|
|
|
|
renderer.scrollBy(0, rows * config.lineHeight);
|
|
if (select != null)
|
|
renderer.scrollCursorIntoView(null, 0.5);
|
|
|
|
renderer.animateScrolling(scrollTop);
|
|
};
|
|
|
|
/**
|
|
* Selects the text from the current position of the document until where a "page down" finishes.
|
|
**/
|
|
this.selectPageDown = function() {
|
|
this.$moveByPage(1, true);
|
|
};
|
|
|
|
/**
|
|
* Selects the text from the current position of the document until where a "page up" finishes.
|
|
**/
|
|
this.selectPageUp = function() {
|
|
this.$moveByPage(-1, true);
|
|
};
|
|
|
|
/**
|
|
* Shifts the document to wherever "page down" is, as well as moving the cursor position.
|
|
**/
|
|
this.gotoPageDown = function() {
|
|
this.$moveByPage(1, false);
|
|
};
|
|
|
|
/**
|
|
* Shifts the document to wherever "page up" is, as well as moving the cursor position.
|
|
**/
|
|
this.gotoPageUp = function() {
|
|
this.$moveByPage(-1, false);
|
|
};
|
|
|
|
/**
|
|
* Scrolls the document to wherever "page down" is, without changing the cursor position.
|
|
**/
|
|
this.scrollPageDown = function() {
|
|
this.$moveByPage(1);
|
|
};
|
|
|
|
/**
|
|
* Scrolls the document to wherever "page up" is, without changing the cursor position.
|
|
**/
|
|
this.scrollPageUp = function() {
|
|
this.$moveByPage(-1);
|
|
};
|
|
|
|
/**
|
|
* Moves the editor to the specified row.
|
|
* @related VirtualRenderer.scrollToRow
|
|
**/
|
|
this.scrollToRow = function(row) {
|
|
this.renderer.scrollToRow(row);
|
|
};
|
|
|
|
/**
|
|
* Scrolls to a line. If `center` is `true`, it puts the line in middle of screen (or attempts to).
|
|
* @param {Number} line The line to scroll to
|
|
* @param {Boolean} center If `true`
|
|
* @param {Boolean} animate If `true` animates scrolling
|
|
* @param {Function} callback Function to be called when the animation has finished
|
|
*
|
|
*
|
|
* @related VirtualRenderer.scrollToLine
|
|
**/
|
|
this.scrollToLine = function(line, center, animate, callback) {
|
|
this.renderer.scrollToLine(line, center, animate, callback);
|
|
};
|
|
|
|
/**
|
|
* Attempts to center the current selection on the screen.
|
|
**/
|
|
this.centerSelection = function() {
|
|
var range = this.getSelectionRange();
|
|
var pos = {
|
|
row: Math.floor(range.start.row + (range.end.row - range.start.row) / 2),
|
|
column: Math.floor(range.start.column + (range.end.column - range.start.column) / 2)
|
|
};
|
|
this.renderer.alignCursor(pos, 0.5);
|
|
};
|
|
|
|
/**
|
|
* Gets the current position of the cursor.
|
|
* @returns {Object} An object that looks something like this:
|
|
*
|
|
* ```json
|
|
* { row: currRow, column: currCol }
|
|
* ```
|
|
*
|
|
* @related Selection.getCursor
|
|
**/
|
|
this.getCursorPosition = function() {
|
|
return this.selection.getCursor();
|
|
};
|
|
|
|
/**
|
|
* Returns the screen position of the cursor.
|
|
* @returns {Number}
|
|
* @related EditSession.documentToScreenPosition
|
|
**/
|
|
this.getCursorPositionScreen = function() {
|
|
return this.session.documentToScreenPosition(this.getCursorPosition());
|
|
};
|
|
|
|
/**
|
|
* {:Selection.getRange}
|
|
* @returns {Range}
|
|
* @related Selection.getRange
|
|
**/
|
|
this.getSelectionRange = function() {
|
|
return this.selection.getRange();
|
|
};
|
|
|
|
|
|
/**
|
|
* Selects all the text in editor.
|
|
* @related Selection.selectAll
|
|
**/
|
|
this.selectAll = function() {
|
|
this.selection.selectAll();
|
|
};
|
|
|
|
/**
|
|
* {:Selection.clearSelection}
|
|
* @related Selection.clearSelection
|
|
**/
|
|
this.clearSelection = function() {
|
|
this.selection.clearSelection();
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor to the specified row and column. Note that this does not de-select the current selection.
|
|
* @param {Number} row The new row number
|
|
* @param {Number} column The new column number
|
|
*
|
|
*
|
|
* @related Selection.moveCursorTo
|
|
**/
|
|
this.moveCursorTo = function(row, column) {
|
|
this.selection.moveCursorTo(row, column);
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor to the position indicated by `pos.row` and `pos.column`.
|
|
* @param {Object} pos An object with two properties, row and column
|
|
*
|
|
*
|
|
* @related Selection.moveCursorToPosition
|
|
**/
|
|
this.moveCursorToPosition = function(pos) {
|
|
this.selection.moveCursorToPosition(pos);
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor's row and column to the next matching bracket or HTML tag.
|
|
*
|
|
**/
|
|
this.jumpToMatching = function(select, expand) {
|
|
var cursor = this.getCursorPosition();
|
|
var iterator = new TokenIterator(this.session, cursor.row, cursor.column);
|
|
var prevToken = iterator.getCurrentToken();
|
|
var token = prevToken || iterator.stepForward();
|
|
|
|
if (!token) return;
|
|
|
|
//get next closing tag or bracket
|
|
var matchType;
|
|
var found = false;
|
|
var depth = {};
|
|
var i = cursor.column - token.start;
|
|
var bracketType;
|
|
var brackets = {
|
|
")": "(",
|
|
"(": "(",
|
|
"]": "[",
|
|
"[": "[",
|
|
"{": "{",
|
|
"}": "{"
|
|
};
|
|
|
|
do {
|
|
if (token.value.match(/[{}()\[\]]/g)) {
|
|
for (; i < token.value.length && !found; i++) {
|
|
if (!brackets[token.value[i]]) {
|
|
continue;
|
|
}
|
|
|
|
bracketType = brackets[token.value[i]] + '.' + token.type.replace("rparen", "lparen");
|
|
|
|
if (isNaN(depth[bracketType])) {
|
|
depth[bracketType] = 0;
|
|
}
|
|
|
|
switch (token.value[i]) {
|
|
case '(':
|
|
case '[':
|
|
case '{':
|
|
depth[bracketType]++;
|
|
break;
|
|
case ')':
|
|
case ']':
|
|
case '}':
|
|
depth[bracketType]--;
|
|
|
|
if (depth[bracketType] === -1) {
|
|
matchType = 'bracket';
|
|
found = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (token.type.indexOf('tag-name') !== -1) {
|
|
if (isNaN(depth[token.value])) {
|
|
depth[token.value] = 0;
|
|
}
|
|
|
|
if (prevToken.value === '<') {
|
|
depth[token.value]++;
|
|
}
|
|
else if (prevToken.value === '</') {
|
|
depth[token.value]--;
|
|
}
|
|
|
|
if (depth[token.value] === -1) {
|
|
matchType = 'tag';
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
prevToken = token;
|
|
token = iterator.stepForward();
|
|
i = 0;
|
|
}
|
|
} while (token && !found);
|
|
|
|
//no match found
|
|
if (!matchType)
|
|
return;
|
|
|
|
var range, pos;
|
|
if (matchType === 'bracket') {
|
|
range = this.session.getBracketRange(cursor);
|
|
if (!range) {
|
|
range = new Range(
|
|
iterator.getCurrentTokenRow(),
|
|
iterator.getCurrentTokenColumn() + i - 1,
|
|
iterator.getCurrentTokenRow(),
|
|
iterator.getCurrentTokenColumn() + i - 1
|
|
);
|
|
pos = range.start;
|
|
if (expand || pos.row === cursor.row && Math.abs(pos.column - cursor.column) < 2)
|
|
range = this.session.getBracketRange(pos);
|
|
}
|
|
}
|
|
else if (matchType === 'tag') {
|
|
if (token && token.type.indexOf('tag-name') !== -1)
|
|
var tag = token.value;
|
|
else
|
|
return;
|
|
|
|
range = new Range(
|
|
iterator.getCurrentTokenRow(),
|
|
iterator.getCurrentTokenColumn() - 2,
|
|
iterator.getCurrentTokenRow(),
|
|
iterator.getCurrentTokenColumn() - 2
|
|
);
|
|
|
|
//find matching tag
|
|
if (range.compare(cursor.row, cursor.column) === 0) {
|
|
found = false;
|
|
do {
|
|
token = prevToken;
|
|
prevToken = iterator.stepBackward();
|
|
|
|
if (prevToken) {
|
|
if (prevToken.type.indexOf('tag-close') !== -1) {
|
|
range.setEnd(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + 1);
|
|
}
|
|
|
|
if (token.value === tag && token.type.indexOf('tag-name') !== -1) {
|
|
if (prevToken.value === '<') {
|
|
depth[tag]++;
|
|
}
|
|
else if (prevToken.value === '</') {
|
|
depth[tag]--;
|
|
}
|
|
|
|
if (depth[tag] === 0)
|
|
found = true;
|
|
}
|
|
}
|
|
} while (prevToken && !found);
|
|
}
|
|
|
|
//we found it
|
|
if (token && token.type.indexOf('tag-name')) {
|
|
pos = range.start;
|
|
if (pos.row == cursor.row && Math.abs(pos.column - cursor.column) < 2)
|
|
pos = range.end;
|
|
}
|
|
}
|
|
|
|
pos = range && range.cursor || pos;
|
|
if (pos) {
|
|
if (select) {
|
|
if (range && expand) {
|
|
this.selection.setRange(range);
|
|
} else if (range && range.isEqual(this.getSelectionRange())) {
|
|
this.clearSelection();
|
|
} else {
|
|
this.selection.selectTo(pos.row, pos.column);
|
|
}
|
|
} else {
|
|
this.selection.moveTo(pos.row, pos.column);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor to the specified line number, and also into the indicated column.
|
|
* @param {Number} lineNumber The line number to go to
|
|
* @param {Number} column A column number to go to
|
|
* @param {Boolean} animate If `true` animates scolling
|
|
*
|
|
**/
|
|
this.gotoLine = function(lineNumber, column, animate) {
|
|
this.selection.clearSelection();
|
|
this.session.unfold({row: lineNumber - 1, column: column || 0});
|
|
|
|
// todo: find a way to automatically exit multiselect mode
|
|
this.exitMultiSelectMode && this.exitMultiSelectMode();
|
|
this.moveCursorTo(lineNumber - 1, column || 0);
|
|
|
|
if (!this.isRowFullyVisible(lineNumber - 1))
|
|
this.scrollToLine(lineNumber - 1, true, animate);
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor to the specified row and column. Note that this does de-select the current selection.
|
|
* @param {Number} row The new row number
|
|
* @param {Number} column The new column number
|
|
*
|
|
*
|
|
* @related Editor.moveCursorTo
|
|
**/
|
|
this.navigateTo = function(row, column) {
|
|
this.selection.moveTo(row, column);
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor up in the document the specified number of times. Note that this does de-select the current selection.
|
|
* @param {Number} times The number of times to change navigation
|
|
*
|
|
*
|
|
**/
|
|
this.navigateUp = function(times) {
|
|
if (this.selection.isMultiLine() && !this.selection.isBackwards()) {
|
|
var selectionStart = this.selection.anchor.getPosition();
|
|
return this.moveCursorToPosition(selectionStart);
|
|
}
|
|
this.selection.clearSelection();
|
|
this.selection.moveCursorBy(-times || -1, 0);
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor down in the document the specified number of times. Note that this does de-select the current selection.
|
|
* @param {Number} times The number of times to change navigation
|
|
*
|
|
*
|
|
**/
|
|
this.navigateDown = function(times) {
|
|
if (this.selection.isMultiLine() && this.selection.isBackwards()) {
|
|
var selectionEnd = this.selection.anchor.getPosition();
|
|
return this.moveCursorToPosition(selectionEnd);
|
|
}
|
|
this.selection.clearSelection();
|
|
this.selection.moveCursorBy(times || 1, 0);
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor left in the document the specified number of times. Note that this does de-select the current selection.
|
|
* @param {Number} times The number of times to change navigation
|
|
*
|
|
*
|
|
**/
|
|
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();
|
|
};
|
|
|
|
/**
|
|
* Moves the cursor right in the document the specified number of times. Note that this does de-select the current selection.
|
|
* @param {Number} times The number of times to change navigation
|
|
*
|
|
*
|
|
**/
|
|
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();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Moves the cursor to the start of the current line. Note that this does de-select the current selection.
|
|
**/
|
|
this.navigateLineStart = function() {
|
|
this.selection.moveCursorLineStart();
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Moves the cursor to the end of the current line. Note that this does de-select the current selection.
|
|
**/
|
|
this.navigateLineEnd = function() {
|
|
this.selection.moveCursorLineEnd();
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Moves the cursor to the end of the current file. Note that this does de-select the current selection.
|
|
**/
|
|
this.navigateFileEnd = function() {
|
|
this.selection.moveCursorFileEnd();
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Moves the cursor to the start of the current file. Note that this does de-select the current selection.
|
|
**/
|
|
this.navigateFileStart = function() {
|
|
this.selection.moveCursorFileStart();
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection.
|
|
**/
|
|
this.navigateWordRight = function() {
|
|
this.selection.moveCursorWordRight();
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection.
|
|
**/
|
|
this.navigateWordLeft = function() {
|
|
this.selection.moveCursorWordLeft();
|
|
this.clearSelection();
|
|
};
|
|
|
|
/**
|
|
* Replaces the first occurrence of `options.needle` with the value in `replacement`.
|
|
* @param {String} replacement The text to replace with
|
|
* @param {Object} options The [[Search `Search`]] options to use
|
|
*
|
|
*
|
|
**/
|
|
this.replace = function(replacement, options) {
|
|
if (options)
|
|
this.$search.set(options);
|
|
|
|
var range = this.$search.find(this.session);
|
|
var replaced = 0;
|
|
if (!range)
|
|
return replaced;
|
|
|
|
if (this.$tryReplace(range, replacement)) {
|
|
replaced = 1;
|
|
}
|
|
|
|
this.selection.setSelectionRange(range);
|
|
this.renderer.scrollSelectionIntoView(range.start, range.end);
|
|
|
|
return replaced;
|
|
};
|
|
|
|
/**
|
|
* Replaces all occurrences of `options.needle` with the value in `replacement`.
|
|
* @param {String} replacement The text to replace with
|
|
* @param {Object} options The [[Search `Search`]] options to use
|
|
*
|
|
*
|
|
**/
|
|
this.replaceAll = function(replacement, options) {
|
|
if (options) {
|
|
this.$search.set(options);
|
|
}
|
|
|
|
var ranges = this.$search.findAll(this.session);
|
|
var replaced = 0;
|
|
if (!ranges.length)
|
|
return replaced;
|
|
|
|
var selection = this.getSelectionRange();
|
|
this.selection.moveTo(0, 0);
|
|
|
|
for (var i = ranges.length - 1; i >= 0; --i) {
|
|
if(this.$tryReplace(ranges[i], replacement)) {
|
|
replaced++;
|
|
}
|
|
}
|
|
|
|
this.selection.setSelectionRange(selection);
|
|
|
|
return replaced;
|
|
};
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* {:Search.getOptions} For more information on `options`, see [[Search `Search`]].
|
|
* @related Search.getOptions
|
|
* @returns {Object}
|
|
**/
|
|
this.getLastSearchOptions = function() {
|
|
return this.$search.getOptions();
|
|
};
|
|
|
|
/**
|
|
* Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]].
|
|
* @param {String} needle The text to search for (optional)
|
|
* @param {Object} options An object defining various search properties
|
|
* @param {Boolean} animate If `true` animate scrolling
|
|
*
|
|
*
|
|
* @related Search.find
|
|
**/
|
|
this.find = function(needle, options, animate) {
|
|
if (!options)
|
|
options = {};
|
|
|
|
if (typeof needle == "string" || needle instanceof RegExp)
|
|
options.needle = needle;
|
|
else if (typeof needle == "object")
|
|
oop.mixin(options, needle);
|
|
|
|
var range = this.selection.getRange();
|
|
if (options.needle == null) {
|
|
needle = this.session.getTextRange(range)
|
|
|| this.$search.$options.needle;
|
|
if (!needle) {
|
|
range = this.session.getWordRange(range.start.row, range.start.column);
|
|
needle = this.session.getTextRange(range);
|
|
}
|
|
this.$search.set({needle: needle});
|
|
}
|
|
|
|
this.$search.set(options);
|
|
if (!options.start)
|
|
this.$search.set({start: range});
|
|
|
|
var newRange = this.$search.find(this.session);
|
|
if (options.preventScroll)
|
|
return newRange;
|
|
if (newRange) {
|
|
this.revealRange(newRange, animate);
|
|
return newRange;
|
|
}
|
|
// clear selection if nothing is found
|
|
if (options.backwards)
|
|
range.start = range.end;
|
|
else
|
|
range.end = range.start;
|
|
this.selection.setRange(range);
|
|
};
|
|
|
|
/**
|
|
* Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]].
|
|
* @param {Object} options search options
|
|
* @param {Boolean} animate If `true` animate scrolling
|
|
*
|
|
*
|
|
* @related Editor.find
|
|
**/
|
|
this.findNext = function(options, animate) {
|
|
this.find({skipCurrent: true, backwards: false}, options, animate);
|
|
};
|
|
|
|
/**
|
|
* Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]].
|
|
* @param {Object} options search options
|
|
* @param {Boolean} animate If `true` animate scrolling
|
|
*
|
|
*
|
|
* @related Editor.find
|
|
**/
|
|
this.findPrevious = function(options, animate) {
|
|
this.find(options, {skipCurrent: true, backwards: true}, animate);
|
|
};
|
|
|
|
this.revealRange = function(range, animate) {
|
|
this.session.unfold(range);
|
|
this.selection.setSelectionRange(range);
|
|
|
|
var scrollTop = this.renderer.scrollTop;
|
|
this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5);
|
|
if (animate !== false)
|
|
this.renderer.animateScrolling(scrollTop);
|
|
};
|
|
|
|
/**
|
|
* {:UndoManager.undo}
|
|
* @related UndoManager.undo
|
|
**/
|
|
this.undo = function() {
|
|
this.session.getUndoManager().undo(this.session);
|
|
this.renderer.scrollCursorIntoView(null, 0.5);
|
|
};
|
|
|
|
/**
|
|
* {:UndoManager.redo}
|
|
* @related UndoManager.redo
|
|
**/
|
|
this.redo = function() {
|
|
this.session.getUndoManager().redo(this.session);
|
|
this.renderer.scrollCursorIntoView(null, 0.5);
|
|
};
|
|
|
|
/**
|
|
*
|
|
* Cleans up the entire editor.
|
|
**/
|
|
this.destroy = function() {
|
|
this.renderer.destroy();
|
|
this._signal("destroy", this);
|
|
if (this.session) {
|
|
this.session.destroy();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element
|
|
* @param {Boolean} enable default true
|
|
**/
|
|
this.setAutoScrollEditorIntoView = function(enable) {
|
|
if (!enable)
|
|
return;
|
|
var rect;
|
|
var self = this;
|
|
var shouldScroll = false;
|
|
if (!this.$scrollAnchor)
|
|
this.$scrollAnchor = document.createElement("div");
|
|
var scrollAnchor = this.$scrollAnchor;
|
|
scrollAnchor.style.cssText = "position:absolute";
|
|
this.container.insertBefore(scrollAnchor, this.container.firstChild);
|
|
var onChangeSelection = this.on("changeSelection", function() {
|
|
shouldScroll = true;
|
|
});
|
|
// needed to not trigger sync reflow
|
|
var onBeforeRender = this.renderer.on("beforeRender", function() {
|
|
if (shouldScroll)
|
|
rect = self.renderer.container.getBoundingClientRect();
|
|
});
|
|
var onAfterRender = this.renderer.on("afterRender", function() {
|
|
if (shouldScroll && rect && (self.isFocused()
|
|
|| self.searchBox && self.searchBox.isFocused())
|
|
) {
|
|
var renderer = self.renderer;
|
|
var pos = renderer.$cursorLayer.$pixelPos;
|
|
var config = renderer.layerConfig;
|
|
var top = pos.top - config.offset;
|
|
if (pos.top >= 0 && top + rect.top < 0) {
|
|
shouldScroll = true;
|
|
} else if (pos.top < config.height &&
|
|
pos.top + rect.top + config.lineHeight > window.innerHeight) {
|
|
shouldScroll = false;
|
|
} else {
|
|
shouldScroll = null;
|
|
}
|
|
if (shouldScroll != null) {
|
|
scrollAnchor.style.top = top + "px";
|
|
scrollAnchor.style.left = pos.left + "px";
|
|
scrollAnchor.style.height = config.lineHeight + "px";
|
|
scrollAnchor.scrollIntoView(shouldScroll);
|
|
}
|
|
shouldScroll = rect = null;
|
|
}
|
|
});
|
|
this.setAutoScrollEditorIntoView = function(enable) {
|
|
if (enable)
|
|
return;
|
|
delete this.setAutoScrollEditorIntoView;
|
|
this.off("changeSelection", onChangeSelection);
|
|
this.renderer.off("afterRender", onAfterRender);
|
|
this.renderer.off("beforeRender", onBeforeRender);
|
|
};
|
|
};
|
|
|
|
|
|
this.$resetCursorStyle = function() {
|
|
var style = this.$cursorStyle || "ace";
|
|
var cursorLayer = this.renderer.$cursorLayer;
|
|
if (!cursorLayer)
|
|
return;
|
|
cursorLayer.setSmoothBlinking(/smooth/.test(style));
|
|
cursorLayer.isBlinking = !this.$readOnly && style != "wide";
|
|
dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style));
|
|
};
|
|
|
|
/**
|
|
* opens a prompt displaying message
|
|
**/
|
|
this.prompt = function(message, options, callback) {
|
|
var editor = this;
|
|
config.loadModule("./ext/prompt", function (module) {
|
|
module.prompt(editor, message, options, callback);
|
|
});
|
|
};
|
|
|
|
}).call(Editor.prototype);
|
|
|
|
|
|
|
|
config.defineOptions(Editor.prototype, "editor", {
|
|
selectionStyle: {
|
|
set: function(style) {
|
|
this.onSelectionChange();
|
|
this._signal("changeSelectionStyle", {data: style});
|
|
},
|
|
initialValue: "line"
|
|
},
|
|
highlightActiveLine: {
|
|
set: function() {this.$updateHighlightActiveLine();},
|
|
initialValue: true
|
|
},
|
|
highlightSelectedWord: {
|
|
set: function(shouldHighlight) {this.$onSelectionChange();},
|
|
initialValue: true
|
|
},
|
|
readOnly: {
|
|
set: function(readOnly) {
|
|
this.textInput.setReadOnly(readOnly);
|
|
this.$resetCursorStyle();
|
|
},
|
|
initialValue: false
|
|
},
|
|
copyWithEmptySelection: {
|
|
set: function(value) {
|
|
this.textInput.setCopyWithEmptySelection(value);
|
|
},
|
|
initialValue: false
|
|
},
|
|
cursorStyle: {
|
|
set: function(val) { this.$resetCursorStyle(); },
|
|
values: ["ace", "slim", "smooth", "wide"],
|
|
initialValue: "ace"
|
|
},
|
|
mergeUndoDeltas: {
|
|
values: [false, true, "always"],
|
|
initialValue: true
|
|
},
|
|
behavioursEnabled: {initialValue: true},
|
|
wrapBehavioursEnabled: {initialValue: true},
|
|
autoScrollEditorIntoView: {
|
|
set: function(val) {this.setAutoScrollEditorIntoView(val);}
|
|
},
|
|
keyboardHandler: {
|
|
set: function(val) { this.setKeyboardHandler(val); },
|
|
get: function() { return this.$keybindingId; },
|
|
handlesSet: true
|
|
},
|
|
value: {
|
|
set: function(val) { this.session.setValue(val); },
|
|
get: function() { return this.getValue(); },
|
|
handlesSet: true,
|
|
hidden: true
|
|
},
|
|
session: {
|
|
set: function(val) { this.setSession(val); },
|
|
get: function() { return this.session; },
|
|
handlesSet: true,
|
|
hidden: true
|
|
},
|
|
|
|
showLineNumbers: {
|
|
set: function(show) {
|
|
this.renderer.$gutterLayer.setShowLineNumbers(show);
|
|
this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER);
|
|
if (show && this.$relativeLineNumbers)
|
|
relativeNumberRenderer.attach(this);
|
|
else
|
|
relativeNumberRenderer.detach(this);
|
|
},
|
|
initialValue: true
|
|
},
|
|
relativeLineNumbers: {
|
|
set: function(value) {
|
|
if (this.$showLineNumbers && value)
|
|
relativeNumberRenderer.attach(this);
|
|
else
|
|
relativeNumberRenderer.detach(this);
|
|
}
|
|
},
|
|
|
|
hScrollBarAlwaysVisible: "renderer",
|
|
vScrollBarAlwaysVisible: "renderer",
|
|
highlightGutterLine: "renderer",
|
|
animatedScroll: "renderer",
|
|
showInvisibles: "renderer",
|
|
showPrintMargin: "renderer",
|
|
printMarginColumn: "renderer",
|
|
printMargin: "renderer",
|
|
fadeFoldWidgets: "renderer",
|
|
showFoldWidgets: "renderer",
|
|
displayIndentGuides: "renderer",
|
|
showGutter: "renderer",
|
|
fontSize: "renderer",
|
|
fontFamily: "renderer",
|
|
maxLines: "renderer",
|
|
minLines: "renderer",
|
|
scrollPastEnd: "renderer",
|
|
fixedWidthGutter: "renderer",
|
|
theme: "renderer",
|
|
hasCssTransforms: "renderer",
|
|
maxPixelHeight: "renderer",
|
|
useTextareaForIME: "renderer",
|
|
|
|
scrollSpeed: "$mouseHandler",
|
|
dragDelay: "$mouseHandler",
|
|
dragEnabled: "$mouseHandler",
|
|
focusTimeout: "$mouseHandler",
|
|
tooltipFollowsMouse: "$mouseHandler",
|
|
|
|
firstLineNumber: "session",
|
|
overwrite: "session",
|
|
newLineMode: "session",
|
|
useWorker: "session",
|
|
useSoftTabs: "session",
|
|
navigateWithinSoftTabs: "session",
|
|
tabSize: "session",
|
|
wrap: "session",
|
|
indentedSoftWrap: "session",
|
|
foldStyle: "session",
|
|
mode: "session"
|
|
});
|
|
|
|
|
|
var relativeNumberRenderer = {
|
|
getText: function(session, row) {
|
|
return (Math.abs(session.selection.lead.row - row) || (row + 1 + (row < 9 ? "\xb7" : ""))) + "";
|
|
},
|
|
getWidth: function(session, lastLineNumber, config) {
|
|
return Math.max(
|
|
lastLineNumber.toString().length,
|
|
(config.lastRow + 1).toString().length,
|
|
2
|
|
) * config.characterWidth;
|
|
},
|
|
update: function(e, editor) {
|
|
editor.renderer.$loop.schedule(editor.renderer.CHANGE_GUTTER);
|
|
},
|
|
attach: function(editor) {
|
|
editor.renderer.$gutterLayer.$renderer = this;
|
|
editor.on("changeSelection", this.update);
|
|
this.update(null, editor);
|
|
},
|
|
detach: function(editor) {
|
|
if (editor.renderer.$gutterLayer.$renderer == this)
|
|
editor.renderer.$gutterLayer.$renderer = null;
|
|
editor.off("changeSelection", this.update);
|
|
this.update(null, editor);
|
|
}
|
|
};
|
|
|
|
exports.Editor = Editor;
|
|
});
|