searchbox.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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 dom = require("../lib/dom");
  33. var lang = require("../lib/lang");
  34. var event = require("../lib/event");
  35. var searchboxCss = require("../requirejs/text!./searchbox.css");
  36. var HashHandler = require("../keyboard/hash_handler").HashHandler;
  37. var keyUtil = require("../lib/keys");
  38. dom.importCssString(searchboxCss, "ace_searchbox");
  39. var html = '<div class="ace_search right">\
  40. <button type="button" action="hide" class="ace_searchbtn_close"></button>\
  41. <div class="ace_search_form">\
  42. <input class="ace_search_field" placeholder="Search for" spellcheck="false"></input>\
  43. <button type="button" action="findNext" class="ace_searchbtn next"></button>\
  44. <button type="button" action="findPrev" class="ace_searchbtn prev"></button>\
  45. <button type="button" action="findAll" class="ace_searchbtn" title="Alt-Enter">All</button>\
  46. </div>\
  47. <div class="ace_replace_form">\
  48. <input class="ace_search_field" placeholder="Replace with" spellcheck="false"></input>\
  49. <button type="button" action="replaceAndFindNext" class="ace_replacebtn">Replace</button>\
  50. <button type="button" action="replaceAll" class="ace_replacebtn">All</button>\
  51. </div>\
  52. <div class="ace_search_options">\
  53. <span action="toggleRegexpMode" class="ace_button" title="RegExp Search">.*</span>\
  54. <span action="toggleCaseSensitive" class="ace_button" title="CaseSensitive Search">Aa</span>\
  55. <span action="toggleWholeWords" class="ace_button" title="Whole Word Search">\\b</span>\
  56. </div>\
  57. </div>'.replace(/>\s+/g, ">");
  58. var SearchBox = function(editor, range, showReplaceForm) {
  59. var div = dom.createElement("div");
  60. div.innerHTML = html;
  61. this.element = div.firstChild;
  62. this.$init();
  63. this.setEditor(editor);
  64. };
  65. (function() {
  66. this.setEditor = function(editor) {
  67. editor.searchBox = this;
  68. editor.container.appendChild(this.element);
  69. this.editor = editor;
  70. };
  71. this.$initElements = function(sb) {
  72. this.searchBox = sb.querySelector(".ace_search_form");
  73. this.replaceBox = sb.querySelector(".ace_replace_form");
  74. this.searchOptions = sb.querySelector(".ace_search_options");
  75. this.regExpOption = sb.querySelector("[action=toggleRegexpMode]");
  76. this.caseSensitiveOption = sb.querySelector("[action=toggleCaseSensitive]");
  77. this.wholeWordOption = sb.querySelector("[action=toggleWholeWords]");
  78. this.searchInput = this.searchBox.querySelector(".ace_search_field");
  79. this.replaceInput = this.replaceBox.querySelector(".ace_search_field");
  80. };
  81. this.$init = function() {
  82. var sb = this.element;
  83. this.$initElements(sb);
  84. var _this = this;
  85. event.addListener(sb, "mousedown", function(e) {
  86. setTimeout(function(){
  87. _this.activeInput.focus();
  88. }, 0);
  89. event.stopPropagation(e);
  90. });
  91. event.addListener(sb, "click", function(e) {
  92. var t = e.target || e.srcElement;
  93. var action = t.getAttribute("action");
  94. if (action && _this[action])
  95. _this[action]();
  96. else if (_this.$searchBarKb.commands[action])
  97. _this.$searchBarKb.commands[action].exec(_this);
  98. event.stopPropagation(e);
  99. });
  100. event.addCommandKeyListener(sb, function(e, hashId, keyCode) {
  101. var keyString = keyUtil.keyCodeToString(keyCode);
  102. var command = _this.$searchBarKb.findKeyCommand(hashId, keyString);
  103. if (command && command.exec) {
  104. command.exec(_this);
  105. event.stopEvent(e);
  106. }
  107. });
  108. this.$onChange = lang.delayedCall(function() {
  109. _this.find(false, false);
  110. });
  111. event.addListener(this.searchInput, "input", function() {
  112. _this.$onChange.schedule(20);
  113. });
  114. event.addListener(this.searchInput, "focus", function() {
  115. _this.activeInput = _this.searchInput;
  116. _this.searchInput.value && _this.highlight();
  117. });
  118. event.addListener(this.replaceInput, "focus", function() {
  119. _this.activeInput = _this.replaceInput;
  120. _this.searchInput.value && _this.highlight();
  121. });
  122. };
  123. //keybinging outsite of the searchbox
  124. this.$closeSearchBarKb = new HashHandler([{
  125. bindKey: "Esc",
  126. name: "closeSearchBar",
  127. exec: function(editor) {
  128. editor.searchBox.hide();
  129. }
  130. }]);
  131. //keybinging outsite of the searchbox
  132. this.$searchBarKb = new HashHandler();
  133. this.$searchBarKb.bindKeys({
  134. "Ctrl-f|Command-f|Ctrl-H|Command-Option-F": function(sb) {
  135. var isReplace = sb.isReplace = !sb.isReplace;
  136. sb.replaceBox.style.display = isReplace ? "" : "none";
  137. sb[isReplace ? "replaceInput" : "searchInput"].focus();
  138. },
  139. "Ctrl-G|Command-G": function(sb) {
  140. sb.findNext();
  141. },
  142. "Ctrl-Shift-G|Command-Shift-G": function(sb) {
  143. sb.findPrev();
  144. },
  145. "esc": function(sb) {
  146. setTimeout(function() { sb.hide();});
  147. },
  148. "Return": function(sb) {
  149. if (sb.activeInput == sb.replaceInput)
  150. sb.replace();
  151. sb.findNext();
  152. },
  153. "Shift-Return": function(sb) {
  154. if (sb.activeInput == sb.replaceInput)
  155. sb.replace();
  156. sb.findPrev();
  157. },
  158. "Alt-Return": function(sb) {
  159. if (sb.activeInput == sb.replaceInput)
  160. sb.replaceAll();
  161. sb.findAll();
  162. },
  163. "Tab": function(sb) {
  164. (sb.activeInput == sb.replaceInput ? sb.searchInput : sb.replaceInput).focus();
  165. }
  166. });
  167. this.$searchBarKb.addCommands([{
  168. name: "toggleRegexpMode",
  169. bindKey: {win: "Alt-R|Alt-/", mac: "Ctrl-Alt-R|Ctrl-Alt-/"},
  170. exec: function(sb) {
  171. sb.regExpOption.checked = !sb.regExpOption.checked;
  172. sb.$syncOptions();
  173. }
  174. }, {
  175. name: "toggleCaseSensitive",
  176. bindKey: {win: "Alt-C|Alt-I", mac: "Ctrl-Alt-R|Ctrl-Alt-I"},
  177. exec: function(sb) {
  178. sb.caseSensitiveOption.checked = !sb.caseSensitiveOption.checked;
  179. sb.$syncOptions();
  180. }
  181. }, {
  182. name: "toggleWholeWords",
  183. bindKey: {win: "Alt-B|Alt-W", mac: "Ctrl-Alt-B|Ctrl-Alt-W"},
  184. exec: function(sb) {
  185. sb.wholeWordOption.checked = !sb.wholeWordOption.checked;
  186. sb.$syncOptions();
  187. }
  188. }]);
  189. this.$syncOptions = function() {
  190. dom.setCssClass(this.regExpOption, "checked", this.regExpOption.checked);
  191. dom.setCssClass(this.wholeWordOption, "checked", this.wholeWordOption.checked);
  192. dom.setCssClass(this.caseSensitiveOption, "checked", this.caseSensitiveOption.checked);
  193. this.find(false, false);
  194. };
  195. this.highlight = function(re) {
  196. this.editor.session.highlight(re || this.editor.$search.$options.re);
  197. this.editor.renderer.updateBackMarkers()
  198. };
  199. this.find = function(skipCurrent, backwards) {
  200. var range = this.editor.find(this.searchInput.value, {
  201. skipCurrent: skipCurrent,
  202. backwards: backwards,
  203. wrap: true,
  204. regExp: this.regExpOption.checked,
  205. caseSensitive: this.caseSensitiveOption.checked,
  206. wholeWord: this.wholeWordOption.checked
  207. });
  208. var noMatch = !range && this.searchInput.value;
  209. dom.setCssClass(this.searchBox, "ace_nomatch", noMatch);
  210. this.editor._emit("findSearchBox", { match: !noMatch });
  211. this.highlight();
  212. };
  213. this.findNext = function() {
  214. this.find(true, false);
  215. };
  216. this.findPrev = function() {
  217. this.find(true, true);
  218. };
  219. this.findAll = function(){
  220. var range = this.editor.findAll(this.searchInput.value, {
  221. regExp: this.regExpOption.checked,
  222. caseSensitive: this.caseSensitiveOption.checked,
  223. wholeWord: this.wholeWordOption.checked
  224. });
  225. var noMatch = !range && this.searchInput.value;
  226. dom.setCssClass(this.searchBox, "ace_nomatch", noMatch);
  227. this.editor._emit("findSearchBox", { match: !noMatch });
  228. this.highlight();
  229. this.hide();
  230. };
  231. this.replace = function() {
  232. if (!this.editor.getReadOnly())
  233. this.editor.replace(this.replaceInput.value);
  234. };
  235. this.replaceAndFindNext = function() {
  236. if (!this.editor.getReadOnly()) {
  237. this.editor.replace(this.replaceInput.value);
  238. this.findNext()
  239. }
  240. };
  241. this.replaceAll = function() {
  242. if (!this.editor.getReadOnly())
  243. this.editor.replaceAll(this.replaceInput.value);
  244. };
  245. this.hide = function() {
  246. this.element.style.display = "none";
  247. this.editor.keyBinding.removeKeyboardHandler(this.$closeSearchBarKb);
  248. this.editor.focus();
  249. };
  250. this.show = function(value, isReplace) {
  251. this.element.style.display = "";
  252. this.replaceBox.style.display = isReplace ? "" : "none";
  253. this.isReplace = isReplace;
  254. if (value)
  255. this.searchInput.value = value;
  256. this.searchInput.focus();
  257. this.searchInput.select();
  258. this.editor.keyBinding.addKeyboardHandler(this.$closeSearchBarKb);
  259. };
  260. this.isFocused = function() {
  261. var el = document.activeElement;
  262. return el == this.searchInput || el == this.replaceInput;
  263. }
  264. }).call(SearchBox.prototype);
  265. exports.SearchBox = SearchBox;
  266. exports.Search = function(editor, isReplace) {
  267. var sb = editor.searchBox || new SearchBox(editor);
  268. sb.show(editor.session.getTextRange(), isReplace);
  269. };
  270. });
  271. /* ------------------------------------------------------------------------------------------
  272. * TODO
  273. * --------------------------------------------------------------------------------------- */
  274. /*
  275. - move search form to the left if it masks current word
  276. - includ all options that search has. ex: regex
  277. - searchbox.searchbox is not that pretty. we should have just searchbox
  278. - disable prev button if it makes sence
  279. */