From 9d0f2e41e8e54ee33f9915c9b387dc32e62923ba Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 30 May 2012 22:45:12 +0400 Subject: [PATCH] lazy highlighter for selected word --- lib/ace/edit_session.js | 37 ++++++++++- lib/ace/editor.js | 56 +++++++++++------ .../editor_highlight_selected_word_test.js | 51 +++++++-------- lib/ace/layer/marker.js | 5 ++ lib/ace/mode/text.js | 62 ------------------- lib/ace/search.js | 2 +- 6 files changed, 105 insertions(+), 108 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 23fc3fcd..ed3b2736 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -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 diff --git a/lib/ace/editor.js b/lib/ace/editor.js index c238dec4..a8efe242 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -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() { diff --git a/lib/ace/editor_highlight_selected_word_test.js b/lib/ace/editor_highlight_selected_word_test.js index 3cdaddd3..c18a78bb 100644 --- a/lib/ace/editor_highlight_selected_word_test.js +++ b/lib/ace/editor_highlight_selected_word_test.js @@ -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); } }; diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 990af0ad..f5999338 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -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; diff --git a/lib/ace/mode/text.js b/lib/ace/mode/text.js index 49de33e7..094432bf 100644 --- a/lib/ace/mode/text.js +++ b/lib/ace/mode/text.js @@ -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; diff --git a/lib/ace/search.js b/lib/ace/search.js index 38e96ea6..c03391e2 100644 --- a/lib/ace/search.js +++ b/lib/ace/search.js @@ -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