jump-to-line.js
8 years ago
jump-to-line.min.js
8 years ago
match-highlighter.js
8 years ago
match-highlighter.min.js
8 years ago
matchesonscrollbar.css
8 years ago
matchesonscrollbar.js
8 years ago
matchesonscrollbar.min.js
8 years ago
search.js
8 years ago
search.min.js
8 years ago
searchcursor.js
8 years ago
searchcursor.min.js
8 years ago
match-highlighter.js
166 lines
| 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others |
| 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE |
| 3 | |
| 4 | // Highlighting text that matches the selection |
| 5 | // |
| 6 | // Defines an option highlightSelectionMatches, which, when enabled, |
| 7 | // will style strings that match the selection throughout the |
| 8 | // document. |
| 9 | // |
| 10 | // The option can be set to true to simply enable it, or to a |
| 11 | // {minChars, style, wordsOnly, showToken, delay} object to explicitly |
| 12 | // configure it. minChars is the minimum amount of characters that should be |
| 13 | // selected for the behavior to occur, and style is the token style to |
| 14 | // apply to the matches. This will be prefixed by "cm-" to create an |
| 15 | // actual CSS class name. If wordsOnly is enabled, the matches will be |
| 16 | // highlighted only if the selected text is a word. showToken, when enabled, |
| 17 | // will cause the current token to be highlighted when nothing is selected. |
| 18 | // delay is used to specify how much time to wait, in milliseconds, before |
| 19 | // highlighting the matches. If annotateScrollbar is enabled, the occurences |
| 20 | // will be highlighted on the scrollbar via the matchesonscrollbar addon. |
| 21 | |
| 22 | (function(mod) { |
| 23 | if (typeof exports == "object" && typeof module == "object") // CommonJS |
| 24 | mod(require("../../lib/codemirror"), require("./matchesonscrollbar")); |
| 25 | else if (typeof define == "function" && define.amd) // AMD |
| 26 | define(["../../lib/codemirror", "./matchesonscrollbar"], mod); |
| 27 | else // Plain browser env |
| 28 | mod(CodeMirror); |
| 29 | })(function(CodeMirror) { |
| 30 | "use strict"; |
| 31 | |
| 32 | var defaults = { |
| 33 | style: "matchhighlight", |
| 34 | minChars: 2, |
| 35 | delay: 100, |
| 36 | wordsOnly: false, |
| 37 | annotateScrollbar: false, |
| 38 | showToken: false, |
| 39 | trim: true |
| 40 | } |
| 41 | |
| 42 | function State(options) { |
| 43 | this.options = {} |
| 44 | for (var name in defaults) |
| 45 | this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name] |
| 46 | this.overlay = this.timeout = null; |
| 47 | this.matchesonscroll = null; |
| 48 | this.active = false; |
| 49 | } |
| 50 | |
| 51 | CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { |
| 52 | if (old && old != CodeMirror.Init) { |
| 53 | removeOverlay(cm); |
| 54 | clearTimeout(cm.state.matchHighlighter.timeout); |
| 55 | cm.state.matchHighlighter = null; |
| 56 | cm.off("cursorActivity", cursorActivity); |
| 57 | cm.off("focus", onFocus) |
| 58 | } |
| 59 | if (val) { |
| 60 | var state = cm.state.matchHighlighter = new State(val); |
| 61 | if (cm.hasFocus()) { |
| 62 | state.active = true |
| 63 | highlightMatches(cm) |
| 64 | } else { |
| 65 | cm.on("focus", onFocus) |
| 66 | } |
| 67 | cm.on("cursorActivity", cursorActivity); |
| 68 | } |
| 69 | }); |
| 70 | |
| 71 | function cursorActivity(cm) { |
| 72 | var state = cm.state.matchHighlighter; |
| 73 | if (state.active || cm.hasFocus()) scheduleHighlight(cm, state) |
| 74 | } |
| 75 | |
| 76 | function onFocus(cm) { |
| 77 | var state = cm.state.matchHighlighter |
| 78 | if (!state.active) { |
| 79 | state.active = true |
| 80 | scheduleHighlight(cm, state) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | function scheduleHighlight(cm, state) { |
| 85 | clearTimeout(state.timeout); |
| 86 | state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay); |
| 87 | } |
| 88 | |
| 89 | function addOverlay(cm, query, hasBoundary, style) { |
| 90 | var state = cm.state.matchHighlighter; |
| 91 | cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style)); |
| 92 | if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) { |
| 93 | var searchFor = hasBoundary ? new RegExp("\\b" + query.replace(/[\\\[.+*?(){|^$]/g, "\\$&") + "\\b") : query; |
| 94 | state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false, |
| 95 | {className: "CodeMirror-selection-highlight-scrollbar"}); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | function removeOverlay(cm) { |
| 100 | var state = cm.state.matchHighlighter; |
| 101 | if (state.overlay) { |
| 102 | cm.removeOverlay(state.overlay); |
| 103 | state.overlay = null; |
| 104 | if (state.matchesonscroll) { |
| 105 | state.matchesonscroll.clear(); |
| 106 | state.matchesonscroll = null; |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | function highlightMatches(cm) { |
| 112 | cm.operation(function() { |
| 113 | var state = cm.state.matchHighlighter; |
| 114 | removeOverlay(cm); |
| 115 | if (!cm.somethingSelected() && state.options.showToken) { |
| 116 | var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken; |
| 117 | var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; |
| 118 | while (start && re.test(line.charAt(start - 1))) --start; |
| 119 | while (end < line.length && re.test(line.charAt(end))) ++end; |
| 120 | if (start < end) |
| 121 | addOverlay(cm, line.slice(start, end), re, state.options.style); |
| 122 | return; |
| 123 | } |
| 124 | var from = cm.getCursor("from"), to = cm.getCursor("to"); |
| 125 | if (from.line != to.line) return; |
| 126 | if (state.options.wordsOnly && !isWord(cm, from, to)) return; |
| 127 | var selection = cm.getRange(from, to) |
| 128 | if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "") |
| 129 | if (selection.length >= state.options.minChars) |
| 130 | addOverlay(cm, selection, false, state.options.style); |
| 131 | }); |
| 132 | } |
| 133 | |
| 134 | function isWord(cm, from, to) { |
| 135 | var str = cm.getRange(from, to); |
| 136 | if (str.match(/^\w+$/) !== null) { |
| 137 | if (from.ch > 0) { |
| 138 | var pos = {line: from.line, ch: from.ch - 1}; |
| 139 | var chr = cm.getRange(pos, from); |
| 140 | if (chr.match(/\W/) === null) return false; |
| 141 | } |
| 142 | if (to.ch < cm.getLine(from.line).length) { |
| 143 | var pos = {line: to.line, ch: to.ch + 1}; |
| 144 | var chr = cm.getRange(to, pos); |
| 145 | if (chr.match(/\W/) === null) return false; |
| 146 | } |
| 147 | return true; |
| 148 | } else return false; |
| 149 | } |
| 150 | |
| 151 | function boundariesAround(stream, re) { |
| 152 | return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && |
| 153 | (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); |
| 154 | } |
| 155 | |
| 156 | function makeOverlay(query, hasBoundary, style) { |
| 157 | return {token: function(stream) { |
| 158 | if (stream.match(query) && |
| 159 | (!hasBoundary || boundariesAround(stream, hasBoundary))) |
| 160 | return style; |
| 161 | stream.next(); |
| 162 | stream.skipTo(query.charAt(0)) || stream.skipToEnd(); |
| 163 | }}; |
| 164 | } |
| 165 | }); |
| 166 |