diff --git a/demo/kitchen-sink/demo.js b/demo/kitchen-sink/demo.js
index 28867a7e..55dad178 100644
--- a/demo/kitchen-sink/demo.js
+++ b/demo/kitchen-sink/demo.js
@@ -66,6 +66,8 @@ var bindDropdown = util.bindDropdown;
var ElasticTabstopsLite = require("ace/ext/elastic_tabstops_lite").ElasticTabstopsLite;
+var IncrementalSearch = require("ace/incremental_search").IncrementalSearch;
+
/*********** create editor ***************************/
var container = document.getElementById("editor-container");
@@ -173,7 +175,7 @@ commands.addCommand({
exec: function() {alert("Fake Save File");}
});
-var keybindings = {
+var keybindings = {
ace: null, // Null = use "default" keymapping
vim: require("ace/keyboard/vim").handler,
emacs: "ace/keyboard/emacs",
@@ -380,7 +382,7 @@ bindDropdown("split", function(value) {
sp.setSplits(1);
} else {
var newEditor = (sp.getSplits() == 1);
- sp.setOrientation(value == "below" ? sp.BELOW : sp.BESIDE);
+ sp.setOrientation(value == "below" ? sp.BELOW : sp.BESIDE);
sp.setSplits(2);
if (newEditor) {
@@ -396,6 +398,14 @@ bindCheckbox("elastic_tabstops", function(checked) {
env.editor.setOption("useElasticTabstops", checked);
});
+var iSearchCheckbox = bindCheckbox("isearch", function(checked) {
+ env.editor.setOption("useIncrementalSearch", checked);
+});
+
+env.editor.addEventListener('incrementalSearchSettingChanged', function(event) {
+ iSearchCheckbox.checked = event.isEnabled;
+});
+
function synchroniseScrolling() {
var s1 = env.split.$editors[0].session;
@@ -451,4 +461,3 @@ var StatusBar = require("./statusbar").StatusBar;
new StatusBar(env.editor, cmdLine.container);
});
-
diff --git a/demo/kitchen-sink/util.js b/demo/kitchen-sink/util.js
index 28525e1a..752e79ba 100644
--- a/demo/kitchen-sink/util.js
+++ b/demo/kitchen-sink/util.js
@@ -171,6 +171,7 @@ exports.bindCheckbox = function(id, callback, noInit) {
};
el.onclick = onCheck;
noInit || onCheck();
+ return el;
};
exports.bindDropdown = function(id, callback, noInit) {
@@ -235,4 +236,3 @@ function dropdown(values) {
});
-
diff --git a/kitchen-sink.html b/kitchen-sink.html
index 614ec7be..86fd256c 100644
--- a/kitchen-sink.html
+++ b/kitchen-sink.html
@@ -250,6 +250,14 @@
+
|
diff --git a/lib/ace/commands/incremental_search_commands.js b/lib/ace/commands/incremental_search_commands.js
new file mode 100644
index 00000000..1cce3912
--- /dev/null
+++ b/lib/ace/commands/incremental_search_commands.js
@@ -0,0 +1,172 @@
+/* ***** 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) {
+
+var config = require("../config");
+
+// These commands can be installed in a normal key handler to start iSearch:
+exports.iSearchStartCommands = [{
+ name: "iSearch",
+ bindKey: {win: "Ctrl-F", mac: "Command-F"},
+ exec: function(editor, options) {
+ config.loadModule(["core", "ace/incremental_search"], function(e) {
+ var iSearch = e.iSearch = e.iSearch || new e.IncrementalSearch();
+ iSearch.activate(editor, options.backwards);
+ if (options.jumpToFirstMatch) iSearch.next(options);
+ });
+ },
+ readOnly: true
+}, {
+ name: "iSearchBackwards",
+ exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {backwards: true}); },
+ readOnly: true
+}, {
+ name: "iSearchAndGo",
+ bindKey: {win: "Ctrl-K", mac: "Command-G"},
+ exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {jumpToFirstMatch: true, useCurrentOrPrevSearch: true}); },
+ readOnly: true
+}, {
+ name: "iSearchBackwardsAndGo",
+ bindKey: {win: "Ctrl-Shift-K", mac: "Command-Shift-G"},
+ exec: function(editor) { editor.execCommand('iSearch', {jumpToFirstMatch: true, backwards: true, useCurrentOrPrevSearch: true}); },
+ readOnly: true
+}];
+
+// These commands are only available when incremental search mode is active:
+exports.iSearchCommands = [{
+ name: "restartSearch",
+ bindKey: {win: "Ctrl-F", mac: "Command-F"},
+ exec: function(iSearch) {
+ iSearch.cancelSearch(true);
+ },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: "searchForward",
+ bindKey: {win: "Ctrl-S|Ctrl-K", mac: "Ctrl-S|Command-G"},
+ exec: function(iSearch, options) {
+ options.useCurrentOrPrevSearch = true;
+ iSearch.next(options);
+ },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: "searchBackward",
+ bindKey: {win: "Ctrl-R|Ctrl-Shift-K", mac: "Ctrl-R|Command-Shift-G"},
+ exec: function(iSearch, options) {
+ options.useCurrentOrPrevSearch = true;
+ options.backwards = true;
+ iSearch.next(options);
+ },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: "extendSearchTerm",
+ exec: function(iSearch, string) {
+ iSearch.addChar(string);
+ },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: "extendSearchTermSpace",
+ bindKey: "space",
+ exec: function(iSearch) { iSearch.addChar(' '); },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: "shrinkSearchTerm",
+ bindKey: "backspace",
+ exec: function(iSearch) {
+ iSearch.removeChar();
+ },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: 'confirmSearch',
+ bindKey: 'return',
+ exec: function(iSearch) { iSearch.deactivate(); },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}, {
+ name: 'cancelSearch',
+ bindKey: 'esc|Ctrl-G',
+ exec: function(iSearch) { iSearch.deactivate(true); },
+ readOnly: true,
+ isIncrementalSearchCommand: true
+}];
+
+
+
+var HashHandler = require("../keyboard/hash_handler").HashHandler;
+var oop = require("../lib/oop");
+
+function IncrementalSearchKeyboardHandler(iSearch) {
+ this.$iSearch = iSearch;
+}
+
+oop.inherits(IncrementalSearchKeyboardHandler, HashHandler);
+
+;(function() {
+
+ this.attach = function(editor) {
+ var iSearch = this.$iSearch;
+ HashHandler.call(this, exports.iSearchCommands, editor.commands.platform);
+ this.$commandExecHandler = editor.commands.addEventListener('exec', function(e) {
+ if (!e.command.isIncrementalSearchCommand) return undefined;
+ e.stopPropagation();
+ e.preventDefault();
+ return e.command.exec(iSearch, e.args || {});
+ });
+ }
+
+ this.detach = function(editor) {
+ if (!this.$commandExecHandler) return;
+ editor.commands.removeEventListener('exec', this.$commandExecHandler);
+ delete this.$commandExecHandler;
+ }
+
+ var handleKeyboard$super = this.handleKeyboard;
+ this.handleKeyboard = function(data, hashId, key, keyCode) {
+ var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode);
+ if (cmd.command) { return cmd; }
+ if (hashId == -1) {
+ var extendCmd = this.commands.extendSearchTerm;
+ if (extendCmd) { return {command: extendCmd, args: key}; }
+ }
+ return {command: "null", passEvent: hashId == 0 || hashId == 4};
+ }
+
+}).call(IncrementalSearchKeyboardHandler.prototype);
+
+
+exports.IncrementalSearchKeyboardHandler = IncrementalSearchKeyboardHandler;
+
+});
diff --git a/lib/ace/editor_highlight_selected_word_test.js b/lib/ace/editor_highlight_selected_word_test.js
index 864de9a1..13e19c23 100644
--- a/lib/ace/editor_highlight_selected_word_test.js
+++ b/lib/ace/editor_highlight_selected_word_test.js
@@ -3,7 +3,7 @@
*
* 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
@@ -14,7 +14,7 @@
* * 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
diff --git a/lib/ace/incremental_search.js b/lib/ace/incremental_search.js
new file mode 100644
index 00000000..a15d7acc
--- /dev/null
+++ b/lib/ace/incremental_search.js
@@ -0,0 +1,257 @@
+/* ***** 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;\
+ border: 8px solid rgba(255, 200, 0, 0.5);\
+ box-shadow: 0 0 4px rgb(255, 200, 0);\
+}\
+.ace_dark div.ace_isearch-result {\
+ border: 8px solid 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});
+ }
+ }
+});
+
+});
diff --git a/lib/ace/incremental_search_test.js b/lib/ace/incremental_search_test.js
new file mode 100644
index 00000000..c351d8e2
--- /dev/null
+++ b/lib/ace/incremental_search_test.js
@@ -0,0 +1,208 @@
+/* ***** 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 ***** */
+
+if (typeof process !== "undefined") {
+ require("amd-loader");
+}
+
+define(function(require, exports, module) {
+"use strict";
+
+var EditSession = require("./edit_session").EditSession;
+var Editor = require("./editor").Editor;
+var MockRenderer = require("./test/mockrenderer").MockRenderer;
+var Range = require("./range").Range;
+var assert = require("./test/assertions");
+var IncrementalSearch = require("./incremental_search").IncrementalSearch;
+
+var editor, iSearch;
+function testRanges(str, ranges) {
+ ranges = ranges || editor.selection.getAllRanges();
+ assert.equal(ranges + "", str + "");
+}
+
+// force "rerender"
+function callHighlighterUpdate() {
+ var session = editor.session,
+ ranges = [],
+ mockMarkerLayer = {
+ drawSingleLineMarker: function(_, markerRanges) {
+ ranges = ranges.concat(markerRanges);
+ }
+ }
+ session.$isearchHighlight.update([], mockMarkerLayer, session, {
+ firstRow: 0, lastRow: session.getRowLength()});
+ return ranges;
+}
+
+module.exports = {
+
+ name: "ACE incremental_search.js",
+
+ setUp: function() {
+ var session = new EditSession(["abc123", "xyz124"]);
+ editor = new Editor(new MockRenderer(), session);
+ iSearch = new IncrementalSearch();
+ },
+
+ "test: keyboard handler setup" : function() {
+ iSearch.activate(editor);
+ assert.equal(editor.getKeyboardHandler(), iSearch.$keyboardHandler);
+ iSearch.deactivate();
+ assert.notEqual(editor.getKeyboardHandler(), iSearch.$keyboardHandler);
+ },
+
+ "test: isearch highlight setup" : function() {
+ var sess = editor.session;
+ iSearch.activate(editor);
+ iSearch.highlight('foo');
+ var highl = sess.$isearchHighlight.id;
+ assert.ok(sess.$isearchHighlight, 'session has no isearch highlighter');
+ assert.equal(sess.getMarkers()[highl.id], highl.id, 'isearch highlight not in markers');
+ iSearch.deactivate();
+ iSearch.activate(editor);
+ iSearch.highlight('bar');
+ var highl2 = sess.$isearchHighlight.id;
+ assert.equal(highl2, highl, 'multiple isearch highlights');
+ },
+
+ "test: find simple text incrementally" : function() {
+ iSearch.activate(editor);
+ var range = iSearch.addChar('1'), // "1"
+ highlightRanges = callHighlighterUpdate(editor.session);
+ testRanges("Range: [0/3] -> [0/4]", [range], "range");
+ testRanges("Range: [0/3] -> [0/4],Range: [1/3] -> [1/4]", highlightRanges, "highlight");
+
+ range = iSearch.addChar('2'); // "12"
+ highlightRanges = callHighlighterUpdate(editor.session);
+ testRanges("Range: [0/3] -> [0/5]", [range], "range");
+ testRanges("Range: [0/3] -> [0/5],Range: [1/3] -> [1/5]", highlightRanges, "highlight");
+
+ range = iSearch.addChar('3'); // "123"
+ highlightRanges = callHighlighterUpdate(editor.session);
+ testRanges("Range: [0/3] -> [0/6]", [range], "range");
+ testRanges("Range: [0/3] -> [0/6]", highlightRanges, "highlight");
+
+ range = iSearch.removeChar(); // "12"
+ highlightRanges = callHighlighterUpdate(editor.session);
+ testRanges("Range: [0/3] -> [0/5]", [range], "range");
+ testRanges("Range: [0/3] -> [0/5],Range: [1/3] -> [1/5]", highlightRanges, "highlight");
+ },
+
+ "test: forward / backward" : function() {
+ iSearch.activate(editor);
+ iSearch.addChar('1'); iSearch.addChar('2');
+ var range = iSearch.next();
+ testRanges("Range: [1/3] -> [1/5]", [range], "range");
+
+ range = iSearch.next(); // nothing to find
+ testRanges("", [range], "range");
+
+ range = iSearch.next({backwards: true}); // backwards
+ testRanges("Range: [1/5] -> [1/3]", [range], "range");
+ },
+
+ "test: cancelSearch" : function() {
+ iSearch.activate(editor);
+ iSearch.addChar('1'); iSearch.addChar('2');
+ var range = iSearch.cancelSearch(true);
+ testRanges("Range: [0/0] -> [0/0]", [range], "range");
+
+ iSearch.addChar('1'); range = iSearch.addChar('2');
+ testRanges("Range: [0/3] -> [0/5]", [range], "range");
+ },
+
+ "test: failing search keeps pos" : function() {
+ iSearch.activate(editor);
+ iSearch.addChar('1'); iSearch.addChar('2');
+ var range = iSearch.addChar('x');
+ testRanges("", [range], "range");
+ assert.position(editor.getCursorPosition(), 0, 5);
+ },
+
+ "test: backwards search" : function() {
+ editor.moveCursorTo(1,0);
+ iSearch.activate(editor, true);
+ iSearch.addChar('1'); var range = iSearch.addChar('2');;
+ testRanges("Range: [0/5] -> [0/3]", [range], "range");
+ assert.position(editor.getCursorPosition(), 0, 3);
+ },
+
+ "test: forwards then backwards, same result, reoriented range" : function() {
+ iSearch.activate(editor);
+ iSearch.addChar('1'); var range = iSearch.addChar('2');;
+ testRanges("Range: [0/3] -> [0/5]", [range], "range");
+ assert.position(editor.getCursorPosition(), 0, 5);
+
+ range = iSearch.next({backwards: true});
+ testRanges("Range: [0/5] -> [0/3]", [range], "range");
+ assert.position(editor.getCursorPosition(), 0, 3);
+ },
+
+ "test: reuse prev search via option" : function() {
+ iSearch.activate(editor);
+ iSearch.addChar('1'); iSearch.addChar('2');;
+ assert.position(editor.getCursorPosition(), 0, 5);
+ iSearch.deactivate();
+
+ iSearch.activate(editor);
+ iSearch.next({backwards: false, useCurrentOrPrevSearch: true});
+ assert.position(editor.getCursorPosition(), 1, 5);
+ },
+
+ "test: don't extend selection range if selection is empty" : function() {
+ iSearch.activate(editor);
+ iSearch.addChar('1'); iSearch.addChar('2');;
+ testRanges("Range: [0/5] -> [0/5]", [editor.getSelectionRange()], "sel range");
+ },
+
+ "test: extend selection range if selection exists" : function() {
+ iSearch.activate(editor);
+ editor.selection.selectTo(0, 1);
+ iSearch.addChar('1'); iSearch.addChar('2');;
+ testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range");
+ },
+
+ "test: extend selection in emacs mark mode" : function() {
+ var emacs = require('./keyboard/emacs');
+ editor.keyBinding.addKeyboardHandler(emacs.handler);
+ emacs.handler.commands.setMark.exec(editor);
+ iSearch.activate(editor);
+ iSearch.addChar('1'); iSearch.addChar('2');;
+ testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range");
+ }
+
+};
+
+});
+
+if (typeof module !== "undefined" && module === require.main) {
+ require("asyncjs").test.testcase(module.exports).exec()
+}
diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js
index 65fc5840..81247be4 100644
--- a/lib/ace/keyboard/emacs.js
+++ b/lib/ace/keyboard/emacs.js
@@ -32,6 +32,9 @@ define(function(require, exports, module) {
"use strict";
var dom = require("../lib/dom");
+require("../incremental_search");
+var iSearchCommandModule = require("../commands/incremental_search_commands");
+
var screenToTextBlockCoordinates = function(x, y) {
var canvasPos = this.scroller.getBoundingClientRect();
@@ -49,6 +52,8 @@ var screenToTextBlockCoordinates = function(x, y) {
var HashHandler = require("./hash_handler").HashHandler;
exports.handler = new HashHandler();
+exports.handler.isEmacs = true
+
var initialized = false;
var $formerLongWords;
var $formerLineStart;
@@ -100,7 +105,7 @@ exports.handler.attach = function(editor) {
}
editor.on("click", $resetMarkMode);
- editor.on("changeSession",$kbSessionChange);
+ editor.on("changeSession", $kbSessionChange);
editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates;
editor.setStyle("emacs-mode");
editor.commands.addCommands(commands);
@@ -296,8 +301,10 @@ exports.emacsKeys = {
"PageUp|M-v": {command: "goorselect", args: ["gotopageup","selectpageup"]},
"S-C-Down": "selectpagedown",
"S-C-Up": "selectpageup",
- "C-s": "findnext",
- "C-r": "findprevious",
+
+ "C-s": "iSearch",
+ "C-r": "iSearchBackwards",
+
"M-C-s": "findnext",
"M-C-r": "findprevious",
"S-M-5": "replace",
@@ -457,6 +464,8 @@ exports.handler.addCommands({
}
});
+exports.handler.addCommands(iSearchCommandModule.iSearchStartCommands);
+
var commands = exports.handler.commands;
commands.yank.isYank = true;
commands.yankRotate.isYank = true;
@@ -482,5 +491,4 @@ exports.killRing = {
}
};
-
});
diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js
index 9a274329..4ce68ae3 100644
--- a/lib/ace/test/all_browser.js
+++ b/lib/ace/test/all_browser.js
@@ -21,6 +21,7 @@ var testNames = [
"ace/editor_navigation_test",
"ace/editor_text_edit_test",
"ace/ext/static_highlight_test",
+ "ace/incremental_search_test",
"ace/keyboard/emacs_test",
"ace/keyboard/keybinding_test",
"ace/layer/text_test",
|