edit_session.js 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600
  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. var oop = require("./lib/oop");
  33. var lang = require("./lib/lang");
  34. var config = require("./config");
  35. var EventEmitter = require("./lib/event_emitter").EventEmitter;
  36. var Selection = require("./selection").Selection;
  37. var TextMode = require("./mode/text").Mode;
  38. var Range = require("./range").Range;
  39. var Document = require("./document").Document;
  40. var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer;
  41. var SearchHighlight = require("./search_highlight").SearchHighlight;
  42. /**
  43. * Stores all the data about [[Editor `Editor`]] state providing easy way to change editors state.
  44. *
  45. * `EditSession` can be attached to only one [[Document `Document`]]. Same `Document` can be attached to several `EditSession`s.
  46. * @class EditSession
  47. **/
  48. //{ events
  49. /**
  50. *
  51. * Emitted when the document changes.
  52. * @event change
  53. * @param {Object} e An object containing a `delta` of information about the change.
  54. **/
  55. /**
  56. * Emitted when the tab size changes, via [[EditSession.setTabSize]].
  57. *
  58. * @event changeTabSize
  59. **/
  60. /**
  61. * Emitted when the ability to overwrite text changes, via [[EditSession.setOverwrite]].
  62. *
  63. * @event changeOverwrite
  64. **/
  65. /**
  66. * Emitted when the gutter changes, either by setting or removing breakpoints, or when the gutter decorations change.
  67. *
  68. * @event changeBreakpoint
  69. **/
  70. /**
  71. * Emitted when a front marker changes.
  72. *
  73. * @event changeFrontMarker
  74. **/
  75. /**
  76. * Emitted when a back marker changes.
  77. *
  78. * @event changeBackMarker
  79. **/
  80. /**
  81. * Emitted when an annotation changes, like through [[EditSession.setAnnotations]].
  82. *
  83. * @event changeAnnotation
  84. **/
  85. /**
  86. * Emitted when a background tokenizer asynchronously processes new rows.
  87. * @event tokenizerUpdate
  88. *
  89. * @param {Object} e An object containing one property, `"data"`, that contains information about the changing rows
  90. *
  91. **/
  92. /**
  93. * Emitted when the current mode changes.
  94. *
  95. * @event changeMode
  96. *
  97. **/
  98. /**
  99. * Emitted when the wrap mode changes.
  100. *
  101. * @event changeWrapMode
  102. *
  103. **/
  104. /**
  105. * Emitted when the wrapping limit changes.
  106. *
  107. * @event changeWrapLimit
  108. *
  109. **/
  110. /**
  111. * Emitted when a code fold is added or removed.
  112. *
  113. * @event changeFold
  114. *
  115. **/
  116. /**
  117. * Emitted when the scroll top changes.
  118. * @event changeScrollTop
  119. *
  120. * @param {Number} scrollTop The new scroll top value
  121. **/
  122. /**
  123. * Emitted when the scroll left changes.
  124. * @event changeScrollLeft
  125. *
  126. * @param {Number} scrollLeft The new scroll left value
  127. **/
  128. //}
  129. /**
  130. *
  131. * Sets up a new `EditSession` and associates it with the given `Document` and `TextMode`.
  132. * @param {Document | String} text [If `text` is a `Document`, it associates the `EditSession` with it. Otherwise, a new `Document` is created, with the initial text]{: #textParam}
  133. * @param {TextMode} mode [The inital language mode to use for the document]{: #modeParam}
  134. *
  135. * @constructor
  136. **/
  137. var EditSession = function(text, mode) {
  138. this.$breakpoints = [];
  139. this.$decorations = [];
  140. this.$frontMarkers = {};
  141. this.$backMarkers = {};
  142. this.$markerId = 1;
  143. this.$undoSelect = true;
  144. this.$foldData = [];
  145. this.$foldData.toString = function() {
  146. return this.join("\n");
  147. };
  148. this.on("changeFold", this.onChangeFold.bind(this));
  149. this.$onChange = this.onChange.bind(this);
  150. if (typeof text != "object" || !text.getLine)
  151. text = new Document(text);
  152. this.setDocument(text);
  153. this.selection = new Selection(this);
  154. config.resetOptions(this);
  155. this.setMode(mode);
  156. config._signal("session", this);
  157. };
  158. (function() {
  159. oop.implement(this, EventEmitter);
  160. /**
  161. * Sets the `EditSession` to point to a new `Document`. If a `BackgroundTokenizer` exists, it also points to `doc`.
  162. *
  163. * @param {Document} doc The new `Document` to use
  164. *
  165. **/
  166. this.setDocument = function(doc) {
  167. if (this.doc)
  168. this.doc.removeListener("change", this.$onChange);
  169. this.doc = doc;
  170. doc.on("change", this.$onChange);
  171. if (this.bgTokenizer)
  172. this.bgTokenizer.setDocument(this.getDocument());
  173. this.resetCaches();
  174. };
  175. /**
  176. * Returns the `Document` associated with this session.
  177. * @return {Document}
  178. **/
  179. this.getDocument = function() {
  180. return this.doc;
  181. };
  182. /**
  183. * @param {Number} row The row to work with
  184. *
  185. **/
  186. this.$resetRowCache = function(docRow) {
  187. if (!docRow) {
  188. this.$docRowCache = [];
  189. this.$screenRowCache = [];
  190. return;
  191. }
  192. var l = this.$docRowCache.length;
  193. var i = this.$getRowCacheIndex(this.$docRowCache, docRow) + 1;
  194. if (l > i) {
  195. this.$docRowCache.splice(i, l);
  196. this.$screenRowCache.splice(i, l);
  197. }
  198. };
  199. this.$getRowCacheIndex = function(cacheArray, val) {
  200. var low = 0;
  201. var hi = cacheArray.length - 1;
  202. while (low <= hi) {
  203. var mid = (low + hi) >> 1;
  204. var c = cacheArray[mid];
  205. if (val > c)
  206. low = mid + 1;
  207. else if (val < c)
  208. hi = mid - 1;
  209. else
  210. return mid;
  211. }
  212. return low -1;
  213. };
  214. this.resetCaches = function() {
  215. this.$modified = true;
  216. this.$wrapData = [];
  217. this.$rowLengthCache = [];
  218. this.$resetRowCache(0);
  219. if (this.bgTokenizer)
  220. this.bgTokenizer.start(0);
  221. };
  222. this.onChangeFold = function(e) {
  223. var fold = e.data;
  224. this.$resetRowCache(fold.start.row);
  225. };
  226. this.onChange = function(delta) {
  227. this.$modified = true;
  228. this.$resetRowCache(delta.start.row);
  229. var removedFolds = this.$updateInternalDataOnChange(delta);
  230. if (!this.$fromUndo && this.$undoManager && !delta.ignore) {
  231. this.$deltasDoc.push(delta);
  232. if (removedFolds && removedFolds.length != 0) {
  233. this.$deltasFold.push({
  234. action: "removeFolds",
  235. folds: removedFolds
  236. });
  237. }
  238. this.$informUndoManager.schedule();
  239. }
  240. this.bgTokenizer && this.bgTokenizer.$updateOnChange(delta);
  241. this._signal("change", delta);
  242. };
  243. /**
  244. * Sets the session text.
  245. * @param {String} text The new text to place
  246. *
  247. **/
  248. this.setValue = function(text) {
  249. this.doc.setValue(text);
  250. this.selection.moveTo(0, 0);
  251. this.$resetRowCache(0);
  252. this.$deltas = [];
  253. this.$deltasDoc = [];
  254. this.$deltasFold = [];
  255. this.setUndoManager(this.$undoManager);
  256. this.getUndoManager().reset();
  257. };
  258. /**
  259. * Returns the current [[Document `Document`]] as a string.
  260. * @method toString
  261. * @returns {String}
  262. * @alias EditSession.getValue
  263. *
  264. **/
  265. /**
  266. * Returns the current [[Document `Document`]] as a string.
  267. * @method getValue
  268. * @returns {String}
  269. * @alias EditSession.toString
  270. **/
  271. this.getValue =
  272. this.toString = function() {
  273. return this.doc.getValue();
  274. };
  275. /**
  276. * Returns the string of the current selection.
  277. **/
  278. this.getSelection = function() {
  279. return this.selection;
  280. };
  281. /**
  282. * {:BackgroundTokenizer.getState}
  283. * @param {Number} row The row to start at
  284. *
  285. * @related BackgroundTokenizer.getState
  286. **/
  287. this.getState = function(row) {
  288. return this.bgTokenizer.getState(row);
  289. };
  290. /**
  291. * Starts tokenizing at the row indicated. Returns a list of objects of the tokenized rows.
  292. * @param {Number} row The row to start at
  293. *
  294. *
  295. *
  296. **/
  297. this.getTokens = function(row) {
  298. return this.bgTokenizer.getTokens(row);
  299. };
  300. /**
  301. * Returns an object indicating the token at the current row. The object has two properties: `index` and `start`.
  302. * @param {Number} row The row number to retrieve from
  303. * @param {Number} column The column number to retrieve from
  304. *
  305. *
  306. **/
  307. this.getTokenAt = function(row, column) {
  308. var tokens = this.bgTokenizer.getTokens(row);
  309. var token, c = 0;
  310. if (column == null) {
  311. i = tokens.length - 1;
  312. c = this.getLine(row).length;
  313. } else {
  314. for (var i = 0; i < tokens.length; i++) {
  315. c += tokens[i].value.length;
  316. if (c >= column)
  317. break;
  318. }
  319. }
  320. token = tokens[i];
  321. if (!token)
  322. return null;
  323. token.index = i;
  324. token.start = c - token.value.length;
  325. return token;
  326. };
  327. /**
  328. * Sets the undo manager.
  329. * @param {UndoManager} undoManager The new undo manager
  330. *
  331. *
  332. **/
  333. this.setUndoManager = function(undoManager) {
  334. this.$undoManager = undoManager;
  335. this.$deltas = [];
  336. this.$deltasDoc = [];
  337. this.$deltasFold = [];
  338. if (this.$informUndoManager)
  339. this.$informUndoManager.cancel();
  340. if (undoManager) {
  341. var self = this;
  342. this.$syncInformUndoManager = function() {
  343. self.$informUndoManager.cancel();
  344. if (self.$deltasFold.length) {
  345. self.$deltas.push({
  346. group: "fold",
  347. deltas: self.$deltasFold
  348. });
  349. self.$deltasFold = [];
  350. }
  351. if (self.$deltasDoc.length) {
  352. self.$deltas.push({
  353. group: "doc",
  354. deltas: self.$deltasDoc
  355. });
  356. self.$deltasDoc = [];
  357. }
  358. if (self.$deltas.length > 0) {
  359. undoManager.execute({
  360. action: "aceupdate",
  361. args: [self.$deltas, self],
  362. merge: self.mergeUndoDeltas
  363. });
  364. }
  365. self.mergeUndoDeltas = false;
  366. self.$deltas = [];
  367. };
  368. this.$informUndoManager = lang.delayedCall(this.$syncInformUndoManager);
  369. }
  370. };
  371. /**
  372. * starts a new group in undo history
  373. **/
  374. this.markUndoGroup = function() {
  375. if (this.$syncInformUndoManager)
  376. this.$syncInformUndoManager();
  377. };
  378. this.$defaultUndoManager = {
  379. undo: function() {},
  380. redo: function() {},
  381. reset: function() {}
  382. };
  383. /**
  384. * Returns the current undo manager.
  385. **/
  386. this.getUndoManager = function() {
  387. return this.$undoManager || this.$defaultUndoManager;
  388. };
  389. /**
  390. * Returns the current value for tabs. If the user is using soft tabs, this will be a series of spaces (defined by [[EditSession.getTabSize `getTabSize()`]]); otherwise it's simply `'\t'`.
  391. **/
  392. this.getTabString = function() {
  393. if (this.getUseSoftTabs()) {
  394. return lang.stringRepeat(" ", this.getTabSize());
  395. } else {
  396. return "\t";
  397. }
  398. };
  399. /**
  400. /**
  401. * Pass `true` to enable the use of soft tabs. Soft tabs means you're using spaces instead of the tab character (`'\t'`).
  402. * @param {Boolean} useSoftTabs Value indicating whether or not to use soft tabs
  403. **/
  404. this.setUseSoftTabs = function(val) {
  405. this.setOption("useSoftTabs", val);
  406. };
  407. /**
  408. * Returns `true` if soft tabs are being used, `false` otherwise.
  409. * @returns {Boolean}
  410. **/
  411. this.getUseSoftTabs = function() {
  412. // todo might need more general way for changing settings from mode, but this is ok for now
  413. return this.$useSoftTabs && !this.$mode.$indentWithTabs;
  414. };
  415. /**
  416. * Set the number of spaces that define a soft tab; for example, passing in `4` transforms the soft tabs to be equivalent to four spaces. This function also emits the `changeTabSize` event.
  417. * @param {Number} tabSize The new tab size
  418. **/
  419. this.setTabSize = function(tabSize) {
  420. this.setOption("tabSize", tabSize);
  421. };
  422. /**
  423. * Returns the current tab size.
  424. **/
  425. this.getTabSize = function() {
  426. return this.$tabSize;
  427. };
  428. /**
  429. * Returns `true` if the character at the position is a soft tab.
  430. * @param {Object} position The position to check
  431. *
  432. *
  433. **/
  434. this.isTabStop = function(position) {
  435. return this.$useSoftTabs && (position.column % this.$tabSize === 0);
  436. };
  437. this.$overwrite = false;
  438. /**
  439. * Pass in `true` to enable overwrites in your session, or `false` to disable.
  440. *
  441. * 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.
  442. *
  443. * @param {Boolean} overwrite Defines wheter or not to set overwrites
  444. *
  445. *
  446. **/
  447. this.setOverwrite = function(overwrite) {
  448. this.setOption("overwrite", overwrite);
  449. };
  450. /**
  451. * Returns `true` if overwrites are enabled; `false` otherwise.
  452. **/
  453. this.getOverwrite = function() {
  454. return this.$overwrite;
  455. };
  456. /**
  457. * Sets the value of overwrite to the opposite of whatever it currently is.
  458. **/
  459. this.toggleOverwrite = function() {
  460. this.setOverwrite(!this.$overwrite);
  461. };
  462. /**
  463. * Adds `className` to the `row`, to be used for CSS stylings and whatnot.
  464. * @param {Number} row The row number
  465. * @param {String} className The class to add
  466. *
  467. *
  468. **/
  469. this.addGutterDecoration = function(row, className) {
  470. if (!this.$decorations[row])
  471. this.$decorations[row] = "";
  472. this.$decorations[row] += " " + className;
  473. this._signal("changeBreakpoint", {});
  474. };
  475. /**
  476. * Removes `className` from the `row`.
  477. * @param {Number} row The row number
  478. * @param {String} className The class to add
  479. *
  480. *
  481. **/
  482. this.removeGutterDecoration = function(row, className) {
  483. this.$decorations[row] = (this.$decorations[row] || "").replace(" " + className, "");
  484. this._signal("changeBreakpoint", {});
  485. };
  486. /**
  487. * Returns an array of numbers, indicating which rows have breakpoints.
  488. * @returns {[Number]}
  489. **/
  490. this.getBreakpoints = function() {
  491. return this.$breakpoints;
  492. };
  493. /**
  494. * Sets a breakpoint on every row number given by `rows`. This function also emites the `'changeBreakpoint'` event.
  495. * @param {Array} rows An array of row indices
  496. *
  497. *
  498. *
  499. **/
  500. this.setBreakpoints = function(rows) {
  501. this.$breakpoints = [];
  502. for (var i=0; i<rows.length; i++) {
  503. this.$breakpoints[rows[i]] = "ace_breakpoint";
  504. }
  505. this._signal("changeBreakpoint", {});
  506. };
  507. /**
  508. * Removes all breakpoints on the rows. This function also emites the `'changeBreakpoint'` event.
  509. **/
  510. this.clearBreakpoints = function() {
  511. this.$breakpoints = [];
  512. this._signal("changeBreakpoint", {});
  513. };
  514. /**
  515. * Sets a breakpoint on the row number given by `rows`. This function also emites the `'changeBreakpoint'` event.
  516. * @param {Number} row A row index
  517. * @param {String} className Class of the breakpoint
  518. *
  519. *
  520. **/
  521. this.setBreakpoint = function(row, className) {
  522. if (className === undefined)
  523. className = "ace_breakpoint";
  524. if (className)
  525. this.$breakpoints[row] = className;
  526. else
  527. delete this.$breakpoints[row];
  528. this._signal("changeBreakpoint", {});
  529. };
  530. /**
  531. * Removes a breakpoint on the row number given by `rows`. This function also emites the `'changeBreakpoint'` event.
  532. * @param {Number} row A row index
  533. *
  534. *
  535. **/
  536. this.clearBreakpoint = function(row) {
  537. delete this.$breakpoints[row];
  538. this._signal("changeBreakpoint", {});
  539. };
  540. /**
  541. * Adds a new marker to the given `Range`. If `inFront` is `true`, a front marker is defined, and the `'changeFrontMarker'` event fires; otherwise, the `'changeBackMarker'` event fires.
  542. * @param {Range} range Define the range of the marker
  543. * @param {String} clazz Set the CSS class for the marker
  544. * @param {Function | String} type Identify the type of the marker
  545. * @param {Boolean} inFront Set to `true` to establish a front marker
  546. *
  547. *
  548. * @return {Number} The new marker id
  549. **/
  550. this.addMarker = function(range, clazz, type, inFront) {
  551. var id = this.$markerId++;
  552. var marker = {
  553. range : range,
  554. type : type || "line",
  555. renderer: typeof type == "function" ? type : null,
  556. clazz : clazz,
  557. inFront: !!inFront,
  558. id: id
  559. };
  560. if (inFront) {
  561. this.$frontMarkers[id] = marker;
  562. this._signal("changeFrontMarker");
  563. } else {
  564. this.$backMarkers[id] = marker;
  565. this._signal("changeBackMarker");
  566. }
  567. return id;
  568. };
  569. /**
  570. * Adds a dynamic marker to the session.
  571. * @param {Object} marker object with update method
  572. * @param {Boolean} inFront Set to `true` to establish a front marker
  573. *
  574. *
  575. * @return {Object} The added marker
  576. **/
  577. this.addDynamicMarker = function(marker, inFront) {
  578. if (!marker.update)
  579. return;
  580. var id = this.$markerId++;
  581. marker.id = id;
  582. marker.inFront = !!inFront;
  583. if (inFront) {
  584. this.$frontMarkers[id] = marker;
  585. this._signal("changeFrontMarker");
  586. } else {
  587. this.$backMarkers[id] = marker;
  588. this._signal("changeBackMarker");
  589. }
  590. return marker;
  591. };
  592. /**
  593. * Removes the marker with the specified ID. If this marker was in front, the `'changeFrontMarker'` event is emitted. If the marker was in the back, the `'changeBackMarker'` event is emitted.
  594. * @param {Number} markerId A number representing a marker
  595. *
  596. *
  597. *
  598. **/
  599. this.removeMarker = function(markerId) {
  600. var marker = this.$frontMarkers[markerId] || this.$backMarkers[markerId];
  601. if (!marker)
  602. return;
  603. var markers = marker.inFront ? this.$frontMarkers : this.$backMarkers;
  604. if (marker) {
  605. delete (markers[markerId]);
  606. this._signal(marker.inFront ? "changeFrontMarker" : "changeBackMarker");
  607. }
  608. };
  609. /**
  610. * Returns an object containing all of the markers, either front or back.
  611. * @param {Boolean} inFront If `true`, indicates you only want front markers; `false` indicates only back markers
  612. *
  613. * @returns {Object}
  614. **/
  615. this.getMarkers = function(inFront) {
  616. return inFront ? this.$frontMarkers : this.$backMarkers;
  617. };
  618. this.highlight = function(re) {
  619. if (!this.$searchHighlight) {
  620. var highlight = new SearchHighlight(null, "ace_selected-word", "text");
  621. this.$searchHighlight = this.addDynamicMarker(highlight);
  622. }
  623. this.$searchHighlight.setRegexp(re);
  624. };
  625. // experimental
  626. this.highlightLines = function(startRow, endRow, clazz, inFront) {
  627. if (typeof endRow != "number") {
  628. clazz = endRow;
  629. endRow = startRow;
  630. }
  631. if (!clazz)
  632. clazz = "ace_step";
  633. var range = new Range(startRow, 0, endRow, Infinity);
  634. range.id = this.addMarker(range, clazz, "fullLine", inFront);
  635. return range;
  636. };
  637. /*
  638. * Error:
  639. * {
  640. * row: 12,
  641. * column: 2, //can be undefined
  642. * text: "Missing argument",
  643. * type: "error" // or "warning" or "info"
  644. * }
  645. */
  646. /**
  647. * Sets annotations for the `EditSession`. This functions emits the `'changeAnnotation'` event.
  648. * @param {Array} annotations A list of annotations
  649. *
  650. **/
  651. this.setAnnotations = function(annotations) {
  652. this.$annotations = annotations;
  653. this._signal("changeAnnotation", {});
  654. };
  655. /**
  656. * Returns the annotations for the `EditSession`.
  657. * @returns {Array}
  658. **/
  659. this.getAnnotations = function() {
  660. return this.$annotations || [];
  661. };
  662. /**
  663. * Clears all the annotations for this session. This function also triggers the `'changeAnnotation'` event.
  664. **/
  665. this.clearAnnotations = function() {
  666. this.setAnnotations([]);
  667. };
  668. /**
  669. * If `text` contains either the newline (`\n`) or carriage-return ('\r') characters, `$autoNewLine` stores that value.
  670. * @param {String} text A block of text
  671. *
  672. *
  673. **/
  674. this.$detectNewLine = function(text) {
  675. var match = text.match(/^.*?(\r?\n)/m);
  676. if (match) {
  677. this.$autoNewLine = match[1];
  678. } else {
  679. this.$autoNewLine = "\n";
  680. }
  681. };
  682. /**
  683. * Given a starting row and column, this method returns the `Range` of the first word boundary it finds.
  684. * @param {Number} row The row to start at
  685. * @param {Number} column The column to start at
  686. *
  687. * @returns {Range}
  688. **/
  689. this.getWordRange = function(row, column) {
  690. var line = this.getLine(row);
  691. var inToken = false;
  692. if (column > 0)
  693. inToken = !!line.charAt(column - 1).match(this.tokenRe);
  694. if (!inToken)
  695. inToken = !!line.charAt(column).match(this.tokenRe);
  696. if (inToken)
  697. var re = this.tokenRe;
  698. else if (/^\s+$/.test(line.slice(column-1, column+1)))
  699. var re = /\s/;
  700. else
  701. var re = this.nonTokenRe;
  702. var start = column;
  703. if (start > 0) {
  704. do {
  705. start--;
  706. }
  707. while (start >= 0 && line.charAt(start).match(re));
  708. start++;
  709. }
  710. var end = column;
  711. while (end < line.length && line.charAt(end).match(re)) {
  712. end++;
  713. }
  714. return new Range(row, start, row, end);
  715. };
  716. /**
  717. * Gets the range of a word, including its right whitespace.
  718. * @param {Number} row The row number to start from
  719. * @param {Number} column The column number to start from
  720. *
  721. * @return {Range}
  722. **/
  723. this.getAWordRange = function(row, column) {
  724. var wordRange = this.getWordRange(row, column);
  725. var line = this.getLine(wordRange.end.row);
  726. while (line.charAt(wordRange.end.column).match(/[ \t]/)) {
  727. wordRange.end.column += 1;
  728. }
  729. return wordRange;
  730. };
  731. /**
  732. * {:Document.setNewLineMode.desc}
  733. * @param {String} newLineMode {:Document.setNewLineMode.param}
  734. *
  735. *
  736. * @related Document.setNewLineMode
  737. **/
  738. this.setNewLineMode = function(newLineMode) {
  739. this.doc.setNewLineMode(newLineMode);
  740. };
  741. /**
  742. *
  743. * Returns the current new line mode.
  744. * @returns {String}
  745. * @related Document.getNewLineMode
  746. **/
  747. this.getNewLineMode = function() {
  748. return this.doc.getNewLineMode();
  749. };
  750. /**
  751. * Identifies if you want to use a worker for the `EditSession`.
  752. * @param {Boolean} useWorker Set to `true` to use a worker
  753. *
  754. **/
  755. this.setUseWorker = function(useWorker) { this.setOption("useWorker", useWorker); };
  756. /**
  757. * Returns `true` if workers are being used.
  758. **/
  759. this.getUseWorker = function() { return this.$useWorker; };
  760. /**
  761. * Reloads all the tokens on the current session. This function calls [[BackgroundTokenizer.start `BackgroundTokenizer.start ()`]] to all the rows; it also emits the `'tokenizerUpdate'` event.
  762. **/
  763. this.onReloadTokenizer = function(e) {
  764. var rows = e.data;
  765. this.bgTokenizer.start(rows.first);
  766. this._signal("tokenizerUpdate", e);
  767. };
  768. this.$modes = {};
  769. /**
  770. * Sets a new text mode for the `EditSession`. This method also emits the `'changeMode'` event. If a [[BackgroundTokenizer `BackgroundTokenizer`]] is set, the `'tokenizerUpdate'` event is also emitted.
  771. * @param {TextMode} mode Set a new text mode
  772. * @param {cb} optional callback
  773. *
  774. **/
  775. this.$mode = null;
  776. this.$modeId = null;
  777. this.setMode = function(mode, cb) {
  778. if (mode && typeof mode === "object") {
  779. if (mode.getTokenizer)
  780. return this.$onChangeMode(mode);
  781. var options = mode;
  782. var path = options.path;
  783. } else {
  784. path = mode || "ace/mode/text";
  785. }
  786. // this is needed if ace isn't on require path (e.g tests in node)
  787. if (!this.$modes["ace/mode/text"])
  788. this.$modes["ace/mode/text"] = new TextMode();
  789. if (this.$modes[path] && !options) {
  790. this.$onChangeMode(this.$modes[path]);
  791. cb && cb();
  792. return;
  793. }
  794. // load on demand
  795. this.$modeId = path;
  796. config.loadModule(["mode", path], function(m) {
  797. if (this.$modeId !== path)
  798. return cb && cb();
  799. if (this.$modes[path] && !options) {
  800. this.$onChangeMode(this.$modes[path]);
  801. } else if (m && m.Mode) {
  802. m = new m.Mode(options);
  803. if (!options) {
  804. this.$modes[path] = m;
  805. m.$id = path;
  806. }
  807. this.$onChangeMode(m);
  808. }
  809. cb && cb();
  810. }.bind(this));
  811. // set mode to text until loading is finished
  812. if (!this.$mode)
  813. this.$onChangeMode(this.$modes["ace/mode/text"], true);
  814. };
  815. this.$onChangeMode = function(mode, $isPlaceholder) {
  816. if (!$isPlaceholder)
  817. this.$modeId = mode.$id;
  818. if (this.$mode === mode)
  819. return;
  820. this.$mode = mode;
  821. this.$stopWorker();
  822. if (this.$useWorker)
  823. this.$startWorker();
  824. var tokenizer = mode.getTokenizer();
  825. if(tokenizer.addEventListener !== undefined) {
  826. var onReloadTokenizer = this.onReloadTokenizer.bind(this);
  827. tokenizer.addEventListener("update", onReloadTokenizer);
  828. }
  829. if (!this.bgTokenizer) {
  830. this.bgTokenizer = new BackgroundTokenizer(tokenizer);
  831. var _self = this;
  832. this.bgTokenizer.addEventListener("update", function(e) {
  833. _self._signal("tokenizerUpdate", e);
  834. });
  835. } else {
  836. this.bgTokenizer.setTokenizer(tokenizer);
  837. }
  838. this.bgTokenizer.setDocument(this.getDocument());
  839. this.tokenRe = mode.tokenRe;
  840. this.nonTokenRe = mode.nonTokenRe;
  841. if (!$isPlaceholder) {
  842. // experimental method, used by c9 findiniles
  843. if (mode.attachToSession)
  844. mode.attachToSession(this);
  845. this.$options.wrapMethod.set.call(this, this.$wrapMethod);
  846. this.$setFolding(mode.foldingRules);
  847. this.bgTokenizer.start(0);
  848. this._emit("changeMode");
  849. }
  850. };
  851. this.$completers = [];
  852. /**
  853. * Sets the completers to be used for this session only. Each completer should provide a getCompletions function (see language_tools)
  854. * @param {Object[]} completers
  855. */
  856. this.setCompleters = function(completers) {
  857. this.$completers = completers;
  858. };
  859. this.getCompleters = function() {
  860. return this.$completers;
  861. };
  862. this.$stopWorker = function() {
  863. if (this.$worker) {
  864. this.$worker.terminate();
  865. this.$worker = null;
  866. }
  867. };
  868. this.$startWorker = function() {
  869. try {
  870. this.$worker = this.$mode.createWorker(this);
  871. } catch (e) {
  872. config.warn("Could not load worker", e);
  873. this.$worker = null;
  874. }
  875. };
  876. /**
  877. * Returns the current text mode.
  878. * @returns {TextMode} The current text mode
  879. **/
  880. this.getMode = function() {
  881. return this.$mode;
  882. };
  883. this.$scrollTop = 0;
  884. /**
  885. * This function sets the scroll top value. It also emits the `'changeScrollTop'` event.
  886. * @param {Number} scrollTop The new scroll top value
  887. *
  888. **/
  889. this.setScrollTop = function(scrollTop) {
  890. // TODO: should we force integer lineheight instead? scrollTop = Math.round(scrollTop);
  891. if (this.$scrollTop === scrollTop || isNaN(scrollTop))
  892. return;
  893. this.$scrollTop = scrollTop;
  894. this._signal("changeScrollTop", scrollTop);
  895. };
  896. /**
  897. * [Returns the value of the distance between the top of the editor and the topmost part of the visible content.]{: #EditSession.getScrollTop}
  898. * @returns {Number}
  899. **/
  900. this.getScrollTop = function() {
  901. return this.$scrollTop;
  902. };
  903. this.$scrollLeft = 0;
  904. /**
  905. * [Sets the value of the distance between the left of the editor and the leftmost part of the visible content.]{: #EditSession.setScrollLeft}
  906. **/
  907. this.setScrollLeft = function(scrollLeft) {
  908. // scrollLeft = Math.round(scrollLeft);
  909. if (this.$scrollLeft === scrollLeft || isNaN(scrollLeft))
  910. return;
  911. this.$scrollLeft = scrollLeft;
  912. this._signal("changeScrollLeft", scrollLeft);
  913. };
  914. /**
  915. * [Returns the value of the distance between the left of the editor and the leftmost part of the visible content.]{: #EditSession.getScrollLeft}
  916. * @returns {Number}
  917. **/
  918. this.getScrollLeft = function() {
  919. return this.$scrollLeft;
  920. };
  921. /**
  922. * Returns the width of the screen.
  923. * @returns {Number}
  924. **/
  925. this.getScreenWidth = function() {
  926. this.$computeWidth();
  927. if (this.lineWidgets)
  928. return Math.max(this.getLineWidgetMaxWidth(), this.screenWidth);
  929. return this.screenWidth;
  930. };
  931. this.getLineWidgetMaxWidth = function() {
  932. if (this.lineWidgetsWidth != null) return this.lineWidgetsWidth;
  933. var width = 0;
  934. this.lineWidgets.forEach(function(w) {
  935. if (w && w.screenWidth > width)
  936. width = w.screenWidth;
  937. });
  938. return this.lineWidgetWidth = width;
  939. };
  940. this.$computeWidth = function(force) {
  941. if (this.$modified || force) {
  942. this.$modified = false;
  943. if (this.$useWrapMode)
  944. return this.screenWidth = this.$wrapLimit;
  945. var lines = this.doc.getAllLines();
  946. var cache = this.$rowLengthCache;
  947. var longestScreenLine = 0;
  948. var foldIndex = 0;
  949. var foldLine = this.$foldData[foldIndex];
  950. var foldStart = foldLine ? foldLine.start.row : Infinity;
  951. var len = lines.length;
  952. for (var i = 0; i < len; i++) {
  953. if (i > foldStart) {
  954. i = foldLine.end.row + 1;
  955. if (i >= len)
  956. break;
  957. foldLine = this.$foldData[foldIndex++];
  958. foldStart = foldLine ? foldLine.start.row : Infinity;
  959. }
  960. if (cache[i] == null)
  961. cache[i] = this.$getStringScreenWidth(lines[i])[0];
  962. if (cache[i] > longestScreenLine)
  963. longestScreenLine = cache[i];
  964. }
  965. this.screenWidth = longestScreenLine;
  966. }
  967. };
  968. /**
  969. * Returns a verbatim copy of the given line as it is in the document
  970. * @param {Number} row The row to retrieve from
  971. *
  972. *
  973. * @returns {String}
  974. *
  975. **/
  976. this.getLine = function(row) {
  977. return this.doc.getLine(row);
  978. };
  979. /**
  980. * Returns an array of strings of the rows between `firstRow` and `lastRow`. This function is inclusive of `lastRow`.
  981. * @param {Number} firstRow The first row index to retrieve
  982. * @param {Number} lastRow The final row index to retrieve
  983. *
  984. * @returns {[String]}
  985. *
  986. **/
  987. this.getLines = function(firstRow, lastRow) {
  988. return this.doc.getLines(firstRow, lastRow);
  989. };
  990. /**
  991. * Returns the number of rows in the document.
  992. * @returns {Number}
  993. **/
  994. this.getLength = function() {
  995. return this.doc.getLength();
  996. };
  997. /**
  998. * {:Document.getTextRange.desc}
  999. * @param {Range} range The range to work with
  1000. *
  1001. * @returns {String}
  1002. **/
  1003. this.getTextRange = function(range) {
  1004. return this.doc.getTextRange(range || this.selection.getRange());
  1005. };
  1006. /**
  1007. * Inserts a block of `text` and the indicated `position`.
  1008. * @param {Object} position The position {row, column} to start inserting at
  1009. * @param {String} text A chunk of text to insert
  1010. * @returns {Object} The position of the last line of `text`. If the length of `text` is 0, this function simply returns `position`.
  1011. *
  1012. *
  1013. **/
  1014. this.insert = function(position, text) {
  1015. return this.doc.insert(position, text);
  1016. };
  1017. /**
  1018. * Removes the `range` from the document.
  1019. * @param {Range} range A specified Range to remove
  1020. * @returns {Object} The new `start` property of the range, which contains `startRow` and `startColumn`. If `range` is empty, this function returns the unmodified value of `range.start`.
  1021. *
  1022. * @related Document.remove
  1023. *
  1024. **/
  1025. this.remove = function(range) {
  1026. return this.doc.remove(range);
  1027. };
  1028. /**
  1029. * Removes a range of full lines. This method also triggers the `'change'` event.
  1030. * @param {Number} firstRow The first row to be removed
  1031. * @param {Number} lastRow The last row to be removed
  1032. * @returns {[String]} Returns all the removed lines.
  1033. *
  1034. * @related Document.removeFullLines
  1035. *
  1036. **/
  1037. this.removeFullLines = function(firstRow, lastRow){
  1038. return this.doc.removeFullLines(firstRow, lastRow);
  1039. };
  1040. /**
  1041. * Reverts previous changes to your document.
  1042. * @param {Array} deltas An array of previous changes
  1043. * @param {Boolean} dontSelect [If `true`, doesn't select the range of where the change occured]{: #dontSelect}
  1044. *
  1045. *
  1046. * @returns {Range}
  1047. **/
  1048. this.undoChanges = function(deltas, dontSelect) {
  1049. if (!deltas.length)
  1050. return;
  1051. this.$fromUndo = true;
  1052. var lastUndoRange = null;
  1053. for (var i = deltas.length - 1; i != -1; i--) {
  1054. var delta = deltas[i];
  1055. if (delta.group == "doc") {
  1056. this.doc.revertDeltas(delta.deltas);
  1057. lastUndoRange =
  1058. this.$getUndoSelection(delta.deltas, true, lastUndoRange);
  1059. } else {
  1060. delta.deltas.forEach(function(foldDelta) {
  1061. this.addFolds(foldDelta.folds);
  1062. }, this);
  1063. }
  1064. }
  1065. this.$fromUndo = false;
  1066. lastUndoRange &&
  1067. this.$undoSelect &&
  1068. !dontSelect &&
  1069. this.selection.setSelectionRange(lastUndoRange);
  1070. return lastUndoRange;
  1071. };
  1072. /**
  1073. * Re-implements a previously undone change to your document.
  1074. * @param {Array} deltas An array of previous changes
  1075. * @param {Boolean} dontSelect {:dontSelect}
  1076. *
  1077. *
  1078. * @returns {Range}
  1079. **/
  1080. this.redoChanges = function(deltas, dontSelect) {
  1081. if (!deltas.length)
  1082. return;
  1083. this.$fromUndo = true;
  1084. var lastUndoRange = null;
  1085. for (var i = 0; i < deltas.length; i++) {
  1086. var delta = deltas[i];
  1087. if (delta.group == "doc") {
  1088. this.doc.applyDeltas(delta.deltas);
  1089. lastUndoRange =
  1090. this.$getUndoSelection(delta.deltas, false, lastUndoRange);
  1091. }
  1092. }
  1093. this.$fromUndo = false;
  1094. lastUndoRange &&
  1095. this.$undoSelect &&
  1096. !dontSelect &&
  1097. this.selection.setSelectionRange(lastUndoRange);
  1098. return lastUndoRange;
  1099. };
  1100. /**
  1101. * Enables or disables highlighting of the range where an undo occured.
  1102. * @param {Boolean} enable If `true`, selects the range of the reinserted change
  1103. *
  1104. **/
  1105. this.setUndoSelect = function(enable) {
  1106. this.$undoSelect = enable;
  1107. };
  1108. this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) {
  1109. function isInsert(delta) {
  1110. return isUndo ? delta.action !== "insert" : delta.action === "insert";
  1111. }
  1112. var delta = deltas[0];
  1113. var range, point;
  1114. var lastDeltaIsInsert = false;
  1115. if (isInsert(delta)) {
  1116. range = Range.fromPoints(delta.start, delta.end);
  1117. lastDeltaIsInsert = true;
  1118. } else {
  1119. range = Range.fromPoints(delta.start, delta.start);
  1120. lastDeltaIsInsert = false;
  1121. }
  1122. for (var i = 1; i < deltas.length; i++) {
  1123. delta = deltas[i];
  1124. if (isInsert(delta)) {
  1125. point = delta.start;
  1126. if (range.compare(point.row, point.column) == -1) {
  1127. range.setStart(point);
  1128. }
  1129. point = delta.end;
  1130. if (range.compare(point.row, point.column) == 1) {
  1131. range.setEnd(point);
  1132. }
  1133. lastDeltaIsInsert = true;
  1134. } else {
  1135. point = delta.start;
  1136. if (range.compare(point.row, point.column) == -1) {
  1137. range = Range.fromPoints(delta.start, delta.start);
  1138. }
  1139. lastDeltaIsInsert = false;
  1140. }
  1141. }
  1142. // Check if this range and the last undo range has something in common.
  1143. // If true, merge the ranges.
  1144. if (lastUndoRange != null) {
  1145. if (Range.comparePoints(lastUndoRange.start, range.start) === 0) {
  1146. lastUndoRange.start.column += range.end.column - range.start.column;
  1147. lastUndoRange.end.column += range.end.column - range.start.column;
  1148. }
  1149. var cmp = lastUndoRange.compareRange(range);
  1150. if (cmp == 1) {
  1151. range.setStart(lastUndoRange.start);
  1152. } else if (cmp == -1) {
  1153. range.setEnd(lastUndoRange.end);
  1154. }
  1155. }
  1156. return range;
  1157. };
  1158. /**
  1159. * Replaces a range in the document with the new `text`.
  1160. *
  1161. * @param {Range} range A specified Range to replace
  1162. * @param {String} text The new text to use as a replacement
  1163. * @returns {Object} An object containing the final row and column, like this:
  1164. * ```
  1165. * {row: endRow, column: 0}
  1166. * ```
  1167. * If the text and range are empty, this function returns an object containing the current `range.start` value.
  1168. * If the text is the exact same as what currently exists, this function returns an object containing the current `range.end` value.
  1169. *
  1170. *
  1171. *
  1172. * @related Document.replace
  1173. *
  1174. *
  1175. **/
  1176. this.replace = function(range, text) {
  1177. return this.doc.replace(range, text);
  1178. };
  1179. /**
  1180. * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this:
  1181. * ```json
  1182. * { row: newRowLocation, column: newColumnLocation }
  1183. * ```
  1184. * @param {Range} fromRange The range of text you want moved within the document
  1185. * @param {Object} toPosition The location (row and column) where you want to move the text to
  1186. * @returns {Range} The new range where the text was moved to.
  1187. *
  1188. *
  1189. *
  1190. **/
  1191. this.moveText = function(fromRange, toPosition, copy) {
  1192. var text = this.getTextRange(fromRange);
  1193. var folds = this.getFoldsInRange(fromRange);
  1194. var toRange = Range.fromPoints(toPosition, toPosition);
  1195. if (!copy) {
  1196. this.remove(fromRange);
  1197. var rowDiff = fromRange.start.row - fromRange.end.row;
  1198. var collDiff = rowDiff ? -fromRange.end.column : fromRange.start.column - fromRange.end.column;
  1199. if (collDiff) {
  1200. if (toRange.start.row == fromRange.end.row && toRange.start.column > fromRange.end.column)
  1201. toRange.start.column += collDiff;
  1202. if (toRange.end.row == fromRange.end.row && toRange.end.column > fromRange.end.column)
  1203. toRange.end.column += collDiff;
  1204. }
  1205. if (rowDiff && toRange.start.row >= fromRange.end.row) {
  1206. toRange.start.row += rowDiff;
  1207. toRange.end.row += rowDiff;
  1208. }
  1209. }
  1210. toRange.end = this.insert(toRange.start, text);
  1211. if (folds.length) {
  1212. var oldStart = fromRange.start;
  1213. var newStart = toRange.start;
  1214. var rowDiff = newStart.row - oldStart.row;
  1215. var collDiff = newStart.column - oldStart.column;
  1216. this.addFolds(folds.map(function(x) {
  1217. x = x.clone();
  1218. if (x.start.row == oldStart.row)
  1219. x.start.column += collDiff;
  1220. if (x.end.row == oldStart.row)
  1221. x.end.column += collDiff;
  1222. x.start.row += rowDiff;
  1223. x.end.row += rowDiff;
  1224. return x;
  1225. }));
  1226. }
  1227. return toRange;
  1228. };
  1229. /**
  1230. * Indents all the rows, from `startRow` to `endRow` (inclusive), by prefixing each row with the token in `indentString`.
  1231. *
  1232. * If `indentString` contains the `'\t'` character, it's replaced by whatever is defined by [[EditSession.getTabString `getTabString()`]].
  1233. * @param {Number} startRow Starting row
  1234. * @param {Number} endRow Ending row
  1235. * @param {String} indentString The indent token
  1236. *
  1237. *
  1238. **/
  1239. this.indentRows = function(startRow, endRow, indentString) {
  1240. indentString = indentString.replace(/\t/g, this.getTabString());
  1241. for (var row=startRow; row<=endRow; row++)
  1242. this.doc.insertInLine({row: row, column: 0}, indentString);
  1243. };
  1244. /**
  1245. * Outdents all the rows defined by the `start` and `end` properties of `range`.
  1246. * @param {Range} range A range of rows
  1247. *
  1248. *
  1249. **/
  1250. this.outdentRows = function (range) {
  1251. var rowRange = range.collapseRows();
  1252. var deleteRange = new Range(0, 0, 0, 0);
  1253. var size = this.getTabSize();
  1254. for (var i = rowRange.start.row; i <= rowRange.end.row; ++i) {
  1255. var line = this.getLine(i);
  1256. deleteRange.start.row = i;
  1257. deleteRange.end.row = i;
  1258. for (var j = 0; j < size; ++j)
  1259. if (line.charAt(j) != ' ')
  1260. break;
  1261. if (j < size && line.charAt(j) == '\t') {
  1262. deleteRange.start.column = j;
  1263. deleteRange.end.column = j + 1;
  1264. } else {
  1265. deleteRange.start.column = 0;
  1266. deleteRange.end.column = j;
  1267. }
  1268. this.remove(deleteRange);
  1269. }
  1270. };
  1271. this.$moveLines = function(firstRow, lastRow, dir) {
  1272. firstRow = this.getRowFoldStart(firstRow);
  1273. lastRow = this.getRowFoldEnd(lastRow);
  1274. if (dir < 0) {
  1275. var row = this.getRowFoldStart(firstRow + dir);
  1276. if (row < 0) return 0;
  1277. var diff = row-firstRow;
  1278. } else if (dir > 0) {
  1279. var row = this.getRowFoldEnd(lastRow + dir);
  1280. if (row > this.doc.getLength()-1) return 0;
  1281. var diff = row-lastRow;
  1282. } else {
  1283. firstRow = this.$clipRowToDocument(firstRow);
  1284. lastRow = this.$clipRowToDocument(lastRow);
  1285. var diff = lastRow - firstRow + 1;
  1286. }
  1287. var range = new Range(firstRow, 0, lastRow, Number.MAX_VALUE);
  1288. var folds = this.getFoldsInRange(range).map(function(x){
  1289. x = x.clone();
  1290. x.start.row += diff;
  1291. x.end.row += diff;
  1292. return x;
  1293. });
  1294. var lines = dir == 0
  1295. ? this.doc.getLines(firstRow, lastRow)
  1296. : this.doc.removeFullLines(firstRow, lastRow);
  1297. this.doc.insertFullLines(firstRow+diff, lines);
  1298. folds.length && this.addFolds(folds);
  1299. return diff;
  1300. };
  1301. /**
  1302. * Shifts all the lines in the document up one, starting from `firstRow` and ending at `lastRow`.
  1303. * @param {Number} firstRow The starting row to move up
  1304. * @param {Number} lastRow The final row to move up
  1305. * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
  1306. *
  1307. **/
  1308. this.moveLinesUp = function(firstRow, lastRow) {
  1309. return this.$moveLines(firstRow, lastRow, -1);
  1310. };
  1311. /**
  1312. * Shifts all the lines in the document down one, starting from `firstRow` and ending at `lastRow`.
  1313. * @param {Number} firstRow The starting row to move down
  1314. * @param {Number} lastRow The final row to move down
  1315. * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
  1316. **/
  1317. this.moveLinesDown = function(firstRow, lastRow) {
  1318. return this.$moveLines(firstRow, lastRow, 1);
  1319. };
  1320. /**
  1321. * Duplicates all the text between `firstRow` and `lastRow`.
  1322. * @param {Number} firstRow The starting row to duplicate
  1323. * @param {Number} lastRow The final row to duplicate
  1324. * @returns {Number} Returns the number of new rows added; in other words, `lastRow - firstRow + 1`.
  1325. *
  1326. *
  1327. **/
  1328. this.duplicateLines = function(firstRow, lastRow) {
  1329. return this.$moveLines(firstRow, lastRow, 0);
  1330. };
  1331. this.$clipRowToDocument = function(row) {
  1332. return Math.max(0, Math.min(row, this.doc.getLength()-1));
  1333. };
  1334. this.$clipColumnToRow = function(row, column) {
  1335. if (column < 0)
  1336. return 0;
  1337. return Math.min(this.doc.getLine(row).length, column);
  1338. };
  1339. this.$clipPositionToDocument = function(row, column) {
  1340. column = Math.max(0, column);
  1341. if (row < 0) {
  1342. row = 0;
  1343. column = 0;
  1344. } else {
  1345. var len = this.doc.getLength();
  1346. if (row >= len) {
  1347. row = len - 1;
  1348. column = this.doc.getLine(len-1).length;
  1349. } else {
  1350. column = Math.min(this.doc.getLine(row).length, column);
  1351. }
  1352. }
  1353. return {
  1354. row: row,
  1355. column: column
  1356. };
  1357. };
  1358. this.$clipRangeToDocument = function(range) {
  1359. if (range.start.row < 0) {
  1360. range.start.row = 0;
  1361. range.start.column = 0;
  1362. } else {
  1363. range.start.column = this.$clipColumnToRow(
  1364. range.start.row,
  1365. range.start.column
  1366. );
  1367. }
  1368. var len = this.doc.getLength() - 1;
  1369. if (range.end.row > len) {
  1370. range.end.row = len;
  1371. range.end.column = this.doc.getLine(len).length;
  1372. } else {
  1373. range.end.column = this.$clipColumnToRow(
  1374. range.end.row,
  1375. range.end.column
  1376. );
  1377. }
  1378. return range;
  1379. };
  1380. // WRAPMODE
  1381. this.$wrapLimit = 80;
  1382. this.$useWrapMode = false;
  1383. this.$wrapLimitRange = {
  1384. min : null,
  1385. max : null
  1386. };
  1387. /**
  1388. * Sets whether or not line wrapping is enabled. If `useWrapMode` is different than the current value, the `'changeWrapMode'` event is emitted.
  1389. * @param {Boolean} useWrapMode Enable (or disable) wrap mode
  1390. *
  1391. *
  1392. **/
  1393. this.setUseWrapMode = function(useWrapMode) {
  1394. if (useWrapMode != this.$useWrapMode) {
  1395. this.$useWrapMode = useWrapMode;
  1396. this.$modified = true;
  1397. this.$resetRowCache(0);
  1398. // If wrapMode is activaed, the wrapData array has to be initialized.
  1399. if (useWrapMode) {
  1400. var len = this.getLength();
  1401. this.$wrapData = Array(len);
  1402. this.$updateWrapData(0, len - 1);
  1403. }
  1404. this._signal("changeWrapMode");
  1405. }
  1406. };
  1407. /**
  1408. * Returns `true` if wrap mode is being used; `false` otherwise.
  1409. * @returns {Boolean}
  1410. **/
  1411. this.getUseWrapMode = function() {
  1412. return this.$useWrapMode;
  1413. };
  1414. // Allow the wrap limit to move freely between min and max. Either
  1415. // parameter can be null to allow the wrap limit to be unconstrained
  1416. // in that direction. Or set both parameters to the same number to pin
  1417. // the limit to that value.
  1418. /**
  1419. * Sets the boundaries of wrap. Either value can be `null` to have an unconstrained wrap, or, they can be the same number to pin the limit. If the wrap limits for `min` or `max` are different, this method also emits the `'changeWrapMode'` event.
  1420. * @param {Number} min The minimum wrap value (the left side wrap)
  1421. * @param {Number} max The maximum wrap value (the right side wrap)
  1422. *
  1423. *
  1424. **/
  1425. this.setWrapLimitRange = function(min, max) {
  1426. if (this.$wrapLimitRange.min !== min || this.$wrapLimitRange.max !== max) {
  1427. this.$wrapLimitRange = { min: min, max: max };
  1428. this.$modified = true;
  1429. // This will force a recalculation of the wrap limit
  1430. if (this.$useWrapMode)
  1431. this._signal("changeWrapMode");
  1432. }
  1433. };
  1434. /**
  1435. * This should generally only be called by the renderer when a resize is detected.
  1436. * @param {Number} desiredLimit The new wrap limit
  1437. * @returns {Boolean}
  1438. *
  1439. * @private
  1440. **/
  1441. this.adjustWrapLimit = function(desiredLimit, $printMargin) {
  1442. var limits = this.$wrapLimitRange;
  1443. if (limits.max < 0)
  1444. limits = {min: $printMargin, max: $printMargin};
  1445. var wrapLimit = this.$constrainWrapLimit(desiredLimit, limits.min, limits.max);
  1446. if (wrapLimit != this.$wrapLimit && wrapLimit > 1) {
  1447. this.$wrapLimit = wrapLimit;
  1448. this.$modified = true;
  1449. if (this.$useWrapMode) {
  1450. this.$updateWrapData(0, this.getLength() - 1);
  1451. this.$resetRowCache(0);
  1452. this._signal("changeWrapLimit");
  1453. }
  1454. return true;
  1455. }
  1456. return false;
  1457. };
  1458. this.$constrainWrapLimit = function(wrapLimit, min, max) {
  1459. if (min)
  1460. wrapLimit = Math.max(min, wrapLimit);
  1461. if (max)
  1462. wrapLimit = Math.min(max, wrapLimit);
  1463. return wrapLimit;
  1464. };
  1465. /**
  1466. * Returns the value of wrap limit.
  1467. * @returns {Number} The wrap limit.
  1468. **/
  1469. this.getWrapLimit = function() {
  1470. return this.$wrapLimit;
  1471. };
  1472. /**
  1473. * Sets the line length for soft wrap in the editor. Lines will break
  1474. * at a minimum of the given length minus 20 chars and at a maximum
  1475. * of the given number of chars.
  1476. * @param {number} limit The maximum line length in chars, for soft wrapping lines.
  1477. */
  1478. this.setWrapLimit = function (limit) {
  1479. this.setWrapLimitRange(limit, limit);
  1480. };
  1481. /**
  1482. * Returns an object that defines the minimum and maximum of the wrap limit; it looks something like this:
  1483. *
  1484. * { min: wrapLimitRange_min, max: wrapLimitRange_max }
  1485. *
  1486. * @returns {Object}
  1487. **/
  1488. this.getWrapLimitRange = function() {
  1489. // Avoid unexpected mutation by returning a copy
  1490. return {
  1491. min : this.$wrapLimitRange.min,
  1492. max : this.$wrapLimitRange.max
  1493. };
  1494. };
  1495. this.$updateInternalDataOnChange = function(delta) {
  1496. var useWrapMode = this.$useWrapMode;
  1497. var action = delta.action;
  1498. var start = delta.start;
  1499. var end = delta.end;
  1500. var firstRow = start.row;
  1501. var lastRow = end.row;
  1502. var len = lastRow - firstRow;
  1503. var removedFolds = null;
  1504. this.$updating = true;
  1505. if (len != 0) {
  1506. if (action === "remove") {
  1507. this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len);
  1508. var foldLines = this.$foldData;
  1509. removedFolds = this.getFoldsInRange(delta);
  1510. this.removeFolds(removedFolds);
  1511. var foldLine = this.getFoldLine(end.row);
  1512. var idx = 0;
  1513. if (foldLine) {
  1514. foldLine.addRemoveChars(end.row, end.column, start.column - end.column);
  1515. foldLine.shiftRow(-len);
  1516. var foldLineBefore = this.getFoldLine(firstRow);
  1517. if (foldLineBefore && foldLineBefore !== foldLine) {
  1518. foldLineBefore.merge(foldLine);
  1519. foldLine = foldLineBefore;
  1520. }
  1521. idx = foldLines.indexOf(foldLine) + 1;
  1522. }
  1523. for (idx; idx < foldLines.length; idx++) {
  1524. var foldLine = foldLines[idx];
  1525. if (foldLine.start.row >= end.row) {
  1526. foldLine.shiftRow(-len);
  1527. }
  1528. }
  1529. lastRow = firstRow;
  1530. } else {
  1531. var args = Array(len);
  1532. args.unshift(firstRow, 0);
  1533. var arr = useWrapMode ? this.$wrapData : this.$rowLengthCache
  1534. arr.splice.apply(arr, args);
  1535. // If some new line is added inside of a foldLine, then split
  1536. // the fold line up.
  1537. var foldLines = this.$foldData;
  1538. var foldLine = this.getFoldLine(firstRow);
  1539. var idx = 0;
  1540. if (foldLine) {
  1541. var cmp = foldLine.range.compareInside(start.row, start.column);
  1542. // Inside of the foldLine range. Need to split stuff up.
  1543. if (cmp == 0) {
  1544. foldLine = foldLine.split(start.row, start.column);
  1545. if (foldLine) {
  1546. foldLine.shiftRow(len);
  1547. foldLine.addRemoveChars(lastRow, 0, end.column - start.column);
  1548. }
  1549. } else
  1550. // Infront of the foldLine but same row. Need to shift column.
  1551. if (cmp == -1) {
  1552. foldLine.addRemoveChars(firstRow, 0, end.column - start.column);
  1553. foldLine.shiftRow(len);
  1554. }
  1555. // Nothing to do if the insert is after the foldLine.
  1556. idx = foldLines.indexOf(foldLine) + 1;
  1557. }
  1558. for (idx; idx < foldLines.length; idx++) {
  1559. var foldLine = foldLines[idx];
  1560. if (foldLine.start.row >= firstRow) {
  1561. foldLine.shiftRow(len);
  1562. }
  1563. }
  1564. }
  1565. } else {
  1566. // Realign folds. E.g. if you add some new chars before a fold, the
  1567. // fold should "move" to the right.
  1568. len = Math.abs(delta.start.column - delta.end.column);
  1569. if (action === "remove") {
  1570. // Get all the folds in the change range and remove them.
  1571. removedFolds = this.getFoldsInRange(delta);
  1572. this.removeFolds(removedFolds);
  1573. len = -len;
  1574. }
  1575. var foldLine = this.getFoldLine(firstRow);
  1576. if (foldLine) {
  1577. foldLine.addRemoveChars(firstRow, start.column, len);
  1578. }
  1579. }
  1580. if (useWrapMode && this.$wrapData.length != this.doc.getLength()) {
  1581. console.error("doc.getLength() and $wrapData.length have to be the same!");
  1582. }
  1583. this.$updating = false;
  1584. if (useWrapMode)
  1585. this.$updateWrapData(firstRow, lastRow);
  1586. else
  1587. this.$updateRowLengthCache(firstRow, lastRow);
  1588. return removedFolds;
  1589. };
  1590. this.$updateRowLengthCache = function(firstRow, lastRow, b) {
  1591. this.$rowLengthCache[firstRow] = null;
  1592. this.$rowLengthCache[lastRow] = null;
  1593. };
  1594. this.$updateWrapData = function(firstRow, lastRow) {
  1595. var lines = this.doc.getAllLines();
  1596. var tabSize = this.getTabSize();
  1597. var wrapData = this.$wrapData;
  1598. var wrapLimit = this.$wrapLimit;
  1599. var tokens;
  1600. var foldLine;
  1601. var row = firstRow;
  1602. lastRow = Math.min(lastRow, lines.length - 1);
  1603. while (row <= lastRow) {
  1604. foldLine = this.getFoldLine(row, foldLine);
  1605. if (!foldLine) {
  1606. tokens = this.$getDisplayTokens(lines[row]);
  1607. wrapData[row] = this.$computeWrapSplits(tokens, wrapLimit, tabSize);
  1608. row ++;
  1609. } else {
  1610. tokens = [];
  1611. foldLine.walk(function(placeholder, row, column, lastColumn) {
  1612. var walkTokens;
  1613. if (placeholder != null) {
  1614. walkTokens = this.$getDisplayTokens(
  1615. placeholder, tokens.length);
  1616. walkTokens[0] = PLACEHOLDER_START;
  1617. for (var i = 1; i < walkTokens.length; i++) {
  1618. walkTokens[i] = PLACEHOLDER_BODY;
  1619. }
  1620. } else {
  1621. walkTokens = this.$getDisplayTokens(
  1622. lines[row].substring(lastColumn, column),
  1623. tokens.length);
  1624. }
  1625. tokens = tokens.concat(walkTokens);
  1626. }.bind(this),
  1627. foldLine.end.row,
  1628. lines[foldLine.end.row].length + 1
  1629. );
  1630. wrapData[foldLine.start.row] = this.$computeWrapSplits(tokens, wrapLimit, tabSize);
  1631. row = foldLine.end.row + 1;
  1632. }
  1633. }
  1634. };
  1635. // "Tokens"
  1636. var CHAR = 1,
  1637. CHAR_EXT = 2,
  1638. PLACEHOLDER_START = 3,
  1639. PLACEHOLDER_BODY = 4,
  1640. PUNCTUATION = 9,
  1641. SPACE = 10,
  1642. TAB = 11,
  1643. TAB_SPACE = 12;
  1644. this.$computeWrapSplits = function(tokens, wrapLimit, tabSize) {
  1645. if (tokens.length == 0) {
  1646. return [];
  1647. }
  1648. var splits = [];
  1649. var displayLength = tokens.length;
  1650. var lastSplit = 0, lastDocSplit = 0;
  1651. var isCode = this.$wrapAsCode;
  1652. var indentedSoftWrap = this.$indentedSoftWrap;
  1653. var maxIndent = wrapLimit <= Math.max(2 * tabSize, 8)
  1654. || indentedSoftWrap === false ? 0 : Math.floor(wrapLimit / 2);
  1655. function getWrapIndent() {
  1656. var indentation = 0;
  1657. if (maxIndent === 0)
  1658. return indentation;
  1659. if (indentedSoftWrap) {
  1660. for (var i = 0; i < tokens.length; i++) {
  1661. var token = tokens[i];
  1662. if (token == SPACE)
  1663. indentation += 1;
  1664. else if (token == TAB)
  1665. indentation += tabSize;
  1666. else if (token == TAB_SPACE)
  1667. continue;
  1668. else
  1669. break;
  1670. }
  1671. }
  1672. if (isCode && indentedSoftWrap !== false)
  1673. indentation += tabSize;
  1674. return Math.min(indentation, maxIndent);
  1675. }
  1676. function addSplit(screenPos) {
  1677. var displayed = tokens.slice(lastSplit, screenPos);
  1678. // The document size is the current size - the extra width for tabs
  1679. // and multipleWidth characters.
  1680. var len = displayed.length;
  1681. displayed.join("").
  1682. // Get all the TAB_SPACEs.
  1683. replace(/12/g, function() {
  1684. len -= 1;
  1685. }).
  1686. // Get all the CHAR_EXT/multipleWidth characters.
  1687. replace(/2/g, function() {
  1688. len -= 1;
  1689. });
  1690. if (!splits.length) {
  1691. indent = getWrapIndent();
  1692. splits.indent = indent;
  1693. }
  1694. lastDocSplit += len;
  1695. splits.push(lastDocSplit);
  1696. lastSplit = screenPos;
  1697. }
  1698. var indent = 0;
  1699. while (displayLength - lastSplit > wrapLimit - indent) {
  1700. // This is, where the split should be.
  1701. var split = lastSplit + wrapLimit - indent;
  1702. // If there is a space or tab at this split position, then making
  1703. // a split is simple.
  1704. if (tokens[split - 1] >= SPACE && tokens[split] >= SPACE) {
  1705. /* disabled see https://github.com/ajaxorg/ace/issues/1186
  1706. // Include all following spaces + tabs in this split as well.
  1707. while (tokens[split] >= SPACE) {
  1708. split ++;
  1709. } */
  1710. addSplit(split);
  1711. continue;
  1712. }
  1713. // === ELSE ===
  1714. // Check if split is inside of a placeholder. Placeholder are
  1715. // not splitable. Therefore, seek the beginning of the placeholder
  1716. // and try to place the split beofre the placeholder's start.
  1717. if (tokens[split] == PLACEHOLDER_START || tokens[split] == PLACEHOLDER_BODY) {
  1718. // Seek the start of the placeholder and do the split
  1719. // before the placeholder. By definition there always
  1720. // a PLACEHOLDER_START between split and lastSplit.
  1721. for (split; split != lastSplit - 1; split--) {
  1722. if (tokens[split] == PLACEHOLDER_START) {
  1723. // split++; << No incremental here as we want to
  1724. // have the position before the Placeholder.
  1725. break;
  1726. }
  1727. }
  1728. // If the PLACEHOLDER_START is not the index of the
  1729. // last split, then we can do the split
  1730. if (split > lastSplit) {
  1731. addSplit(split);
  1732. continue;
  1733. }
  1734. // If the PLACEHOLDER_START IS the index of the last
  1735. // split, then we have to place the split after the
  1736. // placeholder. So, let's seek for the end of the placeholder.
  1737. split = lastSplit + wrapLimit;
  1738. for (split; split < tokens.length; split++) {
  1739. if (tokens[split] != PLACEHOLDER_BODY) {
  1740. break;
  1741. }
  1742. }
  1743. // If spilt == tokens.length, then the placeholder is the last
  1744. // thing in the line and adding a new split doesn't make sense.
  1745. if (split == tokens.length) {
  1746. break; // Breaks the while-loop.
  1747. }
  1748. // Finally, add the split...
  1749. addSplit(split);
  1750. continue;
  1751. }
  1752. // === ELSE ===
  1753. // Search for the first non space/tab/placeholder/punctuation token backwards.
  1754. var minSplit = Math.max(split - (wrapLimit -(wrapLimit>>2)), lastSplit - 1);
  1755. while (split > minSplit && tokens[split] < PLACEHOLDER_START) {
  1756. split --;
  1757. }
  1758. if (isCode) {
  1759. while (split > minSplit && tokens[split] < PLACEHOLDER_START) {
  1760. split --;
  1761. }
  1762. while (split > minSplit && tokens[split] == PUNCTUATION) {
  1763. split --;
  1764. }
  1765. } else {
  1766. while (split > minSplit && tokens[split] < SPACE) {
  1767. split --;
  1768. }
  1769. }
  1770. // If we found one, then add the split.
  1771. if (split > minSplit) {
  1772. addSplit(++split);
  1773. continue;
  1774. }
  1775. // === ELSE ===
  1776. split = lastSplit + wrapLimit;
  1777. // The split is inside of a CHAR or CHAR_EXT token and no space
  1778. // around -> force a split.
  1779. if (tokens[split] == CHAR_EXT)
  1780. split--;
  1781. addSplit(split - indent);
  1782. }
  1783. return splits;
  1784. };
  1785. /**
  1786. * Given a string, returns an array of the display characters, including tabs and spaces.
  1787. * @param {String} str The string to check
  1788. * @param {Number} offset The value to start at
  1789. *
  1790. *
  1791. **/
  1792. this.$getDisplayTokens = function(str, offset) {
  1793. var arr = [];
  1794. var tabSize;
  1795. offset = offset || 0;
  1796. for (var i = 0; i < str.length; i++) {
  1797. var c = str.charCodeAt(i);
  1798. // Tab
  1799. if (c == 9) {
  1800. tabSize = this.getScreenTabSize(arr.length + offset);
  1801. arr.push(TAB);
  1802. for (var n = 1; n < tabSize; n++) {
  1803. arr.push(TAB_SPACE);
  1804. }
  1805. }
  1806. // Space
  1807. else if (c == 32) {
  1808. arr.push(SPACE);
  1809. } else if((c > 39 && c < 48) || (c > 57 && c < 64)) {
  1810. arr.push(PUNCTUATION);
  1811. }
  1812. // full width characters
  1813. else if (c >= 0x1100 && isFullWidth(c)) {
  1814. arr.push(CHAR, CHAR_EXT);
  1815. } else {
  1816. arr.push(CHAR);
  1817. }
  1818. }
  1819. return arr;
  1820. };
  1821. /**
  1822. * Calculates the width of the string `str` on the screen while assuming that the string starts at the first column on the screen.
  1823. * @param {String} str The string to calculate the screen width of
  1824. * @param {Number} maxScreenColumn
  1825. * @param {Number} screenColumn
  1826. * @returns {[Number]} Returns an `int[]` array with two elements:<br/>
  1827. * The first position indicates the number of columns for `str` on screen.<br/>
  1828. * The second value contains the position of the document column that this function read until.
  1829. *
  1830. **/
  1831. this.$getStringScreenWidth = function(str, maxScreenColumn, screenColumn) {
  1832. if (maxScreenColumn == 0)
  1833. return [0, 0];
  1834. if (maxScreenColumn == null)
  1835. maxScreenColumn = Infinity;
  1836. screenColumn = screenColumn || 0;
  1837. var c, column;
  1838. for (column = 0; column < str.length; column++) {
  1839. c = str.charCodeAt(column);
  1840. // tab
  1841. if (c == 9) {
  1842. screenColumn += this.getScreenTabSize(screenColumn);
  1843. }
  1844. // full width characters
  1845. else if (c >= 0x1100 && isFullWidth(c)) {
  1846. screenColumn += 2;
  1847. } else {
  1848. screenColumn += 1;
  1849. }
  1850. if (screenColumn > maxScreenColumn) {
  1851. break;
  1852. }
  1853. }
  1854. return [screenColumn, column];
  1855. };
  1856. this.lineWidgets = null;
  1857. /**
  1858. * Returns number of screenrows in a wrapped line.
  1859. * @param {Number} row The row number to check
  1860. *
  1861. * @returns {Number}
  1862. **/
  1863. this.getRowLength = function(row) {
  1864. if (this.lineWidgets)
  1865. var h = this.lineWidgets[row] && this.lineWidgets[row].rowCount || 0;
  1866. else
  1867. h = 0
  1868. if (!this.$useWrapMode || !this.$wrapData[row]) {
  1869. return 1 + h;
  1870. } else {
  1871. return this.$wrapData[row].length + 1 + h;
  1872. }
  1873. };
  1874. this.getRowLineCount = function(row) {
  1875. if (!this.$useWrapMode || !this.$wrapData[row]) {
  1876. return 1;
  1877. } else {
  1878. return this.$wrapData[row].length + 1;
  1879. }
  1880. };
  1881. this.getRowWrapIndent = function(screenRow) {
  1882. if (this.$useWrapMode) {
  1883. var pos = this.screenToDocumentPosition(screenRow, Number.MAX_VALUE);
  1884. var splits = this.$wrapData[pos.row];
  1885. return splits.length && splits[0] < pos.column ? splits.indent : 0;
  1886. } else {
  1887. return 0;
  1888. }
  1889. }
  1890. /**
  1891. * Returns the position (on screen) for the last character in the provided screen row.
  1892. * @param {Number} screenRow The screen row to check
  1893. * @returns {Number}
  1894. *
  1895. * @related EditSession.documentToScreenColumn
  1896. **/
  1897. this.getScreenLastRowColumn = function(screenRow) {
  1898. var pos = this.screenToDocumentPosition(screenRow, Number.MAX_VALUE);
  1899. return this.documentToScreenColumn(pos.row, pos.column);
  1900. };
  1901. /**
  1902. * For the given document row and column, this returns the column position of the last screen row.
  1903. * @param {Number} docRow
  1904. *
  1905. * @param {Number} docColumn
  1906. **/
  1907. this.getDocumentLastRowColumn = function(docRow, docColumn) {
  1908. var screenRow = this.documentToScreenRow(docRow, docColumn);
  1909. return this.getScreenLastRowColumn(screenRow);
  1910. };
  1911. /**
  1912. * For the given document row and column, this returns the document position of the last row.
  1913. * @param {Number} docRow
  1914. * @param {Number} docColumn
  1915. *
  1916. *
  1917. **/
  1918. this.getDocumentLastRowColumnPosition = function(docRow, docColumn) {
  1919. var screenRow = this.documentToScreenRow(docRow, docColumn);
  1920. return this.screenToDocumentPosition(screenRow, Number.MAX_VALUE / 10);
  1921. };
  1922. /**
  1923. * For the given row, this returns the split data.
  1924. * @returns {String}
  1925. **/
  1926. this.getRowSplitData = function(row) {
  1927. if (!this.$useWrapMode) {
  1928. return undefined;
  1929. } else {
  1930. return this.$wrapData[row];
  1931. }
  1932. };
  1933. /**
  1934. * The distance to the next tab stop at the specified screen column.
  1935. * @param {Number} screenColumn The screen column to check
  1936. *
  1937. *
  1938. * @returns {Number}
  1939. **/
  1940. this.getScreenTabSize = function(screenColumn) {
  1941. return this.$tabSize - screenColumn % this.$tabSize;
  1942. };
  1943. this.screenToDocumentRow = function(screenRow, screenColumn) {
  1944. return this.screenToDocumentPosition(screenRow, screenColumn).row;
  1945. };
  1946. this.screenToDocumentColumn = function(screenRow, screenColumn) {
  1947. return this.screenToDocumentPosition(screenRow, screenColumn).column;
  1948. };
  1949. /**
  1950. * Converts characters coordinates on the screen to characters coordinates within the document. [This takes into account code folding, word wrap, tab size, and any other visual modifications.]{: #conversionConsiderations}
  1951. * @param {Number} screenRow The screen row to check
  1952. * @param {Number} screenColumn The screen column to check
  1953. * @returns {Object} The object returned has two properties: `row` and `column`.
  1954. *
  1955. *
  1956. * @related EditSession.documentToScreenPosition
  1957. *
  1958. **/
  1959. this.screenToDocumentPosition = function(screenRow, screenColumn) {
  1960. if (screenRow < 0)
  1961. return {row: 0, column: 0};
  1962. var line;
  1963. var docRow = 0;
  1964. var docColumn = 0;
  1965. var column;
  1966. var row = 0;
  1967. var rowLength = 0;
  1968. var rowCache = this.$screenRowCache;
  1969. var i = this.$getRowCacheIndex(rowCache, screenRow);
  1970. var l = rowCache.length;
  1971. if (l && i >= 0) {
  1972. var row = rowCache[i];
  1973. var docRow = this.$docRowCache[i];
  1974. var doCache = screenRow > rowCache[l - 1];
  1975. } else {
  1976. var doCache = !l;
  1977. }
  1978. var maxRow = this.getLength() - 1;
  1979. var foldLine = this.getNextFoldLine(docRow);
  1980. var foldStart = foldLine ? foldLine.start.row : Infinity;
  1981. while (row <= screenRow) {
  1982. rowLength = this.getRowLength(docRow);
  1983. if (row + rowLength > screenRow || docRow >= maxRow) {
  1984. break;
  1985. } else {
  1986. row += rowLength;
  1987. docRow++;
  1988. if (docRow > foldStart) {
  1989. docRow = foldLine.end.row+1;
  1990. foldLine = this.getNextFoldLine(docRow, foldLine);
  1991. foldStart = foldLine ? foldLine.start.row : Infinity;
  1992. }
  1993. }
  1994. if (doCache) {
  1995. this.$docRowCache.push(docRow);
  1996. this.$screenRowCache.push(row);
  1997. }
  1998. }
  1999. if (foldLine && foldLine.start.row <= docRow) {
  2000. line = this.getFoldDisplayLine(foldLine);
  2001. docRow = foldLine.start.row;
  2002. } else if (row + rowLength <= screenRow || docRow > maxRow) {
  2003. // clip at the end of the document
  2004. return {
  2005. row: maxRow,
  2006. column: this.getLine(maxRow).length
  2007. };
  2008. } else {
  2009. line = this.getLine(docRow);
  2010. foldLine = null;
  2011. }
  2012. var wrapIndent = 0;
  2013. if (this.$useWrapMode) {
  2014. var splits = this.$wrapData[docRow];
  2015. if (splits) {
  2016. var splitIndex = Math.floor(screenRow - row);
  2017. column = splits[splitIndex];
  2018. if(splitIndex > 0 && splits.length) {
  2019. wrapIndent = splits.indent;
  2020. docColumn = splits[splitIndex - 1] || splits[splits.length - 1];
  2021. line = line.substring(docColumn);
  2022. }
  2023. }
  2024. }
  2025. docColumn += this.$getStringScreenWidth(line, screenColumn - wrapIndent)[1];
  2026. // We remove one character at the end so that the docColumn
  2027. // position returned is not associated to the next row on the screen.
  2028. if (this.$useWrapMode && docColumn >= column)
  2029. docColumn = column - 1;
  2030. if (foldLine)
  2031. return foldLine.idxToPosition(docColumn);
  2032. return {row: docRow, column: docColumn};
  2033. };
  2034. /**
  2035. * Converts document coordinates to screen coordinates. {:conversionConsiderations}
  2036. * @param {Number} docRow The document row to check
  2037. * @param {Number} docColumn The document column to check
  2038. * @returns {Object} The object returned by this method has two properties: `row` and `column`.
  2039. *
  2040. *
  2041. * @related EditSession.screenToDocumentPosition
  2042. *
  2043. **/
  2044. this.documentToScreenPosition = function(docRow, docColumn) {
  2045. // Normalize the passed in arguments.
  2046. if (typeof docColumn === "undefined")
  2047. var pos = this.$clipPositionToDocument(docRow.row, docRow.column);
  2048. else
  2049. pos = this.$clipPositionToDocument(docRow, docColumn);
  2050. docRow = pos.row;
  2051. docColumn = pos.column;
  2052. var screenRow = 0;
  2053. var foldStartRow = null;
  2054. var fold = null;
  2055. // Clamp the docRow position in case it's inside of a folded block.
  2056. fold = this.getFoldAt(docRow, docColumn, 1);
  2057. if (fold) {
  2058. docRow = fold.start.row;
  2059. docColumn = fold.start.column;
  2060. }
  2061. var rowEnd, row = 0;
  2062. var rowCache = this.$docRowCache;
  2063. var i = this.$getRowCacheIndex(rowCache, docRow);
  2064. var l = rowCache.length;
  2065. if (l && i >= 0) {
  2066. var row = rowCache[i];
  2067. var screenRow = this.$screenRowCache[i];
  2068. var doCache = docRow > rowCache[l - 1];
  2069. } else {
  2070. var doCache = !l;
  2071. }
  2072. var foldLine = this.getNextFoldLine(row);
  2073. var foldStart = foldLine ?foldLine.start.row :Infinity;
  2074. while (row < docRow) {
  2075. if (row >= foldStart) {
  2076. rowEnd = foldLine.end.row + 1;
  2077. if (rowEnd > docRow)
  2078. break;
  2079. foldLine = this.getNextFoldLine(rowEnd, foldLine);
  2080. foldStart = foldLine ?foldLine.start.row :Infinity;
  2081. }
  2082. else {
  2083. rowEnd = row + 1;
  2084. }
  2085. screenRow += this.getRowLength(row);
  2086. row = rowEnd;
  2087. if (doCache) {
  2088. this.$docRowCache.push(row);
  2089. this.$screenRowCache.push(screenRow);
  2090. }
  2091. }
  2092. // Calculate the text line that is displayed in docRow on the screen.
  2093. var textLine = "";
  2094. // Check if the final row we want to reach is inside of a fold.
  2095. if (foldLine && row >= foldStart) {
  2096. textLine = this.getFoldDisplayLine(foldLine, docRow, docColumn);
  2097. foldStartRow = foldLine.start.row;
  2098. } else {
  2099. textLine = this.getLine(docRow).substring(0, docColumn);
  2100. foldStartRow = docRow;
  2101. }
  2102. var wrapIndent = 0;
  2103. // Clamp textLine if in wrapMode.
  2104. if (this.$useWrapMode) {
  2105. var wrapRow = this.$wrapData[foldStartRow];
  2106. if (wrapRow) {
  2107. var screenRowOffset = 0;
  2108. while (textLine.length >= wrapRow[screenRowOffset]) {
  2109. screenRow ++;
  2110. screenRowOffset++;
  2111. }
  2112. textLine = textLine.substring(
  2113. wrapRow[screenRowOffset - 1] || 0, textLine.length
  2114. );
  2115. wrapIndent = screenRowOffset > 0 ? wrapRow.indent : 0;
  2116. }
  2117. }
  2118. return {
  2119. row: screenRow,
  2120. column: wrapIndent + this.$getStringScreenWidth(textLine)[0]
  2121. };
  2122. };
  2123. /**
  2124. * For the given document row and column, returns the screen column.
  2125. * @param {Number} row
  2126. * @param {Number} docColumn
  2127. * @returns {Number}
  2128. *
  2129. **/
  2130. this.documentToScreenColumn = function(row, docColumn) {
  2131. return this.documentToScreenPosition(row, docColumn).column;
  2132. };
  2133. /**
  2134. * For the given document row and column, returns the screen row.
  2135. * @param {Number} docRow
  2136. * @param {Number} docColumn
  2137. *
  2138. *
  2139. **/
  2140. this.documentToScreenRow = function(docRow, docColumn) {
  2141. return this.documentToScreenPosition(docRow, docColumn).row;
  2142. };
  2143. /**
  2144. * Returns the length of the screen.
  2145. * @returns {Number}
  2146. **/
  2147. this.getScreenLength = function() {
  2148. var screenRows = 0;
  2149. var fold = null;
  2150. if (!this.$useWrapMode) {
  2151. screenRows = this.getLength();
  2152. // Remove the folded lines again.
  2153. var foldData = this.$foldData;
  2154. for (var i = 0; i < foldData.length; i++) {
  2155. fold = foldData[i];
  2156. screenRows -= fold.end.row - fold.start.row;
  2157. }
  2158. } else {
  2159. var lastRow = this.$wrapData.length;
  2160. var row = 0, i = 0;
  2161. var fold = this.$foldData[i++];
  2162. var foldStart = fold ? fold.start.row :Infinity;
  2163. while (row < lastRow) {
  2164. var splits = this.$wrapData[row];
  2165. screenRows += splits ? splits.length + 1 : 1;
  2166. row ++;
  2167. if (row > foldStart) {
  2168. row = fold.end.row+1;
  2169. fold = this.$foldData[i++];
  2170. foldStart = fold ?fold.start.row :Infinity;
  2171. }
  2172. }
  2173. }
  2174. // todo
  2175. if (this.lineWidgets)
  2176. screenRows += this.$getWidgetScreenLength();
  2177. return screenRows;
  2178. };
  2179. /**
  2180. * @private
  2181. *
  2182. */
  2183. this.$setFontMetrics = function(fm) {
  2184. // todo
  2185. };
  2186. this.destroy = function() {
  2187. if (this.bgTokenizer) {
  2188. this.bgTokenizer.setDocument(null);
  2189. this.bgTokenizer = null;
  2190. }
  2191. this.$stopWorker();
  2192. };
  2193. // For every keystroke this gets called once per char in the whole doc!!
  2194. // Wouldn't hurt to make it a bit faster for c >= 0x1100
  2195. function isFullWidth(c) {
  2196. if (c < 0x1100)
  2197. return false;
  2198. return c >= 0x1100 && c <= 0x115F ||
  2199. c >= 0x11A3 && c <= 0x11A7 ||
  2200. c >= 0x11FA && c <= 0x11FF ||
  2201. c >= 0x2329 && c <= 0x232A ||
  2202. c >= 0x2E80 && c <= 0x2E99 ||
  2203. c >= 0x2E9B && c <= 0x2EF3 ||
  2204. c >= 0x2F00 && c <= 0x2FD5 ||
  2205. c >= 0x2FF0 && c <= 0x2FFB ||
  2206. c >= 0x3000 && c <= 0x303E ||
  2207. c >= 0x3041 && c <= 0x3096 ||
  2208. c >= 0x3099 && c <= 0x30FF ||
  2209. c >= 0x3105 && c <= 0x312D ||
  2210. c >= 0x3131 && c <= 0x318E ||
  2211. c >= 0x3190 && c <= 0x31BA ||
  2212. c >= 0x31C0 && c <= 0x31E3 ||
  2213. c >= 0x31F0 && c <= 0x321E ||
  2214. c >= 0x3220 && c <= 0x3247 ||
  2215. c >= 0x3250 && c <= 0x32FE ||
  2216. c >= 0x3300 && c <= 0x4DBF ||
  2217. c >= 0x4E00 && c <= 0xA48C ||
  2218. c >= 0xA490 && c <= 0xA4C6 ||
  2219. c >= 0xA960 && c <= 0xA97C ||
  2220. c >= 0xAC00 && c <= 0xD7A3 ||
  2221. c >= 0xD7B0 && c <= 0xD7C6 ||
  2222. c >= 0xD7CB && c <= 0xD7FB ||
  2223. c >= 0xF900 && c <= 0xFAFF ||
  2224. c >= 0xFE10 && c <= 0xFE19 ||
  2225. c >= 0xFE30 && c <= 0xFE52 ||
  2226. c >= 0xFE54 && c <= 0xFE66 ||
  2227. c >= 0xFE68 && c <= 0xFE6B ||
  2228. c >= 0xFF01 && c <= 0xFF60 ||
  2229. c >= 0xFFE0 && c <= 0xFFE6;
  2230. };
  2231. }).call(EditSession.prototype);
  2232. require("./edit_session/folding").Folding.call(EditSession.prototype);
  2233. require("./edit_session/bracket_match").BracketMatch.call(EditSession.prototype);
  2234. config.defineOptions(EditSession.prototype, "session", {
  2235. wrap: {
  2236. set: function(value) {
  2237. if (!value || value == "off")
  2238. value = false;
  2239. else if (value == "free")
  2240. value = true;
  2241. else if (value == "printMargin")
  2242. value = -1;
  2243. else if (typeof value == "string")
  2244. value = parseInt(value, 10) || false;
  2245. if (this.$wrap == value)
  2246. return;
  2247. this.$wrap = value;
  2248. if (!value) {
  2249. this.setUseWrapMode(false);
  2250. } else {
  2251. var col = typeof value == "number" ? value : null;
  2252. this.setWrapLimitRange(col, col);
  2253. this.setUseWrapMode(true);
  2254. }
  2255. },
  2256. get: function() {
  2257. if (this.getUseWrapMode()) {
  2258. if (this.$wrap == -1)
  2259. return "printMargin";
  2260. if (!this.getWrapLimitRange().min)
  2261. return "free";
  2262. return this.$wrap;
  2263. }
  2264. return "off";
  2265. },
  2266. handlesSet: true
  2267. },
  2268. wrapMethod: {
  2269. // code|text|auto
  2270. set: function(val) {
  2271. val = val == "auto"
  2272. ? this.$mode.type != "text"
  2273. : val != "text";
  2274. if (val != this.$wrapAsCode) {
  2275. this.$wrapAsCode = val;
  2276. if (this.$useWrapMode) {
  2277. this.$modified = true;
  2278. this.$resetRowCache(0);
  2279. this.$updateWrapData(0, this.getLength() - 1);
  2280. }
  2281. }
  2282. },
  2283. initialValue: "auto"
  2284. },
  2285. indentedSoftWrap: { initialValue: true },
  2286. firstLineNumber: {
  2287. set: function() {this._signal("changeBreakpoint");},
  2288. initialValue: 1
  2289. },
  2290. useWorker: {
  2291. set: function(useWorker) {
  2292. this.$useWorker = useWorker;
  2293. this.$stopWorker();
  2294. if (useWorker)
  2295. this.$startWorker();
  2296. },
  2297. initialValue: true
  2298. },
  2299. useSoftTabs: {initialValue: true},
  2300. tabSize: {
  2301. set: function(tabSize) {
  2302. if (isNaN(tabSize) || this.$tabSize === tabSize) return;
  2303. this.$modified = true;
  2304. this.$rowLengthCache = [];
  2305. this.$tabSize = tabSize;
  2306. this._signal("changeTabSize");
  2307. },
  2308. initialValue: 4,
  2309. handlesSet: true
  2310. },
  2311. overwrite: {
  2312. set: function(val) {this._signal("changeOverwrite");},
  2313. initialValue: false
  2314. },
  2315. newLineMode: {
  2316. set: function(val) {this.doc.setNewLineMode(val)},
  2317. get: function() {return this.doc.getNewLineMode()},
  2318. handlesSet: true
  2319. },
  2320. mode: {
  2321. set: function(val) { this.setMode(val) },
  2322. get: function() { return this.$modeId }
  2323. }
  2324. });
  2325. exports.EditSession = EditSession;
  2326. });