UNPKG

10.8 kBJavaScriptView Raw
1// CodeMirror, copyright (c) by Marijn Haverbeke and others
2// Distributed under an MIT license: https://codemirror.net/LICENSE
3
4// Define search commands. Depends on dialog.js or another
5// implementation of the openDialog method.
6
7// Replace works a little oddly -- it will do the replace on the next
8// Ctrl-G (or whatever is bound to findNext) press. You prevent a
9// replace by making sure the match is no longer selected when hitting
10// Ctrl-G.
11
12(function(mod) {
13 if (typeof exports == "object" && typeof module == "object") // CommonJS
14 mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
15 else if (typeof define == "function" && define.amd) // AMD
16 define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
17 else // Plain browser env
18 mod(CodeMirror);
19})(function(CodeMirror) {
20 "use strict";
21
22 function searchOverlay(query, caseInsensitive) {
23 if (typeof query == "string")
24 query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
25 else if (!query.global)
26 query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
27
28 return {token: function(stream) {
29 query.lastIndex = stream.pos;
30 var match = query.exec(stream.string);
31 if (match && match.index == stream.pos) {
32 stream.pos += match[0].length || 1;
33 return "searching";
34 } else if (match) {
35 stream.pos = match.index;
36 } else {
37 stream.skipToEnd();
38 }
39 }};
40 }
41
42 function SearchState() {
43 this.posFrom = this.posTo = this.lastQuery = this.query = null;
44 this.overlay = null;
45 }
46
47 function getSearchState(cm) {
48 return cm.state.search || (cm.state.search = new SearchState());
49 }
50
51 function queryCaseInsensitive(query) {
52 return typeof query == "string" && query == query.toLowerCase();
53 }
54
55 function getSearchCursor(cm, query, pos) {
56 // Heuristic: if the query string is all lowercase, do a case insensitive search.
57 return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
58 }
59
60 function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
61 cm.openDialog(text, onEnter, {
62 value: deflt,
63 selectValueOnOpen: true,
64 closeOnEnter: false,
65 onClose: function() { clearSearch(cm); },
66 onKeyDown: onKeyDown
67 });
68 }
69
70 function dialog(cm, text, shortText, deflt, f) {
71 if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
72 else f(prompt(shortText, deflt));
73 }
74
75 function confirmDialog(cm, text, shortText, fs) {
76 if (cm.openConfirm) cm.openConfirm(text, fs);
77 else if (confirm(shortText)) fs[0]();
78 }
79
80 function parseString(string) {
81 return string.replace(/\\([nrt\\])/g, function(match, ch) {
82 if (ch == "n") return "\n"
83 if (ch == "r") return "\r"
84 if (ch == "t") return "\t"
85 if (ch == "\\") return "\\"
86 return match
87 })
88 }
89
90 function parseQuery(query) {
91 var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
92 if (isRE) {
93 try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
94 catch(e) {} // Not a regular expression after all, do a string search
95 } else {
96 query = parseString(query)
97 }
98 if (typeof query == "string" ? query == "" : query.test(""))
99 query = /x^/;
100 return query;
101 }
102
103 function startSearch(cm, state, query) {
104 state.queryText = query;
105 state.query = parseQuery(query);
106 cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
107 state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
108 cm.addOverlay(state.overlay);
109 if (cm.showMatchesOnScrollbar) {
110 if (state.annotate) { state.annotate.clear(); state.annotate = null; }
111 state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
112 }
113 }
114
115 function doSearch(cm, rev, persistent, immediate) {
116 var state = getSearchState(cm);
117 if (state.query) return findNext(cm, rev);
118 var q = cm.getSelection() || state.lastQuery;
119 if (q instanceof RegExp && q.source == "x^") q = null
120 if (persistent && cm.openDialog) {
121 var hiding = null
122 var searchNext = function(query, event) {
123 CodeMirror.e_stop(event);
124 if (!query) return;
125 if (query != state.queryText) {
126 startSearch(cm, state, query);
127 state.posFrom = state.posTo = cm.getCursor();
128 }
129 if (hiding) hiding.style.opacity = 1
130 findNext(cm, event.shiftKey, function(_, to) {
131 var dialog
132 if (to.line < 3 && document.querySelector &&
133 (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
134 dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
135 (hiding = dialog).style.opacity = .4
136 })
137 };
138 persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) {
139 var keyName = CodeMirror.keyName(event)
140 var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
141 if (cmd == "findNext" || cmd == "findPrev" ||
142 cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
143 CodeMirror.e_stop(event);
144 startSearch(cm, getSearchState(cm), query);
145 cm.execCommand(cmd);
146 } else if (cmd == "find" || cmd == "findPersistent") {
147 CodeMirror.e_stop(event);
148 searchNext(query, event);
149 }
150 });
151 if (immediate && q) {
152 startSearch(cm, state, q);
153 findNext(cm, rev);
154 }
155 } else {
156 dialog(cm, getQueryDialog(cm), "Search for:", q, function(query) {
157 if (query && !state.query) cm.operation(function() {
158 startSearch(cm, state, query);
159 state.posFrom = state.posTo = cm.getCursor();
160 findNext(cm, rev);
161 });
162 });
163 }
164 }
165
166 function findNext(cm, rev, callback) {cm.operation(function() {
167 var state = getSearchState(cm);
168 var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
169 if (!cursor.find(rev)) {
170 cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
171 if (!cursor.find(rev)) return;
172 }
173 cm.setSelection(cursor.from(), cursor.to());
174 cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
175 state.posFrom = cursor.from(); state.posTo = cursor.to();
176 if (callback) callback(cursor.from(), cursor.to())
177 });}
178
179 function clearSearch(cm) {cm.operation(function() {
180 var state = getSearchState(cm);
181 state.lastQuery = state.query;
182 if (!state.query) return;
183 state.query = state.queryText = null;
184 cm.removeOverlay(state.overlay);
185 if (state.annotate) { state.annotate.clear(); state.annotate = null; }
186 });}
187
188
189 function getQueryDialog(cm) {
190 return '<span class="CodeMirror-search-label">' + cm.phrase("Search:") + '</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">' + cm.phrase("(Use /re/ syntax for regexp search)") + '</span>';
191 }
192 function getReplaceQueryDialog(cm) {
193 return ' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">' + cm.phrase("(Use /re/ syntax for regexp search)") + '</span>';
194 }
195 function getReplacementQueryDialog(cm) {
196 return '<span class="CodeMirror-search-label">' + cm.phrase("With:") + '</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
197 }
198 function getDoReplaceConfirm(cm) {
199 return '<span class="CodeMirror-search-label">' + cm.phrase("Replace?") + '</span> <button>' + cm.phrase("Yes") + '</button> <button>' + cm.phrase("No") + '</button> <button>' + cm.phrase("All") + '</button> <button>' + cm.phrase("Stop") + '</button> ';
200 }
201
202 function replaceAll(cm, query, text) {
203 cm.operation(function() {
204 for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
205 if (typeof query != "string") {
206 var match = cm.getRange(cursor.from(), cursor.to()).match(query);
207 cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
208 } else cursor.replace(text);
209 }
210 });
211 }
212
213 function replace(cm, all) {
214 if (cm.getOption("readOnly")) return;
215 var query = cm.getSelection() || getSearchState(cm).lastQuery;
216 var dialogText = '<span class="CodeMirror-search-label">' + (all ? cm.phrase("Replace all:") : cm.phrase("Replace:")) + '</span>';
217 dialog(cm, dialogText + getReplaceQueryDialog(cm), dialogText, query, function(query) {
218 if (!query) return;
219 query = parseQuery(query);
220 dialog(cm, getReplacementQueryDialog(cm), cm.phrase("Replace with:"), "", function(text) {
221 text = parseString(text)
222 if (all) {
223 replaceAll(cm, query, text)
224 } else {
225 clearSearch(cm);
226 var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
227 var advance = function() {
228 var start = cursor.from(), match;
229 if (!(match = cursor.findNext())) {
230 cursor = getSearchCursor(cm, query);
231 if (!(match = cursor.findNext()) ||
232 (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
233 }
234 cm.setSelection(cursor.from(), cursor.to());
235 cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
236 confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase("Replace?"),
237 [function() {doReplace(match);}, advance,
238 function() {replaceAll(cm, query, text)}]);
239 };
240 var doReplace = function(match) {
241 cursor.replace(typeof query == "string" ? text :
242 text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
243 advance();
244 };
245 advance();
246 }
247 });
248 });
249 }
250
251 CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
252 CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
253 CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
254 CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
255 CodeMirror.commands.findNext = doSearch;
256 CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
257 CodeMirror.commands.clearSearch = clearSearch;
258 CodeMirror.commands.replace = replace;
259 CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
260});