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 Document = require("./document").Document;
var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer;
var SearchHighlight = require("./search_highlight").SearchHighlight;
/**
* class EditSession
@ -307,6 +308,13 @@ var EditSession = function(text, mode) {
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)
* - undoManager (UndoManager): The new undo manager
@ -556,7 +564,8 @@ var EditSession = function(text, mode) {
type : type || "line",
renderer: typeof type == "function" ? type : null,
clazz : clazz,
inFront: !!inFront
inFront: !!inFront,
id: id
}
if (inFront) {
@ -570,6 +579,30 @@ var EditSession = function(text, mode) {
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)
* - markerId (Number): A number representing a marker
@ -1080,7 +1113,7 @@ var EditSession = function(text, mode) {
* {:Document.getTextRange.desc}
**/
this.getTextRange = function(range) {
return this.doc.getTextRange(range);
return this.doc.getTextRange(range || this.selection.getRange());
};
/** related to: Document.insert

View file

@ -523,7 +523,7 @@ var Editor = function(renderer, session) {
* Emitted when a selection has changed.
**/
this.onSelectionChange = function(e) {
var session = this.getSession();
var session = this.session;
if (session.$selectionMarker) {
session.removeMarker(session.$selectionMarker);
@ -538,12 +538,40 @@ var Editor = function(renderer, session) {
this.$updateHighlightActiveLine();
}
var self = this;
if (this.$highlightSelectedWord && !this.$wordHighlightTimer)
this.$wordHighlightTimer = setTimeout(function() {
self.session.$mode.highlightSelection(self);
self.$wordHighlightTimer = null;
}, 30, this);
var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp()
this.session.highlight(re);
};
this.$getSelectionHighLightRegexp = function() {
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;
/**
* Editor.setHighlightSelectedWord(shouldHighlight)
* Editor.setHighlightSelectedWord(shouldHighlight)
* - shouldHighlight (Boolean): Set to `true` to highlight the currently selected word
*
* Determines if the currently selected word should be highlighted.
@ -927,20 +955,12 @@ var Editor = function(renderer, session) {
return;
this.$highlightSelectedWord = shouldHighlight;
if (shouldHighlight) {
this.session.getMode().highlightSelection(this);
} else {
this.session.getMode().clearSelectionHighlight(this);
if (this.$wordHighlightTimer) {
clearTimeout(this.$wordHighlightTimer);
this.$wordHighlightTimer = null;
}
}
this.$onSelectionChange();
};
/**
* Editor.getHighlightSelectedWord() -> Boolean
*
*
* Returns `true` if currently highlighted words are to be highlighted.
**/
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" +
"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 = {
setUp: function(next) {
this.session = new EditSession(lipsum);
@ -87,55 +97,51 @@ module.exports = {
this.editor.moveCursorTo(0, 9);
this.selection.selectWord();
assert.ok(this.editor.$wordHighlightTimer != null);
this.session.$mode.highlightSelection(this.editor);
var highlighter = this.editor.session.$searchHighlight;
assert.ok(highlighter != null);
var range = this.selection.getRange();
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() {
this.editor.moveCursorTo(0, 8);
this.selection.selectWord();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange();
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);
assert.equal(this.session.$selectionOccurrences.length, 0);
this.session.highlight("");
assert.equal(this.session.$searchHighlight.cache.length, 0);
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: highlight another word": function() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange();
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() {
this.selection.clearSelection();
this.session.$mode.highlightSelection(this.editor);
assert.equal(this.session.$selectionOccurrences.length, 0);
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: select a word, no highlight": function() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
this.session.$mode.highlightSelection(this.editor);
this.editor.setHighlightSelectedWord(false);
assert.ok(this.editor.$wordHighlightTimer == null);
var range = this.selection.getRange();
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() {
@ -156,32 +162,29 @@ module.exports = {
this.search.set(currentOptions);
this.selection.setSelectionRange(match);
this.session.$mode.highlightSelection(this.editor);
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() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
this.selection.selectLeft();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange();
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() {
this.selection.moveCursorTo(0, 13);
this.selection.selectWord();
this.selection.selectRight();
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange();
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() {
@ -189,11 +192,10 @@ module.exports = {
this.selection.selectWord();
this.selection.selectLeft();
this.selection.shiftSelection(1);
this.session.$mode.highlightSelection(this.editor);
var range = this.selection.getRange();
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() {
@ -216,10 +218,9 @@ module.exports = {
this.search.set(currentOptions);
this.selection.setSelectionRange(match);
this.session.$mode.highlightSelection(this.editor);
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) {
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);
if (range.isEmpty()) continue;

View file

@ -98,68 +98,6 @@ var Mode = function() {
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) {
if (!this.$embeds) {
return;

View file

@ -54,7 +54,7 @@ var Range = require("./range").Range;
/**
* 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
* * backwards: false