Commit | Line | Data |
---|---|---|
77b7b761 TD |
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 | |
837afb80 TD |
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(mod) { | |
16 | if (typeof exports == "object" && typeof module == "object") // CommonJS | |
17 | mod(require("../../lib/codemirror")); | |
18 | else if (typeof define == "function" && define.amd) // AMD | |
19 | define(["../../lib/codemirror"], mod); | |
20 | else // Plain browser env | |
21 | mod(CodeMirror); | |
22 | })(function(CodeMirror) { | |
23 | "use strict"; | |
77b7b761 | 24 | |
77b7b761 TD |
25 | var DEFAULT_MIN_CHARS = 2; |
26 | var DEFAULT_TOKEN_STYLE = "matchhighlight"; | |
837afb80 | 27 | var DEFAULT_DELAY = 100; |
77b7b761 TD |
28 | |
29 | function State(options) { | |
837afb80 TD |
30 | if (typeof options == "object") { |
31 | this.minChars = options.minChars; | |
32 | this.style = options.style; | |
33 | this.showToken = options.showToken; | |
34 | this.delay = options.delay; | |
35 | } | |
36 | if (this.style == null) this.style = DEFAULT_TOKEN_STYLE; | |
37 | if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS; | |
38 | if (this.delay == null) this.delay = DEFAULT_DELAY; | |
39 | this.overlay = this.timeout = null; | |
77b7b761 TD |
40 | } |
41 | ||
42 | CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { | |
837afb80 | 43 | if (old && old != CodeMirror.Init) { |
77b7b761 TD |
44 | var over = cm.state.matchHighlighter.overlay; |
45 | if (over) cm.removeOverlay(over); | |
837afb80 | 46 | clearTimeout(cm.state.matchHighlighter.timeout); |
77b7b761 | 47 | cm.state.matchHighlighter = null; |
837afb80 TD |
48 | cm.off("cursorActivity", cursorActivity); |
49 | } | |
50 | if (val) { | |
51 | cm.state.matchHighlighter = new State(val); | |
52 | highlightMatches(cm); | |
53 | cm.on("cursorActivity", cursorActivity); | |
77b7b761 TD |
54 | } |
55 | }); | |
56 | ||
837afb80 TD |
57 | function cursorActivity(cm) { |
58 | var state = cm.state.matchHighlighter; | |
59 | clearTimeout(state.timeout); | |
60 | state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay); | |
61 | } | |
62 | ||
77b7b761 TD |
63 | function highlightMatches(cm) { |
64 | cm.operation(function() { | |
65 | var state = cm.state.matchHighlighter; | |
66 | if (state.overlay) { | |
67 | cm.removeOverlay(state.overlay); | |
68 | state.overlay = null; | |
69 | } | |
837afb80 TD |
70 | if (!cm.somethingSelected() && state.showToken) { |
71 | var re = state.showToken === true ? /[\w$]/ : state.showToken; | |
72 | var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; | |
73 | while (start && re.test(line.charAt(start - 1))) --start; | |
74 | while (end < line.length && re.test(line.charAt(end))) ++end; | |
75 | if (start < end) | |
76 | cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style)); | |
77 | return; | |
78 | } | |
79 | if (cm.getCursor("head").line != cm.getCursor("anchor").line) return; | |
80 | var selection = cm.getSelections()[0].replace(/^\s+|\s+$/g, ""); | |
81 | if (selection.length >= state.minChars) | |
82 | cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style)); | |
77b7b761 TD |
83 | }); |
84 | } | |
85 | ||
837afb80 TD |
86 | function boundariesAround(stream, re) { |
87 | return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && | |
88 | (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); | |
89 | } | |
90 | ||
91 | function makeOverlay(query, hasBoundary, style) { | |
77b7b761 | 92 | return {token: function(stream) { |
837afb80 TD |
93 | if (stream.match(query) && |
94 | (!hasBoundary || boundariesAround(stream, hasBoundary))) | |
95 | return style; | |
77b7b761 TD |
96 | stream.next(); |
97 | stream.skipTo(query.charAt(0)) || stream.skipToEnd(); | |
98 | }}; | |
99 | } | |
837afb80 | 100 | }); |