editor.js 87 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2010, Ajax.org B.V.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * * Neither the name of Ajax.org B.V. nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * ***** END LICENSE BLOCK ***** */
  30. define(function(require, exports, module) {
  31. "use strict";
  32. require("./lib/fixoldbrowsers");
  33. var oop = require("./lib/oop");
  34. var dom = require("./lib/dom");
  35. var lang = require("./lib/lang");
  36. var useragent = require("./lib/useragent");
  37. var TextInput = require("./keyboard/textinput").TextInput;
  38. var MouseHandler = require("./mouse/mouse_handler").MouseHandler;
  39. var FoldHandler = require("./mouse/fold_handler").FoldHandler;
  40. var KeyBinding = require("./keyboard/keybinding").KeyBinding;
  41. var EditSession = require("./edit_session").EditSession;
  42. var Search = require("./search").Search;
  43. var Range = require("./range").Range;
  44. var EventEmitter = require("./lib/event_emitter").EventEmitter;
  45. var CommandManager = require("./commands/command_manager").CommandManager;
  46. var defaultCommands = require("./commands/default_commands").commands;
  47. var config = require("./config");
  48. var TokenIterator = require("./token_iterator").TokenIterator;
  49. /**
  50. * The main entry point into the Ace functionality.
  51. *
  52. * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen.
  53. *
  54. * Event sessions dealing with the mouse and keyboard are bubbled up from `Document` to the `Editor`, which decides what to do with them.
  55. * @class Editor
  56. **/
  57. /**
  58. * Creates a new `Editor` object.
  59. *
  60. * @param {VirtualRenderer} renderer Associated `VirtualRenderer` that draws everything
  61. * @param {EditSession} session The `EditSession` to refer to
  62. *
  63. *
  64. * @constructor
  65. **/
  66. var Editor = function(renderer, session) {
  67. var container = renderer.getContainerElement();
  68. this.container = container;
  69. this.renderer = renderer;
  70. this.commands = new CommandManager(useragent.isMac ? "mac" : "win", defaultCommands);
  71. this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
  72. this.renderer.textarea = this.textInput.getElement();
  73. this.keyBinding = new KeyBinding(this);
  74. // TODO detect touch event support
  75. this.$mouseHandler = new MouseHandler(this);
  76. new FoldHandler(this);
  77. this.$blockScrolling = 0;
  78. this.$search = new Search().set({
  79. wrap: true
  80. });
  81. this.$historyTracker = this.$historyTracker.bind(this);
  82. this.commands.on("exec", this.$historyTracker);
  83. this.$initOperationListeners();
  84. this._$emitInputEvent = lang.delayedCall(function() {
  85. this._signal("input", {});
  86. if (this.session && this.session.bgTokenizer)
  87. this.session.bgTokenizer.scheduleStart();
  88. }.bind(this));
  89. this.on("change", function(_, _self) {
  90. _self._$emitInputEvent.schedule(31);
  91. });
  92. this.setSession(session || new EditSession(""));
  93. config.resetOptions(this);
  94. config._signal("editor", this);
  95. };
  96. (function(){
  97. oop.implement(this, EventEmitter);
  98. this.$initOperationListeners = function() {
  99. function last(a) {return a[a.length - 1]}
  100. this.selections = [];
  101. this.commands.on("exec", this.startOperation.bind(this), true);
  102. this.commands.on("afterExec", this.endOperation.bind(this), true);
  103. this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this));
  104. this.on("change", function() {
  105. this.curOp || this.startOperation();
  106. this.curOp.docChanged = true;
  107. }.bind(this), true);
  108. this.on("changeSelection", function() {
  109. this.curOp || this.startOperation();
  110. this.curOp.selectionChanged = true;
  111. }.bind(this), true);
  112. };
  113. this.curOp = null;
  114. this.prevOp = {};
  115. this.startOperation = function(commadEvent) {
  116. if (this.curOp) {
  117. if (!commadEvent || this.curOp.command)
  118. return;
  119. this.prevOp = this.curOp;
  120. }
  121. if (!commadEvent) {
  122. this.previousCommand = null;
  123. commadEvent = {};
  124. }
  125. this.$opResetTimer.schedule();
  126. this.curOp = {
  127. command: commadEvent.command || {},
  128. args: commadEvent.args,
  129. scrollTop: this.renderer.scrollTop
  130. };
  131. if (this.curOp.command.name && this.curOp.command.scrollIntoView !== undefined)
  132. this.$blockScrolling++;
  133. };
  134. this.endOperation = function(e) {
  135. if (this.curOp) {
  136. if (e && e.returnValue === false)
  137. return this.curOp = null;
  138. this._signal("beforeEndOperation");
  139. var command = this.curOp.command;
  140. if (command.name && this.$blockScrolling > 0)
  141. this.$blockScrolling--;
  142. var scrollIntoView = command && command.scrollIntoView;
  143. if (scrollIntoView) {
  144. switch (scrollIntoView) {
  145. case "center-animate":
  146. scrollIntoView = "animate";
  147. /* fall through */
  148. case "center":
  149. this.renderer.scrollCursorIntoView(null, 0.5);
  150. break;
  151. case "animate":
  152. case "cursor":
  153. this.renderer.scrollCursorIntoView();
  154. break;
  155. case "selectionPart":
  156. var range = this.selection.getRange();
  157. var config = this.renderer.layerConfig;
  158. if (range.start.row >= config.lastRow || range.end.row <= config.firstRow) {
  159. this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead);
  160. }
  161. break;
  162. default:
  163. break;
  164. }
  165. if (scrollIntoView == "animate")
  166. this.renderer.animateScrolling(this.curOp.scrollTop);
  167. }
  168. this.prevOp = this.curOp;
  169. this.curOp = null;
  170. }
  171. };
  172. // TODO use property on commands instead of this
  173. this.$mergeableCommands = ["backspace", "del", "insertstring"];
  174. this.$historyTracker = function(e) {
  175. if (!this.$mergeUndoDeltas)
  176. return;
  177. var prev = this.prevOp;
  178. var mergeableCommands = this.$mergeableCommands;
  179. // previous command was the same
  180. var shouldMerge = prev.command && (e.command.name == prev.command.name);
  181. if (e.command.name == "insertstring") {
  182. var text = e.args;
  183. if (this.mergeNextCommand === undefined)
  184. this.mergeNextCommand = true;
  185. shouldMerge = shouldMerge
  186. && this.mergeNextCommand // previous command allows to coalesce with
  187. && (!/\s/.test(text) || /\s/.test(prev.args)); // previous insertion was of same type
  188. this.mergeNextCommand = true;
  189. } else {
  190. shouldMerge = shouldMerge
  191. && mergeableCommands.indexOf(e.command.name) !== -1; // the command is mergeable
  192. }
  193. if (
  194. this.$mergeUndoDeltas != "always"
  195. && Date.now() - this.sequenceStartTime > 2000
  196. ) {
  197. shouldMerge = false; // the sequence is too long
  198. }
  199. if (shouldMerge)
  200. this.session.mergeUndoDeltas = true;
  201. else if (mergeableCommands.indexOf(e.command.name) !== -1)
  202. this.sequenceStartTime = Date.now();
  203. };
  204. /**
  205. * Sets a new key handler, such as "vim" or "windows".
  206. * @param {String} keyboardHandler The new key handler
  207. *
  208. **/
  209. this.setKeyboardHandler = function(keyboardHandler, cb) {
  210. if (keyboardHandler && typeof keyboardHandler === "string") {
  211. this.$keybindingId = keyboardHandler;
  212. var _self = this;
  213. config.loadModule(["keybinding", keyboardHandler], function(module) {
  214. if (_self.$keybindingId == keyboardHandler)
  215. _self.keyBinding.setKeyboardHandler(module && module.handler);
  216. cb && cb();
  217. });
  218. } else {
  219. this.$keybindingId = null;
  220. this.keyBinding.setKeyboardHandler(keyboardHandler);
  221. cb && cb();
  222. }
  223. };
  224. /**
  225. * Returns the keyboard handler, such as "vim" or "windows".
  226. *
  227. * @returns {String}
  228. *
  229. **/
  230. this.getKeyboardHandler = function() {
  231. return this.keyBinding.getKeyboardHandler();
  232. };
  233. /**
  234. * Emitted whenever the [[EditSession]] changes.
  235. * @event changeSession
  236. * @param {Object} e An object with two properties, `oldSession` and `session`, that represent the old and new [[EditSession]]s.
  237. *
  238. **/
  239. /**
  240. * Sets a new editsession to use. This method also emits the `'changeSession'` event.
  241. * @param {EditSession} session The new session to use
  242. *
  243. **/
  244. this.setSession = function(session) {
  245. if (this.session == session)
  246. return;
  247. // make sure operationEnd events are not emitted to wrong session
  248. if (this.curOp) this.endOperation();
  249. this.curOp = {};
  250. var oldSession = this.session;
  251. if (oldSession) {
  252. this.session.off("change", this.$onDocumentChange);
  253. this.session.off("changeMode", this.$onChangeMode);
  254. this.session.off("tokenizerUpdate", this.$onTokenizerUpdate);
  255. this.session.off("changeTabSize", this.$onChangeTabSize);
  256. this.session.off("changeWrapLimit", this.$onChangeWrapLimit);
  257. this.session.off("changeWrapMode", this.$onChangeWrapMode);
  258. this.session.off("changeFold", this.$onChangeFold);
  259. this.session.off("changeFrontMarker", this.$onChangeFrontMarker);
  260. this.session.off("changeBackMarker", this.$onChangeBackMarker);
  261. this.session.off("changeBreakpoint", this.$onChangeBreakpoint);
  262. this.session.off("changeAnnotation", this.$onChangeAnnotation);
  263. this.session.off("changeOverwrite", this.$onCursorChange);
  264. this.session.off("changeScrollTop", this.$onScrollTopChange);
  265. this.session.off("changeScrollLeft", this.$onScrollLeftChange);
  266. var selection = this.session.getSelection();
  267. selection.off("changeCursor", this.$onCursorChange);
  268. selection.off("changeSelection", this.$onSelectionChange);
  269. }
  270. this.session = session;
  271. if (session) {
  272. this.$onDocumentChange = this.onDocumentChange.bind(this);
  273. session.on("change", this.$onDocumentChange);
  274. this.renderer.setSession(session);
  275. this.$onChangeMode = this.onChangeMode.bind(this);
  276. session.on("changeMode", this.$onChangeMode);
  277. this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this);
  278. session.on("tokenizerUpdate", this.$onTokenizerUpdate);
  279. this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer);
  280. session.on("changeTabSize", this.$onChangeTabSize);
  281. this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this);
  282. session.on("changeWrapLimit", this.$onChangeWrapLimit);
  283. this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
  284. session.on("changeWrapMode", this.$onChangeWrapMode);
  285. this.$onChangeFold = this.onChangeFold.bind(this);
  286. session.on("changeFold", this.$onChangeFold);
  287. this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
  288. this.session.on("changeFrontMarker", this.$onChangeFrontMarker);
  289. this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
  290. this.session.on("changeBackMarker", this.$onChangeBackMarker);
  291. this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
  292. this.session.on("changeBreakpoint", this.$onChangeBreakpoint);
  293. this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
  294. this.session.on("changeAnnotation", this.$onChangeAnnotation);
  295. this.$onCursorChange = this.onCursorChange.bind(this);
  296. this.session.on("changeOverwrite", this.$onCursorChange);
  297. this.$onScrollTopChange = this.onScrollTopChange.bind(this);
  298. this.session.on("changeScrollTop", this.$onScrollTopChange);
  299. this.$onScrollLeftChange = this.onScrollLeftChange.bind(this);
  300. this.session.on("changeScrollLeft", this.$onScrollLeftChange);
  301. this.selection = session.getSelection();
  302. this.selection.on("changeCursor", this.$onCursorChange);
  303. this.$onSelectionChange = this.onSelectionChange.bind(this);
  304. this.selection.on("changeSelection", this.$onSelectionChange);
  305. this.onChangeMode();
  306. this.$blockScrolling += 1;
  307. this.onCursorChange();
  308. this.$blockScrolling -= 1;
  309. this.onScrollTopChange();
  310. this.onScrollLeftChange();
  311. this.onSelectionChange();
  312. this.onChangeFrontMarker();
  313. this.onChangeBackMarker();
  314. this.onChangeBreakpoint();
  315. this.onChangeAnnotation();
  316. this.session.getUseWrapMode() && this.renderer.adjustWrapLimit();
  317. this.renderer.updateFull();
  318. } else {
  319. this.selection = null;
  320. this.renderer.setSession(session);
  321. }
  322. this._signal("changeSession", {
  323. session: session,
  324. oldSession: oldSession
  325. });
  326. this.curOp = null;
  327. oldSession && oldSession._signal("changeEditor", {oldEditor: this});
  328. session && session._signal("changeEditor", {editor: this});
  329. };
  330. /**
  331. * Returns the current session being used.
  332. * @returns {EditSession}
  333. **/
  334. this.getSession = function() {
  335. return this.session;
  336. };
  337. /**
  338. * Sets the current document to `val`.
  339. * @param {String} val The new value to set for the document
  340. * @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
  341. *
  342. * @returns {String} The current document value
  343. * @related Document.setValue
  344. **/
  345. this.setValue = function(val, cursorPos) {
  346. this.session.doc.setValue(val);
  347. if (!cursorPos)
  348. this.selectAll();
  349. else if (cursorPos == 1)
  350. this.navigateFileEnd();
  351. else if (cursorPos == -1)
  352. this.navigateFileStart();
  353. return val;
  354. };
  355. /**
  356. * Returns the current session's content.
  357. *
  358. * @returns {String}
  359. * @related EditSession.getValue
  360. **/
  361. this.getValue = function() {
  362. return this.session.getValue();
  363. };
  364. /**
  365. *
  366. * Returns the currently highlighted selection.
  367. * @returns {Selection} The selection object
  368. **/
  369. this.getSelection = function() {
  370. return this.selection;
  371. };
  372. /**
  373. * {:VirtualRenderer.onResize}
  374. * @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed
  375. *
  376. *
  377. * @related VirtualRenderer.onResize
  378. **/
  379. this.resize = function(force) {
  380. this.renderer.onResize(force);
  381. };
  382. /**
  383. * {:VirtualRenderer.setTheme}
  384. * @param {String} theme The path to a theme
  385. * @param {Function} cb optional callback called when theme is loaded
  386. **/
  387. this.setTheme = function(theme, cb) {
  388. this.renderer.setTheme(theme, cb);
  389. };
  390. /**
  391. * {:VirtualRenderer.getTheme}
  392. *
  393. * @returns {String} The set theme
  394. * @related VirtualRenderer.getTheme
  395. **/
  396. this.getTheme = function() {
  397. return this.renderer.getTheme();
  398. };
  399. /**
  400. * {:VirtualRenderer.setStyle}
  401. * @param {String} style A class name
  402. *
  403. *
  404. * @related VirtualRenderer.setStyle
  405. **/
  406. this.setStyle = function(style) {
  407. this.renderer.setStyle(style);
  408. };
  409. /**
  410. * {:VirtualRenderer.unsetStyle}
  411. * @related VirtualRenderer.unsetStyle
  412. **/
  413. this.unsetStyle = function(style) {
  414. this.renderer.unsetStyle(style);
  415. };
  416. /**
  417. * Gets the current font size of the editor text.
  418. */
  419. this.getFontSize = function () {
  420. return this.getOption("fontSize") ||
  421. dom.computedStyle(this.container, "fontSize");
  422. };
  423. /**
  424. * Set a new font size (in pixels) for the editor text.
  425. * @param {String} size A font size ( _e.g._ "12px")
  426. *
  427. *
  428. **/
  429. this.setFontSize = function(size) {
  430. this.setOption("fontSize", size);
  431. };
  432. this.$highlightBrackets = function() {
  433. if (this.session.$bracketHighlight) {
  434. this.session.removeMarker(this.session.$bracketHighlight);
  435. this.session.$bracketHighlight = null;
  436. }
  437. if (this.$highlightPending) {
  438. return;
  439. }
  440. // perform highlight async to not block the browser during navigation
  441. var self = this;
  442. this.$highlightPending = true;
  443. setTimeout(function() {
  444. self.$highlightPending = false;
  445. var session = self.session;
  446. if (!session || !session.bgTokenizer) return;
  447. var pos = session.findMatchingBracket(self.getCursorPosition());
  448. if (pos) {
  449. var range = new Range(pos.row, pos.column, pos.row, pos.column + 1);
  450. } else if (session.$mode.getMatching) {
  451. var range = session.$mode.getMatching(self.session);
  452. }
  453. if (range)
  454. session.$bracketHighlight = session.addMarker(range, "ace_bracket", "text");
  455. }, 50);
  456. };
  457. // todo: move to mode.getMatching
  458. this.$highlightTags = function() {
  459. if (this.$highlightTagPending)
  460. return;
  461. // perform highlight async to not block the browser during navigation
  462. var self = this;
  463. this.$highlightTagPending = true;
  464. setTimeout(function() {
  465. self.$highlightTagPending = false;
  466. var session = self.session;
  467. if (!session || !session.bgTokenizer) return;
  468. var pos = self.getCursorPosition();
  469. var iterator = new TokenIterator(self.session, pos.row, pos.column);
  470. var token = iterator.getCurrentToken();
  471. if (!token || !/\b(?:tag-open|tag-name)/.test(token.type)) {
  472. session.removeMarker(session.$tagHighlight);
  473. session.$tagHighlight = null;
  474. return;
  475. }
  476. if (token.type.indexOf("tag-open") != -1) {
  477. token = iterator.stepForward();
  478. if (!token)
  479. return;
  480. }
  481. var tag = token.value;
  482. var depth = 0;
  483. var prevToken = iterator.stepBackward();
  484. if (prevToken.value == '<'){
  485. //find closing tag
  486. do {
  487. prevToken = token;
  488. token = iterator.stepForward();
  489. if (token && token.value === tag && token.type.indexOf('tag-name') !== -1) {
  490. if (prevToken.value === '<'){
  491. depth++;
  492. } else if (prevToken.value === '</'){
  493. depth--;
  494. }
  495. }
  496. } while (token && depth >= 0);
  497. } else {
  498. //find opening tag
  499. do {
  500. token = prevToken;
  501. prevToken = iterator.stepBackward();
  502. if (token && token.value === tag && token.type.indexOf('tag-name') !== -1) {
  503. if (prevToken.value === '<') {
  504. depth++;
  505. } else if (prevToken.value === '</') {
  506. depth--;
  507. }
  508. }
  509. } while (prevToken && depth <= 0);
  510. //select tag again
  511. iterator.stepForward();
  512. }
  513. if (!token) {
  514. session.removeMarker(session.$tagHighlight);
  515. session.$tagHighlight = null;
  516. return;
  517. }
  518. var row = iterator.getCurrentTokenRow();
  519. var column = iterator.getCurrentTokenColumn();
  520. var range = new Range(row, column, row, column+token.value.length);
  521. //remove range if different
  522. if (session.$tagHighlight && range.compareRange(session.$backMarkers[session.$tagHighlight].range)!==0) {
  523. session.removeMarker(session.$tagHighlight);
  524. session.$tagHighlight = null;
  525. }
  526. if (range && !session.$tagHighlight)
  527. session.$tagHighlight = session.addMarker(range, "ace_bracket", "text");
  528. }, 50);
  529. };
  530. /**
  531. *
  532. * Brings the current `textInput` into focus.
  533. **/
  534. this.focus = function() {
  535. // Safari needs the timeout
  536. // iOS and Firefox need it called immediately
  537. // to be on the save side we do both
  538. var _self = this;
  539. setTimeout(function() {
  540. _self.textInput.focus();
  541. });
  542. this.textInput.focus();
  543. };
  544. /**
  545. * Returns `true` if the current `textInput` is in focus.
  546. * @return {Boolean}
  547. **/
  548. this.isFocused = function() {
  549. return this.textInput.isFocused();
  550. };
  551. /**
  552. *
  553. * Blurs the current `textInput`.
  554. **/
  555. this.blur = function() {
  556. this.textInput.blur();
  557. };
  558. /**
  559. * Emitted once the editor comes into focus.
  560. * @event focus
  561. *
  562. *
  563. **/
  564. this.onFocus = function(e) {
  565. if (this.$isFocused)
  566. return;
  567. this.$isFocused = true;
  568. this.renderer.showCursor();
  569. this.renderer.visualizeFocus();
  570. this._emit("focus", e);
  571. };
  572. /**
  573. * Emitted once the editor has been blurred.
  574. * @event blur
  575. *
  576. *
  577. **/
  578. this.onBlur = function(e) {
  579. if (!this.$isFocused)
  580. return;
  581. this.$isFocused = false;
  582. this.renderer.hideCursor();
  583. this.renderer.visualizeBlur();
  584. this._emit("blur", e);
  585. };
  586. this.$cursorChange = function() {
  587. this.renderer.updateCursor();
  588. };
  589. /**
  590. * Emitted whenever the document is changed.
  591. * @event change
  592. * @param {Object} e Contains a single property, `data`, which has the delta of changes
  593. *
  594. *
  595. *
  596. **/
  597. this.onDocumentChange = function(delta) {
  598. // Rerender and emit "change" event.
  599. var wrap = this.session.$useWrapMode;
  600. var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity);
  601. this.renderer.updateLines(delta.start.row, lastRow, wrap);
  602. this._signal("change", delta);
  603. // Update cursor because tab characters can influence the cursor position.
  604. this.$cursorChange();
  605. this.$updateHighlightActiveLine();
  606. };
  607. this.onTokenizerUpdate = function(e) {
  608. var rows = e.data;
  609. this.renderer.updateLines(rows.first, rows.last);
  610. };
  611. this.onScrollTopChange = function() {
  612. this.renderer.scrollToY(this.session.getScrollTop());
  613. };
  614. this.onScrollLeftChange = function() {
  615. this.renderer.scrollToX(this.session.getScrollLeft());
  616. };
  617. /**
  618. * Emitted when the selection changes.
  619. *
  620. **/
  621. this.onCursorChange = function() {
  622. this.$cursorChange();
  623. if (!this.$blockScrolling) {
  624. config.warn("Automatically scrolling cursor into view after selection change",
  625. "this will be disabled in the next version",
  626. "set editor.$blockScrolling = Infinity to disable this message"
  627. );
  628. this.renderer.scrollCursorIntoView();
  629. }
  630. this.$highlightBrackets();
  631. this.$highlightTags();
  632. this.$updateHighlightActiveLine();
  633. this._signal("changeSelection");
  634. };
  635. this.$updateHighlightActiveLine = function() {
  636. var session = this.getSession();
  637. var highlight;
  638. if (this.$highlightActiveLine) {
  639. if ((this.$selectionStyle != "line" || !this.selection.isMultiLine()))
  640. highlight = this.getCursorPosition();
  641. if (this.renderer.$maxLines && this.session.getLength() === 1 && !(this.renderer.$minLines > 1))
  642. highlight = false;
  643. }
  644. if (session.$highlightLineMarker && !highlight) {
  645. session.removeMarker(session.$highlightLineMarker.id);
  646. session.$highlightLineMarker = null;
  647. } else if (!session.$highlightLineMarker && highlight) {
  648. var range = new Range(highlight.row, highlight.column, highlight.row, Infinity);
  649. range.id = session.addMarker(range, "ace_active-line", "screenLine");
  650. session.$highlightLineMarker = range;
  651. } else if (highlight) {
  652. session.$highlightLineMarker.start.row = highlight.row;
  653. session.$highlightLineMarker.end.row = highlight.row;
  654. session.$highlightLineMarker.start.column = highlight.column;
  655. session._signal("changeBackMarker");
  656. }
  657. };
  658. this.onSelectionChange = function(e) {
  659. var session = this.session;
  660. if (session.$selectionMarker) {
  661. session.removeMarker(session.$selectionMarker);
  662. }
  663. session.$selectionMarker = null;
  664. if (!this.selection.isEmpty()) {
  665. var range = this.selection.getRange();
  666. var style = this.getSelectionStyle();
  667. session.$selectionMarker = session.addMarker(range, "ace_selection", style);
  668. } else {
  669. this.$updateHighlightActiveLine();
  670. }
  671. var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp();
  672. this.session.highlight(re);
  673. this._signal("changeSelection");
  674. };
  675. this.$getSelectionHighLightRegexp = function() {
  676. var session = this.session;
  677. var selection = this.getSelectionRange();
  678. if (selection.isEmpty() || selection.isMultiLine())
  679. return;
  680. var startOuter = selection.start.column - 1;
  681. var endOuter = selection.end.column + 1;
  682. var line = session.getLine(selection.start.row);
  683. var lineCols = line.length;
  684. var needle = line.substring(Math.max(startOuter, 0),
  685. Math.min(endOuter, lineCols));
  686. // Make sure the outer characters are not part of the word.
  687. if ((startOuter >= 0 && /^[\w\d]/.test(needle)) ||
  688. (endOuter <= lineCols && /[\w\d]$/.test(needle)))
  689. return;
  690. needle = line.substring(selection.start.column, selection.end.column);
  691. if (!/^[\w\d]+$/.test(needle))
  692. return;
  693. var re = this.$search.$assembleRegExp({
  694. wholeWord: true,
  695. caseSensitive: true,
  696. needle: needle
  697. });
  698. return re;
  699. };
  700. this.onChangeFrontMarker = function() {
  701. this.renderer.updateFrontMarkers();
  702. };
  703. this.onChangeBackMarker = function() {
  704. this.renderer.updateBackMarkers();
  705. };
  706. this.onChangeBreakpoint = function() {
  707. this.renderer.updateBreakpoints();
  708. };
  709. this.onChangeAnnotation = function() {
  710. this.renderer.setAnnotations(this.session.getAnnotations());
  711. };
  712. this.onChangeMode = function(e) {
  713. this.renderer.updateText();
  714. this._emit("changeMode", e);
  715. };
  716. this.onChangeWrapLimit = function() {
  717. this.renderer.updateFull();
  718. };
  719. this.onChangeWrapMode = function() {
  720. this.renderer.onResize(true);
  721. };
  722. this.onChangeFold = function() {
  723. // Update the active line marker as due to folding changes the current
  724. // line range on the screen might have changed.
  725. this.$updateHighlightActiveLine();
  726. // TODO: This might be too much updating. Okay for now.
  727. this.renderer.updateFull();
  728. };
  729. /**
  730. * Returns the string of text currently highlighted.
  731. * @returns {String}
  732. **/
  733. this.getSelectedText = function() {
  734. return this.session.getTextRange(this.getSelectionRange());
  735. };
  736. /**
  737. * Emitted when text is copied.
  738. * @event copy
  739. * @param {String} text The copied text
  740. *
  741. **/
  742. /**
  743. * Returns the string of text currently highlighted.
  744. * @returns {String}
  745. * @deprecated Use getSelectedText instead.
  746. **/
  747. this.getCopyText = function() {
  748. var text = this.getSelectedText();
  749. this._signal("copy", text);
  750. return text;
  751. };
  752. /**
  753. * Called whenever a text "copy" happens.
  754. **/
  755. this.onCopy = function() {
  756. this.commands.exec("copy", this);
  757. };
  758. /**
  759. * Called whenever a text "cut" happens.
  760. **/
  761. this.onCut = function() {
  762. this.commands.exec("cut", this);
  763. };
  764. /**
  765. * Emitted when text is pasted.
  766. * @event paste
  767. * @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.
  768. *
  769. *
  770. **/
  771. /**
  772. * Called whenever a text "paste" happens.
  773. * @param {String} text The pasted text
  774. *
  775. *
  776. **/
  777. this.onPaste = function(text, event) {
  778. var e = {text: text, event: event};
  779. this.commands.exec("paste", this, e);
  780. };
  781. this.$handlePaste = function(e) {
  782. if (typeof e == "string")
  783. e = {text: e};
  784. this._signal("paste", e);
  785. var text = e.text;
  786. if (!this.inMultiSelectMode || this.inVirtualSelectionMode) {
  787. this.insert(text);
  788. } else {
  789. var lines = text.split(/\r\n|\r|\n/);
  790. var ranges = this.selection.rangeList.ranges;
  791. if (lines.length > ranges.length || lines.length < 2 || !lines[1])
  792. return this.commands.exec("insertstring", this, text);
  793. for (var i = ranges.length; i--;) {
  794. var range = ranges[i];
  795. if (!range.isEmpty())
  796. this.session.remove(range);
  797. this.session.insert(range.start, lines[i]);
  798. }
  799. }
  800. };
  801. this.execCommand = function(command, args) {
  802. return this.commands.exec(command, this, args);
  803. };
  804. /**
  805. * Inserts `text` into wherever the cursor is pointing.
  806. * @param {String} text The new text to add
  807. *
  808. **/
  809. this.insert = function(text, pasted) {
  810. var session = this.session;
  811. var mode = session.getMode();
  812. var cursor = this.getCursorPosition();
  813. if (this.getBehavioursEnabled() && !pasted) {
  814. // Get a transform if the current mode wants one.
  815. var transform = mode.transformAction(session.getState(cursor.row), 'insertion', this, session, text);
  816. if (transform) {
  817. if (text !== transform.text) {
  818. this.session.mergeUndoDeltas = false;
  819. this.$mergeNextCommand = false;
  820. }
  821. text = transform.text;
  822. }
  823. }
  824. if (text == "\t")
  825. text = this.session.getTabString();
  826. // remove selected text
  827. if (!this.selection.isEmpty()) {
  828. var range = this.getSelectionRange();
  829. cursor = this.session.remove(range);
  830. this.clearSelection();
  831. }
  832. else if (this.session.getOverwrite()) {
  833. var range = new Range.fromPoints(cursor, cursor);
  834. range.end.column += text.length;
  835. this.session.remove(range);
  836. }
  837. if (text == "\n" || text == "\r\n") {
  838. var line = session.getLine(cursor.row);
  839. if (cursor.column > line.search(/\S|$/)) {
  840. var d = line.substr(cursor.column).search(/\S|$/);
  841. session.doc.removeInLine(cursor.row, cursor.column, cursor.column + d);
  842. }
  843. }
  844. this.clearSelection();
  845. var start = cursor.column;
  846. var lineState = session.getState(cursor.row);
  847. var line = session.getLine(cursor.row);
  848. var shouldOutdent = mode.checkOutdent(lineState, line, text);
  849. var end = session.insert(cursor, text);
  850. if (transform && transform.selection) {
  851. if (transform.selection.length == 2) { // Transform relative to the current column
  852. this.selection.setSelectionRange(
  853. new Range(cursor.row, start + transform.selection[0],
  854. cursor.row, start + transform.selection[1]));
  855. } else { // Transform relative to the current row.
  856. this.selection.setSelectionRange(
  857. new Range(cursor.row + transform.selection[0],
  858. transform.selection[1],
  859. cursor.row + transform.selection[2],
  860. transform.selection[3]));
  861. }
  862. }
  863. if (session.getDocument().isNewLine(text)) {
  864. var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
  865. session.insert({row: cursor.row+1, column: 0}, lineIndent);
  866. }
  867. if (shouldOutdent)
  868. mode.autoOutdent(lineState, session, cursor.row);
  869. };
  870. this.onTextInput = function(text) {
  871. this.keyBinding.onTextInput(text);
  872. };
  873. this.onCommandKey = function(e, hashId, keyCode) {
  874. this.keyBinding.onCommandKey(e, hashId, keyCode);
  875. };
  876. /**
  877. * 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 emites the `changeOverwrite` event.
  878. * @param {Boolean} overwrite Defines wheter or not to set overwrites
  879. *
  880. *
  881. * @related EditSession.setOverwrite
  882. **/
  883. this.setOverwrite = function(overwrite) {
  884. this.session.setOverwrite(overwrite);
  885. };
  886. /**
  887. * Returns `true` if overwrites are enabled; `false` otherwise.
  888. * @returns {Boolean}
  889. * @related EditSession.getOverwrite
  890. **/
  891. this.getOverwrite = function() {
  892. return this.session.getOverwrite();
  893. };
  894. /**
  895. * Sets the value of overwrite to the opposite of whatever it currently is.
  896. * @related EditSession.toggleOverwrite
  897. **/
  898. this.toggleOverwrite = function() {
  899. this.session.toggleOverwrite();
  900. };
  901. /**
  902. * Sets how fast the mouse scrolling should do.
  903. * @param {Number} speed A value indicating the new speed (in milliseconds)
  904. **/
  905. this.setScrollSpeed = function(speed) {
  906. this.setOption("scrollSpeed", speed);
  907. };
  908. /**
  909. * Returns the value indicating how fast the mouse scroll speed is (in milliseconds).
  910. * @returns {Number}
  911. **/
  912. this.getScrollSpeed = function() {
  913. return this.getOption("scrollSpeed");
  914. };
  915. /**
  916. * Sets the delay (in milliseconds) of the mouse drag.
  917. * @param {Number} dragDelay A value indicating the new delay
  918. **/
  919. this.setDragDelay = function(dragDelay) {
  920. this.setOption("dragDelay", dragDelay);
  921. };
  922. /**
  923. * Returns the current mouse drag delay.
  924. * @returns {Number}
  925. **/
  926. this.getDragDelay = function() {
  927. return this.getOption("dragDelay");
  928. };
  929. /**
  930. * Emitted when the selection style changes, via [[Editor.setSelectionStyle]].
  931. * @event changeSelectionStyle
  932. * @param {Object} data Contains one property, `data`, which indicates the new selection style
  933. **/
  934. /**
  935. * Draw selection markers spanning whole line, or only over selected text. Default value is "line"
  936. * @param {String} style The new selection style "line"|"text"
  937. *
  938. **/
  939. this.setSelectionStyle = function(val) {
  940. this.setOption("selectionStyle", val);
  941. };
  942. /**
  943. * Returns the current selection style.
  944. * @returns {String}
  945. **/
  946. this.getSelectionStyle = function() {
  947. return this.getOption("selectionStyle");
  948. };
  949. /**
  950. * Determines whether or not the current line should be highlighted.
  951. * @param {Boolean} shouldHighlight Set to `true` to highlight the current line
  952. **/
  953. this.setHighlightActiveLine = function(shouldHighlight) {
  954. this.setOption("highlightActiveLine", shouldHighlight);
  955. };
  956. /**
  957. * Returns `true` if current lines are always highlighted.
  958. * @return {Boolean}
  959. **/
  960. this.getHighlightActiveLine = function() {
  961. return this.getOption("highlightActiveLine");
  962. };
  963. this.setHighlightGutterLine = function(shouldHighlight) {
  964. this.setOption("highlightGutterLine", shouldHighlight);
  965. };
  966. this.getHighlightGutterLine = function() {
  967. return this.getOption("highlightGutterLine");
  968. };
  969. /**
  970. * Determines if the currently selected word should be highlighted.
  971. * @param {Boolean} shouldHighlight Set to `true` to highlight the currently selected word
  972. *
  973. **/
  974. this.setHighlightSelectedWord = function(shouldHighlight) {
  975. this.setOption("highlightSelectedWord", shouldHighlight);
  976. };
  977. /**
  978. * Returns `true` if currently highlighted words are to be highlighted.
  979. * @returns {Boolean}
  980. **/
  981. this.getHighlightSelectedWord = function() {
  982. return this.$highlightSelectedWord;
  983. };
  984. this.setAnimatedScroll = function(shouldAnimate){
  985. this.renderer.setAnimatedScroll(shouldAnimate);
  986. };
  987. this.getAnimatedScroll = function(){
  988. return this.renderer.getAnimatedScroll();
  989. };
  990. /**
  991. * If `showInvisibles` is set to `true`, invisible characters&mdash;like spaces or new lines&mdash;are show in the editor.
  992. * @param {Boolean} showInvisibles Specifies whether or not to show invisible characters
  993. *
  994. **/
  995. this.setShowInvisibles = function(showInvisibles) {
  996. this.renderer.setShowInvisibles(showInvisibles);
  997. };
  998. /**
  999. * Returns `true` if invisible characters are being shown.
  1000. * @returns {Boolean}
  1001. **/
  1002. this.getShowInvisibles = function() {
  1003. return this.renderer.getShowInvisibles();
  1004. };
  1005. this.setDisplayIndentGuides = function(display) {
  1006. this.renderer.setDisplayIndentGuides(display);
  1007. };
  1008. this.getDisplayIndentGuides = function() {
  1009. return this.renderer.getDisplayIndentGuides();
  1010. };
  1011. /**
  1012. * If `showPrintMargin` is set to `true`, the print margin is shown in the editor.
  1013. * @param {Boolean} showPrintMargin Specifies whether or not to show the print margin
  1014. *
  1015. **/
  1016. this.setShowPrintMargin = function(showPrintMargin) {
  1017. this.renderer.setShowPrintMargin(showPrintMargin);
  1018. };
  1019. /**
  1020. * Returns `true` if the print margin is being shown.
  1021. * @returns {Boolean}
  1022. **/
  1023. this.getShowPrintMargin = function() {
  1024. return this.renderer.getShowPrintMargin();
  1025. };
  1026. /**
  1027. * Sets the column defining where the print margin should be.
  1028. * @param {Number} showPrintMargin Specifies the new print margin
  1029. *
  1030. **/
  1031. this.setPrintMarginColumn = function(showPrintMargin) {
  1032. this.renderer.setPrintMarginColumn(showPrintMargin);
  1033. };
  1034. /**
  1035. * Returns the column number of where the print margin is.
  1036. * @returns {Number}
  1037. **/
  1038. this.getPrintMarginColumn = function() {
  1039. return this.renderer.getPrintMarginColumn();
  1040. };
  1041. /**
  1042. * If `readOnly` is true, then the editor is set to read-only mode, and none of the content can change.
  1043. * @param {Boolean} readOnly Specifies whether the editor can be modified or not
  1044. *
  1045. **/
  1046. this.setReadOnly = function(readOnly) {
  1047. this.setOption("readOnly", readOnly);
  1048. };
  1049. /**
  1050. * Returns `true` if the editor is set to read-only mode.
  1051. * @returns {Boolean}
  1052. **/
  1053. this.getReadOnly = function() {
  1054. return this.getOption("readOnly");
  1055. };
  1056. /**
  1057. * 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}
  1058. * @param {Boolean} enabled Enables or disables behaviors
  1059. *
  1060. **/
  1061. this.setBehavioursEnabled = function (enabled) {
  1062. this.setOption("behavioursEnabled", enabled);
  1063. };
  1064. /**
  1065. * Returns `true` if the behaviors are currently enabled. {:BehaviorsDef}
  1066. *
  1067. * @returns {Boolean}
  1068. **/
  1069. this.getBehavioursEnabled = function () {
  1070. return this.getOption("behavioursEnabled");
  1071. };
  1072. /**
  1073. * Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets
  1074. * when such a character is typed in.
  1075. * @param {Boolean} enabled Enables or disables wrapping behaviors
  1076. *
  1077. **/
  1078. this.setWrapBehavioursEnabled = function (enabled) {
  1079. this.setOption("wrapBehavioursEnabled", enabled);
  1080. };
  1081. /**
  1082. * Returns `true` if the wrapping behaviors are currently enabled.
  1083. **/
  1084. this.getWrapBehavioursEnabled = function () {
  1085. return this.getOption("wrapBehavioursEnabled");
  1086. };
  1087. /**
  1088. * Indicates whether the fold widgets should be shown or not.
  1089. * @param {Boolean} show Specifies whether the fold widgets are shown
  1090. **/
  1091. this.setShowFoldWidgets = function(show) {
  1092. this.setOption("showFoldWidgets", show);
  1093. };
  1094. /**
  1095. * Returns `true` if the fold widgets are shown.
  1096. * @return {Boolean}
  1097. **/
  1098. this.getShowFoldWidgets = function() {
  1099. return this.getOption("showFoldWidgets");
  1100. };
  1101. this.setFadeFoldWidgets = function(fade) {
  1102. this.setOption("fadeFoldWidgets", fade);
  1103. };
  1104. this.getFadeFoldWidgets = function() {
  1105. return this.getOption("fadeFoldWidgets");
  1106. };
  1107. /**
  1108. * Removes the current selection or one character.
  1109. * @param {String} dir The direction of the deletion to occur, either "left" or "right"
  1110. *
  1111. **/
  1112. this.remove = function(dir) {
  1113. if (this.selection.isEmpty()){
  1114. if (dir == "left")
  1115. this.selection.selectLeft();
  1116. else
  1117. this.selection.selectRight();
  1118. }
  1119. var range = this.getSelectionRange();
  1120. if (this.getBehavioursEnabled()) {
  1121. var session = this.session;
  1122. var state = session.getState(range.start.row);
  1123. var new_range = session.getMode().transformAction(state, 'deletion', this, session, range);
  1124. if (range.end.column === 0) {
  1125. var text = session.getTextRange(range);
  1126. if (text[text.length - 1] == "\n") {
  1127. var line = session.getLine(range.end.row);
  1128. if (/^\s+$/.test(line)) {
  1129. range.end.column = line.length;
  1130. }
  1131. }
  1132. }
  1133. if (new_range)
  1134. range = new_range;
  1135. }
  1136. this.session.remove(range);
  1137. this.clearSelection();
  1138. };
  1139. /**
  1140. * Removes the word directly to the right of the current selection.
  1141. **/
  1142. this.removeWordRight = function() {
  1143. if (this.selection.isEmpty())
  1144. this.selection.selectWordRight();
  1145. this.session.remove(this.getSelectionRange());
  1146. this.clearSelection();
  1147. };
  1148. /**
  1149. * Removes the word directly to the left of the current selection.
  1150. **/
  1151. this.removeWordLeft = function() {
  1152. if (this.selection.isEmpty())
  1153. this.selection.selectWordLeft();
  1154. this.session.remove(this.getSelectionRange());
  1155. this.clearSelection();
  1156. };
  1157. /**
  1158. * Removes all the words to the left of the current selection, until the start of the line.
  1159. **/
  1160. this.removeToLineStart = function() {
  1161. if (this.selection.isEmpty())
  1162. this.selection.selectLineStart();
  1163. this.session.remove(this.getSelectionRange());
  1164. this.clearSelection();
  1165. };
  1166. /**
  1167. * Removes all the words to the right of the current selection, until the end of the line.
  1168. **/
  1169. this.removeToLineEnd = function() {
  1170. if (this.selection.isEmpty())
  1171. this.selection.selectLineEnd();
  1172. var range = this.getSelectionRange();
  1173. if (range.start.column == range.end.column && range.start.row == range.end.row) {
  1174. range.end.column = 0;
  1175. range.end.row++;
  1176. }
  1177. this.session.remove(range);
  1178. this.clearSelection();
  1179. };
  1180. /**
  1181. * Splits the line at the current selection (by inserting an `'\n'`).
  1182. **/
  1183. this.splitLine = function() {
  1184. if (!this.selection.isEmpty()) {
  1185. this.session.remove(this.getSelectionRange());
  1186. this.clearSelection();
  1187. }
  1188. var cursor = this.getCursorPosition();
  1189. this.insert("\n");
  1190. this.moveCursorToPosition(cursor);
  1191. };
  1192. /**
  1193. * Transposes current line.
  1194. **/
  1195. this.transposeLetters = function() {
  1196. if (!this.selection.isEmpty()) {
  1197. return;
  1198. }
  1199. var cursor = this.getCursorPosition();
  1200. var column = cursor.column;
  1201. if (column === 0)
  1202. return;
  1203. var line = this.session.getLine(cursor.row);
  1204. var swap, range;
  1205. if (column < line.length) {
  1206. swap = line.charAt(column) + line.charAt(column-1);
  1207. range = new Range(cursor.row, column-1, cursor.row, column+1);
  1208. }
  1209. else {
  1210. swap = line.charAt(column-1) + line.charAt(column-2);
  1211. range = new Range(cursor.row, column-2, cursor.row, column);
  1212. }
  1213. this.session.replace(range, swap);
  1214. };
  1215. /**
  1216. * Converts the current selection entirely into lowercase.
  1217. **/
  1218. this.toLowerCase = function() {
  1219. var originalRange = this.getSelectionRange();
  1220. if (this.selection.isEmpty()) {
  1221. this.selection.selectWord();
  1222. }
  1223. var range = this.getSelectionRange();
  1224. var text = this.session.getTextRange(range);
  1225. this.session.replace(range, text.toLowerCase());
  1226. this.selection.setSelectionRange(originalRange);
  1227. };
  1228. /**
  1229. * Converts the current selection entirely into uppercase.
  1230. **/
  1231. this.toUpperCase = function() {
  1232. var originalRange = this.getSelectionRange();
  1233. if (this.selection.isEmpty()) {
  1234. this.selection.selectWord();
  1235. }
  1236. var range = this.getSelectionRange();
  1237. var text = this.session.getTextRange(range);
  1238. this.session.replace(range, text.toUpperCase());
  1239. this.selection.setSelectionRange(originalRange);
  1240. };
  1241. /**
  1242. * Inserts an indentation into the current cursor position or indents the selected lines.
  1243. *
  1244. * @related EditSession.indentRows
  1245. **/
  1246. this.indent = function() {
  1247. var session = this.session;
  1248. var range = this.getSelectionRange();
  1249. if (range.start.row < range.end.row) {
  1250. var rows = this.$getSelectedRows();
  1251. session.indentRows(rows.first, rows.last, "\t");
  1252. return;
  1253. } else if (range.start.column < range.end.column) {
  1254. var text = session.getTextRange(range);
  1255. if (!/^\s+$/.test(text)) {
  1256. var rows = this.$getSelectedRows();
  1257. session.indentRows(rows.first, rows.last, "\t");
  1258. return;
  1259. }
  1260. }
  1261. var line = session.getLine(range.start.row);
  1262. var position = range.start;
  1263. var size = session.getTabSize();
  1264. var column = session.documentToScreenColumn(position.row, position.column);
  1265. if (this.session.getUseSoftTabs()) {
  1266. var count = (size - column % size);
  1267. var indentString = lang.stringRepeat(" ", count);
  1268. } else {
  1269. var count = column % size;
  1270. while (line[range.start.column] == " " && count) {
  1271. range.start.column--;
  1272. count--;
  1273. }
  1274. this.selection.setSelectionRange(range);
  1275. indentString = "\t";
  1276. }
  1277. return this.insert(indentString);
  1278. };
  1279. /**
  1280. * Indents the current line.
  1281. * @related EditSession.indentRows
  1282. **/
  1283. this.blockIndent = function() {
  1284. var rows = this.$getSelectedRows();
  1285. this.session.indentRows(rows.first, rows.last, "\t");
  1286. };
  1287. /**
  1288. * Outdents the current line.
  1289. * @related EditSession.outdentRows
  1290. **/
  1291. this.blockOutdent = function() {
  1292. var selection = this.session.getSelection();
  1293. this.session.outdentRows(selection.getRange());
  1294. };
  1295. // TODO: move out of core when we have good mechanism for managing extensions
  1296. this.sortLines = function() {
  1297. var rows = this.$getSelectedRows();
  1298. var session = this.session;
  1299. var lines = [];
  1300. for (i = rows.first; i <= rows.last; i++)
  1301. lines.push(session.getLine(i));
  1302. lines.sort(function(a, b) {
  1303. if (a.toLowerCase() < b.toLowerCase()) return -1;
  1304. if (a.toLowerCase() > b.toLowerCase()) return 1;
  1305. return 0;
  1306. });
  1307. var deleteRange = new Range(0, 0, 0, 0);
  1308. for (var i = rows.first; i <= rows.last; i++) {
  1309. var line = session.getLine(i);
  1310. deleteRange.start.row = i;
  1311. deleteRange.end.row = i;
  1312. deleteRange.end.column = line.length;
  1313. session.replace(deleteRange, lines[i-rows.first]);
  1314. }
  1315. };
  1316. /**
  1317. * Given the currently selected range, this function either comments all the lines, or uncomments all of them.
  1318. **/
  1319. this.toggleCommentLines = function() {
  1320. var state = this.session.getState(this.getCursorPosition().row);
  1321. var rows = this.$getSelectedRows();
  1322. this.session.getMode().toggleCommentLines(state, this.session, rows.first, rows.last);
  1323. };
  1324. this.toggleBlockComment = function() {
  1325. var cursor = this.getCursorPosition();
  1326. var state = this.session.getState(cursor.row);
  1327. var range = this.getSelectionRange();
  1328. this.session.getMode().toggleBlockComment(state, this.session, range, cursor);
  1329. };
  1330. /**
  1331. * Works like [[EditSession.getTokenAt]], except it returns a number.
  1332. * @returns {Number}
  1333. **/
  1334. this.getNumberAt = function(row, column) {
  1335. var _numberRx = /[\-]?[0-9]+(?:\.[0-9]+)?/g;
  1336. _numberRx.lastIndex = 0;
  1337. var s = this.session.getLine(row);
  1338. while (_numberRx.lastIndex < column) {
  1339. var m = _numberRx.exec(s);
  1340. if(m.index <= column && m.index+m[0].length >= column){
  1341. var number = {
  1342. value: m[0],
  1343. start: m.index,
  1344. end: m.index+m[0].length
  1345. };
  1346. return number;
  1347. }
  1348. }
  1349. return null;
  1350. };
  1351. /**
  1352. * If the character before the cursor is a number, this functions changes its value by `amount`.
  1353. * @param {Number} amount The value to change the numeral by (can be negative to decrease value)
  1354. *
  1355. **/
  1356. this.modifyNumber = function(amount) {
  1357. var row = this.selection.getCursor().row;
  1358. var column = this.selection.getCursor().column;
  1359. // get the char before the cursor
  1360. var charRange = new Range(row, column-1, row, column);
  1361. var c = this.session.getTextRange(charRange);
  1362. // if the char is a digit
  1363. if (!isNaN(parseFloat(c)) && isFinite(c)) {
  1364. // get the whole number the digit is part of
  1365. var nr = this.getNumberAt(row, column);
  1366. // if number found
  1367. if (nr) {
  1368. var fp = nr.value.indexOf(".") >= 0 ? nr.start + nr.value.indexOf(".") + 1 : nr.end;
  1369. var decimals = nr.start + nr.value.length - fp;
  1370. var t = parseFloat(nr.value);
  1371. t *= Math.pow(10, decimals);
  1372. if(fp !== nr.end && column < fp){
  1373. amount *= Math.pow(10, nr.end - column - 1);
  1374. } else {
  1375. amount *= Math.pow(10, nr.end - column);
  1376. }
  1377. t += amount;
  1378. t /= Math.pow(10, decimals);
  1379. var nnr = t.toFixed(decimals);
  1380. //update number
  1381. var replaceRange = new Range(row, nr.start, row, nr.end);
  1382. this.session.replace(replaceRange, nnr);
  1383. //reposition the cursor
  1384. this.moveCursorTo(row, Math.max(nr.start +1, column + nnr.length - nr.value.length));
  1385. }
  1386. }
  1387. };
  1388. /**
  1389. * Removes all the lines in the current selection
  1390. * @related EditSession.remove
  1391. **/
  1392. this.removeLines = function() {
  1393. var rows = this.$getSelectedRows();
  1394. this.session.removeFullLines(rows.first, rows.last);
  1395. this.clearSelection();
  1396. };
  1397. this.duplicateSelection = function() {
  1398. var sel = this.selection;
  1399. var doc = this.session;
  1400. var range = sel.getRange();
  1401. var reverse = sel.isBackwards();
  1402. if (range.isEmpty()) {
  1403. var row = range.start.row;
  1404. doc.duplicateLines(row, row);
  1405. } else {
  1406. var point = reverse ? range.start : range.end;
  1407. var endPoint = doc.insert(point, doc.getTextRange(range), false);
  1408. range.start = point;
  1409. range.end = endPoint;
  1410. sel.setSelectionRange(range, reverse);
  1411. }
  1412. };
  1413. /**
  1414. * Shifts all the selected lines down one row.
  1415. *
  1416. * @returns {Number} On success, it returns -1.
  1417. * @related EditSession.moveLinesUp
  1418. **/
  1419. this.moveLinesDown = function() {
  1420. this.$moveLines(1, false);
  1421. };
  1422. /**
  1423. * Shifts all the selected lines up one row.
  1424. * @returns {Number} On success, it returns -1.
  1425. * @related EditSession.moveLinesDown
  1426. **/
  1427. this.moveLinesUp = function() {
  1428. this.$moveLines(-1, false);
  1429. };
  1430. /**
  1431. * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this:
  1432. * ```json
  1433. * { row: newRowLocation, column: newColumnLocation }
  1434. * ```
  1435. * @param {Range} fromRange The range of text you want moved within the document
  1436. * @param {Object} toPosition The location (row and column) where you want to move the text to
  1437. *
  1438. * @returns {Range} The new range where the text was moved to.
  1439. * @related EditSession.moveText
  1440. **/
  1441. this.moveText = function(range, toPosition, copy) {
  1442. return this.session.moveText(range, toPosition, copy);
  1443. };
  1444. /**
  1445. * Copies all the selected lines up one row.
  1446. * @returns {Number} On success, returns 0.
  1447. *
  1448. **/
  1449. this.copyLinesUp = function() {
  1450. this.$moveLines(-1, true);
  1451. };
  1452. /**
  1453. * Copies all the selected lines down one row.
  1454. * @returns {Number} On success, returns the number of new rows added; in other words, `lastRow - firstRow + 1`.
  1455. * @related EditSession.duplicateLines
  1456. *
  1457. **/
  1458. this.copyLinesDown = function() {
  1459. this.$moveLines(1, true);
  1460. };
  1461. /**
  1462. * for internal use
  1463. * @ignore
  1464. *
  1465. **/
  1466. this.$moveLines = function(dir, copy) {
  1467. var rows, moved;
  1468. var selection = this.selection;
  1469. if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) {
  1470. var range = selection.toOrientedRange();
  1471. rows = this.$getSelectedRows(range);
  1472. moved = this.session.$moveLines(rows.first, rows.last, copy ? 0 : dir);
  1473. if (copy && dir == -1) moved = 0;
  1474. range.moveBy(moved, 0);
  1475. selection.fromOrientedRange(range);
  1476. } else {
  1477. var ranges = selection.rangeList.ranges;
  1478. selection.rangeList.detach(this.session);
  1479. this.inVirtualSelectionMode = true;
  1480. var diff = 0;
  1481. var totalDiff = 0;
  1482. var l = ranges.length;
  1483. for (var i = 0; i < l; i++) {
  1484. var rangeIndex = i;
  1485. ranges[i].moveBy(diff, 0);
  1486. rows = this.$getSelectedRows(ranges[i]);
  1487. var first = rows.first;
  1488. var last = rows.last;
  1489. while (++i < l) {
  1490. if (totalDiff) ranges[i].moveBy(totalDiff, 0);
  1491. var subRows = this.$getSelectedRows(ranges[i]);
  1492. if (copy && subRows.first != last)
  1493. break;
  1494. else if (!copy && subRows.first > last + 1)
  1495. break;
  1496. last = subRows.last;
  1497. }
  1498. i--;
  1499. diff = this.session.$moveLines(first, last, copy ? 0 : dir);
  1500. if (copy && dir == -1) rangeIndex = i + 1;
  1501. while (rangeIndex <= i) {
  1502. ranges[rangeIndex].moveBy(diff, 0);
  1503. rangeIndex++;
  1504. }
  1505. if (!copy) diff = 0;
  1506. totalDiff += diff;
  1507. }
  1508. selection.fromOrientedRange(selection.ranges[0]);
  1509. selection.rangeList.attach(this.session);
  1510. this.inVirtualSelectionMode = false;
  1511. }
  1512. };
  1513. /**
  1514. * Returns an object indicating the currently selected rows. The object looks like this:
  1515. *
  1516. * ```json
  1517. * { first: range.start.row, last: range.end.row }
  1518. * ```
  1519. *
  1520. * @returns {Object}
  1521. **/
  1522. this.$getSelectedRows = function(range) {
  1523. range = (range || this.getSelectionRange()).collapseRows();
  1524. return {
  1525. first: this.session.getRowFoldStart(range.start.row),
  1526. last: this.session.getRowFoldEnd(range.end.row)
  1527. };
  1528. };
  1529. this.onCompositionStart = function(text) {
  1530. this.renderer.showComposition(this.getCursorPosition());
  1531. };
  1532. this.onCompositionUpdate = function(text) {
  1533. this.renderer.setCompositionText(text);
  1534. };
  1535. this.onCompositionEnd = function() {
  1536. this.renderer.hideComposition();
  1537. };
  1538. /**
  1539. * {:VirtualRenderer.getFirstVisibleRow}
  1540. *
  1541. * @returns {Number}
  1542. * @related VirtualRenderer.getFirstVisibleRow
  1543. **/
  1544. this.getFirstVisibleRow = function() {
  1545. return this.renderer.getFirstVisibleRow();
  1546. };
  1547. /**
  1548. * {:VirtualRenderer.getLastVisibleRow}
  1549. *
  1550. * @returns {Number}
  1551. * @related VirtualRenderer.getLastVisibleRow
  1552. **/
  1553. this.getLastVisibleRow = function() {
  1554. return this.renderer.getLastVisibleRow();
  1555. };
  1556. /**
  1557. * Indicates if the row is currently visible on the screen.
  1558. * @param {Number} row The row to check
  1559. *
  1560. * @returns {Boolean}
  1561. **/
  1562. this.isRowVisible = function(row) {
  1563. return (row >= this.getFirstVisibleRow() && row <= this.getLastVisibleRow());
  1564. };
  1565. /**
  1566. * Indicates if the entire row is currently visible on the screen.
  1567. * @param {Number} row The row to check
  1568. *
  1569. *
  1570. * @returns {Boolean}
  1571. **/
  1572. this.isRowFullyVisible = function(row) {
  1573. return (row >= this.renderer.getFirstFullyVisibleRow() && row <= this.renderer.getLastFullyVisibleRow());
  1574. };
  1575. /**
  1576. * Returns the number of currently visibile rows.
  1577. * @returns {Number}
  1578. **/
  1579. this.$getVisibleRowCount = function() {
  1580. return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1;
  1581. };
  1582. this.$moveByPage = function(dir, select) {
  1583. var renderer = this.renderer;
  1584. var config = this.renderer.layerConfig;
  1585. var rows = dir * Math.floor(config.height / config.lineHeight);
  1586. this.$blockScrolling++;
  1587. if (select === true) {
  1588. this.selection.$moveSelection(function(){
  1589. this.moveCursorBy(rows, 0);
  1590. });
  1591. } else if (select === false) {
  1592. this.selection.moveCursorBy(rows, 0);
  1593. this.selection.clearSelection();
  1594. }
  1595. this.$blockScrolling--;
  1596. var scrollTop = renderer.scrollTop;
  1597. renderer.scrollBy(0, rows * config.lineHeight);
  1598. if (select != null)
  1599. renderer.scrollCursorIntoView(null, 0.5);
  1600. renderer.animateScrolling(scrollTop);
  1601. };
  1602. /**
  1603. * Selects the text from the current position of the document until where a "page down" finishes.
  1604. **/
  1605. this.selectPageDown = function() {
  1606. this.$moveByPage(1, true);
  1607. };
  1608. /**
  1609. * Selects the text from the current position of the document until where a "page up" finishes.
  1610. **/
  1611. this.selectPageUp = function() {
  1612. this.$moveByPage(-1, true);
  1613. };
  1614. /**
  1615. * Shifts the document to wherever "page down" is, as well as moving the cursor position.
  1616. **/
  1617. this.gotoPageDown = function() {
  1618. this.$moveByPage(1, false);
  1619. };
  1620. /**
  1621. * Shifts the document to wherever "page up" is, as well as moving the cursor position.
  1622. **/
  1623. this.gotoPageUp = function() {
  1624. this.$moveByPage(-1, false);
  1625. };
  1626. /**
  1627. * Scrolls the document to wherever "page down" is, without changing the cursor position.
  1628. **/
  1629. this.scrollPageDown = function() {
  1630. this.$moveByPage(1);
  1631. };
  1632. /**
  1633. * Scrolls the document to wherever "page up" is, without changing the cursor position.
  1634. **/
  1635. this.scrollPageUp = function() {
  1636. this.$moveByPage(-1);
  1637. };
  1638. /**
  1639. * Moves the editor to the specified row.
  1640. * @related VirtualRenderer.scrollToRow
  1641. **/
  1642. this.scrollToRow = function(row) {
  1643. this.renderer.scrollToRow(row);
  1644. };
  1645. /**
  1646. * Scrolls to a line. If `center` is `true`, it puts the line in middle of screen (or attempts to).
  1647. * @param {Number} line The line to scroll to
  1648. * @param {Boolean} center If `true`
  1649. * @param {Boolean} animate If `true` animates scrolling
  1650. * @param {Function} callback Function to be called when the animation has finished
  1651. *
  1652. *
  1653. * @related VirtualRenderer.scrollToLine
  1654. **/
  1655. this.scrollToLine = function(line, center, animate, callback) {
  1656. this.renderer.scrollToLine(line, center, animate, callback);
  1657. };
  1658. /**
  1659. * Attempts to center the current selection on the screen.
  1660. **/
  1661. this.centerSelection = function() {
  1662. var range = this.getSelectionRange();
  1663. var pos = {
  1664. row: Math.floor(range.start.row + (range.end.row - range.start.row) / 2),
  1665. column: Math.floor(range.start.column + (range.end.column - range.start.column) / 2)
  1666. };
  1667. this.renderer.alignCursor(pos, 0.5);
  1668. };
  1669. /**
  1670. * Gets the current position of the cursor.
  1671. * @returns {Object} An object that looks something like this:
  1672. *
  1673. * ```json
  1674. * { row: currRow, column: currCol }
  1675. * ```
  1676. *
  1677. * @related Selection.getCursor
  1678. **/
  1679. this.getCursorPosition = function() {
  1680. return this.selection.getCursor();
  1681. };
  1682. /**
  1683. * Returns the screen position of the cursor.
  1684. * @returns {Number}
  1685. * @related EditSession.documentToScreenPosition
  1686. **/
  1687. this.getCursorPositionScreen = function() {
  1688. return this.session.documentToScreenPosition(this.getCursorPosition());
  1689. };
  1690. /**
  1691. * {:Selection.getRange}
  1692. * @returns {Range}
  1693. * @related Selection.getRange
  1694. **/
  1695. this.getSelectionRange = function() {
  1696. return this.selection.getRange();
  1697. };
  1698. /**
  1699. * Selects all the text in editor.
  1700. * @related Selection.selectAll
  1701. **/
  1702. this.selectAll = function() {
  1703. this.$blockScrolling += 1;
  1704. this.selection.selectAll();
  1705. this.$blockScrolling -= 1;
  1706. };
  1707. /**
  1708. * {:Selection.clearSelection}
  1709. * @related Selection.clearSelection
  1710. **/
  1711. this.clearSelection = function() {
  1712. this.selection.clearSelection();
  1713. };
  1714. /**
  1715. * Moves the cursor to the specified row and column. Note that this does not de-select the current selection.
  1716. * @param {Number} row The new row number
  1717. * @param {Number} column The new column number
  1718. *
  1719. *
  1720. * @related Selection.moveCursorTo
  1721. **/
  1722. this.moveCursorTo = function(row, column) {
  1723. this.selection.moveCursorTo(row, column);
  1724. };
  1725. /**
  1726. * Moves the cursor to the position indicated by `pos.row` and `pos.column`.
  1727. * @param {Object} pos An object with two properties, row and column
  1728. *
  1729. *
  1730. * @related Selection.moveCursorToPosition
  1731. **/
  1732. this.moveCursorToPosition = function(pos) {
  1733. this.selection.moveCursorToPosition(pos);
  1734. };
  1735. /**
  1736. * Moves the cursor's row and column to the next matching bracket or HTML tag.
  1737. *
  1738. **/
  1739. this.jumpToMatching = function(select, expand) {
  1740. var cursor = this.getCursorPosition();
  1741. var iterator = new TokenIterator(this.session, cursor.row, cursor.column);
  1742. var prevToken = iterator.getCurrentToken();
  1743. var token = prevToken || iterator.stepForward();
  1744. if (!token) return;
  1745. //get next closing tag or bracket
  1746. var matchType;
  1747. var found = false;
  1748. var depth = {};
  1749. var i = cursor.column - token.start;
  1750. var bracketType;
  1751. var brackets = {
  1752. ")": "(",
  1753. "(": "(",
  1754. "]": "[",
  1755. "[": "[",
  1756. "{": "{",
  1757. "}": "{"
  1758. };
  1759. do {
  1760. if (token.value.match(/[{}()\[\]]/g)) {
  1761. for (; i < token.value.length && !found; i++) {
  1762. if (!brackets[token.value[i]]) {
  1763. continue;
  1764. }
  1765. bracketType = brackets[token.value[i]] + '.' + token.type.replace("rparen", "lparen");
  1766. if (isNaN(depth[bracketType])) {
  1767. depth[bracketType] = 0;
  1768. }
  1769. switch (token.value[i]) {
  1770. case '(':
  1771. case '[':
  1772. case '{':
  1773. depth[bracketType]++;
  1774. break;
  1775. case ')':
  1776. case ']':
  1777. case '}':
  1778. depth[bracketType]--;
  1779. if (depth[bracketType] === -1) {
  1780. matchType = 'bracket';
  1781. found = true;
  1782. }
  1783. break;
  1784. }
  1785. }
  1786. }
  1787. else if (token && token.type.indexOf('tag-name') !== -1) {
  1788. if (isNaN(depth[token.value])) {
  1789. depth[token.value] = 0;
  1790. }
  1791. if (prevToken.value === '<') {
  1792. depth[token.value]++;
  1793. }
  1794. else if (prevToken.value === '</') {
  1795. depth[token.value]--;
  1796. }
  1797. if (depth[token.value] === -1) {
  1798. matchType = 'tag';
  1799. found = true;
  1800. }
  1801. }
  1802. if (!found) {
  1803. prevToken = token;
  1804. token = iterator.stepForward();
  1805. i = 0;
  1806. }
  1807. } while (token && !found);
  1808. //no match found
  1809. if (!matchType)
  1810. return;
  1811. var range, pos;
  1812. if (matchType === 'bracket') {
  1813. range = this.session.getBracketRange(cursor);
  1814. if (!range) {
  1815. range = new Range(
  1816. iterator.getCurrentTokenRow(),
  1817. iterator.getCurrentTokenColumn() + i - 1,
  1818. iterator.getCurrentTokenRow(),
  1819. iterator.getCurrentTokenColumn() + i - 1
  1820. );
  1821. pos = range.start;
  1822. if (expand || pos.row === cursor.row && Math.abs(pos.column - cursor.column) < 2)
  1823. range = this.session.getBracketRange(pos);
  1824. }
  1825. }
  1826. else if (matchType === 'tag') {
  1827. if (token && token.type.indexOf('tag-name') !== -1)
  1828. var tag = token.value;
  1829. else
  1830. return;
  1831. range = new Range(
  1832. iterator.getCurrentTokenRow(),
  1833. iterator.getCurrentTokenColumn() - 2,
  1834. iterator.getCurrentTokenRow(),
  1835. iterator.getCurrentTokenColumn() - 2
  1836. );
  1837. //find matching tag
  1838. if (range.compare(cursor.row, cursor.column) === 0) {
  1839. found = false;
  1840. do {
  1841. token = prevToken;
  1842. prevToken = iterator.stepBackward();
  1843. if (prevToken) {
  1844. if (prevToken.type.indexOf('tag-close') !== -1) {
  1845. range.setEnd(iterator.getCurrentTokenRow(), iterator.getCurrentTokenColumn() + 1);
  1846. }
  1847. if (token.value === tag && token.type.indexOf('tag-name') !== -1) {
  1848. if (prevToken.value === '<') {
  1849. depth[tag]++;
  1850. }
  1851. else if (prevToken.value === '</') {
  1852. depth[tag]--;
  1853. }
  1854. if (depth[tag] === 0)
  1855. found = true;
  1856. }
  1857. }
  1858. } while (prevToken && !found);
  1859. }
  1860. //we found it
  1861. if (token && token.type.indexOf('tag-name')) {
  1862. pos = range.start;
  1863. if (pos.row == cursor.row && Math.abs(pos.column - cursor.column) < 2)
  1864. pos = range.end;
  1865. }
  1866. }
  1867. pos = range && range.cursor || pos;
  1868. if (pos) {
  1869. if (select) {
  1870. if (range && expand) {
  1871. this.selection.setRange(range);
  1872. } else if (range && range.isEqual(this.getSelectionRange())) {
  1873. this.clearSelection();
  1874. } else {
  1875. this.selection.selectTo(pos.row, pos.column);
  1876. }
  1877. } else {
  1878. this.selection.moveTo(pos.row, pos.column);
  1879. }
  1880. }
  1881. };
  1882. /**
  1883. * Moves the cursor to the specified line number, and also into the indiciated column.
  1884. * @param {Number} lineNumber The line number to go to
  1885. * @param {Number} column A column number to go to
  1886. * @param {Boolean} animate If `true` animates scolling
  1887. *
  1888. **/
  1889. this.gotoLine = function(lineNumber, column, animate) {
  1890. this.selection.clearSelection();
  1891. this.session.unfold({row: lineNumber - 1, column: column || 0});
  1892. this.$blockScrolling += 1;
  1893. // todo: find a way to automatically exit multiselect mode
  1894. this.exitMultiSelectMode && this.exitMultiSelectMode();
  1895. this.moveCursorTo(lineNumber - 1, column || 0);
  1896. this.$blockScrolling -= 1;
  1897. if (!this.isRowFullyVisible(lineNumber - 1))
  1898. this.scrollToLine(lineNumber - 1, true, animate);
  1899. };
  1900. /**
  1901. * Moves the cursor to the specified row and column. Note that this does de-select the current selection.
  1902. * @param {Number} row The new row number
  1903. * @param {Number} column The new column number
  1904. *
  1905. *
  1906. * @related Editor.moveCursorTo
  1907. **/
  1908. this.navigateTo = function(row, column) {
  1909. this.selection.moveTo(row, column);
  1910. };
  1911. /**
  1912. * Moves the cursor up in the document the specified number of times. Note that this does de-select the current selection.
  1913. * @param {Number} times The number of times to change navigation
  1914. *
  1915. *
  1916. **/
  1917. this.navigateUp = function(times) {
  1918. if (this.selection.isMultiLine() && !this.selection.isBackwards()) {
  1919. var selectionStart = this.selection.anchor.getPosition();
  1920. return this.moveCursorToPosition(selectionStart);
  1921. }
  1922. this.selection.clearSelection();
  1923. this.selection.moveCursorBy(-times || -1, 0);
  1924. };
  1925. /**
  1926. * Moves the cursor down in the document the specified number of times. Note that this does de-select the current selection.
  1927. * @param {Number} times The number of times to change navigation
  1928. *
  1929. *
  1930. **/
  1931. this.navigateDown = function(times) {
  1932. if (this.selection.isMultiLine() && this.selection.isBackwards()) {
  1933. var selectionEnd = this.selection.anchor.getPosition();
  1934. return this.moveCursorToPosition(selectionEnd);
  1935. }
  1936. this.selection.clearSelection();
  1937. this.selection.moveCursorBy(times || 1, 0);
  1938. };
  1939. /**
  1940. * Moves the cursor left in the document the specified number of times. Note that this does de-select the current selection.
  1941. * @param {Number} times The number of times to change navigation
  1942. *
  1943. *
  1944. **/
  1945. this.navigateLeft = function(times) {
  1946. if (!this.selection.isEmpty()) {
  1947. var selectionStart = this.getSelectionRange().start;
  1948. this.moveCursorToPosition(selectionStart);
  1949. }
  1950. else {
  1951. times = times || 1;
  1952. while (times--) {
  1953. this.selection.moveCursorLeft();
  1954. }
  1955. }
  1956. this.clearSelection();
  1957. };
  1958. /**
  1959. * Moves the cursor right in the document the specified number of times. Note that this does de-select the current selection.
  1960. * @param {Number} times The number of times to change navigation
  1961. *
  1962. *
  1963. **/
  1964. this.navigateRight = function(times) {
  1965. if (!this.selection.isEmpty()) {
  1966. var selectionEnd = this.getSelectionRange().end;
  1967. this.moveCursorToPosition(selectionEnd);
  1968. }
  1969. else {
  1970. times = times || 1;
  1971. while (times--) {
  1972. this.selection.moveCursorRight();
  1973. }
  1974. }
  1975. this.clearSelection();
  1976. };
  1977. /**
  1978. *
  1979. * Moves the cursor to the start of the current line. Note that this does de-select the current selection.
  1980. **/
  1981. this.navigateLineStart = function() {
  1982. this.selection.moveCursorLineStart();
  1983. this.clearSelection();
  1984. };
  1985. /**
  1986. *
  1987. * Moves the cursor to the end of the current line. Note that this does de-select the current selection.
  1988. **/
  1989. this.navigateLineEnd = function() {
  1990. this.selection.moveCursorLineEnd();
  1991. this.clearSelection();
  1992. };
  1993. /**
  1994. *
  1995. * Moves the cursor to the end of the current file. Note that this does de-select the current selection.
  1996. **/
  1997. this.navigateFileEnd = function() {
  1998. this.selection.moveCursorFileEnd();
  1999. this.clearSelection();
  2000. };
  2001. /**
  2002. *
  2003. * Moves the cursor to the start of the current file. Note that this does de-select the current selection.
  2004. **/
  2005. this.navigateFileStart = function() {
  2006. this.selection.moveCursorFileStart();
  2007. this.clearSelection();
  2008. };
  2009. /**
  2010. *
  2011. * Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection.
  2012. **/
  2013. this.navigateWordRight = function() {
  2014. this.selection.moveCursorWordRight();
  2015. this.clearSelection();
  2016. };
  2017. /**
  2018. *
  2019. * Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection.
  2020. **/
  2021. this.navigateWordLeft = function() {
  2022. this.selection.moveCursorWordLeft();
  2023. this.clearSelection();
  2024. };
  2025. /**
  2026. * Replaces the first occurance of `options.needle` with the value in `replacement`.
  2027. * @param {String} replacement The text to replace with
  2028. * @param {Object} options The [[Search `Search`]] options to use
  2029. *
  2030. *
  2031. **/
  2032. this.replace = function(replacement, options) {
  2033. if (options)
  2034. this.$search.set(options);
  2035. var range = this.$search.find(this.session);
  2036. var replaced = 0;
  2037. if (!range)
  2038. return replaced;
  2039. if (this.$tryReplace(range, replacement)) {
  2040. replaced = 1;
  2041. }
  2042. if (range !== null) {
  2043. this.selection.setSelectionRange(range);
  2044. this.renderer.scrollSelectionIntoView(range.start, range.end);
  2045. }
  2046. return replaced;
  2047. };
  2048. /**
  2049. * Replaces all occurances of `options.needle` with the value in `replacement`.
  2050. * @param {String} replacement The text to replace with
  2051. * @param {Object} options The [[Search `Search`]] options to use
  2052. *
  2053. *
  2054. **/
  2055. this.replaceAll = function(replacement, options) {
  2056. if (options) {
  2057. this.$search.set(options);
  2058. }
  2059. var ranges = this.$search.findAll(this.session);
  2060. var replaced = 0;
  2061. if (!ranges.length)
  2062. return replaced;
  2063. this.$blockScrolling += 1;
  2064. var selection = this.getSelectionRange();
  2065. this.selection.moveTo(0, 0);
  2066. for (var i = ranges.length - 1; i >= 0; --i) {
  2067. if(this.$tryReplace(ranges[i], replacement)) {
  2068. replaced++;
  2069. }
  2070. }
  2071. this.selection.setSelectionRange(selection);
  2072. this.$blockScrolling -= 1;
  2073. return replaced;
  2074. };
  2075. this.$tryReplace = function(range, replacement) {
  2076. var input = this.session.getTextRange(range);
  2077. replacement = this.$search.replace(input, replacement);
  2078. if (replacement !== null) {
  2079. range.end = this.session.replace(range, replacement);
  2080. return range;
  2081. } else {
  2082. return null;
  2083. }
  2084. };
  2085. /**
  2086. * {:Search.getOptions} For more information on `options`, see [[Search `Search`]].
  2087. * @related Search.getOptions
  2088. * @returns {Object}
  2089. **/
  2090. this.getLastSearchOptions = function() {
  2091. return this.$search.getOptions();
  2092. };
  2093. /**
  2094. * Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]].
  2095. * @param {String} needle The text to search for (optional)
  2096. * @param {Object} options An object defining various search properties
  2097. * @param {Boolean} animate If `true` animate scrolling
  2098. *
  2099. *
  2100. * @related Search.find
  2101. **/
  2102. this.find = function(needle, options, animate) {
  2103. if (!options)
  2104. options = {};
  2105. if (typeof needle == "string" || needle instanceof RegExp)
  2106. options.needle = needle;
  2107. else if (typeof needle == "object")
  2108. oop.mixin(options, needle);
  2109. var range = this.selection.getRange();
  2110. if (options.needle == null) {
  2111. needle = this.session.getTextRange(range)
  2112. || this.$search.$options.needle;
  2113. if (!needle) {
  2114. range = this.session.getWordRange(range.start.row, range.start.column);
  2115. needle = this.session.getTextRange(range);
  2116. }
  2117. this.$search.set({needle: needle});
  2118. }
  2119. this.$search.set(options);
  2120. if (!options.start)
  2121. this.$search.set({start: range});
  2122. var newRange = this.$search.find(this.session);
  2123. if (options.preventScroll)
  2124. return newRange;
  2125. if (newRange) {
  2126. this.revealRange(newRange, animate);
  2127. return newRange;
  2128. }
  2129. // clear selection if nothing is found
  2130. if (options.backwards)
  2131. range.start = range.end;
  2132. else
  2133. range.end = range.start;
  2134. this.selection.setRange(range);
  2135. };
  2136. /**
  2137. * Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]].
  2138. * @param {Object} options search options
  2139. * @param {Boolean} animate If `true` animate scrolling
  2140. *
  2141. *
  2142. * @related Editor.find
  2143. **/
  2144. this.findNext = function(options, animate) {
  2145. this.find({skipCurrent: true, backwards: false}, options, animate);
  2146. };
  2147. /**
  2148. * Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]].
  2149. * @param {Object} options search options
  2150. * @param {Boolean} animate If `true` animate scrolling
  2151. *
  2152. *
  2153. * @related Editor.find
  2154. **/
  2155. this.findPrevious = function(options, animate) {
  2156. this.find(options, {skipCurrent: true, backwards: true}, animate);
  2157. };
  2158. this.revealRange = function(range, animate) {
  2159. this.$blockScrolling += 1;
  2160. this.session.unfold(range);
  2161. this.selection.setSelectionRange(range);
  2162. this.$blockScrolling -= 1;
  2163. var scrollTop = this.renderer.scrollTop;
  2164. this.renderer.scrollSelectionIntoView(range.start, range.end, 0.5);
  2165. if (animate !== false)
  2166. this.renderer.animateScrolling(scrollTop);
  2167. };
  2168. /**
  2169. * {:UndoManager.undo}
  2170. * @related UndoManager.undo
  2171. **/
  2172. this.undo = function() {
  2173. this.$blockScrolling++;
  2174. this.session.getUndoManager().undo();
  2175. this.$blockScrolling--;
  2176. this.renderer.scrollCursorIntoView(null, 0.5);
  2177. };
  2178. /**
  2179. * {:UndoManager.redo}
  2180. * @related UndoManager.redo
  2181. **/
  2182. this.redo = function() {
  2183. this.$blockScrolling++;
  2184. this.session.getUndoManager().redo();
  2185. this.$blockScrolling--;
  2186. this.renderer.scrollCursorIntoView(null, 0.5);
  2187. };
  2188. /**
  2189. *
  2190. * Cleans up the entire editor.
  2191. **/
  2192. this.destroy = function() {
  2193. this.renderer.destroy();
  2194. this._signal("destroy", this);
  2195. if (this.session) {
  2196. this.session.destroy();
  2197. }
  2198. };
  2199. /**
  2200. * Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element
  2201. * @param {Boolean} enable default true
  2202. **/
  2203. this.setAutoScrollEditorIntoView = function(enable) {
  2204. if (!enable)
  2205. return;
  2206. var rect;
  2207. var self = this;
  2208. var shouldScroll = false;
  2209. if (!this.$scrollAnchor)
  2210. this.$scrollAnchor = document.createElement("div");
  2211. var scrollAnchor = this.$scrollAnchor;
  2212. scrollAnchor.style.cssText = "position:absolute";
  2213. this.container.insertBefore(scrollAnchor, this.container.firstChild);
  2214. var onChangeSelection = this.on("changeSelection", function() {
  2215. shouldScroll = true;
  2216. });
  2217. // needed to not trigger sync reflow
  2218. var onBeforeRender = this.renderer.on("beforeRender", function() {
  2219. if (shouldScroll)
  2220. rect = self.renderer.container.getBoundingClientRect();
  2221. });
  2222. var onAfterRender = this.renderer.on("afterRender", function() {
  2223. if (shouldScroll && rect && (self.isFocused()
  2224. || self.searchBox && self.searchBox.isFocused())
  2225. ) {
  2226. var renderer = self.renderer;
  2227. var pos = renderer.$cursorLayer.$pixelPos;
  2228. var config = renderer.layerConfig;
  2229. var top = pos.top - config.offset;
  2230. if (pos.top >= 0 && top + rect.top < 0) {
  2231. shouldScroll = true;
  2232. } else if (pos.top < config.height &&
  2233. pos.top + rect.top + config.lineHeight > window.innerHeight) {
  2234. shouldScroll = false;
  2235. } else {
  2236. shouldScroll = null;
  2237. }
  2238. if (shouldScroll != null) {
  2239. scrollAnchor.style.top = top + "px";
  2240. scrollAnchor.style.left = pos.left + "px";
  2241. scrollAnchor.style.height = config.lineHeight + "px";
  2242. scrollAnchor.scrollIntoView(shouldScroll);
  2243. }
  2244. shouldScroll = rect = null;
  2245. }
  2246. });
  2247. this.setAutoScrollEditorIntoView = function(enable) {
  2248. if (enable)
  2249. return;
  2250. delete this.setAutoScrollEditorIntoView;
  2251. this.off("changeSelection", onChangeSelection);
  2252. this.renderer.off("afterRender", onAfterRender);
  2253. this.renderer.off("beforeRender", onBeforeRender);
  2254. };
  2255. };
  2256. this.$resetCursorStyle = function() {
  2257. var style = this.$cursorStyle || "ace";
  2258. var cursorLayer = this.renderer.$cursorLayer;
  2259. if (!cursorLayer)
  2260. return;
  2261. cursorLayer.setSmoothBlinking(/smooth/.test(style));
  2262. cursorLayer.isBlinking = !this.$readOnly && style != "wide";
  2263. dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style));
  2264. };
  2265. }).call(Editor.prototype);
  2266. config.defineOptions(Editor.prototype, "editor", {
  2267. selectionStyle: {
  2268. set: function(style) {
  2269. this.onSelectionChange();
  2270. this._signal("changeSelectionStyle", {data: style});
  2271. },
  2272. initialValue: "line"
  2273. },
  2274. highlightActiveLine: {
  2275. set: function() {this.$updateHighlightActiveLine();},
  2276. initialValue: true
  2277. },
  2278. highlightSelectedWord: {
  2279. set: function(shouldHighlight) {this.$onSelectionChange();},
  2280. initialValue: true
  2281. },
  2282. readOnly: {
  2283. set: function(readOnly) {
  2284. // disabled to not break vim mode!
  2285. // this.textInput.setReadOnly(readOnly);
  2286. this.$resetCursorStyle();
  2287. },
  2288. initialValue: false
  2289. },
  2290. cursorStyle: {
  2291. set: function(val) { this.$resetCursorStyle(); },
  2292. values: ["ace", "slim", "smooth", "wide"],
  2293. initialValue: "ace"
  2294. },
  2295. mergeUndoDeltas: {
  2296. values: [false, true, "always"],
  2297. initialValue: true
  2298. },
  2299. behavioursEnabled: {initialValue: true},
  2300. wrapBehavioursEnabled: {initialValue: true},
  2301. autoScrollEditorIntoView: {
  2302. set: function(val) {this.setAutoScrollEditorIntoView(val)}
  2303. },
  2304. keyboardHandler: {
  2305. set: function(val) { this.setKeyboardHandler(val); },
  2306. get: function() { return this.keybindingId; },
  2307. handlesSet: true
  2308. },
  2309. hScrollBarAlwaysVisible: "renderer",
  2310. vScrollBarAlwaysVisible: "renderer",
  2311. highlightGutterLine: "renderer",
  2312. animatedScroll: "renderer",
  2313. showInvisibles: "renderer",
  2314. showPrintMargin: "renderer",
  2315. printMarginColumn: "renderer",
  2316. printMargin: "renderer",
  2317. fadeFoldWidgets: "renderer",
  2318. showFoldWidgets: "renderer",
  2319. showLineNumbers: "renderer",
  2320. showGutter: "renderer",
  2321. displayIndentGuides: "renderer",
  2322. fontSize: "renderer",
  2323. fontFamily: "renderer",
  2324. maxLines: "renderer",
  2325. minLines: "renderer",
  2326. scrollPastEnd: "renderer",
  2327. fixedWidthGutter: "renderer",
  2328. theme: "renderer",
  2329. scrollSpeed: "$mouseHandler",
  2330. dragDelay: "$mouseHandler",
  2331. dragEnabled: "$mouseHandler",
  2332. focusTimout: "$mouseHandler",
  2333. tooltipFollowsMouse: "$mouseHandler",
  2334. firstLineNumber: "session",
  2335. overwrite: "session",
  2336. newLineMode: "session",
  2337. useWorker: "session",
  2338. useSoftTabs: "session",
  2339. tabSize: "session",
  2340. wrap: "session",
  2341. indentedSoftWrap: "session",
  2342. foldStyle: "session",
  2343. mode: "session"
  2344. });
  2345. exports.Editor = Editor;
  2346. });