range_list.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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. var comparePoints = Range.comparePoints;
  34. var RangeList = function() {
  35. this.ranges = [];
  36. };
  37. (function() {
  38. this.comparePoints = comparePoints;
  39. this.pointIndex = function(pos, excludeEdges, startIndex) {
  40. var list = this.ranges;
  41. for (var i = startIndex || 0; i < list.length; i++) {
  42. var range = list[i];
  43. var cmpEnd = comparePoints(pos, range.end);
  44. if (cmpEnd > 0)
  45. continue;
  46. var cmpStart = comparePoints(pos, range.start);
  47. if (cmpEnd === 0)
  48. return excludeEdges && cmpStart !== 0 ? -i-2 : i;
  49. if (cmpStart > 0 || (cmpStart === 0 && !excludeEdges))
  50. return i;
  51. return -i-1;
  52. }
  53. return -i - 1;
  54. };
  55. this.add = function(range) {
  56. var excludeEdges = !range.isEmpty();
  57. var startIndex = this.pointIndex(range.start, excludeEdges);
  58. if (startIndex < 0)
  59. startIndex = -startIndex - 1;
  60. var endIndex = this.pointIndex(range.end, excludeEdges, startIndex);
  61. if (endIndex < 0)
  62. endIndex = -endIndex - 1;
  63. else
  64. endIndex++;
  65. return this.ranges.splice(startIndex, endIndex - startIndex, range);
  66. };
  67. this.addList = function(list) {
  68. var removed = [];
  69. for (var i = list.length; i--; ) {
  70. removed.push.call(removed, this.add(list[i]));
  71. }
  72. return removed;
  73. };
  74. this.substractPoint = function(pos) {
  75. var i = this.pointIndex(pos);
  76. if (i >= 0)
  77. return this.ranges.splice(i, 1);
  78. };
  79. // merge overlapping ranges
  80. this.merge = function() {
  81. var removed = [];
  82. var list = this.ranges;
  83. list = list.sort(function(a, b) {
  84. return comparePoints(a.start, b.start);
  85. });
  86. var next = list[0], range;
  87. for (var i = 1; i < list.length; i++) {
  88. range = next;
  89. next = list[i];
  90. var cmp = comparePoints(range.end, next.start);
  91. if (cmp < 0)
  92. continue;
  93. if (cmp == 0 && !range.isEmpty() && !next.isEmpty())
  94. continue;
  95. if (comparePoints(range.end, next.end) < 0) {
  96. range.end.row = next.end.row;
  97. range.end.column = next.end.column;
  98. }
  99. list.splice(i, 1);
  100. removed.push(next);
  101. next = range;
  102. i--;
  103. }
  104. this.ranges = list;
  105. return removed;
  106. };
  107. this.contains = function(row, column) {
  108. return this.pointIndex({row: row, column: column}) >= 0;
  109. };
  110. this.containsPoint = function(pos) {
  111. return this.pointIndex(pos) >= 0;
  112. };
  113. this.rangeAtPoint = function(pos) {
  114. var i = this.pointIndex(pos);
  115. if (i >= 0)
  116. return this.ranges[i];
  117. };
  118. this.clipRows = function(startRow, endRow) {
  119. var list = this.ranges;
  120. if (list[0].start.row > endRow || list[list.length - 1].start.row < startRow)
  121. return [];
  122. var startIndex = this.pointIndex({row: startRow, column: 0});
  123. if (startIndex < 0)
  124. startIndex = -startIndex - 1;
  125. var endIndex = this.pointIndex({row: endRow, column: 0}, startIndex);
  126. if (endIndex < 0)
  127. endIndex = -endIndex - 1;
  128. var clipped = [];
  129. for (var i = startIndex; i < endIndex; i++) {
  130. clipped.push(list[i]);
  131. }
  132. return clipped;
  133. };
  134. this.removeAll = function() {
  135. return this.ranges.splice(0, this.ranges.length);
  136. };
  137. this.attach = function(session) {
  138. if (this.session)
  139. this.detach();
  140. this.session = session;
  141. this.onChange = this.$onChange.bind(this);
  142. this.session.on('change', this.onChange);
  143. };
  144. this.detach = function() {
  145. if (!this.session)
  146. return;
  147. this.session.removeListener('change', this.onChange);
  148. this.session = null;
  149. };
  150. this.$onChange = function(delta) {
  151. if (delta.action == "insert"){
  152. var start = delta.start;
  153. var end = delta.end;
  154. } else {
  155. var end = delta.start;
  156. var start = delta.end;
  157. }
  158. var startRow = start.row;
  159. var endRow = end.row;
  160. var lineDif = endRow - startRow;
  161. var colDiff = -start.column + end.column;
  162. var ranges = this.ranges;
  163. for (var i = 0, n = ranges.length; i < n; i++) {
  164. var r = ranges[i];
  165. if (r.end.row < startRow)
  166. continue;
  167. if (r.start.row > startRow)
  168. break;
  169. if (r.start.row == startRow && r.start.column >= start.column ) {
  170. if (r.start.column == start.column && this.$insertRight) {
  171. // do nothing
  172. } else {
  173. r.start.column += colDiff;
  174. r.start.row += lineDif;
  175. }
  176. }
  177. if (r.end.row == startRow && r.end.column >= start.column) {
  178. if (r.end.column == start.column && this.$insertRight) {
  179. continue;
  180. }
  181. // special handling for the case when two ranges share an edge
  182. if (r.end.column == start.column && colDiff > 0 && i < n - 1) {
  183. if (r.end.column > r.start.column && r.end.column == ranges[i+1].start.column)
  184. r.end.column -= colDiff;
  185. }
  186. r.end.column += colDiff;
  187. r.end.row += lineDif;
  188. }
  189. }
  190. if (lineDif != 0 && i < n) {
  191. for (; i < n; i++) {
  192. var r = ranges[i];
  193. r.start.row += lineDif;
  194. r.end.row += lineDif;
  195. }
  196. }
  197. };
  198. }).call(RangeList.prototype);
  199. exports.RangeList = RangeList;
  200. });