mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
260 lines
9 KiB
JavaScript
260 lines
9 KiB
JavaScript
|
/* ***** 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;
|
||
|
} 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);
|
||
|
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});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
});
|