Skip to content

Commit 3c8b5a7

Browse files
committed
[searchcursor addon] Refactor
1 parent 09adaf4 commit 3c8b5a7

2 files changed

Lines changed: 158 additions & 160 deletions

File tree

addon/search/searchcursor.js

Lines changed: 154 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,189 +1,187 @@
11
// CodeMirror, copyright (c) by Marijn Haverbeke and others
22
// Distributed under an MIT license: http://codemirror.net/LICENSE
33

4-
(function(mod) {
4+
;(function(mod) {
55
if (typeof exports == "object" && typeof module == "object") // CommonJS
6-
mod(require("../../lib/codemirror"));
6+
mod(require("../../lib/codemirror"))
77
else if (typeof define == "function" && define.amd) // AMD
8-
define(["../../lib/codemirror"], mod);
8+
define(["../../lib/codemirror"], mod)
99
else // Plain browser env
10-
mod(CodeMirror);
10+
mod(CodeMirror)
1111
})(function(CodeMirror) {
12-
"use strict";
13-
var Pos = CodeMirror.Pos;
12+
"use strict"
13+
var Pos = CodeMirror.Pos
14+
15+
function ensureGlobal(regexp) {
16+
return regexp.global ? regexp : new RegExp(regexp.source, regexp.ignoreCase ? "ig" : "g")
17+
}
18+
19+
function searchRegexpForward(doc, regexp, start) {
20+
regexp = ensureGlobal(regexp)
21+
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
22+
regexp.lastIndex = ch
23+
var string = doc.getLine(line), match = regexp.exec(string)
24+
if (match && match[0].length)
25+
return {from: Pos(line, match.index),
26+
to: Pos(line, match.index + match[0].length),
27+
match: match}
28+
}
29+
}
30+
31+
function searchRegexpBackward(doc, regexp, start) {
32+
regexp = ensureGlobal(regexp)
33+
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
34+
var string = doc.getLine(line), cutOff = 0, match
35+
if (ch > -1) string = string.slice(0, ch)
36+
for (;;) {
37+
regexp.lastIndex = cutOff
38+
var newMatch = regexp.exec(string)
39+
if (!newMatch) break
40+
match = newMatch
41+
cutOff = match.index + (match[0].length || 1)
42+
if (cutOff == line.length) break
43+
}
44+
45+
if (match && match[0].length)
46+
return {from: Pos(line, match.index),
47+
to: Pos(line, match.index + match[0].length),
48+
match: match}
49+
}
50+
}
51+
52+
function doFold(str) { return str.toLowerCase() }
53+
function noFold(str) { return str }
54+
55+
// Maps a position in a case-folded line back to a position in the original line
56+
// (compensating for codepoints increasing in number during folding)
57+
function adjustPos(orig, folded, pos) {
58+
if (orig.length == folded.length) return pos
59+
for (var pos1 = Math.min(pos, orig.length);;) {
60+
var len1 = orig.slice(0, pos1).toLowerCase().length
61+
if (len1 < pos) ++pos1
62+
else if (len1 > pos) --pos1
63+
else return pos1
64+
}
65+
}
66+
67+
function searchStringForward(doc, query, start, caseFold) {
68+
// Empty string would match anything and never progress, so we
69+
// define it to match nothing instead.
70+
if (!query.length) return null
71+
var fold = caseFold ? doFold : noFold
72+
var lines = fold(query).split(/\r|\n\r?/)
73+
74+
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
75+
var orig = doc.getLine(line).slice(ch), string = fold(orig)
76+
if (lines.length == 1) {
77+
var found = string.indexOf(lines[0])
78+
if (found == -1) continue search
79+
var start = adjustPos(orig, string, found) + ch
80+
return {from: Pos(line, adjustPos(orig, string, found) + ch),
81+
to: Pos(line, adjustPos(orig, string, found + lines[0].length) + ch)}
82+
} else {
83+
var cutFrom = string.length - lines[0].length
84+
if (string.slice(cutFrom) != lines[0]) continue search
85+
for (var i = 1; i < lines.length - 1; i++)
86+
if (fold(doc.getLine(line + i)) != lines[i]) continue search
87+
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
88+
if (end.slice(0, lastLine.length) != lastLine) continue search
89+
return {from: Pos(line, adjustPos(orig, string, cutFrom) + ch),
90+
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length))}
91+
}
92+
}
93+
}
94+
95+
function searchStringBackward(doc, query, start, caseFold) {
96+
if (!query.length) return null
97+
var fold = caseFold ? doFold : noFold
98+
var lines = fold(query).split(/\r|\n\r?/)
99+
100+
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
101+
var orig = doc.getLine(line)
102+
if (ch > -1) orig = orig.slice(0, ch)
103+
var string = fold(orig)
104+
if (lines.length == 1) {
105+
var found = string.lastIndexOf(lines[0])
106+
if (found == -1) continue search
107+
return {from: Pos(line, adjustPos(orig, string, found)),
108+
to: Pos(line, adjustPos(orig, string, found + lines[0].length))}
109+
} else {
110+
var lastLine = lines[lines.length - 1]
111+
if (string.slice(0, lastLine.length) != lastLine) continue search
112+
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
113+
if (fold(doc.getLine(start + i)) != lines[i]) continue search
114+
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
115+
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
116+
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length)),
117+
to: Pos(line, adjustPos(orig, string, lastLine.length))}
118+
}
119+
}
120+
}
14121

