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)
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;) {
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 }
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) {
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
205
this.afterEmptyMatch = false
207
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
208
this.pos = {from: pos, to: pos}
211
if (typeof options == "object") {
212
caseFold = options.caseFold
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)
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)
230
this.matches = function(reverse, pos) {
231
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
236
SearchCursor.prototype = {
237
findNext: function() {return this.find(false)},
238
findPrevious: function() {return this.find(true)},
240
find: function(reverse) {
241
var head = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
242
if (this.afterEmptyMatch && this.atOccurrence) {
244
head = Pos(head.line, head.ch)
249
head.ch = (this.doc.getLine(head.line) || "").length;
253
if (head.ch > (this.doc.getLine(head.line) || "").length) {
258
if (CodeMirror.cmpPos(head, this.doc.clipPos(head)) != 0) {
259
return this.atOccurrence = false
262
var result = this.matches(reverse, head)
263
this.afterEmptyMatch = result && CodeMirror.cmpPos(result.from, result.to) == 0
267
this.atOccurrence = true
268
return this.pos.match || true
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
276
from: function() {if (this.atOccurrence) return this.pos.from},
277
to: function() {if (this.atOccurrence) return this.pos.to},
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))
288
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
289
return new SearchCursor(this.doc, query, pos, caseFold)
291
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
292
return new SearchCursor(this, query, pos, caseFold)
295
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
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()})
303
this.setSelections(ranges, 0)