1 |
|
2 |
|
3 |
|
4 | (function(mod) {
|
5 | if (typeof exports == "object" && typeof module == "object")
|
6 | mod(require("../../lib/codemirror"))
|
7 | else if (typeof define == "function" && define.amd)
|
8 | define(["../../lib/codemirror"], mod)
|
9 | else
|
10 | mod(CodeMirror)
|
11 | })(function(CodeMirror) {
|
12 | "use strict"
|
13 | var Pos = CodeMirror.Pos
|
14 |
|
15 | function regexpFlags(regexp) {
|
16 | var flags = regexp.flags
|
17 | return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
|
18 | + (regexp.global ? "g" : "")
|
19 | + (regexp.multiline ? "m" : "")
|
20 | }
|
21 |
|
22 | function ensureFlags(regexp, flags) {
|
23 | var current = regexpFlags(regexp), target = current
|
24 | for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
|
25 | target += flags.charAt(i)
|
26 | return current == target ? regexp : new RegExp(regexp.source, target)
|
27 | }
|
28 |
|
29 | function maybeMultiline(regexp) {
|
30 | return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
|
31 | }
|
32 |
|
33 | function searchRegexpForward(doc, regexp, start) {
|
34 | regexp = ensureFlags(regexp, "g")
|
35 | for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
|
36 | regexp.lastIndex = ch
|
37 | var string = doc.getLine(line), match = regexp.exec(string)
|
38 | if (match)
|
39 | return {from: Pos(line, match.index),
|
40 | to: Pos(line, match.index + match[0].length),
|
41 | match: match}
|
42 | }
|
43 | }
|
44 |
|
45 | function searchRegexpForwardMultiline(doc, regexp, start) {
|
46 | if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
|
47 |
|
48 | regexp = ensureFlags(regexp, "gm")
|
49 | var string, chunk = 1
|
50 | for (var line = start.line, last = doc.lastLine(); line <= last;) {
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | for (var i = 0; i < chunk; i++) {
|
57 | if (line > last) break
|
58 | var curLine = doc.getLine(line++)
|
59 | string = string == null ? curLine : string + "\n" + curLine
|
60 | }
|
61 | chunk = chunk * 2
|
62 | regexp.lastIndex = start.ch
|
63 | var match = regexp.exec(string)
|
64 | if (match) {
|
65 | var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
66 | var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
|
67 | return {from: Pos(startLine, startCh),
|
68 | to: Pos(startLine + inside.length - 1,
|
69 | inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
70 | match: match}
|
71 | }
|
72 | }
|
73 | }
|
74 |
|
75 | function lastMatchIn(string, regexp) {
|
76 | var cutOff = 0, match
|
77 | for (;;) {
|
78 | regexp.lastIndex = cutOff
|
79 | var newMatch = regexp.exec(string)
|
80 | if (!newMatch) return match
|
81 | match = newMatch
|
82 | cutOff = match.index + (match[0].length || 1)
|
83 | if (cutOff == string.length) return match
|
84 | }
|
85 | }
|
86 |
|
87 | function searchRegexpBackward(doc, regexp, start) {
|
88 | regexp = ensureFlags(regexp, "g")
|
89 | for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
|
90 | var string = doc.getLine(line)
|
91 | if (ch > -1) string = string.slice(0, ch)
|
92 | var match = lastMatchIn(string, regexp)
|
93 | if (match)
|
94 | return {from: Pos(line, match.index),
|
95 | to: Pos(line, match.index + match[0].length),
|
96 | match: match}
|
97 | }
|
98 | }
|
99 |
|
100 | function searchRegexpBackwardMultiline(doc, regexp, start) {
|
101 | regexp = ensureFlags(regexp, "gm")
|
102 | var string, chunk = 1
|
103 | for (var line = start.line, first = doc.firstLine(); line >= first;) {
|
104 | for (var i = 0; i < chunk; i++) {
|
105 | var curLine = doc.getLine(line--)
|
106 | string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
|
107 | }
|
108 | chunk *= 2
|
109 |
|
110 | var match = lastMatchIn(string, regexp)
|
111 | if (match) {
|
112 | var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
113 | var startLine = line + before.length, startCh = before[before.length - 1].length
|
114 | return {from: Pos(startLine, startCh),
|
115 | to: Pos(startLine + inside.length - 1,
|
116 | inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
117 | match: match}
|
118 | }
|
119 | }
|
120 | }
|
121 |
|
122 | var doFold, noFold
|
123 | if (String.prototype.normalize) {
|
124 | doFold = function(str) { return str.normalize("NFD").toLowerCase() }
|
125 | noFold = function(str) { return str.normalize("NFD") }
|
126 | } else {
|
127 | doFold = function(str) { return str.toLowerCase() }
|
128 | noFold = function(str) { return str }
|
129 | }
|
130 |
|
131 |
|
132 |
|
133 | function adjustPos(orig, folded, pos, foldFunc) {
|
134 | if (orig.length == folded.length) return pos
|
135 | for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
|
136 | if (min == max) return min
|
137 | var mid = (min + max) >> 1
|
138 | var len = foldFunc(orig.slice(0, mid)).length
|
139 | if (len == pos) return mid
|
140 | else if (len > pos) max = mid
|
141 | else min = mid + 1
|
142 | }
|
143 | }
|
144 |
|
145 | function searchStringForward(doc, query, start, caseFold) {
|
146 |
|
147 |
|
148 | if (!query.length) return null
|
149 | var fold = caseFold ? doFold : noFold
|
150 | var lines = fold(query).split(/\r|\n\r?/)
|
151 |
|
152 | search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
|
153 | var orig = doc.getLine(line).slice(ch), string = fold(orig)
|
154 | if (lines.length == 1) {
|
155 | var found = string.indexOf(lines[0])
|
156 | if (found == -1) continue search
|
157 | var start = adjustPos(orig, string, found, fold) + ch
|
158 | return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
|
159 | to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
|
160 | } else {
|
161 | var cutFrom = string.length - lines[0].length
|
162 | if (string.slice(cutFrom) != lines[0]) continue search
|
163 | for (var i = 1; i < lines.length - 1; i++)
|
164 | if (fold(doc.getLine(line + i)) != lines[i]) continue search
|
165 | var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
|
166 | if (endString.slice(0, lastLine.length) != lastLine) continue search
|
167 | return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
|
168 | to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
|
169 | }
|
170 | }
|
171 | }
|
172 |
|
173 | function searchStringBackward(doc, query, start, caseFold) {
|
174 | if (!query.length) return null
|
175 | var fold = caseFold ? doFold : noFold
|
176 | var lines = fold(query).split(/\r|\n\r?/)
|
177 |
|
178 | search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
|
179 | var orig = doc.getLine(line)
|
180 | if (ch > -1) orig = orig.slice(0, ch)
|
181 | var string = fold(orig)
|
182 | if (lines.length == 1) {
|
183 | var found = string.lastIndexOf(lines[0])
|
184 | if (found == -1) continue search
|
185 | return {from: Pos(line, adjustPos(orig, string, found, fold)),
|
186 | to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
|
187 | } else {
|
188 | var lastLine = lines[lines.length - 1]
|
189 | if (string.slice(0, lastLine.length) != lastLine) continue search
|
190 | for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
|
191 | if (fold(doc.getLine(start + i)) != lines[i]) continue search
|
192 | var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
|
193 | if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
|
194 | return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
|
195 | to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
|
196 | }
|
197 | }
|
198 | }
|
199 |
|
200 | function SearchCursor(doc, query, pos, options) {
|
201 | this.atOccurrence = false
|
202 | this.doc = doc
|
203 | pos = pos ? doc.clipPos(pos) : Pos(0, 0)
|
204 | this.pos = {from: pos, to: pos}
|
205 |
|
206 | var caseFold
|
207 | if (typeof options == "object") {
|
208 | caseFold = options.caseFold
|
209 | } else {
|
210 | caseFold = options
|
211 | options = null
|
212 | }
|
213 |
|
214 | if (typeof query == "string") {
|
215 | if (caseFold == null) caseFold = false
|
216 | this.matches = function(reverse, pos) {
|
217 | return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
|
218 | }
|
219 | } else {
|
220 | query = ensureFlags(query, "gm")
|
221 | if (!options || options.multiline !== false)
|
222 | this.matches = function(reverse, pos) {
|
223 | return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
|
224 | }
|
225 | else
|
226 | this.matches = function(reverse, pos) {
|
227 | return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
|
228 | }
|
229 | }
|
230 | }
|
231 |
|
232 | SearchCursor.prototype = {
|
233 | findNext: function() {return this.find(false)},
|
234 | findPrevious: function() {return this.find(true)},
|
235 |
|
236 | find: function(reverse) {
|
237 | var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
|
238 |
|
239 |
|
240 |
|
241 | while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
|
242 | if (reverse) {
|
243 | if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
|
244 | else if (result.from.line == this.doc.firstLine()) result = null
|
245 | else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
|
246 | } else {
|
247 | if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
|
248 | else if (result.to.line == this.doc.lastLine()) result = null
|
249 | else result = this.matches(reverse, Pos(result.to.line + 1, 0))
|
250 | }
|
251 | }
|
252 |
|
253 | if (result) {
|
254 | this.pos = result
|
255 | this.atOccurrence = true
|
256 | return this.pos.match || true
|
257 | } else {
|
258 | var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
|
259 | this.pos = {from: end, to: end}
|
260 | return this.atOccurrence = false
|
261 | }
|
262 | },
|
263 |
|
264 | from: function() {if (this.atOccurrence) return this.pos.from},
|
265 | to: function() {if (this.atOccurrence) return this.pos.to},
|
266 |
|
267 | replace: function(newText, origin) {
|
268 | if (!this.atOccurrence) return
|
269 | var lines = CodeMirror.splitLines(newText)
|
270 | this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
|
271 | this.pos.to = Pos(this.pos.from.line + lines.length - 1,
|
272 | lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
|
273 | }
|
274 | }
|
275 |
|
276 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
|
277 | return new SearchCursor(this.doc, query, pos, caseFold)
|
278 | })
|
279 | CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
|
280 | return new SearchCursor(this, query, pos, caseFold)
|
281 | })
|
282 |
|
283 | CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
|
284 | var ranges = []
|
285 | var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
|
286 | while (cur.findNext()) {
|
287 | if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
|
288 | ranges.push({anchor: cur.from(), head: cur.to()})
|
289 | }
|
290 | if (ranges.length)
|
291 | this.setSelections(ranges, 0)
|
292 | })
|
293 | });
|