range.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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(ace_require, exports, module) {
  31. "use strict";
  32. var comparePoints = function(p1, p2) {
  33. return p1.row - p2.row || p1.column - p2.column;
  34. };
  35. /**
  36. * This object is used in various places to indicate a region within the editor. To better visualize how this works, imagine a rectangle. Each quadrant of the rectangle is analogous to a range, as ranges contain a starting row and starting column, and an ending row, and ending column.
  37. * @class Range
  38. **/
  39. /**
  40. * Creates a new `Range` object with the given starting and ending row and column points.
  41. * @param {Number} startRow The starting row
  42. * @param {Number} startColumn The starting column
  43. * @param {Number} endRow The ending row
  44. * @param {Number} endColumn The ending column
  45. *
  46. * @constructor
  47. **/
  48. var Range = function(startRow, startColumn, endRow, endColumn) {
  49. this.start = {
  50. row: startRow,
  51. column: startColumn
  52. };
  53. this.end = {
  54. row: endRow,
  55. column: endColumn
  56. };
  57. };
  58. (function() {
  59. /**
  60. * Returns `true` if and only if the starting row and column, and ending row and column, are equivalent to those given by `range`.
  61. * @param {Range} range A range to check against
  62. *
  63. * @return {Boolean}
  64. **/
  65. this.isEqual = function(range) {
  66. return this.start.row === range.start.row &&
  67. this.end.row === range.end.row &&
  68. this.start.column === range.start.column &&
  69. this.end.column === range.end.column;
  70. };
  71. /**
  72. *
  73. * Returns a string containing the range's row and column information, given like this:
  74. * ```
  75. * [start.row/start.column] -> [end.row/end.column]
  76. * ```
  77. * @return {String}
  78. **/
  79. this.toString = function() {
  80. return ("Range: [" + this.start.row + "/" + this.start.column +
  81. "] -> [" + this.end.row + "/" + this.end.column + "]");
  82. };
  83. /**
  84. *
  85. * Returns `true` if the `row` and `column` provided are within the given range. This can better be expressed as returning `true` if:
  86. * ```javascript
  87. * this.start.row <= row <= this.end.row &&
  88. * this.start.column <= column <= this.end.column
  89. * ```
  90. * @param {Number} row A row to check for
  91. * @param {Number} column A column to check for
  92. * @returns {Boolean}
  93. * @related Range.compare
  94. **/
  95. this.contains = function(row, column) {
  96. return this.compare(row, column) == 0;
  97. };
  98. /**
  99. * Compares `this` range (A) with another range (B).
  100. * @param {Range} range A range to compare with
  101. *
  102. * @related Range.compare
  103. * @returns {Number} This method returns one of the following numbers:<br/>
  104. * <br/>
  105. * * `-2`: (B) is in front of (A), and doesn't intersect with (A)<br/>
  106. * * `-1`: (B) begins before (A) but ends inside of (A)<br/>
  107. * * `0`: (B) is completely inside of (A) OR (A) is completely inside of (B)<br/>
  108. * * `+1`: (B) begins inside of (A) but ends outside of (A)<br/>
  109. * * `+2`: (B) is after (A) and doesn't intersect with (A)<br/>
  110. * * `42`: FTW state: (B) ends in (A) but starts outside of (A)
  111. **/
  112. this.compareRange = function(range) {
  113. var cmp,
  114. end = range.end,
  115. start = range.start;
  116. cmp = this.compare(end.row, end.column);
  117. if (cmp == 1) {
  118. cmp = this.compare(start.row, start.column);
  119. if (cmp == 1) {
  120. return 2;
  121. } else if (cmp == 0) {
  122. return 1;
  123. } else {
  124. return 0;
  125. }
  126. } else if (cmp == -1) {
  127. return -2;
  128. } else {
  129. cmp = this.compare(start.row, start.column);
  130. if (cmp == -1) {
  131. return -1;
  132. } else if (cmp == 1) {
  133. return 42;
  134. } else {
  135. return 0;
  136. }
  137. }
  138. };
  139. /**
  140. * Checks the row and column points of `p` with the row and column points of the calling range.
  141. *
  142. * @param {Range} p A point to compare with
  143. *
  144. * @related Range.compare
  145. * @returns {Number} This method returns one of the following numbers:<br/>
  146. * * `0` if the two points are exactly equal<br/>
  147. * * `-1` if `p.row` is less then the calling range<br/>
  148. * * `1` if `p.row` is greater than the calling range<br/>
  149. * <br/>
  150. * If the starting row of the calling range is equal to `p.row`, and:<br/>
  151. * * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
  152. * * Otherwise, it returns -1<br/>
  153. *<br/>
  154. * If the ending row of the calling range is equal to `p.row`, and:<br/>
  155. * * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/>
  156. * * Otherwise, it returns 1<br/>
  157. **/
  158. this.comparePoint = function(p) {
  159. return this.compare(p.row, p.column);
  160. };
  161. /**
  162. * Checks the start and end points of `range` and compares them to the calling range. Returns `true` if the `range` is contained within the caller's range.
  163. * @param {Range} range A range to compare with
  164. *
  165. * @returns {Boolean}
  166. * @related Range.comparePoint
  167. **/
  168. this.containsRange = function(range) {
  169. return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0;
  170. };
  171. /**
  172. * Returns `true` if passed in `range` intersects with the one calling this method.
  173. * @param {Range} range A range to compare with
  174. *
  175. * @returns {Boolean}
  176. **/
  177. this.intersects = function(range) {
  178. var cmp = this.compareRange(range);
  179. return (cmp == -1 || cmp == 0 || cmp == 1);
  180. };
  181. /**
  182. * Returns `true` if the caller's ending row point is the same as `row`, and if the caller's ending column is the same as `column`.
  183. * @param {Number} row A row point to compare with
  184. * @param {Number} column A column point to compare with
  185. *
  186. * @returns {Boolean}
  187. **/
  188. this.isEnd = function(row, column) {
  189. return this.end.row == row && this.end.column == column;
  190. };
  191. /**
  192. * Returns `true` if the caller's starting row point is the same as `row`, and if the caller's starting column is the same as `column`.
  193. * @param {Number} row A row point to compare with
  194. * @param {Number} column A column point to compare with
  195. *
  196. * @returns {Boolean}
  197. **/
  198. this.isStart = function(row, column) {
  199. return this.start.row == row && this.start.column == column;
  200. };
  201. /**
  202. * Sets the starting row and column for the range.
  203. * @param {Number} row A row point to set
  204. * @param {Number} column A column point to set
  205. *
  206. **/
  207. this.setStart = function(row, column) {
  208. if (typeof row == "object") {
  209. this.start.column = row.column;
  210. this.start.row = row.row;
  211. } else {
  212. this.start.row = row;
  213. this.start.column = column;
  214. }
  215. };
  216. /**
  217. * Sets the starting row and column for the range.
  218. * @param {Number} row A row point to set
  219. * @param {Number} column A column point to set
  220. *
  221. **/
  222. this.setEnd = function(row, column) {
  223. if (typeof row == "object") {
  224. this.end.column = row.column;
  225. this.end.row = row.row;
  226. } else {
  227. this.end.row = row;
  228. this.end.column = column;
  229. }
  230. };
  231. /**
  232. * Returns `true` if the `row` and `column` are within the given range.
  233. * @param {Number} row A row point to compare with
  234. * @param {Number} column A column point to compare with
  235. *
  236. *
  237. * @returns {Boolean}
  238. * @related Range.compare
  239. **/
  240. this.inside = function(row, column) {
  241. if (this.compare(row, column) == 0) {
  242. if (this.isEnd(row, column) || this.isStart(row, column)) {
  243. return false;
  244. } else {
  245. return true;
  246. }
  247. }
  248. return false;
  249. };
  250. /**
  251. * Returns `true` if the `row` and `column` are within the given range's starting points.
  252. * @param {Number} row A row point to compare with
  253. * @param {Number} column A column point to compare with
  254. *
  255. * @returns {Boolean}
  256. * @related Range.compare
  257. **/
  258. this.insideStart = function(row, column) {
  259. if (this.compare(row, column) == 0) {
  260. if (this.isEnd(row, column)) {
  261. return false;
  262. } else {
  263. return true;
  264. }
  265. }
  266. return false;
  267. };
  268. /**
  269. * Returns `true` if the `row` and `column` are within the given range's ending points.
  270. * @param {Number} row A row point to compare with
  271. * @param {Number} column A column point to compare with
  272. *
  273. * @returns {Boolean}
  274. * @related Range.compare
  275. *
  276. **/
  277. this.insideEnd = function(row, column) {
  278. if (this.compare(row, column) == 0) {
  279. if (this.isStart(row, column)) {
  280. return false;
  281. } else {
  282. return true;
  283. }
  284. }
  285. return false;
  286. };
  287. /**
  288. * Checks the row and column points with the row and column points of the calling range.
  289. * @param {Number} row A row point to compare with
  290. * @param {Number} column A column point to compare with
  291. *
  292. *
  293. * @returns {Number} This method returns one of the following numbers:<br/>
  294. * `0` if the two points are exactly equal <br/>
  295. * `-1` if `p.row` is less then the calling range <br/>
  296. * `1` if `p.row` is greater than the calling range <br/>
  297. * <br/>
  298. * If the starting row of the calling range is equal to `p.row`, and: <br/>
  299. * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
  300. * Otherwise, it returns -1<br/>
  301. * <br/>
  302. * If the ending row of the calling range is equal to `p.row`, and: <br/>
  303. * `p.column` is less than or equal to the calling range's ending column, this returns `0` <br/>
  304. * Otherwise, it returns 1
  305. **/
  306. this.compare = function(row, column) {
  307. if (!this.isMultiLine()) {
  308. if (row === this.start.row) {
  309. return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0);
  310. }
  311. }
  312. if (row < this.start.row)
  313. return -1;
  314. if (row > this.end.row)
  315. return 1;
  316. if (this.start.row === row)
  317. return column >= this.start.column ? 0 : -1;
  318. if (this.end.row === row)
  319. return column <= this.end.column ? 0 : 1;
  320. return 0;
  321. };
  322. /**
  323. * Checks the row and column points with the row and column points of the calling range.
  324. * @param {Number} row A row point to compare with
  325. * @param {Number} column A column point to compare with
  326. *
  327. * @returns {Number} This method returns one of the following numbers:<br/>
  328. * <br/>
  329. * `0` if the two points are exactly equal<br/>
  330. * `-1` if `p.row` is less then the calling range<br/>
  331. * `1` if `p.row` is greater than the calling range, or if `isStart` is `true`.<br/>
  332. * <br/>
  333. * If the starting row of the calling range is equal to `p.row`, and:<br/>
  334. * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
  335. * Otherwise, it returns -1<br/>
  336. * <br/>
  337. * If the ending row of the calling range is equal to `p.row`, and:<br/>
  338. * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/>
  339. * Otherwise, it returns 1
  340. *
  341. **/
  342. this.compareStart = function(row, column) {
  343. if (this.start.row == row && this.start.column == column) {
  344. return -1;
  345. } else {
  346. return this.compare(row, column);
  347. }
  348. };
  349. /**
  350. * Checks the row and column points with the row and column points of the calling range.
  351. * @param {Number} row A row point to compare with
  352. * @param {Number} column A column point to compare with
  353. *
  354. *
  355. * @returns {Number} This method returns one of the following numbers:<br/>
  356. * `0` if the two points are exactly equal<br/>
  357. * `-1` if `p.row` is less then the calling range<br/>
  358. * `1` if `p.row` is greater than the calling range, or if `isEnd` is `true.<br/>
  359. * <br/>
  360. * If the starting row of the calling range is equal to `p.row`, and:<br/>
  361. * `p.column` is greater than or equal to the calling range's starting column, this returns `0`<br/>
  362. * Otherwise, it returns -1<br/>
  363. *<br/>
  364. * If the ending row of the calling range is equal to `p.row`, and:<br/>
  365. * `p.column` is less than or equal to the calling range's ending column, this returns `0`<br/>
  366. * Otherwise, it returns 1
  367. */
  368. this.compareEnd = function(row, column) {
  369. if (this.end.row == row && this.end.column == column) {
  370. return 1;
  371. } else {
  372. return this.compare(row, column);
  373. }
  374. };
  375. /**
  376. * Checks the row and column points with the row and column points of the calling range.
  377. * @param {Number} row A row point to compare with
  378. * @param {Number} column A column point to compare with
  379. *
  380. *
  381. * @returns {Number} This method returns one of the following numbers:<br/>
  382. * * `1` if the ending row of the calling range is equal to `row`, and the ending column of the calling range is equal to `column`<br/>
  383. * * `-1` if the starting row of the calling range is equal to `row`, and the starting column of the calling range is equal to `column`<br/>
  384. * <br/>
  385. * Otherwise, it returns the value after calling [[Range.compare `compare()`]].
  386. *
  387. **/
  388. this.compareInside = function(row, column) {
  389. if (this.end.row == row && this.end.column == column) {
  390. return 1;
  391. } else if (this.start.row == row && this.start.column == column) {
  392. return -1;
  393. } else {
  394. return this.compare(row, column);
  395. }
  396. };
  397. /**
  398. * Returns the part of the current `Range` that occurs within the boundaries of `firstRow` and `lastRow` as a new `Range` object.
  399. * @param {Number} firstRow The starting row
  400. * @param {Number} lastRow The ending row
  401. *
  402. *
  403. * @returns {Range}
  404. **/
  405. this.clipRows = function(firstRow, lastRow) {
  406. if (this.end.row > lastRow)
  407. var end = {row: lastRow + 1, column: 0};
  408. else if (this.end.row < firstRow)
  409. var end = {row: firstRow, column: 0};
  410. if (this.start.row > lastRow)
  411. var start = {row: lastRow + 1, column: 0};
  412. else if (this.start.row < firstRow)
  413. var start = {row: firstRow, column: 0};
  414. return Range.fromPoints(start || this.start, end || this.end);
  415. };
  416. /**
  417. * Changes the row and column points for the calling range for both the starting and ending points.
  418. * @param {Number} row A new row to extend to
  419. * @param {Number} column A new column to extend to
  420. *
  421. *
  422. * @returns {Range} The original range with the new row
  423. **/
  424. this.extend = function(row, column) {
  425. var cmp = this.compare(row, column);
  426. if (cmp == 0)
  427. return this;
  428. else if (cmp == -1)
  429. var start = {row: row, column: column};
  430. else
  431. var end = {row: row, column: column};
  432. return Range.fromPoints(start || this.start, end || this.end);
  433. };
  434. this.isEmpty = function() {
  435. return (this.start.row === this.end.row && this.start.column === this.end.column);
  436. };
  437. /**
  438. *
  439. * Returns `true` if the range spans across multiple lines.
  440. * @returns {Boolean}
  441. **/
  442. this.isMultiLine = function() {
  443. return (this.start.row !== this.end.row);
  444. };
  445. /**
  446. *
  447. * Returns a duplicate of the calling range.
  448. * @returns {Range}
  449. **/
  450. this.clone = function() {
  451. return Range.fromPoints(this.start, this.end);
  452. };
  453. /**
  454. *
  455. * Returns a range containing the starting and ending rows of the original range, but with a column value of `0`.
  456. * @returns {Range}
  457. **/
  458. this.collapseRows = function() {
  459. if (this.end.column == 0)
  460. return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0);
  461. else
  462. return new Range(this.start.row, 0, this.end.row, 0);
  463. };
  464. /**
  465. * Given the current `Range`, this function converts those starting and ending points into screen positions, and then returns a new `Range` object.
  466. * @param {EditSession} session The `EditSession` to retrieve coordinates from
  467. *
  468. *
  469. * @returns {Range}
  470. **/
  471. this.toScreenRange = function(session) {
  472. var screenPosStart = session.documentToScreenPosition(this.start);
  473. var screenPosEnd = session.documentToScreenPosition(this.end);
  474. return new Range(
  475. screenPosStart.row, screenPosStart.column,
  476. screenPosEnd.row, screenPosEnd.column
  477. );
  478. };
  479. /* experimental */
  480. this.moveBy = function(row, column) {
  481. this.start.row += row;
  482. this.start.column += column;
  483. this.end.row += row;
  484. this.end.column += column;
  485. };
  486. }).call(Range.prototype);
  487. /**
  488. * Creates and returns a new `Range` based on the row and column of the given parameters.
  489. * @param {Range} start A starting point to use
  490. * @param {Range} end An ending point to use
  491. *
  492. * @returns {Range}
  493. **/
  494. Range.fromPoints = function(start, end) {
  495. return new Range(start.row, start.column, end.row, end.column);
  496. };
  497. Range.comparePoints = comparePoints;
  498. Range.comparePoints = function(p1, p2) {
  499. return p1.row - p2.row || p1.column - p2.column;
  500. };
  501. exports.Range = Range;
  502. });