editor.js 77 KB

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