placeholder.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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 Range = ace_require("./range").Range;
  33. var EventEmitter = ace_require("./lib/event_emitter").EventEmitter;
  34. var oop = ace_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. this.selectionBefore = session.selection.toJSON();
  84. if (session.selection.inMultiSelectMode)
  85. session.selection.toSingleRange();
  86. this.pos = doc.createAnchor(this.$pos.row, this.$pos.column);
  87. var pos = this.pos;
  88. pos.$insertRight = true;
  89. pos.detach();
  90. pos.markerId = session.addMarker(new Range(pos.row, pos.column, pos.row, pos.column + this.length), this.mainClass, null, false);
  91. this.others = [];
  92. this.$others.forEach(function(other) {
  93. var anchor = doc.createAnchor(other.row, other.column);
  94. anchor.$insertRight = true;
  95. anchor.detach();
  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. });
  114. };
  115. /**
  116. * PlaceHolder.hideOtherMarkers()
  117. *
  118. * Hides all over markers in the [[EditSession `EditSession`]] that are not the currently selected one.
  119. *
  120. **/
  121. this.hideOtherMarkers = function() {
  122. if (!this.othersActive) return;
  123. this.othersActive = false;
  124. for (var i = 0; i < this.others.length; i++) {
  125. this.session.removeMarker(this.others[i].markerId);
  126. }
  127. };
  128. /**
  129. * PlaceHolder@onUpdate(e)
  130. *
  131. * Emitted when the place holder updates.
  132. *
  133. **/
  134. this.onUpdate = function(delta) {
  135. if (this.$updating)
  136. return this.updateAnchors(delta);
  137. var range = delta;
  138. if (range.start.row !== range.end.row) return;
  139. if (range.start.row !== this.pos.row) return;
  140. this.$updating = true;
  141. var lengthDiff = delta.action === "insert" ? range.end.column - range.start.column : range.start.column - range.end.column;
  142. var inMainRange = range.start.column >= this.pos.column && range.start.column <= this.pos.column + this.length + 1;
  143. var distanceFromStart = range.start.column - this.pos.column;
  144. this.updateAnchors(delta);
  145. if (inMainRange)
  146. this.length += lengthDiff;
  147. if (inMainRange && !this.session.$fromUndo) {
  148. if (delta.action === 'insert') {
  149. for (var i = this.others.length - 1; i >= 0; i--) {
  150. var otherPos = this.others[i];
  151. var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
  152. this.doc.insertMergedLines(newPos, delta.lines);
  153. }
  154. } else if (delta.action === 'remove') {
  155. for (var i = this.others.length - 1; i >= 0; i--) {
  156. var otherPos = this.others[i];
  157. var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart};
  158. this.doc.remove(new Range(newPos.row, newPos.column, newPos.row, newPos.column - lengthDiff));
  159. }
  160. }
  161. }
  162. this.$updating = false;
  163. this.updateMarkers();
  164. };
  165. this.updateAnchors = function(delta) {
  166. this.pos.onChange(delta);
  167. for (var i = this.others.length; i--;)
  168. this.others[i].onChange(delta);
  169. this.updateMarkers();
  170. };
  171. this.updateMarkers = function() {
  172. if (this.$updating)
  173. return;
  174. var _self = this;
  175. var session = this.session;
  176. var updateMarker = function(pos, className) {
  177. session.removeMarker(pos.markerId);
  178. pos.markerId = session.addMarker(new Range(pos.row, pos.column, pos.row, pos.column+_self.length), className, null, false);
  179. };
  180. updateMarker(this.pos, this.mainClass);
  181. for (var i = this.others.length; i--;)
  182. updateMarker(this.others[i], this.othersClass);
  183. };
  184. /**
  185. * PlaceHolder@onCursorChange(e)
  186. *
  187. * Emitted when the cursor changes.
  188. *
  189. **/
  190. this.onCursorChange = function(event) {
  191. if (this.$updating || !this.session) return;
  192. var pos = this.session.selection.getCursor();
  193. if (pos.row === this.pos.row && pos.column >= this.pos.column && pos.column <= this.pos.column + this.length) {
  194. this.showOtherMarkers();
  195. this._emit("cursorEnter", event);
  196. } else {
  197. this.hideOtherMarkers();
  198. this._emit("cursorLeave", event);
  199. }
  200. };
  201. /**
  202. * PlaceHolder.detach()
  203. *
  204. * TODO
  205. *
  206. **/
  207. this.detach = function() {
  208. this.session.removeMarker(this.pos && this.pos.markerId);
  209. this.hideOtherMarkers();
  210. this.doc.off("change", this.$onUpdate);
  211. this.session.selection.off("changeCursor", this.$onCursorChange);
  212. this.session.setUndoSelect(true);
  213. this.session = null;
  214. };
  215. /**
  216. * PlaceHolder.cancel()
  217. *
  218. * TODO
  219. *
  220. **/
  221. this.cancel = function() {
  222. if (this.$undoStackDepth === -1)
  223. return;
  224. var undoManager = this.session.getUndoManager();
  225. var undosRequired = (undoManager.$undoStack || undoManager.$undostack).length - this.$undoStackDepth;
  226. for (var i = 0; i < undosRequired; i++) {
  227. undoManager.undo(this.session, true);
  228. }
  229. if (this.selectionBefore)
  230. this.session.selection.fromJSON(this.selectionBefore);
  231. };
  232. }).call(PlaceHolder.prototype);
  233. exports.PlaceHolder = PlaceHolder;
  234. });