2 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
3 mod(require("../../lib/codemirror"));
4 else if (typeof define
== "function" && define
.amd
) // AMD
5 define(["../../lib/codemirror"], mod
);
6 else // Plain browser env
8 })(function(CodeMirror
) {
10 var Pos
= CodeMirror
.Pos
;
12 function SearchCursor(doc
, query
, pos
, caseFold
) {
13 this.atOccurrence
= false; this.doc
= doc
;
14 if (caseFold
== null && typeof query
== "string") caseFold
= false;
16 pos
= pos
? doc
.clipPos(pos
) : Pos(0, 0);
17 this.pos
= {from: pos
, to
: pos
};
19 // The matches method is filled in based on the type of query.
20 // It takes a position and a direction, and returns an object
21 // describing the next occurrence of the query, or null if no
22 // more matches were found.
23 if (typeof query
!= "string") { // Regexp match
24 if (!query
.global
) query
= new RegExp(query
.source
, query
.ignoreCase
? "ig" : "g");
25 this.matches = function(reverse
, pos
) {
28 var line
= doc
.getLine(pos
.line
).slice(0, pos
.ch
), cutOff
= 0, match
, start
;
30 query
.lastIndex
= cutOff
;
31 var newMatch
= query
.exec(line
);
35 cutOff
= match
.index
+ (match
[0].length
|| 1);
36 if (cutOff
== line
.length
) break;
38 var matchLen
= (match
&& match
[0].length
) || 0;
40 if (start
== 0 && line
.length
== 0) {match
= undefined;}
41 else if (start
!= doc
.getLine(pos
.line
).length
) {
46 query
.lastIndex
= pos
.ch
;
47 var line
= doc
.getLine(pos
.line
), match
= query
.exec(line
);
48 var matchLen
= (match
&& match
[0].length
) || 0;
49 var start
= match
&& match
.index
;
50 if (start
+ matchLen
!= line
.length
&& !matchLen
) matchLen
= 1;
52 if (match
&& matchLen
)
53 return {from: Pos(pos
.line
, start
),
54 to
: Pos(pos
.line
, start
+ matchLen
),
57 } else { // String query
58 var origQuery
= query
;
59 if (caseFold
) query
= query
.toLowerCase();
60 var fold
= caseFold
? function(str
){return str
.toLowerCase();} : function(str
){return str
;};
61 var target
= query
.split("\n");
62 // Different methods for single-line and multi-line queries
63 if (target
.length
== 1) {
65 // Empty string would match anything and never progress, so
66 // we define it to match nothing instead.
67 this.matches = function() {};
69 this.matches = function(reverse
, pos
) {
71 var orig
= doc
.getLine(pos
.line
).slice(0, pos
.ch
), line
= fold(orig
);
72 var match
= line
.lastIndexOf(query
);
74 match
= adjustPos(orig
, line
, match
);
75 return {from: Pos(pos
.line
, match
), to
: Pos(pos
.line
, match
+ origQuery
.length
)};
78 var orig
= doc
.getLine(pos
.line
).slice(pos
.ch
), line
= fold(orig
);
79 var match
= line
.indexOf(query
);
81 match
= adjustPos(orig
, line
, match
) + pos
.ch
;
82 return {from: Pos(pos
.line
, match
), to
: Pos(pos
.line
, match
+ origQuery
.length
)};
88 var origTarget
= origQuery
.split("\n");
89 this.matches = function(reverse
, pos
) {
90 var last
= target
.length
- 1;
92 if (pos
.line
- (target
.length
- 1) < doc
.firstLine()) return;
93 if (fold(doc
.getLine(pos
.line
).slice(0, origTarget
[last
].length
)) != target
[target
.length
- 1]) return;
94 var to
= Pos(pos
.line
, origTarget
[last
].length
);
95 for (var ln
= pos
.line
- 1, i
= last
- 1; i
>= 1; --i
, --ln
)
96 if (target
[i
] != fold(doc
.getLine(ln
))) return;
97 var line
= doc
.getLine(ln
), cut
= line
.length
- origTarget
[0].length
;
98 if (fold(line
.slice(cut
)) != target
[0]) return;
99 return {from: Pos(ln
, cut
), to
: to
};
101 if (pos
.line
+ (target
.length
- 1) > doc
.lastLine()) return;
102 var line
= doc
.getLine(pos
.line
), cut
= line
.length
- origTarget
[0].length
;
103 if (fold(line
.slice(cut
)) != target
[0]) return;
104 var from = Pos(pos
.line
, cut
);
105 for (var ln
= pos
.line
+ 1, i
= 1; i
< last
; ++i
, ++ln
)
106 if (target
[i
] != fold(doc
.getLine(ln
))) return;
107 if (doc
.getLine(ln
).slice(0, origTarget
[last
].length
) != target
[last
]) return;
108 return {from: from, to
: Pos(ln
, origTarget
[last
].length
)};
115 SearchCursor
.prototype = {
116 findNext: function() {return this.find(false);},
117 findPrevious: function() {return this.find(true);},
119 find: function(reverse
) {
120 var self
= this, pos
= this.doc
.clipPos(reverse
? this.pos
.from : this.pos
.to
);
121 function savePosAndFail(line
) {
122 var pos
= Pos(line
, 0);
123 self
.pos
= {from: pos
, to
: pos
};
124 self
.atOccurrence
= false;
129 if (this.pos
= this.matches(reverse
, pos
)) {
130 this.atOccurrence
= true;
131 return this.pos
.match
|| true;
134 if (!pos
.line
) return savePosAndFail(0);
135 pos
= Pos(pos
.line
-1, this.doc
.getLine(pos
.line
-1).length
);
138 var maxLine
= this.doc
.lineCount();
139 if (pos
.line
== maxLine
- 1) return savePosAndFail(maxLine
);
140 pos
= Pos(pos
.line
+ 1, 0);
145 from: function() {if (this.atOccurrence
) return this.pos
.from;},
146 to: function() {if (this.atOccurrence
) return this.pos
.to
;},
148 replace: function(newText
) {
149 if (!this.atOccurrence
) return;
150 var lines
= CodeMirror
.splitLines(newText
);
151 this.doc
.replaceRange(lines
, this.pos
.from, this.pos
.to
);
152 this.pos
.to
= Pos(this.pos
.from.line
+ lines
.length
- 1,
153 lines
[lines
.length
- 1].length
+ (lines
.length
== 1 ? this.pos
.from.ch
: 0));
157 // Maps a position in a case-folded line back to a position in the original line
158 // (compensating for codepoints increasing in number during folding)
159 function adjustPos(orig
, folded
, pos
) {
160 if (orig
.length
== folded
.length
) return pos
;
161 for (var pos1
= Math
.min(pos
, orig
.length
);;) {
162 var len1
= orig
.slice(0, pos1
).toLowerCase().length
;
163 if (len1
< pos
) ++pos1
;
164 else if (len1
> pos
) --pos1
;
169 CodeMirror
.defineExtension("getSearchCursor", function(query
, pos
, caseFold
) {
170 return new SearchCursor(this.doc
, query
, pos
, caseFold
);
172 CodeMirror
.defineDocExtension("getSearchCursor", function(query
, pos
, caseFold
) {
173 return new SearchCursor(this, query
, pos
, caseFold
);
176 CodeMirror
.defineExtension("selectMatches", function(query
, caseFold
) {
177 var ranges
= [], next
;
178 var cur
= this.getSearchCursor(query
, this.getCursor("from"), caseFold
);
179 while (next
= cur
.findNext()) {
180 if (CodeMirror
.cmpPos(cur
.to(), this.getCursor("to")) > 0) break;
181 ranges
.push({anchor
: cur
.from(), head
: cur
.to()});
184 this.setSelections(ranges
, 0);