| 1 |
// Highlighting text that matches the selection |
| 2 |
// |
| 3 |
// Defines an option highlightSelectionMatches, which, when enabled, |
| 4 |
// will style strings that match the selection throughout the |
| 5 |
// document. |
| 6 |
// |
| 7 |
// The option can be set to true to simply enable it, or to a |
| 8 |
// {minChars, style, showToken} object to explicitly configure it. |
| 9 |
// minChars is the minimum amount of characters that should be |
| 10 |
// selected for the behavior to occur, and style is the token style to |
| 11 |
// apply to the matches. This will be prefixed by "cm-" to create an |
| 12 |
// actual CSS class name. showToken, when enabled, will cause the |
| 13 |
// current token to be highlighted when nothing is selected. |
| 14 |
|
| 15 |
(function() { |
| 16 |
var DEFAULT_MIN_CHARS = 2; |
| 17 |
var DEFAULT_TOKEN_STYLE = "matchhighlight"; |
| 18 |
var DEFAULT_DELAY = 100; |
| 19 |
|
| 20 |
function State(options) { |
| 21 |
if (typeof options == "object") { |
| 22 |
this.minChars = options.minChars; |
| 23 |
this.style = options.style; |
| 24 |
this.showToken = options.showToken; |
| 25 |
this.delay = options.delay; |
| 26 |
} |
| 27 |
if (this.style == null) this.style = DEFAULT_TOKEN_STYLE; |
| 28 |
if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS; |
| 29 |
if (this.delay == null) this.delay = DEFAULT_DELAY; |
| 30 |
this.overlay = this.timeout = null; |
| 31 |
} |
| 32 |
|
| 33 |
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { |
| 34 |
if (old && old != CodeMirror.Init) { |
| 35 |
var over = cm.state.matchHighlighter.overlay; |
| 36 |
if (over) cm.removeOverlay(over); |
| 37 |
clearTimeout(cm.state.matchHighlighter.timeout); |
| 38 |
cm.state.matchHighlighter = null; |
| 39 |
cm.off("cursorActivity", cursorActivity); |
| 40 |
} |
| 41 |
if (val) { |
| 42 |
cm.state.matchHighlighter = new State(val); |
| 43 |
highlightMatches(cm); |
| 44 |
cm.on("cursorActivity", cursorActivity); |
| 45 |
} |
| 46 |
}); |
| 47 |
|
| 48 |
function cursorActivity(cm) { |
| 49 |
var state = cm.state.matchHighlighter; |
| 50 |
clearTimeout(state.timeout); |
| 51 |
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay); |
| 52 |
} |
| 53 |
|
| 54 |
function highlightMatches(cm) { |
| 55 |
cm.operation(function() { |
| 56 |
var state = cm.state.matchHighlighter; |
| 57 |
if (state.overlay) { |
| 58 |
cm.removeOverlay(state.overlay); |
| 59 |
state.overlay = null; |
| 60 |
} |
| 61 |
if (!cm.somethingSelected() && state.showToken) { |
| 62 |
var re = state.showToken === true ? /[\w$]/ : state.showToken; |
| 63 |
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; |
| 64 |
while (start && re.test(line.charAt(start - 1))) --start; |
| 65 |
while (end < line.length && re.test(line.charAt(end))) ++end; |
| 66 |
if (start < end) |
| 67 |
cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style)); |
| 68 |
return; |
| 69 |
} |
| 70 |
if (cm.getCursor("head").line != cm.getCursor("anchor").line) return; |
| 71 |
var selection = cm.getSelection().replace(/^\s+|\s+$/g, ""); |
| 72 |
if (selection.length >= state.minChars) |
| 73 |
cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style)); |
| 74 |
}); |
| 75 |
} |
| 76 |
|
| 77 |
function boundariesAround(stream, re) { |
| 78 |
return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && |
| 79 |
(stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); |
| 80 |
} |
| 81 |
|
| 82 |
function makeOverlay(query, hasBoundary, style) { |
| 83 |
return {token: function(stream) { |
| 84 |
if (stream.match(query) && |
| 85 |
(!hasBoundary || boundariesAround(stream, hasBoundary))) |
| 86 |
return style; |
| 87 |
stream.next(); |
| 88 |
stream.skipTo(query.charAt(0)) || stream.skipToEnd(); |
| 89 |
}}; |
| 90 |
} |
| 91 |
})(); |
| 92 |
|