popup.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2012, 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 Renderer = require("../virtual_renderer").VirtualRenderer;
  33. var Editor = require("../editor").Editor;
  34. var Range = require("../range").Range;
  35. var event = require("../lib/event");
  36. var lang = require("../lib/lang");
  37. var dom = require("../lib/dom");
  38. var $singleLineEditor = function(el) {
  39. var renderer = new Renderer(el);
  40. renderer.$maxLines = 4;
  41. var editor = new Editor(renderer);
  42. editor.setHighlightActiveLine(false);
  43. editor.setShowPrintMargin(false);
  44. editor.renderer.setShowGutter(false);
  45. editor.renderer.setHighlightGutterLine(false);
  46. editor.$mouseHandler.$focusWaitTimout = 0;
  47. editor.$highlightTagPending = true;
  48. return editor;
  49. };
  50. var AcePopup = function(parentNode) {
  51. var el = dom.createElement("div");
  52. var popup = new $singleLineEditor(el);
  53. if (parentNode)
  54. parentNode.appendChild(el);
  55. el.style.display = "none";
  56. popup.renderer.content.style.cursor = "default";
  57. popup.renderer.setStyle("ace_autocomplete");
  58. popup.setOption("displayIndentGuides", false);
  59. popup.setOption("dragDelay", 150);
  60. var noop = function(){};
  61. popup.focus = noop;
  62. popup.$isFocused = true;
  63. popup.renderer.$cursorLayer.restartTimer = noop;
  64. popup.renderer.$cursorLayer.element.style.opacity = 0;
  65. popup.renderer.$maxLines = 8;
  66. popup.renderer.$keepTextAreaAtCursor = false;
  67. popup.setHighlightActiveLine(false);
  68. // set default highlight color
  69. popup.session.highlight("");
  70. popup.session.$searchHighlight.clazz = "ace_highlight-marker";
  71. popup.on("mousedown", function(e) {
  72. var pos = e.getDocumentPosition();
  73. popup.selection.moveToPosition(pos);
  74. selectionMarker.start.row = selectionMarker.end.row = pos.row;
  75. e.stop();
  76. });
  77. var lastMouseEvent;
  78. var hoverMarker = new Range(-1,0,-1,Infinity);
  79. var selectionMarker = new Range(-1,0,-1,Infinity);
  80. selectionMarker.id = popup.session.addMarker(selectionMarker, "ace_active-line", "fullLine");
  81. popup.setSelectOnHover = function(val) {
  82. if (!val) {
  83. hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine");
  84. } else if (hoverMarker.id) {
  85. popup.session.removeMarker(hoverMarker.id);
  86. hoverMarker.id = null;
  87. }
  88. };
  89. popup.setSelectOnHover(false);
  90. popup.on("mousemove", function(e) {
  91. if (!lastMouseEvent) {
  92. lastMouseEvent = e;
  93. return;
  94. }
  95. if (lastMouseEvent.x == e.x && lastMouseEvent.y == e.y) {
  96. return;
  97. }
  98. lastMouseEvent = e;
  99. lastMouseEvent.scrollTop = popup.renderer.scrollTop;
  100. var row = lastMouseEvent.getDocumentPosition().row;
  101. if (hoverMarker.start.row != row) {
  102. if (!hoverMarker.id)
  103. popup.setRow(row);
  104. setHoverMarker(row);
  105. }
  106. });
  107. popup.renderer.on("beforeRender", function() {
  108. if (lastMouseEvent && hoverMarker.start.row != -1) {
  109. lastMouseEvent.$pos = null;
  110. var row = lastMouseEvent.getDocumentPosition().row;
  111. if (!hoverMarker.id)
  112. popup.setRow(row);
  113. setHoverMarker(row, true);
  114. }
  115. });
  116. popup.renderer.on("afterRender", function() {
  117. var row = popup.getRow();
  118. var t = popup.renderer.$textLayer;
  119. var selected = t.element.childNodes[row - t.config.firstRow];
  120. if (selected == t.selectedNode)
  121. return;
  122. if (t.selectedNode)
  123. dom.removeCssClass(t.selectedNode, "ace_selected");
  124. t.selectedNode = selected;
  125. if (selected)
  126. dom.addCssClass(selected, "ace_selected");
  127. });
  128. var hideHoverMarker = function() { setHoverMarker(-1) };
  129. var setHoverMarker = function(row, suppressRedraw) {
  130. if (row !== hoverMarker.start.row) {
  131. hoverMarker.start.row = hoverMarker.end.row = row;
  132. if (!suppressRedraw)
  133. popup.session._emit("changeBackMarker");
  134. popup._emit("changeHoverMarker");
  135. }
  136. };
  137. popup.getHoveredRow = function() {
  138. return hoverMarker.start.row;
  139. };
  140. event.addListener(popup.container, "mouseout", hideHoverMarker);
  141. popup.on("hide", hideHoverMarker);
  142. popup.on("changeSelection", hideHoverMarker);
  143. popup.session.doc.getLength = function() {
  144. return popup.data.length;
  145. };
  146. popup.session.doc.getLine = function(i) {
  147. var data = popup.data[i];
  148. if (typeof data == "string")
  149. return data;
  150. return (data && data.value) || "";
  151. };
  152. var bgTokenizer = popup.session.bgTokenizer;
  153. bgTokenizer.$tokenizeRow = function(row) {
  154. var data = popup.data[row];
  155. var tokens = [];
  156. if (!data)
  157. return tokens;
  158. if (typeof data == "string")
  159. data = {value: data};
  160. if (!data.caption)
  161. data.caption = data.value || data.name;
  162. var last = -1;
  163. var flag, c;
  164. for (var i = 0; i < data.caption.length; i++) {
  165. c = data.caption[i];
  166. flag = data.matchMask & (1 << i) ? 1 : 0;
  167. if (last !== flag) {
  168. tokens.push({type: data.className || "" + ( flag ? "completion-highlight" : ""), value: c});
  169. last = flag;
  170. } else {
  171. tokens[tokens.length - 1].value += c;
  172. }
  173. }
  174. if (data.meta) {
  175. var maxW = popup.renderer.$size.scrollerWidth / popup.renderer.layerConfig.characterWidth;
  176. var metaData = data.meta;
  177. if (metaData.length + data.caption.length > maxW - 2) {
  178. // trim meta to fit this popup and add ellipsis
  179. metaData = metaData.substr(0, maxW - data.caption.length - 3) + "\u2026"
  180. }
  181. tokens.push({type: "rightAlignedText", value: metaData});
  182. }
  183. return tokens;
  184. };
  185. bgTokenizer.$updateOnChange = noop;
  186. bgTokenizer.start = noop;
  187. popup.session.$computeWidth = function() {
  188. return this.screenWidth = 0;
  189. };
  190. popup.$blockScrolling = Infinity;
  191. // public
  192. popup.isOpen = false;
  193. popup.isTopdown = false;
  194. popup.data = [];
  195. popup.setData = function(list) {
  196. popup.setValue(lang.stringRepeat("\n", list.length), -1);
  197. popup.data = list || [];
  198. popup.setRow(0);
  199. };
  200. popup.getData = function(row) {
  201. return popup.data[row];
  202. };
  203. popup.getRow = function() {
  204. return selectionMarker.start.row;
  205. };
  206. popup.setRow = function(line) {
  207. line = Math.max(0, Math.min(this.data.length, line));
  208. if (selectionMarker.start.row != line) {
  209. popup.selection.clearSelection();
  210. selectionMarker.start.row = selectionMarker.end.row = line || 0;
  211. popup.session._emit("changeBackMarker");
  212. popup.moveCursorTo(line || 0, 0);
  213. if (popup.isOpen)
  214. popup._signal("select");
  215. }
  216. };
  217. popup.on("changeSelection", function() {
  218. if (popup.isOpen)
  219. popup.setRow(popup.selection.lead.row);
  220. popup.renderer.scrollCursorIntoView();
  221. });
  222. popup.hide = function() {
  223. this.container.style.display = "none";
  224. this._signal("hide");
  225. popup.isOpen = false;
  226. };
  227. popup.show = function(pos, lineHeight, topdownOnly) {
  228. var el = this.container;
  229. var screenHeight = window.innerHeight;
  230. var screenWidth = window.innerWidth;
  231. var renderer = this.renderer;
  232. // var maxLines = Math.min(renderer.$maxLines, this.session.getLength());
  233. var maxH = renderer.$maxLines * lineHeight * 1.4;
  234. var top = pos.top + this.$borderSize;
  235. if (top + maxH > screenHeight - lineHeight && !topdownOnly) {
  236. el.style.top = "";
  237. el.style.bottom = screenHeight - top + "px";
  238. popup.isTopdown = false;
  239. } else {
  240. top += lineHeight;
  241. el.style.top = top + "px";
  242. el.style.bottom = "";
  243. popup.isTopdown = true;
  244. }
  245. el.style.display = "";
  246. this.renderer.$textLayer.checkForSizeChanges();
  247. var left = pos.left;
  248. if (left + el.offsetWidth > screenWidth)
  249. left = screenWidth - el.offsetWidth;
  250. el.style.left = left + "px";
  251. this._signal("show");
  252. lastMouseEvent = null;
  253. popup.isOpen = true;
  254. };
  255. popup.getTextLeftOffset = function() {
  256. return this.$borderSize + this.renderer.$padding + this.$imageSize;
  257. };
  258. popup.$imageSize = 0;
  259. popup.$borderSize = 1;
  260. return popup;
  261. };
  262. dom.importCssString("\
  263. .ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\
  264. background-color: #CAD6FA;\
  265. z-index: 1;\
  266. }\
  267. .ace_editor.ace_autocomplete .ace_line-hover {\
  268. border: 1px solid #abbffe;\
  269. margin-top: -1px;\
  270. background: rgba(233,233,253,0.4);\
  271. }\
  272. .ace_editor.ace_autocomplete .ace_line-hover {\
  273. position: absolute;\
  274. z-index: 2;\
  275. }\
  276. .ace_editor.ace_autocomplete .ace_scroller {\
  277. background: none;\
  278. border: none;\
  279. box-shadow: none;\
  280. }\
  281. .ace_rightAlignedText {\
  282. color: gray;\
  283. display: inline-block;\
  284. position: absolute;\
  285. right: 4px;\
  286. text-align: right;\
  287. z-index: -1;\
  288. }\
  289. .ace_editor.ace_autocomplete .ace_completion-highlight{\
  290. color: #000;\
  291. text-shadow: 0 0 0.01em;\
  292. }\
  293. .ace_editor.ace_autocomplete {\
  294. width: 280px;\
  295. z-index: 200000;\
  296. background: #fbfbfb;\
  297. color: #444;\
  298. border: 1px lightgray solid;\
  299. position: fixed;\
  300. box-shadow: 2px 3px 5px rgba(0,0,0,.2);\
  301. line-height: 1.4;\
  302. }");
  303. exports.AcePopup = AcePopup;
  304. });