Merge pull request #5987 from WoltLab/acp-dahsboard-box-hight
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / 3rdParty / codemirror / addon / search / searchcursor.js
CommitLineData
44f4c339 1// CodeMirror, copyright (c) by Marijn Haverbeke and others
373d1232 2// Distributed under an MIT license: https://codemirror.net/LICENSE
44f4c339 3
837afb80
TD
4(function(mod) {
5 if (typeof exports == "object" && typeof module == "object") // CommonJS
373d1232 6 mod(require("../../lib/codemirror"))
837afb80 7 else if (typeof define == "function" && define.amd) // AMD
373d1232 8 define(["../../lib/codemirror"], mod)
837afb80 9 else // Plain browser env
373d1232 10 mod(CodeMirror)
837afb80 11})(function(CodeMirror) {
373d1232
TD
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 // This grows the search buffer in exponentially-sized chunks
52 // between matches, so that nearby matches are fast and don't
53 // require concatenating the whole document (in case we're
54 // searching for something that has tons of matches), but at the
55 // same time, the amount of retries is limited.
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
704cb1bf
MS
75 function lastMatchIn(string, regexp, endMargin) {
76 var match, from = 0
77 while (from <= string.length) {
78 regexp.lastIndex = from
373d1232 79 var newMatch = regexp.exec(string)
704cb1bf
MS
80 if (!newMatch) break
81 var end = newMatch.index + newMatch[0].length
82 if (end > string.length - endMargin) break
83 if (!match || end > match.index + match[0].length)
84 match = newMatch
85 from = newMatch.index + 1
373d1232 86 }
704cb1bf 87 return match
373d1232
TD
88 }
89
90 function searchRegexpBackward(doc, regexp, start) {
91 regexp = ensureFlags(regexp, "g")
92 for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
93 var string = doc.getLine(line)
704cb1bf 94 var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)
373d1232
TD
95 if (match)
96 return {from: Pos(line, match.index),
97 to: Pos(line, match.index + match[0].length),
98 match: match}
99 }
100 }
101
102 function searchRegexpBackwardMultiline(doc, regexp, start) {
704cb1bf 103 if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)
373d1232 104 regexp = ensureFlags(regexp, "gm")
704cb1bf 105 var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch
373d1232 106 for (var line = start.line, first = doc.firstLine(); line >= first;) {
704cb1bf 107 for (var i = 0; i < chunkSize && line >= first; i++) {
373d1232 108 var curLine = doc.getLine(line--)
704cb1bf 109 string = string == null ? curLine : curLine + "\n" + string
373d1232 110 }
704cb1bf 111 chunkSize *= 2
373d1232 112
704cb1bf 113 var match = lastMatchIn(string, regexp, endMargin)
373d1232
TD
114 if (match) {
115 var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
116 var startLine = line + before.length, startCh = before[before.length - 1].length
117 return {from: Pos(startLine, startCh),
118 to: Pos(startLine + inside.length - 1,
119 inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
120 match: match}
121 }
122 }
123 }
124
125 var doFold, noFold
126 if (String.prototype.normalize) {
127 doFold = function(str) { return str.normalize("NFD").toLowerCase() }
128 noFold = function(str) { return str.normalize("NFD") }
129 } else {
130 doFold = function(str) { return str.toLowerCase() }
131 noFold = function(str) { return str }
132 }
133
134 // Maps a position in a case-folded line back to a position in the original line
135 // (compensating for codepoints increasing in number during folding)
136 function adjustPos(orig, folded, pos, foldFunc) {
137 if (orig.length == folded.length) return pos
138 for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
139 if (min == max) return min
140 var mid = (min + max) >> 1
141 var len = foldFunc(orig.slice(0, mid)).length
142 if (len == pos) return mid
143 else if (len > pos) max = mid
144 else min = mid + 1
145 }
146 }
147
148 function searchStringForward(doc, query, start, caseFold) {
149 // Empty string would match anything and never progress, so we
150 // define it to match nothing instead.
151 if (!query.length) return null
152 var fold = caseFold ? doFold : noFold
153 var lines = fold(query).split(/\r|\n\r?/)
154
155 search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
156 var orig = doc.getLine(line).slice(ch), string = fold(orig)
157 if (lines.length == 1) {
158 var found = string.indexOf(lines[0])
159 if (found == -1) continue search
160 var start = adjustPos(orig, string, found, fold) + ch
161 return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
162 to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
163 } else {
164 var cutFrom = string.length - lines[0].length
165 if (string.slice(cutFrom) != lines[0]) continue search
166 for (var i = 1; i < lines.length - 1; i++)
167 if (fold(doc.getLine(line + i)) != lines[i]) continue search
168 var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
169 if (endString.slice(0, lastLine.length) != lastLine) continue search
170 return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
171 to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
172 }
173 }
174 }
175
176 function searchStringBackward(doc, query, start, caseFold) {
177 if (!query.length) return null
178 var fold = caseFold ? doFold : noFold
179 var lines = fold(query).split(/\r|\n\r?/)
180
181 search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
182 var orig = doc.getLine(line)
183 if (ch > -1) orig = orig.slice(0, ch)
184 var string = fold(orig)
185 if (lines.length == 1) {
186 var found = string.lastIndexOf(lines[0])
187 if (found == -1) continue search
188 return {from: Pos(line, adjustPos(orig, string, found, fold)),
189 to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
190 } else {
191 var lastLine = lines[lines.length - 1]
192 if (string.slice(0, lastLine.length) != lastLine) continue search
193 for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
194 if (fold(doc.getLine(start + i)) != lines[i]) continue search
195 var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
196 if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
197 return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
198 to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
199 }
200 }
201 }
202
203 function SearchCursor(doc, query, pos, options) {
204 this.atOccurrence = false
205 this.doc = doc
206 pos = pos ? doc.clipPos(pos) : Pos(0, 0)
207 this.pos = {from: pos, to: pos}
208
209 var caseFold
210 if (typeof options == "object") {
211 caseFold = options.caseFold
212 } else { // Backwards compat for when caseFold was the 4th argument
213 caseFold = options
214 options = null
215 }
216
217 if (typeof query == "string") {
218 if (caseFold == null) caseFold = false
77b7b761 219 this.matches = function(reverse, pos) {
373d1232
TD
220 return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
221 }
222 } else {
223 query = ensureFlags(query, "gm")
224 if (!options || options.multiline !== false)
225 this.matches = function(reverse, pos) {
226 return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
77b7b761 227 }
373d1232 228 else
77b7b761 229 this.matches = function(reverse, pos) {
373d1232
TD
230 return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
231 }
77b7b761
TD
232 }
233 }
234
235 SearchCursor.prototype = {
373d1232
TD
236 findNext: function() {return this.find(false)},
237 findPrevious: function() {return this.find(true)},
77b7b761
TD
238
239 find: function(reverse) {
373d1232 240 var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
77b7b761 241
373d1232 242 // Implements weird auto-growing behavior on null-matches for
704cb1bf 243 // backwards-compatibility with the vim code (unfortunately)
373d1232 244 while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
77b7b761 245 if (reverse) {
373d1232
TD
246 if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
247 else if (result.from.line == this.doc.firstLine()) result = null
248 else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
249 } else {
250 if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
251 else if (result.to.line == this.doc.lastLine()) result = null
252 else result = this.matches(reverse, Pos(result.to.line + 1, 0))
77b7b761
TD
253 }
254 }
373d1232
TD
255
256 if (result) {
257 this.pos = result
258 this.atOccurrence = true
259 return this.pos.match || true
260 } else {
261 var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
262 this.pos = {from: end, to: end}
263 return this.atOccurrence = false
264 }
77b7b761
TD
265 },
266
373d1232
TD
267 from: function() {if (this.atOccurrence) return this.pos.from},
268 to: function() {if (this.atOccurrence) return this.pos.to},
77b7b761 269
44f4c339 270 replace: function(newText, origin) {
373d1232
TD
271 if (!this.atOccurrence) return
272 var lines = CodeMirror.splitLines(newText)
273 this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
77b7b761 274 this.pos.to = Pos(this.pos.from.line + lines.length - 1,
373d1232 275 lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
837afb80
TD
276 }
277 }
278
77b7b761 279 CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
373d1232
TD
280 return new SearchCursor(this.doc, query, pos, caseFold)
281 })
77b7b761 282 CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
373d1232
TD
283 return new SearchCursor(this, query, pos, caseFold)
284 })
837afb80
TD
285
286 CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
373d1232
TD
287 var ranges = []
288 var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
44f4c339 289 while (cur.findNext()) {
373d1232
TD
290 if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
291 ranges.push({anchor: cur.from(), head: cur.to()})
837afb80
TD
292 }
293 if (ranges.length)
373d1232
TD
294 this.setSelections(ranges, 0)
295 })
837afb80 296});