15122
function SearchCursor(doc, query, pos, caseFold) {
16-
this.atOccurrence = false; this.doc = doc;
17-
if (caseFold == null && typeof query == "string") caseFold = false;
18-
19-
pos = pos ? doc.clipPos(pos) : Pos(0, 0);
20-
this.pos = {from: pos, to: pos};
21-
22-
// The matches method is filled in based on the type of query.
23-
// It takes a position and a direction, and returns an object
24-
// describing the next occurrence of the query, or null if no
25-
// more matches were found.
26-
if (typeof query != "string") { // Regexp match
27-
if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
123+
this.atOccurrence = false
124+
this.doc = doc
125+
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
126+
this.pos = {from: pos, to: pos}
127+
128+
if (typeof query == "string") {
129+
if (caseFold == null) caseFold = false
28130
this.matches = function(reverse, pos) {
29-
if (reverse) {
30-
query.lastIndex = 0;
31-
var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
32-
for (;;) {
33-
query.lastIndex = cutOff;
34-
var newMatch = query.exec(line);
35-
if (!newMatch) break;
36-
match = newMatch;
37-
start = match.index;
38-
cutOff = match.index + (match[0].length || 1);
39-
if (cutOff == line.length) break;
40-
}
41-
var matchLen = (match && match[0].length) || 0;
42-
if (!matchLen) {
43-
if (start == 0 && line.length == 0) {match = undefined;}
44-
else if (start != doc.getLine(pos.line).length) {
45-
matchLen++;
46-
}
47-
}
48-
} else {
49-
query.lastIndex = pos.ch;
50-
var line = doc.getLine(pos.line), match = query.exec(line);
51-
var matchLen = (match && match[0].length) || 0;
52-
var start = match && match.index;
53-
if (start + matchLen != line.length && !matchLen) matchLen = 1;
54-
}
55-
if (match && matchLen)
56-
return {from: Pos(pos.line, start),
57-
to: Pos(pos.line, start + matchLen),
58-
match: match};
59-
};
60-
} else { // String query
61-
var origQuery = query;
62-
if (caseFold) query = query.toLowerCase();
63-
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
64-
var target = query.split("\n");
65-
// Different methods for single-line and multi-line queries
66-
if (target.length == 1) {
67-
if (!query.length) {
68-
// Empty string would match anything and never progress, so
69-
// we define it to match nothing instead.
70-
this.matches = function() {};
71-
} else {
72-
this.matches = function(reverse, pos) {
73-
if (reverse) {
74-
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
75-
var match = line.lastIndexOf(query);
76-
if (match > -1) {
77-
match = adjustPos(orig, line, match);
78-
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
79-
}
80-
} else {
81-
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
82-
var match = line.indexOf(query);
83-
if (match > -1) {
84-
match = adjustPos(orig, line, match) + pos.ch;
85-
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
86-
}
87-
}
88-
};
89-
}
90-
} else {
91-
var origTarget = origQuery.split("\n");
92-
this.matches = function(reverse, pos) {
93-
var last = target.length - 1;
94-
if (reverse) {
95-
if (pos.line - (target.length - 1) < doc.firstLine()) return;
96-
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
97-
var to = Pos(pos.line, origTarget[last].length);
98-
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
99-
if (target[i] != fold(doc.getLine(ln))) return;
100-
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
101-
if (fold(line.slice(cut)) != target[0]) return;
102-
return {from: Pos(ln, cut), to: to};
103-
} else {
104-
if (pos.line + (target.length - 1) > doc.lastLine()) return;
105-
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
106-
if (fold(line.slice(cut)) != target[0]) return;
107-
var from = Pos(pos.line, cut);
108-
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
109-
if (target[i] != fold(doc.getLine(ln))) return;
110-
if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
111-
return {from: from, to: Pos(ln, origTarget[last].length)};
112-
}
113-
};
131+
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
132+
}
133+
} else {
134+
query = ensureGlobal(query)
135+
this.matches = function(reverse, pos) {
136+
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
114137
}
115138
}
116139
}
117140

118141
SearchCursor.prototype = {
119-
findNext: function() {return this.find(false);},
120-
findPrevious: function() {return this.find(true);},
142+
findNext: function() {return this.find(false)},
143+
findPrevious: function() {return this.find(true)},
121144

122145
find: function(reverse) {
123-
var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
124-
function savePosAndFail(line) {
125-
var pos = Pos(line, 0);
126-
self.pos = {from: pos, to: pos};
127-
self.atOccurrence = false;
128-
return false;
129-
}
130-
131-
for (;;) {
132-
if (this.pos = this.matches(reverse, pos)) {
133-
this.atOccurrence = true;
134-
return this.pos.match || true;
135-
}
136-
if (reverse) {
137-
if (!pos.line) return savePosAndFail(0);
138-
pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
139-
}
140-
else {
141-
var maxLine = this.doc.lineCount();
142-
if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
143-
pos = Pos(pos.line + 1, 0);
144-
}
146+
var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
147+
if (result) {
148+
this.pos = result
149+
this.atOccurrence = true
150+
return this.pos.match || true
151+
} else {
152+
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
153+
this.pos = {from: end, to: end}
154+
return this.atOccurrence = false
145155
}
146156
},
147157

148-
from: function() {if (this.atOccurrence) return this.pos.from;},
149-
to: function() {if (this.atOccurrence) return this.pos.to;},
158+
from: function() {if (this.atOccurrence) return this.pos.from},
159+
to: function() {if (this.atOccurrence) return this.pos.to},
150160

151161
replace: function(newText, origin) {
152-
if (!this.atOccurrence) return;
153-
var lines = CodeMirror.splitLines(newText);
154-
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
162+
if (!this.atOccurrence) return
163+
var lines = CodeMirror.splitLines(newText)
164+
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
155165
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
156-
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
157-
}
158-
};
159-
160-
// Maps a position in a case-folded line back to a position in the original line
161-
// (compensating for codepoints increasing in number during folding)
162-
function adjustPos(orig, folded, pos) {
163-
if (orig.length == folded.length) return pos;
164-
for (var pos1 = Math.min(pos, orig.length);;) {
165-
var len1 = orig.slice(0, pos1).toLowerCase().length;
166-
if (len1 < pos) ++pos1;
167-
else if (len1 > pos) --pos1;
168-
else return pos1;
166+
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
169167
}
170168
}
171169

172170
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
173-
return new SearchCursor(this.doc, query, pos, caseFold);
174-
});
171+
return new SearchCursor(this.doc, query, pos, caseFold)
172+
})
175173
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
176-
return new SearchCursor(this, query, pos, caseFold);
177-
});
174+
return new SearchCursor(this, query, pos, caseFold)
175+
})
178176

179177
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
180-
var ranges = [];
181-
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
178+
var ranges = []
179+
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
182180
while (cur.findNext()) {
183-
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
184-
ranges.push({anchor: cur.from(), head: cur.to()});
181+
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
182+
ranges.push({anchor: cur.from(), head: cur.to()})
185183
}
186184
if (ranges.length)
187-
this.setSelections(ranges, 0);
188-
});
189-
});
185+
this.setSelections(ranges, 0)
186+
})
187+
})

test/search_test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5);
3232
});
3333

34-
test("multiline", "hallo", "goodbye", function(doc) {
35-
run(doc, "llo\ngoo", false, 0, 2, 1, 3);
36-
run(doc, "blah\nhall", false);
37-
run(doc, "bye\neye", false);
34+
test("multiline", "hallo", "a", "b", "goodbye", function(doc) {
35+
run(doc, "llo\na\nb\ngoo", false, 0, 2, 3, 3);
36+
run(doc, "blah\na\nb\nhall", false);
37+
run(doc, "bye\nx\neye", false);
3838
});
3939

4040
test("regexp", "abcde", "abcde", function(doc) {

0 commit comments

Comments
 (0)