placeholder.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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 EventEmitter = require("./lib/event_emitter").EventEmitter;
  34. var oop = require("./lib/oop");
  35. /**
  36. * @class PlaceHolder
  37. *
  38. **/
  39. /**
  40. * - session (Document): The document to associate with the anchor
  41. * - length (Number): The starting row position
  42. * - pos (Number): The starting column position
  43. * - others (String):
  44. * - mainClass (String):
  45. * - othersClass (String):
  46. *
  47. * @constructor
  48. **/
  49. var PlaceHolder = function(session, length, pos, others, mainClass, othersClass) {
  50. var _self = this;
  51. this.length = length;
  52. this.session = session;
  53. this.doc = session.getDocument();
  54. this.mainClass = mainClass;
  55. this.othersClass = othersClass;
  56. this.$onUpdate = this.onUpdate.bind(this);
  57. this.doc.on("change", this.$onUpdate);
  58. this.$others = others;
  59. this.$onCursorChange = function() {
  60. setTimeout(function() {
  61. _self.onCursorChange();
  62. });
  63. };
  64. this.$pos = pos;
  65. // Used for reset
  66. var undoStack = session.getUndoManager().$undoStack || session.getUndoManager().$undostack || {length: -1};
  67. this.$undoStackDepth = undoStack.length;
  68. this.setup();
  69. session.selection.on("changeCursor", this.$onCursorChange);
  70. };
  71. (function() {
  72. oop.implement(this, EventEmitter);
  73. /**
  74. * PlaceHolder.setup()
  75. *
  76. * TODO
  77. *
  78. **/
  79. this.setup = function() {
  80. var _self = this;
  81. var doc = this.doc;
  82. var session = this.session;
  83. var pos = this.$pos;
  84. this.selectionBefore = session.selection.toJSON();
  85. if (session.selection.inMultiSelectMode)
  86. session.selection.toSingleRange();
  87. this.pos = doc.createAnchor(pos.row, pos.column);
  88. this.markerId = session.addMarker(new Range(pos.row, pos.column, pos.row, pos.column + this.length), this.mainClass, null, false);
  89. this.pos.on("change", function(event) {
  90. session.removeMarker(_self.markerId);
  91. _self.markerId = session.addMarker(new Range(event.value.row, event.value.column, event.value.row, event.value.column+_self.length), _self.mainClass, null, false);
  92. });
  93. this.others = [];
  94. this.$others.forEach(function(other) {
  95. var anchor = doc.createAnchor(other.row, other.column);
  96. _self.others.push(anchor);
  97. });
  98. session.setUndoSelect(false);
  99. };
  100. /**
  101. * PlaceHolder.showOtherMarkers()
  102. *
  103. * TODO
  104. *
  105. **/
  106. this.showOtherMarkers = function() {
  107. if(this.othersActive) return;
  108. var session = this.session;
  109. var _self = this;
  110. this.othersActive = true;
  111. this.others.forEach(function(anchor) {
  112. anchor.markerId = session.addMarker(new Range(anchor.row, anchor.column, anchor.row, anchor.column+_self.length), _self.othersClass, null, false);
  113. anchor.on("change", function(event) {
  114. session.removeMarker(anchor.markerId);
  115. anchor.markerId = session.addMarker(new Range(event.value.row, event.value.column, event.value.row, event.value.column+_self.length), _self.othersClass, null, false);
  116. });
  117. });
  118. };
  119. /**
  120. * PlaceHolder.hideOtherMarkers()
  121. *
  122. * Hides all over markers in the [[EditSession `EditSession`]] that are not the currently selected one.
  123. *
  124. **/
  125. this.hideOtherMarkers = function() {
  126. if(!this.othersActive) return;
  127. this.othersActive = false;
  128. for (var i = 0; i < this.others.length; i++) {
  129. this.session.removeMarker(this.others[i].markerId);
  130. }
  131. };
  132. /**
  133. * PlaceHolder@onUpdate(e)
  134. *
  135. * Emitted when the place holder updates.
  136. *
  137. **/
  138. this.onUpdate = function(delta) {
  139. var range = delta;
  140. if(range.start.row !== range.end.row) return;
  141. if(range.start.row !== this.pos.row) return;
  142. if (this.$updating) return;
  143. this.$updating = true;
  144. var lengthDiff = delta.action === "insert" ? range.end.column - range.start.column : range.start.column - range.end.column;
  145. if(range.start.column >= this.pos.column && range.start.column <= this.pos.column + this.length + 1) {
  146. var distanceFromStart = range.start.column - this.pos.column;
  147. this.length += lengthDiff;
  148. if(!this.session.$fromUndo) {
  149. if(delta.action === 'insert') {
  150. for (var i = this.others.length - 1; i >= 0; i--) {
  151. var otherPos = this.others[i];
  152. var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
  153. if(otherPos.row === range.start.row && range.start.column < otherPos.column)
  154. newPos.column += lengthDiff;
  155. this.doc.insertMergedLines(newPos, delta.lines);
  156. }
  157. } else if(delta.action === 'remove') {
  158. for (var i = this.others.length - 1; i >= 0; i--) {
  159. var otherPos = this.others[i];
  160. var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
  161. if(otherPos.row === range.start.row && range.start.column < otherPos.column)
  162. newPos.column += lengthDiff;
  163. this.doc.remove(new Range(newPos.row, newPos.column, newPos.row, newPos.column - lengthDiff));
  164. }
  165. }
  166. // Special case: insert in beginning
  167. if(range.start.column === this.pos.column && delta.action === 'insert') {
  168. setTimeout(function() {
  169. this.pos.setPosition(this.pos.row, this.pos.column - lengthDiff);
  170. for (var i = 0; i < this.others.length; i++) {
  171. var other = this.others[i];
  172. var newPos = {row: other.row, column: other.column - lengthDiff};
  173. if(other.row === range.start.row && range.start.column < other.column)
  174. newPos.column += lengthDiff;
  175. other.setPosition(newPos.row, newPos.column);
  176. }
  177. }.bind(this), 0);
  178. }
  179. else if(range.start.column === this.pos.column && delta.action === 'remove') {
  180. setTimeout(function() {
  181. for (var i = 0; i < this.others.length; i++) {
  182. var other = this.others[i];
  183. if(other.row === range.start.row && range.start.column < other.column) {
  184. other.setPosition(other.row, other.column - lengthDiff);
  185. }
  186. }
  187. }.bind(this), 0);
  188. }
  189. }
  190. this.pos._emit("change", {value: this.pos});
  191. for (var i = 0; i < this.others.length; i++) {
  192. this.others[i]._emit("change", {value: this.others[i]});
  193. }
  194. }
  195. this.$updating = false;
  196. };
  197. /**
  198. * PlaceHolder@onCursorChange(e)
  199. *
  200. * Emitted when the cursor changes.
  201. *
  202. **/
  203. this.onCursorChange = function(event) {
  204. if (this.$updating || !this.session) return;
  205. var pos = this.session.selection.getCursor();
  206. if (pos.row === this.pos.row && pos.column >= this.pos.column && pos.column <= this.pos.column + this.length) {
  207. this.showOtherMarkers();
  208. this._emit("cursorEnter", event);
  209. } else {
  210. this.hideOtherMarkers();
  211. this._emit("cursorLeave", event);
  212. }
  213. };
  214. /**
  215. * PlaceHolder.detach()
  216. *
  217. * TODO
  218. *
  219. **/
  220. this.detach = function() {
  221. this.session.removeMarker(this.markerId);
  222. this.hideOtherMarkers();
  223. this.doc.removeEventListener("change", this.$onUpdate);
  224. this.session.selection.removeEventListener("changeCursor", this.$onCursorChange);
  225. this.pos.detach();
  226. for (var i = 0; i < this.others.length; i++) {
  227. this.others[i].detach();
  228. }
  229. this.session.setUndoSelect(true);
  230. this.session = null;
  231. };
  232. /**
  233. * PlaceHolder.cancel()
  234. *
  235. * TODO
  236. *
  237. **/
  238. this.cancel = function() {
  239. if(this.$undoStackDepth === -1)
  240. throw Error("Canceling placeholders only supported with undo manager attached to session.");
  241. var undoManager = this.session.getUndoManager();
  242. var undosRequired = (undoManager.$undoStack || undoManager.$undostack).length - this.$undoStackDepth;
  243. for (var i = 0; i < undosRequired; i++) {
  244. undoManager.undo(true);
  245. }
  246. if (this.selectionBefore)
  247. this.session.selection.fromJSON(this.selectionBefore);
  248. };
  249. }).call(PlaceHolder.prototype);
  250. exports.PlaceHolder = PlaceHolder;
  251. });