virtual_renderer.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695
  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 dom = require("./lib/dom");
  34. var useragent = require("./lib/useragent");
  35. var config = require("./config");
  36. var GutterLayer = require("./layer/gutter").Gutter;
  37. var MarkerLayer = require("./layer/marker").Marker;
  38. var TextLayer = require("./layer/text").Text;
  39. var CursorLayer = require("./layer/cursor").Cursor;
  40. var ScrollBarH = require("./scrollbar").ScrollBarH;
  41. var ScrollBarV = require("./scrollbar").ScrollBarV;
  42. var RenderLoop = require("./renderloop").RenderLoop;
  43. var EventEmitter = require("./lib/event_emitter").EventEmitter;
  44. var editorCss = require("./requirejs/text!./css/editor.css");
  45. dom.importCssString(editorCss, "ace_editor");
  46. /**
  47. * The class that is responsible for drawing everything you see on the screen!
  48. * @class VirtualRenderer
  49. **/
  50. /**
  51. * Constructs a new `VirtualRenderer` within the `container` specified, applying the given `theme`.
  52. * @param {DOMElement} container The root element of the editor
  53. * @param {String} theme The starting theme
  54. *
  55. * @constructor
  56. **/
  57. var VirtualRenderer = function(container, theme) {
  58. var _self = this;
  59. this.container = container || dom.createElement("div");
  60. // TODO: this breaks rendering in Cloud9 with multiple ace instances
  61. // // Imports CSS once per DOM document ('ace_editor' serves as an identifier).
  62. // dom.importCssString(editorCss, "ace_editor", container.ownerDocument);
  63. // in IE <= 9 the native cursor always shines through
  64. this.$keepTextAreaAtCursor = true;
  65. dom.addCssClass(this.container, "ace_editor");
  66. this.setTheme(theme);
  67. this.$gutter = dom.createElement("div");
  68. this.$gutter.className = "ace_gutter";
  69. this.container.appendChild(this.$gutter);
  70. this.scroller = dom.createElement("div");
  71. this.scroller.className = "ace_scroller";
  72. this.container.appendChild(this.scroller);
  73. this.content = dom.createElement("div");
  74. this.content.className = "ace_content";
  75. this.scroller.appendChild(this.content);
  76. this.$gutterLayer = new GutterLayer(this.$gutter);
  77. this.$gutterLayer.on("changeGutterWidth", this.onGutterResize.bind(this));
  78. this.$markerBack = new MarkerLayer(this.content);
  79. var textLayer = this.$textLayer = new TextLayer(this.content);
  80. this.canvas = textLayer.element;
  81. this.$markerFront = new MarkerLayer(this.content);
  82. this.$cursorLayer = new CursorLayer(this.content);
  83. // Indicates whether the horizontal scrollbar is visible
  84. this.$horizScroll = false;
  85. this.$vScroll = false;
  86. this.scrollBar =
  87. this.scrollBarV = new ScrollBarV(this.container, this);
  88. this.scrollBarH = new ScrollBarH(this.container, this);
  89. this.scrollBarV.addEventListener("scroll", function(e) {
  90. if (!_self.$scrollAnimation)
  91. _self.session.setScrollTop(e.data - _self.scrollMargin.top);
  92. });
  93. this.scrollBarH.addEventListener("scroll", function(e) {
  94. if (!_self.$scrollAnimation)
  95. _self.session.setScrollLeft(e.data - _self.scrollMargin.left);
  96. });
  97. this.scrollTop = 0;
  98. this.scrollLeft = 0;
  99. this.cursorPos = {
  100. row : 0,
  101. column : 0
  102. };
  103. this.$textLayer.addEventListener("changeCharacterSize", function() {
  104. _self.updateCharacterSize();
  105. _self.onResize(true);
  106. _self._signal("changeCharacterSize");
  107. });
  108. this.$size = {
  109. width: 0,
  110. height: 0,
  111. scrollerHeight: 0,
  112. scrollerWidth: 0,
  113. $dirty: true
  114. };
  115. this.layerConfig = {
  116. width : 1,
  117. padding : 0,
  118. firstRow : 0,
  119. firstRowScreen: 0,
  120. lastRow : 0,
  121. lineHeight : 0,
  122. characterWidth : 0,
  123. minHeight : 1,
  124. maxHeight : 1,
  125. offset : 0,
  126. height : 1
  127. };
  128. this.scrollMargin = {
  129. left: 0,
  130. right: 0,
  131. top: 0,
  132. bottom: 0,
  133. v: 0,
  134. h: 0
  135. };
  136. this.$loop = new RenderLoop(
  137. this.$renderChanges.bind(this),
  138. this.container.ownerDocument.defaultView
  139. );
  140. this.$loop.schedule(this.CHANGE_FULL);
  141. this.updateCharacterSize();
  142. this.setPadding(4);
  143. config.resetOptions(this);
  144. config._emit("renderer", this);
  145. };
  146. (function() {
  147. this.CHANGE_CURSOR = 1;
  148. this.CHANGE_MARKER = 2;
  149. this.CHANGE_GUTTER = 4;
  150. this.CHANGE_SCROLL = 8;
  151. this.CHANGE_LINES = 16;
  152. this.CHANGE_TEXT = 32;
  153. this.CHANGE_SIZE = 64;
  154. this.CHANGE_MARKER_BACK = 128;
  155. this.CHANGE_MARKER_FRONT = 256;
  156. this.CHANGE_FULL = 512;
  157. this.CHANGE_H_SCROLL = 1024;
  158. // this.$logChanges = function(changes) {
  159. // var a = ""
  160. // if (changes & this.CHANGE_CURSOR) a += " cursor";
  161. // if (changes & this.CHANGE_MARKER) a += " marker";
  162. // if (changes & this.CHANGE_GUTTER) a += " gutter";
  163. // if (changes & this.CHANGE_SCROLL) a += " scroll";
  164. // if (changes & this.CHANGE_LINES) a += " lines";
  165. // if (changes & this.CHANGE_TEXT) a += " text";
  166. // if (changes & this.CHANGE_SIZE) a += " size";
  167. // if (changes & this.CHANGE_MARKER_BACK) a += " marker_back";
  168. // if (changes & this.CHANGE_MARKER_FRONT) a += " marker_front";
  169. // if (changes & this.CHANGE_FULL) a += " full";
  170. // if (changes & this.CHANGE_H_SCROLL) a += " h_scroll";
  171. // console.log(a.trim())
  172. // };
  173. oop.implement(this, EventEmitter);
  174. this.updateCharacterSize = function() {
  175. if (this.$textLayer.allowBoldFonts != this.$allowBoldFonts) {
  176. this.$allowBoldFonts = this.$textLayer.allowBoldFonts;
  177. this.setStyle("ace_nobold", !this.$allowBoldFonts);
  178. }
  179. this.layerConfig.characterWidth =
  180. this.characterWidth = this.$textLayer.getCharacterWidth();
  181. this.layerConfig.lineHeight =
  182. this.lineHeight = this.$textLayer.getLineHeight();
  183. this.$updatePrintMargin();
  184. };
  185. /**
  186. *
  187. * Associates the renderer with an [[EditSession `EditSession`]].
  188. **/
  189. this.setSession = function(session) {
  190. this.session = session;
  191. if (this.scrollMargin.top && session.getScrollTop() <= 0)
  192. session.setScrollTop(-this.scrollMargin.top);
  193. this.$cursorLayer.setSession(session);
  194. this.$markerBack.setSession(session);
  195. this.$markerFront.setSession(session);
  196. this.$gutterLayer.setSession(session);
  197. this.$textLayer.setSession(session);
  198. this.$loop.schedule(this.CHANGE_FULL);
  199. };
  200. /**
  201. * Triggers a partial update of the text, from the range given by the two parameters.
  202. * @param {Number} firstRow The first row to update
  203. * @param {Number} lastRow The last row to update
  204. *
  205. *
  206. **/
  207. this.updateLines = function(firstRow, lastRow) {
  208. if (lastRow === undefined)
  209. lastRow = Infinity;
  210. if (!this.$changedLines) {
  211. this.$changedLines = {
  212. firstRow: firstRow,
  213. lastRow: lastRow
  214. };
  215. }
  216. else {
  217. if (this.$changedLines.firstRow > firstRow)
  218. this.$changedLines.firstRow = firstRow;
  219. if (this.$changedLines.lastRow < lastRow)
  220. this.$changedLines.lastRow = lastRow;
  221. }
  222. if (this.$changedLines.firstRow > this.layerConfig.lastRow ||
  223. this.$changedLines.lastRow < this.layerConfig.firstRow)
  224. return;
  225. this.$loop.schedule(this.CHANGE_LINES);
  226. };
  227. this.onChangeTabSize = function() {
  228. this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER);
  229. this.$textLayer.onChangeTabSize();
  230. };
  231. /**
  232. * Triggers a full update of the text, for all the rows.
  233. **/
  234. this.updateText = function() {
  235. this.$loop.schedule(this.CHANGE_TEXT);
  236. };
  237. /**
  238. * Triggers a full update of all the layers, for all the rows.
  239. * @param {Boolean} force If `true`, forces the changes through
  240. *
  241. *
  242. **/
  243. this.updateFull = function(force) {
  244. if (force)
  245. this.$renderChanges(this.CHANGE_FULL, true);
  246. else
  247. this.$loop.schedule(this.CHANGE_FULL);
  248. };
  249. /**
  250. *
  251. * Updates the font size.
  252. **/
  253. this.updateFontSize = function() {
  254. this.$textLayer.checkForSizeChanges();
  255. };
  256. this.$changes = 0;
  257. this.$updateSizeAsync = function() {
  258. if (this.$loop.pending)
  259. this.$size.$dirty = true;
  260. else
  261. this.onResize();
  262. };
  263. /**
  264. * [Triggers a resize of the editor.]{: #VirtualRenderer.onResize}
  265. * @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed
  266. * @param {Number} gutterWidth The width of the gutter in pixels
  267. * @param {Number} width The width of the editor in pixels
  268. * @param {Number} height The hiehgt of the editor, in pixels
  269. *
  270. *
  271. **/
  272. this.onResize = function(force, gutterWidth, width, height) {
  273. // if (force)
  274. // console.log("force resize requested", width, height)
  275. if (this.resizing > 2)
  276. return;
  277. else if (this.resizing > 0)
  278. this.resizing++;
  279. else
  280. this.resizing = force ? 1 : 0;
  281. // `|| el.scrollHeight` is required for outosizing editors on ie
  282. // where elements with clientHeight = 0 alsoe have clientWidth = 0
  283. var el = this.container;
  284. if (!height)
  285. height = el.clientHeight || el.scrollHeight;
  286. if (!width)
  287. width = el.clientWidth || el.scrollWidth;
  288. var changes = this.$updateCachedSize(force, gutterWidth, width, height);
  289. // console.log("resizing to", width, height, JSON.stringify(this.$size))
  290. // setTimeout(function() {
  291. // console.log("actual size ", this.container.clientWidth, this.container.clientHeight)
  292. // }.bind(this), 500)
  293. if (!this.$size.scrollerHeight || (!width && !height))
  294. return this.resizing = 0;
  295. if (force)
  296. this.$gutterLayer.$padding = null;
  297. if (force)
  298. this.$renderChanges(changes | this.$changes, true);
  299. else
  300. this.$loop.schedule(changes | this.$changes);
  301. if (this.resizing)
  302. this.resizing = 0;
  303. };
  304. this.$updateCachedSize = function(force, gutterWidth, width, height) {
  305. height -= (this.$extraHeight || 0);
  306. var changes = 0;
  307. var size = this.$size;
  308. var oldSize = {
  309. width: size.width,
  310. height: size.height,
  311. scrollerHeight: size.scrollerHeight,
  312. scrollerWidth: size.scrollerWidth
  313. };
  314. if (height && (force || size.height != height)) {
  315. size.height = height;
  316. changes = this.CHANGE_SIZE;
  317. size.scrollerHeight = size.height;
  318. if (this.$horizScroll)
  319. size.scrollerHeight -= this.scrollBarH.getHeight();
  320. // this.scrollBarV.setHeight(size.scrollerHeight);
  321. this.scrollBarV.element.style.bottom = this.scrollBarH.getHeight() + "px";
  322. if (this.session) {
  323. //this.session.setScrollTop(this.getScrollTop());
  324. changes = changes | this.CHANGE_SCROLL;
  325. }
  326. }
  327. if (width && (force || size.width != width)) {
  328. changes = this.CHANGE_SIZE;
  329. size.width = width;
  330. if (gutterWidth == null)
  331. gutterWidth = this.$showGutter ? this.$gutter.offsetWidth : 0;
  332. this.gutterWidth = gutterWidth;
  333. this.scrollBarH.element.style.left =
  334. this.scroller.style.left = gutterWidth + "px";
  335. size.scrollerWidth = Math.max(0, width - gutterWidth - this.scrollBarV.getWidth());
  336. this.scrollBarH.element.style.right =
  337. this.scroller.style.right = this.scrollBarV.getWidth() + "px";
  338. this.scroller.style.bottom = this.scrollBarH.getHeight() + "px";
  339. // this.scrollBarH.element.style.setWidth(size.scrollerWidth);
  340. if (this.session && this.session.getUseWrapMode() && this.adjustWrapLimit() || force)
  341. changes = changes | this.CHANGE_FULL;
  342. }
  343. size.$dirty = !width || !height;
  344. if (changes)
  345. this._signal("resize", oldSize);
  346. return changes;
  347. };
  348. this.onGutterResize = function() {
  349. var gutterWidth = this.$showGutter ? this.$gutter.offsetWidth : 0;
  350. if (gutterWidth != this.gutterWidth)
  351. this.$changes |= this.$updateCachedSize(true, gutterWidth, this.$size.width, this.$size.height);
  352. if (this.session.getUseWrapMode() && this.adjustWrapLimit()) {
  353. this.$loop.schedule(this.CHANGE_FULL);
  354. } else if (this.$size.$dirty) {
  355. this.$loop.schedule(this.CHANGE_FULL);
  356. } else {
  357. this.$computeLayerConfig();
  358. this.$loop.schedule(this.CHANGE_MARKER);
  359. }
  360. };
  361. /**
  362. * Adjusts the wrap limit, which is the number of characters that can fit within the width of the edit area on screen.
  363. **/
  364. this.adjustWrapLimit = function() {
  365. var availableWidth = this.$size.scrollerWidth - this.$padding * 2;
  366. var limit = Math.floor(availableWidth / this.characterWidth);
  367. return this.session.adjustWrapLimit(limit, this.$showPrintMargin && this.$printMarginColumn);
  368. };
  369. /**
  370. * Identifies whether you want to have an animated scroll or not.
  371. * @param {Boolean} shouldAnimate Set to `true` to show animated scrolls
  372. *
  373. **/
  374. this.setAnimatedScroll = function(shouldAnimate){
  375. this.setOption("animatedScroll", shouldAnimate);
  376. };
  377. /**
  378. * Returns whether an animated scroll happens or not.
  379. * @returns {Boolean}
  380. **/
  381. this.getAnimatedScroll = function() {
  382. return this.$animatedScroll;
  383. };
  384. /**
  385. * Identifies whether you want to show invisible characters or not.
  386. * @param {Boolean} showInvisibles Set to `true` to show invisibles
  387. *
  388. **/
  389. this.setShowInvisibles = function(showInvisibles) {
  390. this.setOption("showInvisibles", showInvisibles);
  391. };
  392. /**
  393. * Returns whether invisible characters are being shown or not.
  394. * @returns {Boolean}
  395. **/
  396. this.getShowInvisibles = function() {
  397. return this.getOption("showInvisibles");
  398. };
  399. this.getDisplayIndentGuides = function() {
  400. return this.getOption("displayIndentGuides");
  401. };
  402. this.setDisplayIndentGuides = function(display) {
  403. this.setOption("displayIndentGuides", display);
  404. };
  405. /**
  406. * Identifies whether you want to show the print margin or not.
  407. * @param {Boolean} showPrintMargin Set to `true` to show the print margin
  408. *
  409. **/
  410. this.setShowPrintMargin = function(showPrintMargin) {
  411. this.setOption("showPrintMargin", showPrintMargin);
  412. };
  413. /**
  414. * Returns whether the print margin is being shown or not.
  415. * @returns {Boolean}
  416. **/
  417. this.getShowPrintMargin = function() {
  418. return this.getOption("showPrintMargin");
  419. };
  420. /**
  421. * Identifies whether you want to show the print margin column or not.
  422. * @param {Boolean} showPrintMargin Set to `true` to show the print margin column
  423. *
  424. **/
  425. this.setPrintMarginColumn = function(showPrintMargin) {
  426. this.setOption("printMarginColumn", showPrintMargin);
  427. };
  428. /**
  429. * Returns whether the print margin column is being shown or not.
  430. * @returns {Boolean}
  431. **/
  432. this.getPrintMarginColumn = function() {
  433. return this.getOption("printMarginColumn");
  434. };
  435. /**
  436. * Returns `true` if the gutter is being shown.
  437. * @returns {Boolean}
  438. **/
  439. this.getShowGutter = function(){
  440. return this.getOption("showGutter");
  441. };
  442. /**
  443. * Identifies whether you want to show the gutter or not.
  444. * @param {Boolean} show Set to `true` to show the gutter
  445. *
  446. **/
  447. this.setShowGutter = function(show){
  448. return this.setOption("showGutter", show);
  449. };
  450. this.getFadeFoldWidgets = function(){
  451. return this.getOption("fadeFoldWidgets")
  452. };
  453. this.setFadeFoldWidgets = function(show) {
  454. this.setOption("fadeFoldWidgets", show);
  455. };
  456. this.setHighlightGutterLine = function(shouldHighlight) {
  457. this.setOption("highlightGutterLine", shouldHighlight);
  458. };
  459. this.getHighlightGutterLine = function() {
  460. return this.getOption("highlightGutterLine");
  461. };
  462. this.$updateGutterLineHighlight = function() {
  463. var pos = this.$cursorLayer.$pixelPos;
  464. var height = this.layerConfig.lineHeight;
  465. if (this.session.getUseWrapMode()) {
  466. var cursor = this.session.selection.getCursor();
  467. cursor.column = 0;
  468. pos = this.$cursorLayer.getPixelPosition(cursor, true);
  469. height *= this.session.getRowLength(cursor.row);
  470. }
  471. this.$gutterLineHighlight.style.top = pos.top - this.layerConfig.offset + "px";
  472. this.$gutterLineHighlight.style.height = height + "px";
  473. };
  474. this.$updatePrintMargin = function() {
  475. if (!this.$showPrintMargin && !this.$printMarginEl)
  476. return;
  477. if (!this.$printMarginEl) {
  478. var containerEl = dom.createElement("div");
  479. containerEl.className = "ace_layer ace_print-margin-layer";
  480. this.$printMarginEl = dom.createElement("div");
  481. this.$printMarginEl.className = "ace_print-margin";
  482. containerEl.appendChild(this.$printMarginEl);
  483. this.content.insertBefore(containerEl, this.content.firstChild);
  484. }
  485. var style = this.$printMarginEl.style;
  486. style.left = ((this.characterWidth * this.$printMarginColumn) + this.$padding) + "px";
  487. style.visibility = this.$showPrintMargin ? "visible" : "hidden";
  488. if (this.session && this.session.$wrap == -1)
  489. this.adjustWrapLimit();
  490. };
  491. /**
  492. *
  493. * Returns the root element containing this renderer.
  494. * @returns {DOMElement}
  495. **/
  496. this.getContainerElement = function() {
  497. return this.container;
  498. };
  499. /**
  500. *
  501. * Returns the element that the mouse events are attached to
  502. * @returns {DOMElement}
  503. **/
  504. this.getMouseEventTarget = function() {
  505. return this.content;
  506. };
  507. /**
  508. *
  509. * Returns the element to which the hidden text area is added.
  510. * @returns {DOMElement}
  511. **/
  512. this.getTextAreaContainer = function() {
  513. return this.container;
  514. };
  515. // move text input over the cursor
  516. // this is required for iOS and IME
  517. this.$moveTextAreaToCursor = function() {
  518. if (!this.$keepTextAreaAtCursor)
  519. return;
  520. var config = this.layerConfig;
  521. var posTop = this.$cursorLayer.$pixelPos.top;
  522. var posLeft = this.$cursorLayer.$pixelPos.left;
  523. posTop -= config.offset;
  524. var h = this.lineHeight;
  525. if (posTop < 0 || posTop > config.height - h)
  526. return;
  527. var w = this.characterWidth;
  528. if (this.$composition) {
  529. var val = this.textarea.value.replace(/^\x01+/, "");
  530. w *= (this.session.$getStringScreenWidth(val)[0]+2);
  531. h += 2;
  532. posTop -= 1;
  533. }
  534. posLeft -= this.scrollLeft;
  535. if (posLeft > this.$size.scrollerWidth - w)
  536. posLeft = this.$size.scrollerWidth - w;
  537. posLeft -= this.scrollBar.width;
  538. this.textarea.style.height = h + "px";
  539. this.textarea.style.width = w + "px";
  540. this.textarea.style.right = Math.max(0, this.$size.scrollerWidth - posLeft - w) + "px";
  541. this.textarea.style.bottom = Math.max(0, this.$size.height - posTop - h) + "px";
  542. };
  543. /**
  544. *
  545. * [Returns the index of the first visible row.]{: #VirtualRenderer.getFirstVisibleRow}
  546. * @returns {Number}
  547. **/
  548. this.getFirstVisibleRow = function() {
  549. return this.layerConfig.firstRow;
  550. };
  551. /**
  552. *
  553. * Returns the index of the first fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
  554. * @returns {Number}
  555. **/
  556. this.getFirstFullyVisibleRow = function() {
  557. return this.layerConfig.firstRow + (this.layerConfig.offset === 0 ? 0 : 1);
  558. };
  559. /**
  560. *
  561. * Returns the index of the last fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
  562. * @returns {Number}
  563. **/
  564. this.getLastFullyVisibleRow = function() {
  565. var flint = Math.floor((this.layerConfig.height + this.layerConfig.offset) / this.layerConfig.lineHeight);
  566. return this.layerConfig.firstRow - 1 + flint;
  567. };
  568. /**
  569. *
  570. * [Returns the index of the last visible row.]{: #VirtualRenderer.getLastVisibleRow}
  571. * @returns {Number}
  572. **/
  573. this.getLastVisibleRow = function() {
  574. return this.layerConfig.lastRow;
  575. };
  576. this.$padding = null;
  577. /**
  578. * Sets the padding for all the layers.
  579. * @param {Number} padding A new padding value (in pixels)
  580. *
  581. *
  582. *
  583. **/
  584. this.setPadding = function(padding) {
  585. this.$padding = padding;
  586. this.$textLayer.setPadding(padding);
  587. this.$cursorLayer.setPadding(padding);
  588. this.$markerFront.setPadding(padding);
  589. this.$markerBack.setPadding(padding);
  590. this.$loop.schedule(this.CHANGE_FULL);
  591. this.$updatePrintMargin();
  592. };
  593. this.setScrollMargin = function(top, bottom, left, right) {
  594. var sm = this.scrollMargin;
  595. sm.top = top|0;
  596. sm.bottom = bottom|0;
  597. sm.right = right|0;
  598. sm.left = left|0;
  599. sm.v = sm.top + sm.bottom;
  600. sm.h = sm.left + sm.right;
  601. if (sm.top && this.scrollTop <= 0 && this.session)
  602. this.session.setScrollTop(sm.top);
  603. this.updateFull();
  604. };
  605. /**
  606. * Returns whether the horizontal scrollbar is set to be always visible.
  607. * @returns {Boolean}
  608. **/
  609. this.getHScrollBarAlwaysVisible = function() {
  610. return this.$hScrollBarAlwaysVisible;
  611. };
  612. /**
  613. * Identifies whether you want to show the horizontal scrollbar or not.
  614. * @param {Boolean} alwaysVisible Set to `true` to make the horizontal scroll bar visible
  615. **/
  616. this.setHScrollBarAlwaysVisible = function(alwaysVisible) {
  617. this.setOption("hScrollBarAlwaysVisible", alwaysVisible);
  618. };
  619. /**
  620. * Returns whether the horizontal scrollbar is set to be always visible.
  621. * @returns {Boolean}
  622. **/
  623. this.getVScrollBarAlwaysVisible = function() {
  624. return this.$hScrollBarAlwaysVisible;
  625. };
  626. /**
  627. * Identifies whether you want to show the horizontal scrollbar or not.
  628. * @param {Boolean} alwaysVisible Set to `true` to make the horizontal scroll bar visible
  629. **/
  630. this.setVScrollBarAlwaysVisible = function(alwaysVisible) {
  631. this.setOption("vScrollBarAlwaysVisible", alwaysVisible);
  632. };
  633. this.$updateScrollBarV = function() {
  634. this.scrollBarV.setInnerHeight(this.layerConfig.maxHeight + this.scrollMargin.v);
  635. this.scrollBarV.setScrollTop(this.scrollTop + this.scrollMargin.top);
  636. };
  637. this.$updateScrollBarH = function() {
  638. this.scrollBarH.setInnerWidth(this.layerConfig.width + 2 * this.$padding + this.scrollMargin.h);
  639. this.scrollBarH.setScrollLeft(this.scrollLeft + this.scrollMargin.left);
  640. };
  641. this.$frozen = false;
  642. this.freeze = function() {
  643. this.$frozen = true;
  644. };
  645. this.unfreeze = function() {
  646. this.$frozen = false;
  647. };
  648. this.$renderChanges = function(changes, force) {
  649. if (this.$changes) {
  650. changes |= this.$changes;
  651. this.$changes = 0;
  652. }
  653. if ((!this.session || !this.container.offsetWidth || this.$frozen) || (!changes && !force)) {
  654. this.$changes |= changes;
  655. return;
  656. }
  657. if (this.$size.$dirty) {
  658. this.$changes |= changes;
  659. return this.onResize(true);
  660. }
  661. if (!this.lineHeight) {
  662. this.$textLayer.checkForSizeChanges();
  663. }
  664. // this.$logChanges(changes);
  665. this._signal("beforeRender");
  666. var config = this.layerConfig;
  667. // text, scrolling and resize changes can cause the view port size to change
  668. if (changes & this.CHANGE_FULL ||
  669. changes & this.CHANGE_SIZE ||
  670. changes & this.CHANGE_TEXT ||
  671. changes & this.CHANGE_LINES ||
  672. changes & this.CHANGE_SCROLL ||
  673. changes & this.CHANGE_H_SCROLL
  674. ) {
  675. changes |= this.$computeLayerConfig();
  676. config = this.layerConfig;
  677. // update scrollbar first to not lose scroll position when gutter calls resize
  678. this.$updateScrollBarV();
  679. if (changes & this.CHANGE_H_SCROLL)
  680. this.$updateScrollBarH();
  681. this.$gutterLayer.element.style.marginTop = (-config.offset) + "px";
  682. this.content.style.marginTop = (-config.offset) + "px";
  683. this.content.style.width = config.width + 2 * this.$padding + "px";
  684. this.content.style.height = config.minHeight + "px";
  685. }
  686. // horizontal scrolling
  687. if (changes & this.CHANGE_H_SCROLL) {
  688. this.content.style.marginLeft = -this.scrollLeft + "px";
  689. this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller" : "ace_scroller ace_scroll-left";
  690. }
  691. // full
  692. if (changes & this.CHANGE_FULL) {
  693. this.$textLayer.update(config);
  694. if (this.$showGutter)
  695. this.$gutterLayer.update(config);
  696. this.$markerBack.update(config);
  697. this.$markerFront.update(config);
  698. this.$cursorLayer.update(config);
  699. this.$moveTextAreaToCursor();
  700. this.$highlightGutterLine && this.$updateGutterLineHighlight();
  701. this._signal("afterRender");
  702. return;
  703. }
  704. // scrolling
  705. if (changes & this.CHANGE_SCROLL) {
  706. if (changes & this.CHANGE_TEXT || changes & this.CHANGE_LINES)
  707. this.$textLayer.update(config);
  708. else
  709. this.$textLayer.scrollLines(config);
  710. if (this.$showGutter)
  711. this.$gutterLayer.update(config);
  712. this.$markerBack.update(config);
  713. this.$markerFront.update(config);
  714. this.$cursorLayer.update(config);
  715. this.$highlightGutterLine && this.$updateGutterLineHighlight();
  716. this.$moveTextAreaToCursor();
  717. this._signal("afterRender");
  718. return;
  719. }
  720. if (changes & this.CHANGE_TEXT) {
  721. this.$textLayer.update(config);
  722. if (this.$showGutter)
  723. this.$gutterLayer.update(config);
  724. }
  725. else if (changes & this.CHANGE_LINES) {
  726. if (this.$updateLines() || (changes & this.CHANGE_GUTTER) && this.$showGutter)
  727. this.$gutterLayer.update(config);
  728. }
  729. else if (changes & this.CHANGE_TEXT || changes & this.CHANGE_GUTTER) {
  730. if (this.$showGutter)
  731. this.$gutterLayer.update(config);
  732. }
  733. if (changes & this.CHANGE_CURSOR) {
  734. this.$cursorLayer.update(config);
  735. this.$moveTextAreaToCursor();
  736. this.$highlightGutterLine && this.$updateGutterLineHighlight();
  737. }
  738. if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT)) {
  739. this.$markerFront.update(config);
  740. }
  741. if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK)) {
  742. this.$markerBack.update(config);
  743. }
  744. this._signal("afterRender");
  745. };
  746. this.$autosize = function() {
  747. var height = this.session.getScreenLength() * this.lineHeight;
  748. var maxHeight = this.$maxLines * this.lineHeight;
  749. var desiredHeight = Math.max(
  750. (this.$minLines||1) * this.lineHeight,
  751. Math.min(maxHeight, height)
  752. ) + this.scrollMargin.v + (this.$extraHeight || 0);
  753. var vScroll = height > maxHeight;
  754. if (desiredHeight != this.desiredHeight ||
  755. this.$size.height != this.desiredHeight || vScroll != this.$vScroll) {
  756. if (vScroll != this.$vScroll) {
  757. this.$vScroll = vScroll;
  758. this.scrollBarV.setVisible(vScroll);
  759. }
  760. var w = this.container.clientWidth;
  761. this.container.style.height = desiredHeight + "px";
  762. this.$updateCachedSize(true, this.$gutterWidth, w, desiredHeight);
  763. // this.$loop.changes = 0;
  764. this.desiredHeight = desiredHeight;
  765. }
  766. };
  767. this.$computeLayerConfig = function() {
  768. if (this.$maxLines && this.lineHeight > 1)
  769. this.$autosize();
  770. var session = this.session;
  771. var hideScrollbars = this.$size.height <= 2 * this.lineHeight;
  772. var screenLines = this.session.getScreenLength();
  773. var maxHeight = screenLines * this.lineHeight;
  774. var offset = this.scrollTop % this.lineHeight;
  775. var minHeight = this.$size.scrollerHeight + this.lineHeight;
  776. var longestLine = this.$getLongestLine();
  777. var horizScroll = !hideScrollbars && (this.$hScrollBarAlwaysVisible ||
  778. this.$size.scrollerWidth - longestLine - 2 * this.$padding < 0);
  779. var hScrollChanged = this.$horizScroll !== horizScroll;
  780. if (hScrollChanged) {
  781. this.$horizScroll = horizScroll;
  782. this.scrollBarH.setVisible(horizScroll);
  783. }
  784. if (!this.$maxLines && this.$scrollPastEnd) {
  785. if (this.scrollTop > maxHeight - this.$size.scrollerHeight)
  786. maxHeight += Math.min(
  787. (this.$size.scrollerHeight - this.lineHeight) * this.$scrollPastEnd,
  788. this.scrollTop - maxHeight + this.$size.scrollerHeight
  789. );
  790. }
  791. var vScroll = !hideScrollbars && (this.$vScrollBarAlwaysVisible ||
  792. this.$size.scrollerHeight - maxHeight < 0);
  793. var vScrollChanged = this.$vScroll !== vScroll;
  794. if (vScrollChanged) {
  795. this.$vScroll = vScroll;
  796. this.scrollBarV.setVisible(vScroll);
  797. }
  798. this.session.setScrollTop(Math.max(-this.scrollMargin.top,
  799. Math.min(this.scrollTop, maxHeight - this.$size.scrollerHeight + this.scrollMargin.v)));
  800. this.session.setScrollLeft(Math.max(-this.scrollMargin.left, Math.min(this.scrollLeft,
  801. longestLine + 2 * this.$padding - this.$size.scrollerWidth + this.scrollMargin.h)));
  802. var lineCount = Math.ceil(minHeight / this.lineHeight) - 1;
  803. var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight));
  804. var lastRow = firstRow + lineCount;
  805. // Map lines on the screen to lines in the document.
  806. var firstRowScreen, firstRowHeight;
  807. var lineHeight = this.lineHeight;
  808. firstRow = session.screenToDocumentRow(firstRow, 0);
  809. // Check if firstRow is inside of a foldLine. If true, then use the first
  810. // row of the foldLine.
  811. var foldLine = session.getFoldLine(firstRow);
  812. if (foldLine) {
  813. firstRow = foldLine.start.row;
  814. }
  815. firstRowScreen = session.documentToScreenRow(firstRow, 0);
  816. firstRowHeight = session.getRowLength(firstRow) * lineHeight;
  817. lastRow = Math.min(session.screenToDocumentRow(lastRow, 0), session.getLength() - 1);
  818. minHeight = this.$size.scrollerHeight + session.getRowLength(lastRow) * lineHeight +
  819. firstRowHeight;
  820. offset = this.scrollTop - firstRowScreen * lineHeight;
  821. var changes = 0;
  822. // Horizontal scrollbar visibility may have changed, which changes
  823. // the client height of the scroller
  824. if (hScrollChanged || vScrollChanged) {
  825. changes = this.$updateCachedSize(true, this.gutterWidth, this.$size.width, this.$size.height);
  826. this._signal("scrollbarVisibilityChanged");
  827. if (vScrollChanged)
  828. longestLine = this.$getLongestLine();
  829. }
  830. this.layerConfig = {
  831. width : longestLine,
  832. padding : this.$padding,
  833. firstRow : firstRow,
  834. firstRowScreen: firstRowScreen,
  835. lastRow : lastRow,
  836. lineHeight : lineHeight,
  837. characterWidth : this.characterWidth,
  838. minHeight : minHeight,
  839. maxHeight : maxHeight,
  840. offset : offset,
  841. height : this.$size.scrollerHeight
  842. };
  843. // For debugging.
  844. // console.log(JSON.stringify(this.layerConfig));
  845. return changes;
  846. };
  847. this.$updateLines = function() {
  848. var firstRow = this.$changedLines.firstRow;
  849. var lastRow = this.$changedLines.lastRow;
  850. this.$changedLines = null;
  851. var layerConfig = this.layerConfig;
  852. if (firstRow > layerConfig.lastRow + 1) { return; }
  853. if (lastRow < layerConfig.firstRow) { return; }
  854. // if the last row is unknown -> redraw everything
  855. if (lastRow === Infinity) {
  856. if (this.$showGutter)
  857. this.$gutterLayer.update(layerConfig);
  858. this.$textLayer.update(layerConfig);
  859. return;
  860. }
  861. // else update only the changed rows
  862. this.$textLayer.updateLines(layerConfig, firstRow, lastRow);
  863. return true;
  864. };
  865. this.$getLongestLine = function() {
  866. var charCount = this.session.getScreenWidth();
  867. if (this.showInvisibles && !this.session.$useWrapMode)
  868. charCount += 1;
  869. return Math.max(this.$size.scrollerWidth - 2 * this.$padding, Math.round(charCount * this.characterWidth));
  870. };
  871. /**
  872. *
  873. * Schedules an update to all the front markers in the document.
  874. **/
  875. this.updateFrontMarkers = function() {
  876. this.$markerFront.setMarkers(this.session.getMarkers(true));
  877. this.$loop.schedule(this.CHANGE_MARKER_FRONT);
  878. };
  879. /**
  880. *
  881. * Schedules an update to all the back markers in the document.
  882. **/
  883. this.updateBackMarkers = function() {
  884. this.$markerBack.setMarkers(this.session.getMarkers());
  885. this.$loop.schedule(this.CHANGE_MARKER_BACK);
  886. };
  887. /**
  888. *
  889. * Deprecated; (moved to [[EditSession]])
  890. * @deprecated
  891. **/
  892. this.addGutterDecoration = function(row, className){
  893. this.$gutterLayer.addGutterDecoration(row, className);
  894. };
  895. /**
  896. * Deprecated; (moved to [[EditSession]])
  897. * @deprecated
  898. **/
  899. this.removeGutterDecoration = function(row, className){
  900. this.$gutterLayer.removeGutterDecoration(row, className);
  901. };
  902. /**
  903. *
  904. * Redraw breakpoints.
  905. **/
  906. this.updateBreakpoints = function(rows) {
  907. this.$loop.schedule(this.CHANGE_GUTTER);
  908. };
  909. /**
  910. *
  911. * Sets annotations for the gutter.
  912. * @param {Array} annotations An array containing annotations
  913. *
  914. *
  915. **/
  916. this.setAnnotations = function(annotations) {
  917. this.$gutterLayer.setAnnotations(annotations);
  918. this.$loop.schedule(this.CHANGE_GUTTER);
  919. };
  920. /**
  921. *
  922. * Updates the cursor icon.
  923. **/
  924. this.updateCursor = function() {
  925. this.$loop.schedule(this.CHANGE_CURSOR);
  926. };
  927. /**
  928. *
  929. * Hides the cursor icon.
  930. **/
  931. this.hideCursor = function() {
  932. this.$cursorLayer.hideCursor();
  933. };
  934. /**
  935. *
  936. * Shows the cursor icon.
  937. **/
  938. this.showCursor = function() {
  939. this.$cursorLayer.showCursor();
  940. };
  941. this.scrollSelectionIntoView = function(anchor, lead, offset) {
  942. // first scroll anchor into view then scroll lead into view
  943. this.scrollCursorIntoView(anchor, offset);
  944. this.scrollCursorIntoView(lead, offset);
  945. };
  946. /**
  947. *
  948. * Scrolls the cursor into the first visibile area of the editor
  949. **/
  950. this.scrollCursorIntoView = function(cursor, offset) {
  951. // the editor is not visible
  952. if (this.$size.scrollerHeight === 0)
  953. return;
  954. var pos = this.$cursorLayer.getPixelPosition(cursor);
  955. var left = pos.left;
  956. var top = pos.top;
  957. var scrollTop = this.$scrollAnimation ? this.session.getScrollTop() : this.scrollTop;
  958. if (scrollTop > top) {
  959. if (offset)
  960. top -= offset * this.$size.scrollerHeight;
  961. if (top == 0)
  962. top = - this.scrollMargin.top;
  963. else if (top == 0)
  964. top = + this.scrollMargin.bottom;
  965. this.session.setScrollTop(top);
  966. } else if (scrollTop + this.$size.scrollerHeight < top + this.lineHeight) {
  967. if (offset)
  968. top += offset * this.$size.scrollerHeight;
  969. this.session.setScrollTop(top + this.lineHeight - this.$size.scrollerHeight);
  970. }
  971. var scrollLeft = this.scrollLeft;
  972. if (scrollLeft > left) {
  973. if (left < this.$padding + 2 * this.layerConfig.characterWidth)
  974. left = -this.scrollMargin.left;
  975. this.session.setScrollLeft(left);
  976. } else if (scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) {
  977. this.session.setScrollLeft(Math.round(left + this.characterWidth - this.$size.scrollerWidth));
  978. } else if (scrollLeft <= this.$padding && left - scrollLeft < this.characterWidth) {
  979. this.session.setScrollLeft(0);
  980. }
  981. };
  982. /**
  983. * {:EditSession.getScrollTop}
  984. * @related EditSession.getScrollTop
  985. * @returns {Number}
  986. **/
  987. this.getScrollTop = function() {
  988. return this.session.getScrollTop();
  989. };
  990. /**
  991. * {:EditSession.getScrollLeft}
  992. * @related EditSession.getScrollLeft
  993. * @returns {Number}
  994. **/
  995. this.getScrollLeft = function() {
  996. return this.session.getScrollLeft();
  997. };
  998. /**
  999. *
  1000. * Returns the first visible row, regardless of whether it's fully visible or not.
  1001. * @returns {Number}
  1002. **/
  1003. this.getScrollTopRow = function() {
  1004. return this.scrollTop / this.lineHeight;
  1005. };
  1006. /**
  1007. *
  1008. * Returns the last visible row, regardless of whether it's fully visible or not.
  1009. * @returns {Number}
  1010. **/
  1011. this.getScrollBottomRow = function() {
  1012. return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1);
  1013. };
  1014. /**
  1015. * Gracefully scrolls from the top of the editor to the row indicated.
  1016. * @param {Number} row A row id
  1017. *
  1018. *
  1019. * @related EditSession.setScrollTop
  1020. **/
  1021. this.scrollToRow = function(row) {
  1022. this.session.setScrollTop(row * this.lineHeight);
  1023. };
  1024. this.alignCursor = function(cursor, alignment) {
  1025. if (typeof cursor == "number")
  1026. cursor = {row: cursor, column: 0};
  1027. var pos = this.$cursorLayer.getPixelPosition(cursor);
  1028. var h = this.$size.scrollerHeight - this.lineHeight;
  1029. var offset = pos.top - h * (alignment || 0);
  1030. this.session.setScrollTop(offset);
  1031. return offset;
  1032. };
  1033. this.STEPS = 8;
  1034. this.$calcSteps = function(fromValue, toValue){
  1035. var i = 0;
  1036. var l = this.STEPS;
  1037. var steps = [];
  1038. var func = function(t, x_min, dx) {
  1039. return dx * (Math.pow(t - 1, 3) + 1) + x_min;
  1040. };
  1041. for (i = 0; i < l; ++i)
  1042. steps.push(func(i / this.STEPS, fromValue, toValue - fromValue));
  1043. return steps;
  1044. };
  1045. /**
  1046. * Gracefully scrolls the editor to the row indicated.
  1047. * @param {Number} line A line number
  1048. * @param {Boolean} center If `true`, centers the editor the to indicated line
  1049. * @param {Boolean} animate If `true` animates scrolling
  1050. * @param {Function} callback Function to be called after the animation has finished
  1051. *
  1052. *
  1053. **/
  1054. this.scrollToLine = function(line, center, animate, callback) {
  1055. var pos = this.$cursorLayer.getPixelPosition({row: line, column: 0});
  1056. var offset = pos.top;
  1057. if (center)
  1058. offset -= this.$size.scrollerHeight / 2;
  1059. var initialScroll = this.scrollTop;
  1060. this.session.setScrollTop(offset);
  1061. if (animate !== false)
  1062. this.animateScrolling(initialScroll, callback);
  1063. };
  1064. this.animateScrolling = function(fromValue, callback) {
  1065. var toValue = this.scrollTop;
  1066. if (!this.$animatedScroll)
  1067. return;
  1068. var _self = this;
  1069. if (fromValue == toValue)
  1070. return;
  1071. if (this.$scrollAnimation) {
  1072. var oldSteps = this.$scrollAnimation.steps;
  1073. if (oldSteps.length) {
  1074. fromValue = oldSteps[0];
  1075. if (fromValue == toValue)
  1076. return;
  1077. }
  1078. }
  1079. var steps = _self.$calcSteps(fromValue, toValue);
  1080. this.$scrollAnimation = {from: fromValue, to: toValue, steps: steps};
  1081. clearInterval(this.$timer);
  1082. _self.session.setScrollTop(steps.shift());
  1083. this.$timer = setInterval(function() {
  1084. if (steps.length) {
  1085. _self.session.setScrollTop(steps.shift());
  1086. // trick session to think it's already scrolled to not loose toValue
  1087. _self.session.$scrollTop = toValue;
  1088. } else if (toValue != null) {
  1089. _self.session.$scrollTop = -1;
  1090. _self.session.setScrollTop(toValue);
  1091. toValue = null;
  1092. } else {
  1093. // do this on separate step to not get spurious scroll event from scrollbar
  1094. _self.$timer = clearInterval(_self.$timer);
  1095. _self.$scrollAnimation = null;
  1096. callback && callback();
  1097. }
  1098. }, 10);
  1099. };
  1100. /**
  1101. * Scrolls the editor to the y pixel indicated.
  1102. * @param {Number} scrollTop The position to scroll to
  1103. *
  1104. *
  1105. * @returns {Number}
  1106. **/
  1107. this.scrollToY = function(scrollTop) {
  1108. // after calling scrollBar.setScrollTop
  1109. // scrollbar sends us event with same scrollTop. ignore it
  1110. if (this.scrollTop !== scrollTop) {
  1111. this.$loop.schedule(this.CHANGE_SCROLL);
  1112. this.scrollTop = scrollTop;
  1113. }
  1114. };
  1115. /**
  1116. * Scrolls the editor across the x-axis to the pixel indicated.
  1117. * @param {Number} scrollLeft The position to scroll to
  1118. *
  1119. *
  1120. * @returns {Number}
  1121. **/
  1122. this.scrollToX = function(scrollLeft) {
  1123. if (this.scrollLeft !== scrollLeft)
  1124. this.scrollLeft = scrollLeft;
  1125. this.$loop.schedule(this.CHANGE_H_SCROLL);
  1126. };
  1127. /**
  1128. * Scrolls the editor across both x- and y-axes.
  1129. * @param {Number} x The x value to scroll to
  1130. * @param {Number} y The y value to scroll to
  1131. **/
  1132. this.scrollTo = function(x, y) {
  1133. this.session.setScrollTop(y);
  1134. this.session.setScrollLeft(y);
  1135. };
  1136. /**
  1137. * Scrolls the editor across both x- and y-axes.
  1138. * @param {Number} deltaX The x value to scroll by
  1139. * @param {Number} deltaY The y value to scroll by
  1140. **/
  1141. this.scrollBy = function(deltaX, deltaY) {
  1142. deltaY && this.session.setScrollTop(this.session.getScrollTop() + deltaY);
  1143. deltaX && this.session.setScrollLeft(this.session.getScrollLeft() + deltaX);
  1144. };
  1145. /**
  1146. * Returns `true` if you can still scroll by either parameter; in other words, you haven't reached the end of the file or line.
  1147. * @param {Number} deltaX The x value to scroll by
  1148. * @param {Number} deltaY The y value to scroll by
  1149. *
  1150. *
  1151. * @returns {Boolean}
  1152. **/
  1153. this.isScrollableBy = function(deltaX, deltaY) {
  1154. if (deltaY < 0 && this.session.getScrollTop() >= 1 - this.scrollMargin.top)
  1155. return true;
  1156. if (deltaY > 0 && this.session.getScrollTop() + this.$size.scrollerHeight
  1157. - this.layerConfig.maxHeight - (this.$size.scrollerHeight - this.lineHeight) * this.$scrollPastEnd
  1158. < -1 + this.scrollMargin.bottom)
  1159. return true;
  1160. if (deltaX < 0 && this.session.getScrollLeft() >= 1 - this.scrollMargin.left)
  1161. return true;
  1162. if (deltaX > 0 && this.session.getScrollLeft() + this.$size.scrollerWidth
  1163. - this.layerConfig.width < -1 + this.scrollMargin.right)
  1164. return true;
  1165. };
  1166. this.pixelToScreenCoordinates = function(x, y) {
  1167. var canvasPos = this.scroller.getBoundingClientRect();
  1168. var offset = (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth;
  1169. var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight);
  1170. var col = Math.round(offset);
  1171. return {row: row, column: col, side: offset - col > 0 ? 1 : -1};
  1172. };
  1173. this.screenToTextCoordinates = function(x, y) {
  1174. var canvasPos = this.scroller.getBoundingClientRect();
  1175. var col = Math.round(
  1176. (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth
  1177. );
  1178. var row = (y + this.scrollTop - canvasPos.top) / this.lineHeight;
  1179. return this.session.screenToDocumentPosition(row, Math.max(col, 0));
  1180. };
  1181. /**
  1182. * Returns an object containing the `pageX` and `pageY` coordinates of the document position.
  1183. * @param {Number} row The document row position
  1184. * @param {Number} column The document column position
  1185. *
  1186. *
  1187. *
  1188. * @returns {Object}
  1189. **/
  1190. this.textToScreenCoordinates = function(row, column) {
  1191. var canvasPos = this.scroller.getBoundingClientRect();
  1192. var pos = this.session.documentToScreenPosition(row, column);
  1193. var x = this.$padding + Math.round(pos.column * this.characterWidth);
  1194. var y = pos.row * this.lineHeight;
  1195. return {
  1196. pageX: canvasPos.left + x - this.scrollLeft,
  1197. pageY: canvasPos.top + y - this.scrollTop
  1198. };
  1199. };
  1200. /**
  1201. *
  1202. * Focuses the current container.
  1203. **/
  1204. this.visualizeFocus = function() {
  1205. dom.addCssClass(this.container, "ace_focus");
  1206. };
  1207. /**
  1208. *
  1209. * Blurs the current container.
  1210. **/
  1211. this.visualizeBlur = function() {
  1212. dom.removeCssClass(this.container, "ace_focus");
  1213. };
  1214. /**
  1215. * @param {Number} position
  1216. *
  1217. * @private
  1218. **/
  1219. this.showComposition = function(position) {
  1220. if (!this.$composition)
  1221. this.$composition = {
  1222. keepTextAreaAtCursor: this.$keepTextAreaAtCursor,
  1223. cssText: this.textarea.style.cssText
  1224. };
  1225. this.$keepTextAreaAtCursor = true;
  1226. dom.addCssClass(this.textarea, "ace_composition");
  1227. this.textarea.style.cssText = "";
  1228. this.$moveTextAreaToCursor();
  1229. };
  1230. /**
  1231. * @param {String} text A string of text to use
  1232. *
  1233. * Sets the inner text of the current composition to `text`.
  1234. **/
  1235. this.setCompositionText = function(text) {
  1236. this.$moveTextAreaToCursor();
  1237. };
  1238. /**
  1239. *
  1240. * Hides the current composition.
  1241. **/
  1242. this.hideComposition = function() {
  1243. if (!this.$composition)
  1244. return;
  1245. dom.removeCssClass(this.textarea, "ace_composition");
  1246. this.$keepTextAreaAtCursor = this.$composition.keepTextAreaAtCursor;
  1247. this.textarea.style.cssText = this.$composition.cssText;
  1248. this.$composition = null;
  1249. };
  1250. /**
  1251. * [Sets a new theme for the editor. `theme` should exist, and be a directory path, like `ace/theme/textmate`.]{: #VirtualRenderer.setTheme}
  1252. * @param {String} theme The path to a theme
  1253. * @param {Function} cb optional callback
  1254. *
  1255. **/
  1256. this.setTheme = function(theme, cb) {
  1257. var _self = this;
  1258. this.$themeValue = theme;
  1259. _self._dispatchEvent('themeChange',{theme:theme});
  1260. if (!theme || typeof theme == "string") {
  1261. var moduleName = theme || "ace/theme/textmate";
  1262. config.loadModule(["theme", moduleName], afterLoad);
  1263. } else {
  1264. afterLoad(theme);
  1265. }
  1266. function afterLoad(module) {
  1267. if (_self.$themeValue != theme)
  1268. return cb && cb();
  1269. if (!module.cssClass)
  1270. return;
  1271. dom.importCssString(
  1272. module.cssText,
  1273. module.cssClass,
  1274. _self.container.ownerDocument
  1275. );
  1276. if (_self.theme)
  1277. dom.removeCssClass(_self.container, _self.theme.cssClass);
  1278. // this is kept only for backwards compatibility
  1279. _self.$theme = module.cssClass;
  1280. _self.theme = module;
  1281. dom.addCssClass(_self.container, module.cssClass);
  1282. dom.setCssClass(_self.container, "ace_dark", module.isDark);
  1283. var padding = "padding" in module ? module.padding : 4;
  1284. if (_self.$padding && padding != _self.$padding)
  1285. _self.setPadding(padding);
  1286. // force re-measure of the gutter width
  1287. if (_self.$size) {
  1288. _self.$size.width = 0;
  1289. _self.onResize();
  1290. }
  1291. _self._dispatchEvent('themeLoaded', {theme:module});
  1292. cb && cb();
  1293. }
  1294. };
  1295. /**
  1296. * [Returns the path of the current theme.]{: #VirtualRenderer.getTheme}
  1297. * @returns {String}
  1298. **/
  1299. this.getTheme = function() {
  1300. return this.$themeValue;
  1301. };
  1302. // Methods allows to add / remove CSS classnames to the editor element.
  1303. // This feature can be used by plug-ins to provide a visual indication of
  1304. // a certain mode that editor is in.
  1305. /**
  1306. * [Adds a new class, `style`, to the editor.]{: #VirtualRenderer.setStyle}
  1307. * @param {String} style A class name
  1308. *
  1309. **/
  1310. this.setStyle = function(style, include) {
  1311. dom.setCssClass(this.container, style, include != false);
  1312. };
  1313. /**
  1314. * [Removes the class `style` from the editor.]{: #VirtualRenderer.unsetStyle}
  1315. * @param {String} style A class name
  1316. *
  1317. **/
  1318. this.unsetStyle = function(style) {
  1319. dom.removeCssClass(this.container, style);
  1320. };
  1321. /**
  1322. * @param {String} cursorStyle A css cursor style
  1323. *
  1324. **/
  1325. this.setMouseCursor = function(cursorStyle) {
  1326. this.content.style.cursor = cursorStyle;
  1327. };
  1328. /**
  1329. * Destroys the text and cursor layers for this renderer.
  1330. **/
  1331. this.destroy = function() {
  1332. this.$textLayer.destroy();
  1333. this.$cursorLayer.destroy();
  1334. };
  1335. }).call(VirtualRenderer.prototype);
  1336. config.defineOptions(VirtualRenderer.prototype, "renderer", {
  1337. animatedScroll: {initialValue: false},
  1338. showInvisibles: {
  1339. set: function(value) {
  1340. if (this.$textLayer.setShowInvisibles(value))
  1341. this.$loop.schedule(this.CHANGE_TEXT);
  1342. },
  1343. initialValue: false
  1344. },
  1345. showPrintMargin: {
  1346. set: function() { this.$updatePrintMargin(); },
  1347. initialValue: true
  1348. },
  1349. printMarginColumn: {
  1350. set: function() { this.$updatePrintMargin(); },
  1351. initialValue: 80
  1352. },
  1353. printMargin: {
  1354. set: function(val) {
  1355. if (typeof val == "number")
  1356. this.$printMarginColumn = val;
  1357. this.$showPrintMargin = !!val;
  1358. this.$updatePrintMargin();
  1359. },
  1360. get: function() {
  1361. return this.$showPrintMargin && this.$printMarginColumn;
  1362. }
  1363. },
  1364. showGutter: {
  1365. set: function(show){
  1366. this.$gutter.style.display = show ? "block" : "none";
  1367. this.onGutterResize();
  1368. },
  1369. initialValue: true
  1370. },
  1371. fadeFoldWidgets: {
  1372. set: function(show) {
  1373. dom.setCssClass(this.$gutter, "ace_fade-fold-widgets", show);
  1374. },
  1375. initialValue: false
  1376. },
  1377. showFoldWidgets: {
  1378. set: function(show) {this.$gutterLayer.setShowFoldWidgets(show)},
  1379. initialValue: true
  1380. },
  1381. displayIndentGuides: {
  1382. set: function(show) {
  1383. if (this.$textLayer.setDisplayIndentGuides(show))
  1384. this.$loop.schedule(this.CHANGE_TEXT);
  1385. },
  1386. initialValue: true
  1387. },
  1388. highlightGutterLine: {
  1389. set: function(shouldHighlight) {
  1390. if (!this.$gutterLineHighlight) {
  1391. this.$gutterLineHighlight = dom.createElement("div");
  1392. this.$gutterLineHighlight.className = "ace_gutter-active-line";
  1393. this.$gutter.appendChild(this.$gutterLineHighlight);
  1394. return;
  1395. }
  1396. this.$gutterLineHighlight.style.display = shouldHighlight ? "" : "none";
  1397. // if cursorlayer have never been updated there's nothing on screen to update
  1398. if (this.$cursorLayer.$pixelPos)
  1399. this.$updateGutterLineHighlight();
  1400. },
  1401. initialValue: false,
  1402. value: true
  1403. },
  1404. hScrollBarAlwaysVisible: {
  1405. set: function(val) {
  1406. if (!this.$hScrollBarAlwaysVisible || !this.$horizScroll)
  1407. this.$loop.schedule(this.CHANGE_SCROLL);
  1408. },
  1409. initialValue: false
  1410. },
  1411. vScrollBarAlwaysVisible: {
  1412. set: function(val) {
  1413. if (!this.$vScrollBarAlwaysVisible || !this.$vScroll)
  1414. this.$loop.schedule(this.CHANGE_SCROLL);
  1415. },
  1416. initialValue: false
  1417. },
  1418. fontSize: {
  1419. set: function(size) {
  1420. if (typeof size == "number")
  1421. size = size + "px";
  1422. this.container.style.fontSize = size;
  1423. this.updateFontSize();
  1424. },
  1425. initialValue: 12
  1426. },
  1427. fontFamily: {
  1428. set: function(name) {
  1429. this.container.style.fontFamily = name;
  1430. this.updateFontSize();
  1431. }
  1432. },
  1433. maxLines: {
  1434. set: function(val) {
  1435. this.updateFull();
  1436. }
  1437. },
  1438. minLines: {
  1439. set: function(val) {
  1440. this.updateFull();
  1441. }
  1442. },
  1443. scrollPastEnd: {
  1444. set: function(val) {
  1445. val = +val || 0;
  1446. if (this.$scrollPastEnd == val)
  1447. return;
  1448. this.$scrollPastEnd = val;
  1449. this.$loop.schedule(this.CHANGE_SCROLL);
  1450. },
  1451. initialValue: 0,
  1452. handlesSet: true
  1453. },
  1454. fixedWidthGutter: {
  1455. set: function(val) {
  1456. this.$gutterLayer.$fixedWidth = !!val;
  1457. this.$loop.schedule(this.CHANGE_GUTTER);
  1458. }
  1459. }
  1460. });
  1461. exports.VirtualRenderer = VirtualRenderer;
  1462. });