Mercurial
comparison .cms/lib/codemirror/addon/search/searchcursor.js @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others | |
2 // Distributed under an MIT license: https://codemirror.net/5/LICENSE | |
3 | |
4 (function(mod) { | |
5 if (typeof exports == "object" && typeof module == "object") // CommonJS | |
6 mod(require("../../lib/codemirror")) | |
7 else if (typeof define == "function" && define.amd) // AMD | |
8 define(["../../lib/codemirror"], mod) | |
9 else // Plain browser env | |
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 // 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 | |
75 function lastMatchIn(string, regexp, endMargin) { | |
76 var match, from = 0 | |
77 while (from <= string.length) { | |
78 regexp.lastIndex = from | |
79 var newMatch = regexp.exec(string) | |
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 | |
86 } | |
87 return match | |
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) | |
94 var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch) | |
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) { | |
103 if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start) | |
104 regexp = ensureFlags(regexp, "gm") | |
105 var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch | |
106 for (var line = start.line, first = doc.firstLine(); line >= first;) { | |
107 for (var i = 0; i < chunkSize && line >= first; i++) { | |
108 var curLine = doc.getLine(line--) | |
109 string = string == null ? curLine : curLine + "\n" + string | |
110 } | |
111 chunkSize *= 2 | |
112 | |
113 var match = lastMatchIn(string, regexp, endMargin) | |
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.afterEmptyMatch = false | |
206 this.doc = doc | |
207 pos = pos ? doc.clipPos(pos) : Pos(0, 0) | |
208 this.pos = {from: pos, to: pos} | |
209 | |
210 var caseFold | |
211 if (typeof options == "object") { | |
212 caseFold = options.caseFold | |
213 } else { // Backwards compat for when caseFold was the 4th argument | |
214 caseFold = options | |
215 options = null | |
216 } | |
217 | |
218 if (typeof query == "string") { | |
219 if (caseFold == null) caseFold = false | |
220 this.matches = function(reverse, pos) { | |
221 return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold) | |
222 } | |
223 } else { | |
224 query = ensureFlags(query, "gm") | |
225 if (!options || options.multiline !== false) | |
226 this.matches = function(reverse, pos) { | |
227 return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos) | |
228 } | |
229 else | |
230 this.matches = function(reverse, pos) { | |
231 return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos) | |
232 } | |
233 } | |
234 } | |
235 | |
236 SearchCursor.prototype = { | |
237 findNext: function() {return this.find(false)}, | |
238 findPrevious: function() {return this.find(true)}, | |
239 | |
240 find: function(reverse) { | |
241 var head = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); | |
242 if (this.afterEmptyMatch && this.atOccurrence) { | |
243 // do not return the same 0 width match twice | |
244 head = Pos(head.line, head.ch) | |
245 if (reverse) { | |
246 head.ch--; | |
247 if (head.ch < 0) { | |
248 head.line--; | |
249 head.ch = (this.doc.getLine(head.line) || "").length; | |
250 } | |
251 } else { | |
252 head.ch++; | |
253 if (head.ch > (this.doc.getLine(head.line) || "").length) { | |
254 head.ch = 0; | |
255 head.line++; | |
256 } | |
257 } | |
258 if (CodeMirror.cmpPos(head, this.doc.clipPos(head)) != 0) { | |
259 return this.atOccurrence = false | |
260 } | |
261 } | |
262 var result = this.matches(reverse, head) | |
263 this.afterEmptyMatch = result && CodeMirror.cmpPos(result.from, result.to) == 0 | |
264 | |
265 if (result) { | |
266 this.pos = result | |
267 this.atOccurrence = true | |
268 return this.pos.match || true | |
269 } else { | |
270 var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0) | |
271 this.pos = {from: end, to: end} | |
272 return this.atOccurrence = false | |
273 } | |
274 }, | |
275 | |
276 from: function() {if (this.atOccurrence) return this.pos.from}, | |
277 to: function() {if (this.atOccurrence) return this.pos.to}, | |
278 | |
279 replace: function(newText, origin) { | |
280 if (!this.atOccurrence) return | |
281 var lines = CodeMirror.splitLines(newText) | |
282 this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin) | |
283 this.pos.to = Pos(this.pos.from.line + lines.length - 1, | |
284 lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)) | |
285 } | |
286 } | |
287 | |
288 CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { | |
289 return new SearchCursor(this.doc, query, pos, caseFold) | |
290 }) | |
291 CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { | |
292 return new SearchCursor(this, query, pos, caseFold) | |
293 }) | |
294 | |
295 CodeMirror.defineExtension("selectMatches", function(query, caseFold) { | |
296 var ranges = [] | |
297 var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold) | |
298 while (cur.findNext()) { | |
299 if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break | |
300 ranges.push({anchor: cur.from(), head: cur.to()}) | |
301 } | |
302 if (ranges.length) | |
303 this.setSelections(ranges, 0) | |
304 }) | |
305 }); |