anchor.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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 oop = require("./lib/oop");
  33. var EventEmitter = require("./lib/event_emitter").EventEmitter;
  34. /**
  35. *
  36. * Defines the floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the cursor 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(e) {
  92. var delta = e.data;
  93. var range = delta.range;
  94. if (range.start.row == range.end.row && range.start.row != this.row)
  95. return;
  96. if (range.start.row > this.row)
  97. return;
  98. if (range.start.row == this.row && range.start.column > this.column)
  99. return;
  100. var row = this.row;
  101. var column = this.column;
  102. var start = range.start;
  103. var end = range.end;
  104. if (delta.action === "insertText") {
  105. if (start.row === row && start.column <= column) {
  106. if (start.column === column && this.$insertRight) {
  107. // do nothing
  108. } else if (start.row === end.row) {
  109. column += end.column - start.column;
  110. } else {
  111. column -= start.column;
  112. row += end.row - start.row;
  113. }
  114. } else if (start.row !== end.row && start.row < row) {
  115. row += end.row - start.row;
  116. }
  117. } else if (delta.action === "insertLines") {
  118. if (start.row <= row) {
  119. row += end.row - start.row;
  120. }
  121. } else if (delta.action === "removeText") {
  122. if (start.row === row && start.column < column) {
  123. if (end.column >= column)
  124. column = start.column;
  125. else
  126. column = Math.max(0, column - (end.column - start.column));
  127. } else if (start.row !== end.row && start.row < row) {
  128. if (end.row === row)
  129. column = Math.max(0, column - end.column) + start.column;
  130. row -= (end.row - start.row);
  131. } else if (end.row === row) {
  132. row -= end.row - start.row;
  133. column = Math.max(0, column - end.column) + start.column;
  134. }
  135. } else if (delta.action == "removeLines") {
  136. if (start.row <= row) {
  137. if (end.row <= row)
  138. row -= end.row - start.row;
  139. else {
  140. row = start.row;
  141. column = 0;
  142. }
  143. }
  144. }
  145. this.setPosition(row, column, true);
  146. };
  147. /**
  148. * Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped.
  149. * @param {Number} row The row index to move the anchor to
  150. * @param {Number} column The column index to move the anchor to
  151. * @param {Boolean} noClip Identifies if you want the position to be clipped
  152. *
  153. **/
  154. this.setPosition = function(row, column, noClip) {
  155. var pos;
  156. if (noClip) {
  157. pos = {
  158. row: row,
  159. column: column
  160. };
  161. } else {
  162. pos = this.$clipPositionToDocument(row, column);
  163. }
  164. if (this.row == pos.row && this.column == pos.column)
  165. return;
  166. var old = {
  167. row: this.row,
  168. column: this.column
  169. };
  170. this.row = pos.row;
  171. this.column = pos.column;
  172. this._emit("change", {
  173. old: old,
  174. value: pos
  175. });
  176. };
  177. /**
  178. * When called, the `'change'` event listener is removed.
  179. *
  180. **/
  181. this.detach = function() {
  182. this.document.removeEventListener("change", this.$onChange);
  183. };
  184. this.attach = function(doc) {
  185. this.document = doc || this.document;
  186. this.document.on("change", this.$onChange);
  187. };
  188. /**
  189. * Clips the anchor position to the specified row and column.
  190. * @param {Number} row The row index to clip the anchor to
  191. * @param {Number} column The column index to clip the anchor to
  192. *
  193. **/
  194. this.$clipPositionToDocument = function(row, column) {
  195. var pos = {};
  196. if (row >= this.document.getLength()) {
  197. pos.row = Math.max(0, this.document.getLength() - 1);
  198. pos.column = this.document.getLine(pos.row).length;
  199. }
  200. else if (row < 0) {
  201. pos.row = 0;
  202. pos.column = 0;
  203. }
  204. else {
  205. pos.row = row;
  206. pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column));
  207. }
  208. if (column < 0)
  209. pos.column = 0;
  210. return pos;
  211. };
  212. }).call(Anchor.prototype);
  213. });