text: /* ***** 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);
  
  ;(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);
          this.$mousedownHandler = ed.addEventListener('mousedown', this.onMouseDown.bind(this));
          this.selectionFix(ed);
          this.statusMessage(true);
      }
  
      this.deactivate = function(reset) {
          this.cancelSearch(reset);
          this.$editor.keyBinding.removeKeyboardHandler(this.$keyboardHandler);
          if (this.$mousedownHandler) {
              this.$editor.removeEventListener('mousedown', this.$mousedownHandler);
              delete this.$mousedownHandler;
          }
          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;
          }
          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);
          if (found) {
              if (options.backwards) found = Range.fromPoints(found.end, found.start);
              this.$editor.moveCursorToPosition(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.addChar = function(c) {
          return this.highlightAndFindWithNeedle(false, function(needle) {
              return needle + c;
          });
      }
  
      this.removeChar = function(c) {
          return this.highlightAndFindWithNeedle(false, function(needle) {
              return needle.length > 0 ? needle.substring(0, needle.length-1) : needle;
          });
      }
  
      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.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});
          }
      }
  });
  
  });
  