lazy highlighter for selected word

This commit is contained in:
nightwing 2012-05-30 22:45:12 +04:00
commit 9d0f2e41e8
6 changed files with 105 additions and 108 deletions

View file

@ -51,6 +51,7 @@ var TextMode = require("./mode/text").Mode;
var Range = require("./range").Range; var Range = require("./range").Range;
var Document = require("./document").Document; var Document = require("./document").Document;
var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer; var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer;
var SearchHighlight = require("./search_highlight").SearchHighlight;
/** /**
* class EditSession * class EditSession
@ -307,6 +308,13 @@ var EditSession = function(text, mode) {
return token; return token;
}; };
this.highlight = function(re) {
if (!this.$searchHighlight) {
var highlight = new SearchHighlight(null, "ace_selected_word", "text");
this.$searchHighlight = this.addDynamicMarker(highlight);
}
this.$searchHighlight.setRegexp(re);
}
/** /**
* EditSession.setUndoManager(undoManager) * EditSession.setUndoManager(undoManager)
* - undoManager (UndoManager): The new undo manager * - undoManager (UndoManager): The new undo manager
@ -556,7 +564,8 @@ var EditSession = function(text, mode) {
type : type || "line", type : type || "line",
renderer: typeof type == "function" ? type : null, renderer: typeof type == "function" ? type : null,
clazz : clazz, clazz : clazz,
inFront: !!inFront inFront: !!inFront,
id: id
} }
if (inFront) { if (inFront) {
@ -570,6 +579,30 @@ var EditSession = function(text, mode) {
return id; return id;
}; };
/**
* EditSession.addDynamicMarker(marker) -> {update}
* - marker : object with update method
* - inFront (Boolean): Set to `true` to establish a front marker
*
**/
this.addDynamicMarker = function(marker, inFront) {
if (!marker.update)
return;
var id = this.$markerId++;
marker.id = id;
marker.inFront = !!inFront;
if (inFront) {
this.$frontMarkers[id] = marker;
this._emit("changeFrontMarker")
} else {
this.$backMarkers[id] = marker;
this._emit("changeBackMarker")
}
return marker;
};
/** /**
* EditSession.removeMarker(markerId) * EditSession.removeMarker(markerId)
* - markerId (Number): A number representing a marker * - markerId (Number): A number representing a marker
@ -1080,7 +1113,7 @@ var EditSession = function(text, mode) {
* {:Document.getTextRange.desc} * {:Document.getTextRange.desc}
**/ **/
this.getTextRange = function(range) { this.getTextRange = function(range) {
return this.doc.getTextRange(range); return this.doc.getTextRange(range || this.selection.getRange());
}; };
/** related to: Document.insert /** related to: Document.insert

View file

@ -523,7 +523,7 @@ var Editor = function(renderer, session) {
* Emitted when a selection has changed. * Emitted when a selection has changed.
**/ **/
this.onSelectionChange = function(e) { this.onSelectionChange = function(e) {
var session = this.getSession(); var session = this.session;
if (session.$selectionMarker) { if (session.$selectionMarker) {
session.removeMarker(session.$selectionMarker); session.removeMarker(session.$selectionMarker);
@ -538,12 +538,40 @@ var Editor = function(renderer, session) {
this.$updateHighlightActiveLine(); this.$updateHighlightActiveLine();
} }
var self = this; var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp()
if (this.$highlightSelectedWord && !this.$wordHighlightTimer) this.session.highlight(re);
this.$wordHighlightTimer = setTimeout(function() { };
self.session.$mode.highlightSelection(self);
self.$wordHighlightTimer = null; this.$getSelectionHighLightRegexp = function() {
}, 30, this); var session = this.session;
var selection = this.getSelectionRange();
if (selection.isEmpty() || selection.isMultiLine())
return;
var startOuter = selection.start.column - 1;
var endOuter = selection.end.column + 1;
var line = session.getLine(selection.start.row);
var lineCols = line.length;
var needle = line.substring(Math.max(startOuter, 0),
Math.min(endOuter, lineCols));
// Make sure the outer characters are not part of the word.
if ((startOuter >= 0 && /^[\w\d]/.test(needle)) ||
(endOuter <= lineCols && /[\w\d]$/.test(needle)))
return;
needle = line.substring(selection.start.column, selection.end.column);
if (!/^[\w\d]+$/.test(needle))
return;
var re = this.$search.$assembleRegExp({
wholeWord: true,
caseSensitive: true,
needle: needle
});
return re;
}; };
/** /**
@ -917,7 +945,7 @@ var Editor = function(renderer, session) {
this.$highlightSelectedWord = true; this.$highlightSelectedWord = true;
/** /**
* Editor.setHighlightSelectedWord(shouldHighlight) * Editor.setHighlightSelectedWord(shouldHighlight)
* - shouldHighlight (Boolean): Set to `true` to highlight the currently selected word * - shouldHighlight (Boolean): Set to `true` to highlight the currently selected word
* *
* Determines if the currently selected word should be highlighted. * Determines if the currently selected word should be highlighted.
@ -927,20 +955,12 @@ var Editor = function(renderer, session) {
return; return;
this.$highlightSelectedWord = shouldHighlight; this.$highlightSelectedWord = shouldHighlight;
if (shouldHighlight) { this.$onSelectionChange();
this.session.getMode().highlightSelection(this);
} else {
this.session.getMode().clearSelectionHighlight(this);
if (this.$wordHighlightTimer) {
clearTimeout(this.$wordHighlightTimer);
this.$wordHighlightTimer = null;
}
}
}; };
/** /**
* Editor.getHighlightSelectedWord() -> Boolean * Editor.getHighlightSelectedWord() -> Boolean
* *
* Returns `true` if currently highlighted words are to be highlighted. * Returns `true` if currently highlighted words are to be highlighted.
**/ **/
this.getHighlightSelectedWord = function() { this.getHighlightSelectedWord = function() {

View file

@ -70,6 +70,16 @@ var lipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"libero vehicula odio, eget bibendum mauris velit eu lorem.\n" + "libero vehicula odio, eget bibendum mauris velit eu lorem.\n" +
"consectetur"; "consectetur";
function callHighlighterUpdate(session, firstRow, lastRow) {
var rangeCount = 0;
var mockMarkerLayer = { drawSingleLineMarker: function() {rangeCount++;} }
session.$searchHighlight.update([], mockMarkerLayer, session, {
firstRow: firstRow,
lastRow: lastRow
});
return rangeCount;
}
module.exports = { module.exports = {
setUp: function(next) { setUp: function(next) {
this.session = new EditSession(lipsum); this.session = new EditSession(lipsum);
@ -87,55 +97,51 @@ module.exports = {
this.editor.moveCursorTo(0, 9); this.editor.moveCursorTo(0, 9);
this.selection.selectWord(); this.selection.selectWord();
assert.ok(this.editor.$wordHighlightTimer != null); var highlighter = this.editor.session.$searchHighlight;
this.session.$mode.highlightSelection(this.editor); assert.ok(highlighter != null);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "ipsum"); assert.equal(this.session.getTextRange(range), "ipsum");
assert.equal(this.session.$selectionOccurrences.length, 1); assert.equal(highlighter.cache.length, 0);
assert.equal(callHighlighterUpdate(this.session, 0, 0), 2);
}, },
"test: highlight a word and clear highlight": function() { "test: highlight a word and clear highlight": function() {
this.editor.moveCursorTo(0, 8); this.editor.moveCursorTo(0, 8);
this.selection.selectWord(); this.selection.selectWord();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "ipsum"); assert.equal(this.session.getTextRange(range), "ipsum");
assert.equal(this.session.$selectionOccurrences.length, 1); assert.equal(callHighlighterUpdate(this.session, 0, 0), 2);
this.session.getMode().clearSelectionHighlight(this.editor); this.session.highlight("");
assert.equal(this.session.$selectionOccurrences.length, 0); assert.equal(this.session.$searchHighlight.cache.length, 0);
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
}, },
"test: highlight another word": function() { "test: highlight another word": function() {
this.selection.moveCursorTo(0, 14); this.selection.moveCursorTo(0, 14);
this.selection.selectWord(); this.selection.selectWord();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolor"); assert.equal(this.session.getTextRange(range), "dolor");
assert.equal(this.session.$selectionOccurrences.length, 3); assert.equal(callHighlighterUpdate(this.session, 0, 0), 4);
}, },
"test: no selection, no highlight": function() { "test: no selection, no highlight": function() {
this.selection.clearSelection(); this.selection.clearSelection();
this.session.$mode.highlightSelection(this.editor); assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
assert.equal(this.session.$selectionOccurrences.length, 0);
}, },
"test: select a word, no highlight": function() { "test: select a word, no highlight": function() {
this.selection.moveCursorTo(0, 14); this.selection.moveCursorTo(0, 14);
this.selection.selectWord(); this.selection.selectWord();
this.session.$mode.highlightSelection(this.editor);
this.editor.setHighlightSelectedWord(false); this.editor.setHighlightSelectedWord(false);
assert.ok(this.editor.$wordHighlightTimer == null);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolor"); assert.equal(this.session.getTextRange(range), "dolor");
assert.equal(this.session.$selectionOccurrences.length, 0); assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
}, },
"test: select a word with no matches": function() { "test: select a word with no matches": function() {
@ -156,32 +162,29 @@ module.exports = {
this.search.set(currentOptions); this.search.set(currentOptions);
this.selection.setSelectionRange(match); this.selection.setSelectionRange(match);
this.session.$mode.highlightSelection(this.editor);
assert.equal(this.session.getTextRange(match), "Mauris"); assert.equal(this.session.getTextRange(match), "Mauris");
assert.equal(this.session.$selectionOccurrences.length, 0); assert.equal(callHighlighterUpdate(this.session, 0, 0), 1);
}, },
"test: partial word selection 1": function() { "test: partial word selection 1": function() {
this.selection.moveCursorTo(0, 14); this.selection.moveCursorTo(0, 14);
this.selection.selectWord(); this.selection.selectWord();
this.selection.selectLeft(); this.selection.selectLeft();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolo"); assert.equal(this.session.getTextRange(range), "dolo");
assert.equal(this.session.$selectionOccurrences.length, 0); assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
}, },
"test: partial word selection 2": function() { "test: partial word selection 2": function() {
this.selection.moveCursorTo(0, 13); this.selection.moveCursorTo(0, 13);
this.selection.selectWord(); this.selection.selectWord();
this.selection.selectRight(); this.selection.selectRight();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolor "); assert.equal(this.session.getTextRange(range), "dolor ");
assert.equal(this.session.$selectionOccurrences.length, 0); assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
}, },
"test: partial word selection 3": function() { "test: partial word selection 3": function() {
@ -189,11 +192,10 @@ module.exports = {
this.selection.selectWord(); this.selection.selectWord();
this.selection.selectLeft(); this.selection.selectLeft();
this.selection.shiftSelection(1); this.selection.shiftSelection(1);
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange(); var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "olor"); assert.equal(this.session.getTextRange(range), "olor");
assert.equal(this.session.$selectionOccurrences.length, 0); assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
}, },
"test: select last word": function() { "test: select last word": function() {
@ -216,10 +218,9 @@ module.exports = {
this.search.set(currentOptions); this.search.set(currentOptions);
this.selection.setSelectionRange(match); this.selection.setSelectionRange(match);
this.session.$mode.highlightSelection(this.editor);
assert.equal(this.session.getTextRange(match), "consectetur"); assert.equal(this.session.getTextRange(match), "consectetur");
assert.equal(this.session.$selectionOccurrences.length, 2); assert.equal(callHighlighterUpdate(this.session, 0, 1), 3);
} }
}; };

View file

@ -76,6 +76,11 @@ var Marker = function(parentEl) {
for ( var key in this.markers) { for ( var key in this.markers) {
var marker = this.markers[key]; var marker = this.markers[key];
if (!marker.range) {
marker.update(html, this, this.session, config);
continue;
}
var range = marker.range.clipRows(config.firstRow, config.lastRow); var range = marker.range.clipRows(config.firstRow, config.lastRow);
if (range.isEmpty()) continue; if (range.isEmpty()) continue;

View file

@ -98,68 +98,6 @@ var Mode = function() {
return null; return null;
}; };
this.highlightSelection = function(editor) {
var session = editor.session;
if (!session.$selectionOccurrences)
session.$selectionOccurrences = [];
if (session.$selectionOccurrences.length)
this.clearSelectionHighlight(editor);
var selection = editor.getSelectionRange();
if (selection.isEmpty() || selection.isMultiLine())
return;
var startOuter = selection.start.column - 1;
var endOuter = selection.end.column + 1;
var line = session.getLine(selection.start.row);
var lineCols = line.length;
var needle = line.substring(Math.max(startOuter, 0),
Math.min(endOuter, lineCols));
// Make sure the outer characters are not part of the word.
if ((startOuter >= 0 && /^[\w\d]/.test(needle)) ||
(endOuter <= lineCols && /[\w\d]$/.test(needle)))
return;
needle = line.substring(selection.start.column, selection.end.column);
if (!/^[\w\d]+$/.test(needle))
return;
var cursor = editor.getCursorPosition();
var newOptions = {
wrap: true,
wholeWord: true,
caseSensitive: true,
needle: needle
};
var currentOptions = editor.$search.getOptions();
editor.$search.set(newOptions);
var ranges = editor.$search.findAll(session);
ranges.forEach(function(range) {
if (!range.contains(cursor.row, cursor.column)) {
var marker = session.addMarker(range, "ace_selected_word", "text");
session.$selectionOccurrences.push(marker);
}
});
editor.$search.set(currentOptions);
};
this.clearSelectionHighlight = function(editor) {
if (!editor.session.$selectionOccurrences)
return;
editor.session.$selectionOccurrences.forEach(function(marker) {
editor.session.removeMarker(marker);
});
editor.session.$selectionOccurrences = [];
};
this.createModeDelegates = function (mapping) { this.createModeDelegates = function (mapping) {
if (!this.$embeds) { if (!this.$embeds) {
return; return;

View file

@ -54,7 +54,7 @@ var Range = require("./range").Range;
/** /**
* new Search() * new Search()
* *
* Creates a new `Search` object. The following search options ae avaliable: * Creates a new `Search` object. The following search options are avaliable:
* *
* * needle: string or regular expression * * needle: string or regular expression
* * backwards: false * * backwards: false