searchbox.js 11 KB

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