| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- /* ***** BEGIN LICENSE BLOCK *****
- * Distributed under the BSD license:
- *
- * Copyright (c) 2010, Ajax.org B.V.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of Ajax.org B.V. nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * ***** END LICENSE BLOCK ***** */
- define(function(require, exports, module) {
- "use strict";
- var oop = require("./lib/oop");
- var Range = require("./range").Range;
- var Search = require("./search").Search;
- var SearchHighlight = require("./search_highlight").SearchHighlight;
- var iSearchCommandModule = require("./commands/incremental_search_commands");
- var ISearchKbd = iSearchCommandModule.IncrementalSearchKeyboardHandler;
- /**
- * @class IncrementalSearch
- *
- * Implements immediate searching while the user is typing. When incremental
- * search is activated, keystrokes into the editor will be used for composing
- * a search term. Immediately after every keystroke the search is updated:
- * - so-far-matching characters are highlighted
- * - the cursor is moved to the next match
- *
- **/
- /**
- *
- *
- * Creates a new `IncrementalSearch` object.
- *
- * @constructor
- **/
- function IncrementalSearch() {
- this.$options = {wrap: false, skipCurrent: false};
- this.$keyboardHandler = new ISearchKbd(this);
- }
- oop.inherits(IncrementalSearch, Search);
- // regexp handling
- function isRegExp(obj) {
- return obj instanceof RegExp;
- }
- function regExpToObject(re) {
- var string = String(re),
- start = string.indexOf('/'),
- flagStart = string.lastIndexOf('/');
- return {
- expression: string.slice(start+1, flagStart),
- flags: string.slice(flagStart+1)
- }
- }
- function stringToRegExp(string, flags) {
- try {
- return new RegExp(string, flags);
- } catch (e) { return string; }
- }
- function objectToRegExp(obj) {
- return stringToRegExp(obj.expression, obj.flags);
- }
- // iSearch class
- ;(function() {
- this.activate = function(ed, backwards) {
- this.$editor = ed;
- this.$startPos = this.$currentPos = ed.getCursorPosition();
- this.$options.needle = '';
- this.$options.backwards = backwards;
- ed.keyBinding.addKeyboardHandler(this.$keyboardHandler);
- // we need to completely intercept paste, just registering an event handler does not work
- this.$originalEditorOnPaste = ed.onPaste; ed.onPaste = this.onPaste.bind(this);
- this.$mousedownHandler = ed.addEventListener('mousedown', this.onMouseDown.bind(this));
- this.selectionFix(ed);
- this.statusMessage(true);
- };
- this.deactivate = function(reset) {
- this.cancelSearch(reset);
- var ed = this.$editor;
- ed.keyBinding.removeKeyboardHandler(this.$keyboardHandler);
- if (this.$mousedownHandler) {
- ed.removeEventListener('mousedown', this.$mousedownHandler);
- delete this.$mousedownHandler;
- }
- ed.onPaste = this.$originalEditorOnPaste;
- this.message('');
- };
- this.selectionFix = function(editor) {
- // Fix selection bug: When clicked inside the editor
- // editor.selection.$isEmpty is false even if the mouse click did not
- // open a selection. This is interpreted by the move commands to
- // extend the selection. To only extend the selection when there is
- // one, we clear it here
- if (editor.selection.isEmpty() && !editor.session.$emacsMark) {
- editor.clearSelection();
- }
- };
- this.highlight = function(regexp) {
- var sess = this.$editor.session,
- hl = sess.$isearchHighlight = sess.$isearchHighlight || sess.addDynamicMarker(
- new SearchHighlight(null, "ace_isearch-result", "text"));
- hl.setRegexp(regexp);
- sess._emit("changeBackMarker"); // force highlight layer redraw
- };
- this.cancelSearch = function(reset) {
- var e = this.$editor;
- this.$prevNeedle = this.$options.needle;
- this.$options.needle = '';
- if (reset) {
- e.moveCursorToPosition(this.$startPos);
- this.$currentPos = this.$startPos;
- } else {
- e.pushEmacsMark && e.pushEmacsMark(this.$startPos, false);
- }
- this.highlight(null);
- return Range.fromPoints(this.$currentPos, this.$currentPos);
- };
- this.highlightAndFindWithNeedle = function(moveToNext, needleUpdateFunc) {
- if (!this.$editor) return null;
- var options = this.$options;
- // get search term
- if (needleUpdateFunc) {
- options.needle = needleUpdateFunc.call(this, options.needle || '') || '';
- }
- if (options.needle.length === 0) {
- this.statusMessage(true);
- return this.cancelSearch(true);
- }
- // try to find the next occurence and enable highlighting marker
- options.start = this.$currentPos;
- var session = this.$editor.session,
- found = this.find(session),
- shouldSelect = this.$editor.emacsMark ?
- !!this.$editor.emacsMark() : !this.$editor.selection.isEmpty();
- if (found) {
- if (options.backwards) found = Range.fromPoints(found.end, found.start);
- this.$editor.selection.setRange(Range.fromPoints(shouldSelect ? this.$startPos : found.end, found.end));
- if (moveToNext) this.$currentPos = found.end;
- // highlight after cursor move, so selection works properly
- this.highlight(options.re);
- }
- this.statusMessage(found);
- return found;
- };
- this.addString = function(s) {
- return this.highlightAndFindWithNeedle(false, function(needle) {
- if (!isRegExp(needle))
- return needle + s;
- var reObj = regExpToObject(needle);
- reObj.expression += s;
- return objectToRegExp(reObj);
- });
- };
- this.removeChar = function(c) {
- return this.highlightAndFindWithNeedle(false, function(needle) {
- if (!isRegExp(needle))
- return needle.substring(0, needle.length-1);
- var reObj = regExpToObject(needle);
- reObj.expression = reObj.expression.substring(0, reObj.expression.length-1);
- return objectToRegExp(reObj);
- });
- };
- this.next = function(options) {
- // try to find the next occurence of whatever we have searched for
- // earlier.
- // options = {[backwards: BOOL], [useCurrentOrPrevSearch: BOOL]}
- options = options || {};
- this.$options.backwards = !!options.backwards;
- this.$currentPos = this.$editor.getCursorPosition();
- return this.highlightAndFindWithNeedle(true, function(needle) {
- return options.useCurrentOrPrevSearch && needle.length === 0 ?
- this.$prevNeedle || '' : needle;
- });
- };
- this.onMouseDown = function(evt) {
- // when mouse interaction happens then we quit incremental search
- this.deactivate();
- return true;
- };
- this.onPaste = function(text) {
- this.addString(text);
- };
- this.convertNeedleToRegExp = function() {
- return this.highlightAndFindWithNeedle(false, function(needle) {
- return isRegExp(needle) ? needle : stringToRegExp(needle, 'ig');
- });
- };
- this.convertNeedleToString = function() {
- return this.highlightAndFindWithNeedle(false, function(needle) {
- return isRegExp(needle) ? regExpToObject(needle).expression : needle;
- });
- };
- this.statusMessage = function(found) {
- var options = this.$options, msg = '';
- msg += options.backwards ? 'reverse-' : '';
- msg += 'isearch: ' + options.needle;
- msg += found ? '' : ' (not found)';
- this.message(msg);
- };
- this.message = function(msg) {
- if (this.$editor.showCommandLine) {
- this.$editor.showCommandLine(msg);
- this.$editor.focus();
- } else {
- console.log(msg);
- }
- };
- }).call(IncrementalSearch.prototype);
- exports.IncrementalSearch = IncrementalSearch;
- /**
- *
- * Config settings for enabling/disabling [[IncrementalSearch `IncrementalSearch`]].
- *
- **/
- var dom = require('./lib/dom');
- dom.importCssString && dom.importCssString("\
- .ace_marker-layer .ace_isearch-result {\
- position: absolute;\
- z-index: 6;\
- -moz-box-sizing: border-box;\
- -webkit-box-sizing: border-box;\
- box-sizing: border-box;\
- }\
- div.ace_isearch-result {\
- border-radius: 4px;\
- background-color: rgba(255, 200, 0, 0.5);\
- box-shadow: 0 0 4px rgb(255, 200, 0);\
- }\
- .ace_dark div.ace_isearch-result {\
- background-color: rgb(100, 110, 160);\
- box-shadow: 0 0 4px rgb(80, 90, 140);\
- }", "incremental-search-highlighting");
- // support for default keyboard handler
- var commands = require("./commands/command_manager");
- (function() {
- this.setupIncrementalSearch = function(editor, val) {
- if (this.usesIncrementalSearch == val) return;
- this.usesIncrementalSearch = val;
- var iSearchCommands = iSearchCommandModule.iSearchStartCommands;
- var method = val ? 'addCommands' : 'removeCommands';
- this[method](iSearchCommands);
- };
- }).call(commands.CommandManager.prototype);
- // incremental search config option
- var Editor = require("./editor").Editor;
- require("./config").defineOptions(Editor.prototype, "editor", {
- useIncrementalSearch: {
- set: function(val) {
- this.keyBinding.$handlers.forEach(function(handler) {
- if (handler.setupIncrementalSearch) {
- handler.setupIncrementalSearch(this, val);
- }
- });
- this._emit('incrementalSearchSettingChanged', {isEnabled: val});
- }
- }
- });
- });
|