Commit | Line | Data |
---|---|---|
837afb80 TD |
1 | (function(mod) { |
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 | |
7 | mod(CodeMirror); | |
8 | })(function(CodeMirror) { | |
9 | "use strict"; | |
77b7b761 TD |
10 | var Pos = CodeMirror.Pos; |
11 | ||
12 | function SearchCursor(doc, query, pos, caseFold) { | |
13 | this.atOccurrence = false; this.doc = doc; | |
14 | if (caseFold == null && typeof query == "string") caseFold = false; | |
15 | ||
16 | pos = pos ? doc.clipPos(pos) : Pos(0, 0); | |
17 | this.pos = {from: pos, to: pos}; | |
18 | ||
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) { | |
26 | if (reverse) { | |
27 | query.lastIndex = 0; | |
28 | var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; | |
29 | for (;;) { | |
30 | query.lastIndex = cutOff; | |
31 | var newMatch = query.exec(line); | |
32 | if (!newMatch) break; | |
33 | match = newMatch; | |
34 | start = match.index; | |
35 | cutOff = match.index + (match[0].length || 1); | |
36 | if (cutOff == line.length) break; | |
37 | } | |
38 | var matchLen = (match && match[0].length) || 0; | |
39 | if (!matchLen) { | |
40 | if (start == 0 && line.length == 0) {match = undefined;} | |
41 | else if (start != doc.getLine(pos.line).length) { | |
42 | matchLen++; | |
43 | } | |
44 | } | |
45 | } else { | |
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; | |
51 | } | |
52 | if (match && matchLen) | |
53 | return {from: Pos(pos.line, start), | |
54 | to: Pos(pos.line, start + matchLen), | |
55 | match: match}; | |
56 | }; | |
57 | } else { // String query | |
837afb80 | 58 | var origQuery = query; |
77b7b761 TD |
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) { | |
64 | if (!query.length) { | |
65 | // Empty string would match anything and never progress, so | |
66 | // we define it to match nothing instead. | |
67 | this.matches = function() {}; | |
68 | } else { | |
69 | this.matches = function(reverse, pos) { | |
837afb80 TD |
70 | if (reverse) { |
71 | var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); | |
72 | var match = line.lastIndexOf(query); | |
73 | if (match > -1) { | |
74 | match = adjustPos(orig, line, match); | |
75 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; | |
76 | } | |
77 | } else { | |
78 | var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); | |
79 | var match = line.indexOf(query); | |
80 | if (match > -1) { | |
81 | match = adjustPos(orig, line, match) + pos.ch; | |
82 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; | |
83 | } | |
84 | } | |
77b7b761 TD |
85 | }; |
86 | } | |
87 | } else { | |
837afb80 | 88 | var origTarget = origQuery.split("\n"); |
77b7b761 | 89 | this.matches = function(reverse, pos) { |
837afb80 TD |
90 | var last = target.length - 1; |
91 | if (reverse) { | |
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}; | |
100 | } else { | |
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)}; | |
77b7b761 TD |
109 | } |
110 | }; | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | SearchCursor.prototype = { | |
116 | findNext: function() {return this.find(false);}, | |
117 | findPrevious: function() {return this.find(true);}, | |
118 | ||
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; | |
125 | return false; | |
126 | } | |
127 | ||
128 | for (;;) { | |
129 | if (this.pos = this.matches(reverse, pos)) { | |
77b7b761 TD |
130 | this.atOccurrence = true; |
131 | return this.pos.match || true; | |
132 | } | |
133 | if (reverse) { | |
134 | if (!pos.line) return savePosAndFail(0); | |
135 | pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); | |
136 | } | |
137 | else { | |
138 | var maxLine = this.doc.lineCount(); | |
139 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine); | |
140 | pos = Pos(pos.line + 1, 0); | |
141 | } | |
142 | } | |
143 | }, | |
144 | ||
145 | from: function() {if (this.atOccurrence) return this.pos.from;}, | |
146 | to: function() {if (this.atOccurrence) return this.pos.to;}, | |
147 | ||
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)); | |
154 | } | |
155 | }; | |
156 | ||
837afb80 TD |
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; | |
165 | else return pos1; | |
166 | } | |
167 | } | |
168 | ||
77b7b761 TD |
169 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { |
170 | return new SearchCursor(this.doc, query, pos, caseFold); | |
171 | }); | |
172 | CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { | |
173 | return new SearchCursor(this, query, pos, caseFold); | |
174 | }); | |
837afb80 TD |
175 | |
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()}); | |
182 | } | |
183 | if (ranges.length) | |
184 | this.setSelections(ranges, 0); | |
185 | }); | |
186 | }); |