1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: https://codemirror.net/LICENSE
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
11 })(function(CodeMirror
) {
13 var Pos
= CodeMirror
.Pos
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" : "")
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
)
29 function maybeMultiline(regexp
) {
30 return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp
.source
)
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) {
37 var string
= doc
.getLine(line
), match
= regexp
.exec(string
)
39 return {from: Pos(line
, match
.index
),
40 to
: Pos(line
, match
.index
+ match
[0].length
),
45 function searchRegexpForwardMultiline(doc
, regexp
, start
) {
46 if (!maybeMultiline(regexp
)) return searchRegexpForward(doc
, regexp
, start
)
48 regexp
= ensureFlags(regexp
, "gm")
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
62 regexp
.lastIndex
= start
.ch
63 var match
= regexp
.exec(string
)
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
),
75 function lastMatchIn(string
, regexp
, endMargin
) {
77 while (from <= string
.length
) {
78 regexp
.lastIndex
= from
79 var newMatch
= regexp
.exec(string
)
81 var end
= newMatch
.index
+ newMatch
[0].length
82 if (end
> string
.length
- endMargin
) break
83 if (!match
|| end
> match
.index
+ match
[0].length
)
85 from = newMatch
.index
+ 1
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
)
96 return {from: Pos(line
, match
.index
),
97 to
: Pos(line
, match
.index
+ match
[0].length
),
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
113 var match
= lastMatchIn(string
, regexp
, endMargin
)
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
),
126 if (String
.prototype.normalize
) {
127 doFold = function(str
) { return str
.normalize("NFD").toLowerCase() }
128 noFold = function(str
) { return str
.normalize("NFD") }
130 doFold = function(str
) { return str
.toLowerCase() }
131 noFold = function(str
) { return str
}
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
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?/)
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
)}
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
))}
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?/)
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
))}
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
))}
203 function SearchCursor(doc
, query
, pos
, options
) {
204 this.atOccurrence
= false
206 pos
= pos
? doc
.clipPos(pos
) : Pos(0, 0)
207 this.pos
= {from: pos
, to
: pos
}
210 if (typeof options
== "object") {
211 caseFold
= options
.caseFold
212 } else { // Backwards compat for when caseFold was the 4th argument
217 if (typeof query
== "string") {
218 if (caseFold
== null) caseFold
= false
219 this.matches = function(reverse
, pos
) {
220 return (reverse
? searchStringBackward
: searchStringForward
)(doc
, query
, pos
, caseFold
)
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
)
229 this.matches = function(reverse
, pos
) {
230 return (reverse
? searchRegexpBackward
: searchRegexpForward
)(doc
, query
, pos
)
235 SearchCursor
.prototype = {
236 findNext: function() {return this.find(false)},
237 findPrevious: function() {return this.find(true)},
239 find: function(reverse
) {
240 var result
= this.matches(reverse
, this.doc
.clipPos(reverse
? this.pos
.from : this.pos
.to
))
242 // Implements weird auto-growing behavior on null-matches for
243 // backwards-compatibility with the vim code (unfortunately)
244 while (result
&& CodeMirror
.cmpPos(result
.from, result
.to
) == 0) {
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)))
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))
258 this.atOccurrence
= true
259 return this.pos
.match
|| true
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
267 from: function() {if (this.atOccurrence
) return this.pos
.from},
268 to: function() {if (this.atOccurrence
) return this.pos
.to
},
270 replace: function(newText
, origin
) {
271 if (!this.atOccurrence
) return
272 var lines
= CodeMirror
.splitLines(newText
)
273 this.doc
.replaceRange(lines
, this.pos
.from, this.pos
.to
, origin
)
274 this.pos
.to
= Pos(this.pos
.from.line
+ lines
.length
- 1,
275 lines
[lines
.length
- 1].length
+ (lines
.length
== 1 ? this.pos
.from.ch
: 0))
279 CodeMirror
.defineExtension("getSearchCursor", function(query
, pos
, caseFold
) {
280 return new SearchCursor(this.doc
, query
, pos
, caseFold
)
282 CodeMirror
.defineDocExtension("getSearchCursor", function(query
, pos
, caseFold
) {
283 return new SearchCursor(this, query
, pos
, caseFold
)
286 CodeMirror
.defineExtension("selectMatches", function(query
, caseFold
) {
288 var cur
= this.getSearchCursor(query
, this.getCursor("from"), caseFold
)
289 while (cur
.findNext()) {
290 if (CodeMirror
.cmpPos(cur
.to(), this.getCursor("to")) > 0) break
291 ranges
.push({anchor
: cur
.from(), head
: cur
.to()})
294 this.setSelections(ranges
, 0)