language_tools.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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 snippetManager = require("../snippets").snippetManager;
  33. var Autocomplete = require("../autocomplete").Autocomplete;
  34. var config = require("../config");
  35. var lang = require("../lib/lang");
  36. var util = require("../autocomplete/util");
  37. var textCompleter = require("../autocomplete/text_completer");
  38. var keyWordCompleter = {
  39. getCompletions: function(editor, session, pos, prefix, callback) {
  40. if (session.$mode.completer) {
  41. return session.$mode.completer.getCompletions(editor, session, pos, prefix, callback);
  42. }
  43. var state = editor.session.getState(pos.row);
  44. var completions = session.$mode.getCompletions(state, session, pos, prefix);
  45. callback(null, completions);
  46. }
  47. };
  48. var snippetCompleter = {
  49. getCompletions: function(editor, session, pos, prefix, callback) {
  50. var snippetMap = snippetManager.snippetMap;
  51. var completions = [];
  52. snippetManager.getActiveScopes(editor).forEach(function(scope) {
  53. var snippets = snippetMap[scope] || [];
  54. for (var i = snippets.length; i--;) {
  55. var s = snippets[i];
  56. var caption = s.name || s.tabTrigger;
  57. if (!caption)
  58. continue;
  59. completions.push({
  60. caption: caption,
  61. snippet: s.content,
  62. meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet",
  63. type: "snippet"
  64. });
  65. }
  66. }, this);
  67. callback(null, completions);
  68. },
  69. getDocTooltip: function(item) {
  70. if (item.type == "snippet" && !item.docHTML) {
  71. item.docHTML = [
  72. "<b>", lang.escapeHTML(item.caption), "</b>", "<hr></hr>",
  73. lang.escapeHTML(item.snippet)
  74. ].join("");
  75. }
  76. }
  77. };
  78. var completers = [snippetCompleter, textCompleter, keyWordCompleter];
  79. // Allows default completers to be removed or replaced with a explict set of completers
  80. // A null argument here will result in an empty completer array, not a null attribute
  81. exports.setCompleters = function(val) {
  82. completers = val || [];
  83. };
  84. exports.addCompleter = function(completer) {
  85. completers.push(completer);
  86. };
  87. // Exports existing completer so that user can construct his own set of completers.
  88. exports.textCompleter = textCompleter;
  89. exports.keyWordCompleter = keyWordCompleter;
  90. exports.snippetCompleter = snippetCompleter;
  91. var expandSnippet = {
  92. name: "expandSnippet",
  93. exec: function(editor) {
  94. return snippetManager.expandWithTab(editor);
  95. },
  96. bindKey: "Tab"
  97. };
  98. var onChangeMode = function(e, editor) {
  99. loadSnippetsForMode(editor.session.$mode);
  100. };
  101. var loadSnippetsForMode = function(mode) {
  102. var id = mode.$id;
  103. if (!snippetManager.files)
  104. snippetManager.files = {};
  105. loadSnippetFile(id);
  106. if (mode.modes)
  107. mode.modes.forEach(loadSnippetsForMode);
  108. };
  109. var loadSnippetFile = function(id) {
  110. if (!id || snippetManager.files[id])
  111. return;
  112. var snippetFilePath = id.replace("mode", "snippets");
  113. snippetManager.files[id] = {};
  114. config.loadModule(snippetFilePath, function(m) {
  115. if (m) {
  116. snippetManager.files[id] = m;
  117. if (!m.snippets && m.snippetText)
  118. m.snippets = snippetManager.parseSnippetFile(m.snippetText);
  119. snippetManager.register(m.snippets || [], m.scope);
  120. if (m.includeScopes) {
  121. snippetManager.snippetMap[m.scope].includeScopes = m.includeScopes;
  122. m.includeScopes.forEach(function(x) {
  123. loadSnippetFile("ace/mode/" + x);
  124. });
  125. }
  126. }
  127. });
  128. };
  129. function getCompletionPrefix(editor) {
  130. var pos = editor.getCursorPosition();
  131. var line = editor.session.getLine(pos.row);
  132. var prefix;
  133. // Try to find custom prefixes on the completers
  134. editor.completers.forEach(function(completer) {
  135. if (completer.identifierRegexps) {
  136. completer.identifierRegexps.forEach(function(identifierRegex) {
  137. if (!prefix && identifierRegex)
  138. prefix = util.retrievePrecedingIdentifier(line, pos.column, identifierRegex);
  139. });
  140. }
  141. });
  142. return prefix || util.retrievePrecedingIdentifier(line, pos.column);
  143. }
  144. var doLiveAutocomplete = function(e) {
  145. var editor = e.editor;
  146. var prefix = getCompletionPrefix(editor);
  147. if (editor.useHueAutocompleter) {
  148. if (prefix && e.command.name === "insertstring") {
  149. var renderer = editor.renderer;
  150. var lineHeight = renderer.layerConfig.lineHeight;
  151. var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
  152. var rect = editor.container.getBoundingClientRect();
  153. pos.top += rect.top - renderer.layerConfig.offset;
  154. pos.left += rect.left - editor.renderer.scrollLeft;
  155. pos.left += renderer.gutterWidth;
  156. huePubSub.publish('hue.ace.autocompleter.show', { position: pos, lineHeight: lineHeight });
  157. } else if (e.command.name === "backspace" && !prefix) {
  158. huePubSub.publish('hue.ace.autocompleter.hide');
  159. }
  160. return;
  161. }
  162. var hasCompleter = editor.completer && editor.completer.activated;
  163. // We don't want to autocomplete with no prefix
  164. if (e.command.name === "backspace") {
  165. if (hasCompleter && !prefix)
  166. editor.completer.detach();
  167. }
  168. else if (e.command.name === "insertstring") {
  169. // Only autocomplete if there's a prefix that can be matched
  170. if (prefix && !hasCompleter) {
  171. if (!editor.completer) {
  172. // Create new autocompleter
  173. editor.completer = new Autocomplete();
  174. }
  175. // Disable autoInsert
  176. editor.completer.autoInsert = false;
  177. editor.completer.showPopup(editor);
  178. }
  179. }
  180. };
  181. var Editor = require("../editor").Editor;
  182. require("../config").defineOptions(Editor.prototype, "editor", {
  183. enableBasicAutocompletion: {
  184. set: function(val) {
  185. if (val) {
  186. if (!this.completers)
  187. this.completers = Array.isArray(val)? val: completers;
  188. this.commands.addCommand(Autocomplete.startCommand);
  189. } else {
  190. this.commands.removeCommand(Autocomplete.startCommand);
  191. }
  192. },
  193. value: false
  194. },
  195. /**
  196. * Enable live autocomplete. If the value is an array, it is assumed to be an array of completers
  197. * and will use them instead of the default completers.
  198. */
  199. enableLiveAutocompletion: {
  200. set: function(val) {
  201. if (val) {
  202. if (!this.completers)
  203. this.completers = Array.isArray(val)? val: completers;
  204. // On each change automatically trigger the autocomplete
  205. this.commands.on('afterExec', doLiveAutocomplete);
  206. } else {
  207. this.commands.removeListener('afterExec', doLiveAutocomplete);
  208. }
  209. },
  210. value: false
  211. },
  212. enableSnippets: {
  213. set: function(val) {
  214. if (val) {
  215. this.commands.addCommand(expandSnippet);
  216. this.on("changeMode", onChangeMode);
  217. onChangeMode(null, this);
  218. } else {
  219. this.commands.removeCommand(expandSnippet);
  220. this.off("changeMode", onChangeMode);
  221. }
  222. },
  223. value: false
  224. }
  225. });
  226. });