fold_line.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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 Range = require("../range").Range;
  33. /*
  34. * If an array is passed in, the folds are expected to be sorted already.
  35. */
  36. function FoldLine(foldData, folds) {
  37. this.foldData = foldData;
  38. if (Array.isArray(folds)) {
  39. this.folds = folds;
  40. } else {
  41. folds = this.folds = [ folds ];
  42. }
  43. var last = folds[folds.length - 1];
  44. this.range = new Range(folds[0].start.row, folds[0].start.column,
  45. last.end.row, last.end.column);
  46. this.start = this.range.start;
  47. this.end = this.range.end;
  48. this.folds.forEach(function(fold) {
  49. fold.setFoldLine(this);
  50. }, this);
  51. }
  52. (function() {
  53. /*
  54. * Note: This doesn't update wrapData!
  55. */
  56. this.shiftRow = function(shift) {
  57. this.start.row += shift;
  58. this.end.row += shift;
  59. this.folds.forEach(function(fold) {
  60. fold.start.row += shift;
  61. fold.end.row += shift;
  62. });
  63. };
  64. this.addFold = function(fold) {
  65. if (fold.sameRow) {
  66. if (fold.start.row < this.startRow || fold.endRow > this.endRow) {
  67. throw new Error("Can't add a fold to this FoldLine as it has no connection");
  68. }
  69. this.folds.push(fold);
  70. this.folds.sort(function(a, b) {
  71. return -a.range.compareEnd(b.start.row, b.start.column);
  72. });
  73. if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) {
  74. this.end.row = fold.end.row;
  75. this.end.column = fold.end.column;
  76. } else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) {
  77. this.start.row = fold.start.row;
  78. this.start.column = fold.start.column;
  79. }
  80. } else if (fold.start.row == this.end.row) {
  81. this.folds.push(fold);
  82. this.end.row = fold.end.row;
  83. this.end.column = fold.end.column;
  84. } else if (fold.end.row == this.start.row) {
  85. this.folds.unshift(fold);
  86. this.start.row = fold.start.row;
  87. this.start.column = fold.start.column;
  88. } else {
  89. throw new Error("Trying to add fold to FoldRow that doesn't have a matching row");
  90. }
  91. fold.foldLine = this;
  92. };
  93. this.containsRow = function(row) {
  94. return row >= this.start.row && row <= this.end.row;
  95. };
  96. this.walk = function(callback, endRow, endColumn) {
  97. var lastEnd = 0,
  98. folds = this.folds,
  99. fold,
  100. cmp, stop, isNewRow = true;
  101. if (endRow == null) {
  102. endRow = this.end.row;
  103. endColumn = this.end.column;
  104. }
  105. for (var i = 0; i < folds.length; i++) {
  106. fold = folds[i];
  107. cmp = fold.range.compareStart(endRow, endColumn);
  108. // This fold is after the endRow/Column.
  109. if (cmp == -1) {
  110. callback(null, endRow, endColumn, lastEnd, isNewRow);
  111. return;
  112. }
  113. stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow);
  114. stop = !stop && callback(fold.placeholder, fold.start.row, fold.start.column, lastEnd);
  115. // If the user requested to stop the walk or endRow/endColumn is
  116. // inside of this fold (cmp == 0), then end here.
  117. if (stop || cmp === 0) {
  118. return;
  119. }
  120. // Note the new lastEnd might not be on the same line. However,
  121. // it's the callback's job to recognize this.
  122. isNewRow = !fold.sameRow;
  123. lastEnd = fold.end.column;
  124. }
  125. callback(null, endRow, endColumn, lastEnd, isNewRow);
  126. };
  127. this.getNextFoldTo = function(row, column) {
  128. var fold, cmp;
  129. for (var i = 0; i < this.folds.length; i++) {
  130. fold = this.folds[i];
  131. cmp = fold.range.compareEnd(row, column);
  132. if (cmp == -1) {
  133. return {
  134. fold: fold,
  135. kind: "after"
  136. };
  137. } else if (cmp === 0) {
  138. return {
  139. fold: fold,
  140. kind: "inside"
  141. };
  142. }
  143. }
  144. return null;
  145. };
  146. this.addRemoveChars = function(row, column, len) {
  147. var ret = this.getNextFoldTo(row, column),
  148. fold, folds;
  149. if (ret) {
  150. fold = ret.fold;
  151. if (ret.kind == "inside"
  152. && fold.start.column != column
  153. && fold.start.row != row)
  154. {
  155. //throwing here breaks whole editor
  156. //TODO: properly handle this
  157. window.console && window.console.log(row, column, fold);
  158. } else if (fold.start.row == row) {
  159. folds = this.folds;
  160. var i = folds.indexOf(fold);
  161. if (i === 0) {
  162. this.start.column += len;
  163. }
  164. for (i; i < folds.length; i++) {
  165. fold = folds[i];
  166. fold.start.column += len;
  167. if (!fold.sameRow) {
  168. return;
  169. }
  170. fold.end.column += len;
  171. }
  172. this.end.column += len;
  173. }
  174. }
  175. };
  176. this.split = function(row, column) {
  177. var pos = this.getNextFoldTo(row, column);
  178. if (!pos || pos.kind == "inside")
  179. return null;
  180. var fold = pos.fold;
  181. var folds = this.folds;
  182. var foldData = this.foldData;
  183. var i = folds.indexOf(fold);
  184. var foldBefore = folds[i - 1];
  185. this.end.row = foldBefore.end.row;
  186. this.end.column = foldBefore.end.column;
  187. // Remove the folds after row/column and create a new FoldLine
  188. // containing these removed folds.
  189. folds = folds.splice(i, folds.length - i);
  190. var newFoldLine = new FoldLine(foldData, folds);
  191. foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine);
  192. return newFoldLine;
  193. };
  194. this.merge = function(foldLineNext) {
  195. var folds = foldLineNext.folds;
  196. for (var i = 0; i < folds.length; i++) {
  197. this.addFold(folds[i]);
  198. }
  199. // Remove the foldLineNext - no longer needed, as
  200. // it's merged now with foldLineNext.
  201. var foldData = this.foldData;
  202. foldData.splice(foldData.indexOf(foldLineNext), 1);
  203. };
  204. this.toString = function() {
  205. var ret = [this.range.toString() + ": [" ];
  206. this.folds.forEach(function(fold) {
  207. ret.push(" " + fold.toString());
  208. });
  209. ret.push("]");
  210. return ret.join("\n");
  211. };
  212. this.idxToPosition = function(idx) {
  213. var lastFoldEndColumn = 0;
  214. for (var i = 0; i < this.folds.length; i++) {
  215. var fold = this.folds[i];
  216. idx -= fold.start.column - lastFoldEndColumn;
  217. if (idx < 0) {
  218. return {
  219. row: fold.start.row,
  220. column: fold.start.column + idx
  221. };
  222. }
  223. idx -= fold.placeholder.length;
  224. if (idx < 0) {
  225. return fold.start;
  226. }
  227. lastFoldEndColumn = fold.end.column;
  228. }
  229. return {
  230. row: this.end.row,
  231. column: this.end.column + idx
  232. };
  233. };
  234. }).call(FoldLine.prototype);
  235. exports.FoldLine = FoldLine;
  236. });