anchor.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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 oop = ace_require("./lib/oop");
  33. var EventEmitter = ace_require("./lib/event_emitter").EventEmitter;
  34. /**
  35. *
  36. * Defines a floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the anchor is updated.
  37. *
  38. * @class Anchor
  39. **/
  40. /**
  41. * Creates a new `Anchor` and associates it with a document.
  42. *
  43. * @param {Document} doc The document to associate with the anchor
  44. * @param {Number} row The starting row position
  45. * @param {Number} column The starting column position
  46. *
  47. * @constructor
  48. **/
  49. var Anchor = exports.Anchor = function(doc, row, column) {
  50. this.$onChange = this.onChange.bind(this);
  51. this.attach(doc);
  52. if (typeof column == "undefined")
  53. this.setPosition(row.row, row.column);
  54. else
  55. this.setPosition(row, column);
  56. };
  57. (function() {
  58. oop.implement(this, EventEmitter);
  59. /**
  60. * Returns an object identifying the `row` and `column` position of the current anchor.
  61. * @returns {Object}
  62. **/
  63. this.getPosition = function() {
  64. return this.$clipPositionToDocument(this.row, this.column);
  65. };
  66. /**
  67. *
  68. * Returns the current document.
  69. * @returns {Document}
  70. **/
  71. this.getDocument = function() {
  72. return this.document;
  73. };
  74. /**
  75. * experimental: allows anchor to stick to the next on the left
  76. */
  77. this.$insertRight = false;
  78. /**
  79. * Fires whenever the anchor position changes.
  80. *
  81. * Both of these objects have a `row` and `column` property corresponding to the position.
  82. *
  83. * Events that can trigger this function include [[Anchor.setPosition `setPosition()`]].
  84. *
  85. * @event change
  86. * @param {Object} e An object containing information about the anchor position. It has two properties:
  87. * - `old`: An object describing the old Anchor position
  88. * - `value`: An object describing the new Anchor position
  89. *
  90. **/
  91. this.onChange = function(delta) {
  92. if (delta.start.row == delta.end.row && delta.start.row != this.row)
  93. return;
  94. if (delta.start.row > this.row)
  95. return;
  96. var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight);
  97. this.setPosition(point.row, point.column, true);
  98. };
  99. function $pointsInOrder(point1, point2, equalPointsInOrder) {
  100. var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column;
  101. return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter);
  102. }
  103. function $getTransformedPoint(delta, point, moveIfEqual) {
  104. // Get delta info.
  105. var deltaIsInsert = delta.action == "insert";
  106. var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row);
  107. var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column);
  108. var deltaStart = delta.start;
  109. var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range.
  110. // DELTA AFTER POINT: No change needed.
  111. if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
  112. return {
  113. row: point.row,
  114. column: point.column
  115. };
  116. }
  117. // DELTA BEFORE POINT: Move point by delta shift.
  118. if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
  119. return {
  120. row: point.row + deltaRowShift,
  121. column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
  122. };
  123. }
  124. // DELTA ENVELOPS POINT (delete only): Move point to delta start.
  125. // TODO warn if delta.action != "remove" ?
  126. return {
  127. row: deltaStart.row,
  128. column: deltaStart.column
  129. };
  130. }
  131. /**
  132. * Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped.
  133. * @param {Number} row The row index to move the anchor to
  134. * @param {Number} column The column index to move the anchor to
  135. * @param {Boolean} noClip Identifies if you want the position to be clipped
  136. *
  137. **/
  138. this.setPosition = function(row, column, noClip) {
  139. var pos;
  140. if (noClip) {
  141. pos = {
  142. row: row,
  143. column: column
  144. };
  145. } else {
  146. pos = this.$clipPositionToDocument(row, column);
  147. }
  148. if (this.row == pos.row && this.column == pos.column)
  149. return;
  150. var old = {
  151. row: this.row,
  152. column: this.column
  153. };
  154. this.row = pos.row;
  155. this.column = pos.column;
  156. this._signal("change", {
  157. old: old,
  158. value: pos
  159. });
  160. };
  161. /**
  162. * When called, the `"change"` event listener is removed.
  163. *
  164. **/
  165. this.detach = function() {
  166. this.document.off("change", this.$onChange);
  167. };
  168. this.attach = function(doc) {
  169. this.document = doc || this.document;
  170. this.document.on("change", this.$onChange);
  171. };
  172. /**
  173. * Clips the anchor position to the specified row and column.
  174. * @param {Number} row The row index to clip the anchor to
  175. * @param {Number} column The column index to clip the anchor to
  176. *
  177. **/
  178. this.$clipPositionToDocument = function(row, column) {
  179. var pos = {};
  180. if (row >= this.document.getLength()) {
  181. pos.row = Math.max(0, this.document.getLength() - 1);
  182. pos.column = this.document.getLine(pos.row).length;
  183. }
  184. else if (row < 0) {
  185. pos.row = 0;
  186. pos.column = 0;
  187. }
  188. else {
  189. pos.row = row;
  190. pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column));
  191. }
  192. if (column < 0)
  193. pos.column = 0;
  194. return pos;
  195. };
  196. }).call(Anchor.prototype);
  197. });