edit_session.js 81 KB

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