gutter.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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 dom = require("../lib/dom");
  33. var oop = require("../lib/oop");
  34. var lang = require("../lib/lang");
  35. var EventEmitter = require("../lib/event_emitter").EventEmitter;
  36. var Gutter = function(parentEl) {
  37. this.element = dom.createElement("div");
  38. this.element.className = "ace_layer ace_gutter-layer";
  39. parentEl.appendChild(this.element);
  40. this.setShowFoldWidgets(this.$showFoldWidgets);
  41. this.gutterWidth = 0;
  42. this.$annotations = [];
  43. this.$updateAnnotations = this.$updateAnnotations.bind(this);
  44. this.$cells = [];
  45. };
  46. (function() {
  47. oop.implement(this, EventEmitter);
  48. this.setSession = function(session) {
  49. if (this.session)
  50. this.session.removeEventListener("change", this.$updateAnnotations);
  51. this.session = session;
  52. if (session)
  53. session.on("change", this.$updateAnnotations);
  54. };
  55. this.addGutterDecoration = function(row, className){
  56. if (window.console)
  57. console.warn && console.warn("deprecated use session.addGutterDecoration");
  58. this.session.addGutterDecoration(row, className);
  59. };
  60. this.removeGutterDecoration = function(row, className){
  61. if (window.console)
  62. console.warn && console.warn("deprecated use session.removeGutterDecoration");
  63. this.session.removeGutterDecoration(row, className);
  64. };
  65. this.setAnnotations = function(annotations) {
  66. // iterate over sparse array
  67. this.$annotations = [];
  68. for (var i = 0; i < annotations.length; i++) {
  69. var annotation = annotations[i];
  70. var row = annotation.row;
  71. var rowInfo = this.$annotations[row];
  72. if (!rowInfo)
  73. rowInfo = this.$annotations[row] = {text: []};
  74. var annoText = annotation.text;
  75. annoText = annoText ? lang.escapeHTML(annoText) : annotation.html || "";
  76. if (rowInfo.text.indexOf(annoText) === -1)
  77. rowInfo.text.push(annoText);
  78. var type = annotation.type;
  79. if (type == "error")
  80. rowInfo.className = " ace_error";
  81. else if (type == "warning" && rowInfo.className != " ace_error")
  82. rowInfo.className = " ace_warning";
  83. else if (type == "info" && (!rowInfo.className))
  84. rowInfo.className = " ace_info";
  85. }
  86. };
  87. this.$updateAnnotations = function (delta) {
  88. if (!this.$annotations.length)
  89. return;
  90. var firstRow = delta.start.row;
  91. var len = delta.end.row - firstRow;
  92. if (len === 0) {
  93. // do nothing
  94. } else if (delta.action == 'remove') {
  95. this.$annotations.splice(firstRow, len + 1, null);
  96. } else {
  97. var args = new Array(len + 1);
  98. args.unshift(firstRow, 1);
  99. this.$annotations.splice.apply(this.$annotations, args);
  100. }
  101. };
  102. this.update = function(config) {
  103. var session = this.session;
  104. var firstRow = config.firstRow;
  105. var lastRow = Math.min(config.lastRow + config.gutterOffset, // needed to compensate for hor scollbar
  106. session.getLength() - 1);
  107. var fold = session.getNextFoldLine(firstRow);
  108. var foldStart = fold ? fold.start.row : Infinity;
  109. var foldWidgets = this.$showFoldWidgets && session.foldWidgets;
  110. var breakpoints = session.$breakpoints;
  111. var decorations = session.$decorations;
  112. var firstLineNumber = session.$firstLineNumber;
  113. var lastLineNumber = 0;
  114. var gutterRenderer = session.gutterRenderer || this.$renderer;
  115. var cell = null;
  116. var index = -1;
  117. var row = firstRow;
  118. while (true) {
  119. if (row > foldStart) {
  120. row = fold.end.row + 1;
  121. fold = session.getNextFoldLine(row, fold);
  122. foldStart = fold ? fold.start.row : Infinity;
  123. }
  124. if (row > lastRow) {
  125. while (this.$cells.length > index + 1) {
  126. cell = this.$cells.pop();
  127. this.element.removeChild(cell.element);
  128. }
  129. break;
  130. }
  131. cell = this.$cells[++index];
  132. if (!cell) {
  133. cell = {element: null, textNode: null, foldWidget: null};
  134. cell.element = dom.createElement("div");
  135. cell.textNode = document.createTextNode('');
  136. cell.element.appendChild(cell.textNode);
  137. this.element.appendChild(cell.element);
  138. this.$cells[index] = cell;
  139. }
  140. var className = "ace_gutter-cell ";
  141. if (breakpoints[row])
  142. className += breakpoints[row];
  143. if (decorations[row])
  144. className += decorations[row];
  145. if (this.$annotations[row])
  146. className += this.$annotations[row].className;
  147. if (cell.element.className != className)
  148. cell.element.className = className;
  149. var height = session.getRowLength(row) * config.lineHeight + "px";
  150. if (height != cell.element.style.height)
  151. cell.element.style.height = height;
  152. if (foldWidgets) {
  153. var c = foldWidgets[row];
  154. // check if cached value is invalidated and we need to recompute
  155. if (c == null)
  156. c = foldWidgets[row] = session.getFoldWidget(row);
  157. }
  158. if (c) {
  159. if (!cell.foldWidget) {
  160. cell.foldWidget = dom.createElement("span");
  161. cell.element.appendChild(cell.foldWidget);
  162. }
  163. var className = "ace_fold-widget ace_" + c;
  164. if (c == "start" && row == foldStart && row < fold.end.row)
  165. className += " ace_closed";
  166. else
  167. className += " ace_open";
  168. if (cell.foldWidget.className != className)
  169. cell.foldWidget.className = className;
  170. var height = config.lineHeight + "px";
  171. if (cell.foldWidget.style.height != height)
  172. cell.foldWidget.style.height = height;
  173. } else {
  174. if (cell.foldWidget) {
  175. cell.element.removeChild(cell.foldWidget);
  176. cell.foldWidget = null;
  177. }
  178. }
  179. var text = lastLineNumber = gutterRenderer
  180. ? gutterRenderer.getText(session, row)
  181. : row + firstLineNumber;
  182. if (text != cell.textNode.data)
  183. cell.textNode.data = text;
  184. row++;
  185. }
  186. this.element.style.height = config.minHeight + "px";
  187. if (this.$fixedWidth || session.$useWrapMode)
  188. lastLineNumber = session.getLength() + firstLineNumber;
  189. var gutterWidth = gutterRenderer
  190. ? gutterRenderer.getWidth(session, lastLineNumber, config)
  191. : lastLineNumber.toString().length * config.characterWidth;
  192. gutterWidth += 6; // add some padding
  193. this.gutterWidth = gutterWidth;
  194. this.element.style.width = Math.ceil(this.gutterWidth) + "px";
  195. this._emit("changeGutterWidth", gutterWidth);
  196. };
  197. this.$fixedWidth = false;
  198. this.$showLineNumbers = true;
  199. this.$renderer = "";
  200. this.setShowLineNumbers = function(show) {
  201. this.$renderer = !show && {
  202. getWidth: function() {return ""},
  203. getText: function() {return ""}
  204. };
  205. };
  206. this.getShowLineNumbers = function() {
  207. return this.$showLineNumbers;
  208. };
  209. this.$showFoldWidgets = true;
  210. this.setShowFoldWidgets = function(show) {
  211. if (show)
  212. dom.addCssClass(this.element, "ace_folding-enabled");
  213. else
  214. dom.removeCssClass(this.element, "ace_folding-enabled");
  215. this.$showFoldWidgets = show;
  216. this.$padding = null;
  217. };
  218. this.getShowFoldWidgets = function() {
  219. return this.$showFoldWidgets;
  220. };
  221. this.$computePadding = function() {
  222. if (!this.element.firstChild)
  223. return {left: 0, right: 0};
  224. var style = dom.computedStyle(this.element.firstChild);
  225. this.$padding = {};
  226. this.$padding.left = parseInt(style.paddingLeft) + 1 || 0;
  227. this.$padding.right = parseInt(style.paddingRight) || 0;
  228. return this.$padding;
  229. };
  230. this.getRegion = function(point) {
  231. var padding = this.$padding || this.$computePadding();
  232. var rect = this.element.getBoundingClientRect();
  233. if (point.x < padding.left + rect.left)
  234. return "markers";
  235. if (this.$showFoldWidgets && point.x > rect.right - padding.right)
  236. return "foldWidgets";
  237. };
  238. }).call(Gutter.prototype);
  239. exports.Gutter = Gutter;
  240. });