[incremental search] adding basic functionality

This commit is contained in:
Robert Krahn 2013-03-10 06:32:53 -07:00
commit 3e6a036995
4 changed files with 294 additions and 0 deletions

View file

@ -83,6 +83,9 @@ env.editor.setAnimatedScroll(true);
// add multiple cursor support to editor
require("ace/multi_select").MultiSelect(env.editor);
// add incremental search
window.iSearch = new (require("ace/incremental_search").IncrementalSearch)();
var consoleEl = dom.createElement("div");
container.parentNode.appendChild(consoleEl);
consoleEl.style.cssText = "position:fixed; bottom:1px; right:0;\

View file

@ -0,0 +1,163 @@
/* ***** 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 lang = require("./lib/lang");
var oop = require("./lib/oop");
var Range = require("./range").Range;
var Search = require("./search").Search;
/**
* @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. Options:
*
* @constructor
**/
function IncrementalSearch() {
this.$options = {wrap: false, skipCurrent: false};
this.$keyboardHandler = this;
}
oop.inherits(IncrementalSearch, Search);
;(function() {
var iSearch = this;
iSearch.activate = function(editor) {
this.$editor = editor;
this.$startRange = this.$currentRange = editor.selection.toOrientedRange();
this.installKeyboardHandler(editor);
}
this.deactivate = function() {
this.uninstallKeyboardHandler(this.$editor);
delete this.$editor;
}
iSearch.highlightAndFindWithNeedle = function(dir, moveToMatch, needleUpdateFunc) {
if (!this.$editor) return null;
dir = dir || 'forward';
var session = this.$editor.session,
options = this.$options;
if (needleUpdateFunc) options.needle = needleUpdateFunc(options.needle || '') || '';
if (dir === "forward") {
options.start = this.$currentRange.end;
options.backwards = false;
} else {
options.start = this.$currentRange.start;
options.backwards = true;
}
var range = this.find(session);
if (range && moveToMatch) {
this.$editor.selection.setRange(range);
this.$currentRange = range;
}
session.highlight(options.re);
return range;
}
this.addChar = function(c) {
return this.highlightAndFindWithNeedle('forward', false, function(needle) {
return needle + c;
});
},
iSearch.removeChar = function(c) {
return this.highlightAndFindWithNeedle('forward', false, function(needle) {
return needle.length > 0 ? needle.substring(0, needle.length-1) : needle;
});
}
iSearch.forward = function() {
return this.highlightAndFindWithNeedle('forward', true);
}
iSearch.backward = function() {
return this.highlightAndFindWithNeedle('backward', true);
}
this.installKeyboardHandler = function(editor) {
this.$origKeyboardHandlers = [].concat(editor.keyBinding.$handlers);
this.$origKeyboardHandlers.reverse().forEach(function(handler) {
editor.keyBinding.removeKeyboardHandler(handler);
});
editor.keyBinding.addKeyboardHandler(this.$keyboardHandler);
}
this.uninstallKeyboardHandler = function(editor) {
editor.keyBinding.removeKeyboardHandler(this.$keyboardHandler);
if (this.$origKeyboardHandlers) {
this.$origKeyboardHandlers.forEach(function(handler) {
editor.keyBinding.addKeyboardHandler(handler);
});
delete this.$origKeyboardHandlers;
}
}
iSearch.handleKeyboard = function(data, hashId, key, keyCode) {
console.log("data: %s, hashId: %s, key: %s, keyCode: %s",
data, hashId, key, keyCode);
if (hashId === 0) {
if (key === 'backspace') this.removeChar();
else if (key.length === 1) this.addChar(key);
}
if (hashId === 1) {
if (key === 's') this.forward();
if (key === 'r') this.backward();
}
console.log(this.$options.needle);
return {command: 'null'}
}
}).call(IncrementalSearch.prototype);
exports.IncrementalSearch = IncrementalSearch;
});

View file

@ -0,0 +1,127 @@
/* ***** 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.$searchHighlight.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: 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");
range = iSearch.forward(); // "12", cursor forward
highlightRanges = callHighlighterUpdate(editor.session);
testRanges("Range: [1/3] -> [1/5]", [range], "range");
testRanges("Range: [0/3] -> [0/5],Range: [1/3] -> [1/5]", highlightRanges, "highlight");
}
// // "test: find simple text in document" : function() {
// var session = new EditSession(["juhu kinners 123", "456"]);
// var search = new Search().set({
// needle: "kinners"
// });
// session.getSelection().moveCursorTo(0, 13);
// var range = search.find(session);
// assert.position(range.start, 0, 5);
// assert.position(range.end, 0, 12);
// },
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}

View file

@ -45,6 +45,7 @@ var testNames = [
"ace/range_test",
"ace/range_list_test",
"ace/search_test",
"ace/incremental_search_test",
"ace/selection_test",
"ace/token_iterator_test",
"ace/virtual_renderer_test"