lazy highlighter for selected word
This commit is contained in:
parent
b1bb0b9ccc
commit
9d0f2e41e8
6 changed files with 105 additions and 108 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue