Mercurial
comparison .cms/lib/codemirror/addon/search/search.js @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others | |
2 // Distributed under an MIT license: https://codemirror.net/5/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 // default search panel location | |
23 CodeMirror.defineOption("search", {bottom: false}); | |
24 | |
25 function searchOverlay(query, caseInsensitive) { | |
26 if (typeof query == "string") | |
27 query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); | |
28 else if (!query.global) | |
29 query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); | |
30 | |
31 return {token: function(stream) { | |
32 query.lastIndex = stream.pos; | |
33 var match = query.exec(stream.string); | |
34 if (match && match.index == stream.pos) { | |
35 stream.pos += match[0].length || 1; | |
36 return "searching"; | |
37 } else if (match) { | |
38 stream.pos = match.index; | |
39 } else { | |
40 stream.skipToEnd(); | |
41 } | |
42 }}; | |
43 } | |
44 | |
45 function SearchState() { | |
46 this.posFrom = this.posTo = this.lastQuery = this.query = null; | |
47 this.overlay = null; | |
48 } | |
49 | |
50 function getSearchState(cm) { | |
51 return cm.state.search || (cm.state.search = new SearchState()); | |
52 } | |
53 | |
54 function queryCaseInsensitive(query) { | |
55 return typeof query == "string" && query == query.toLowerCase(); | |
56 } | |
57 | |
58 function getSearchCursor(cm, query, pos) { | |
59 // Heuristic: if the query string is all lowercase, do a case insensitive search. | |
60 return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true}); | |
61 } | |
62 | |
63 function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { | |
64 cm.openDialog(text, onEnter, { | |
65 value: deflt, | |
66 selectValueOnOpen: true, | |
67 closeOnEnter: false, | |
68 onClose: function() { clearSearch(cm); }, | |
69 onKeyDown: onKeyDown, | |
70 bottom: cm.options.search.bottom | |
71 }); | |
72 } | |
73 | |
74 function dialog(cm, text, shortText, deflt, f) { | |
75 if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom}); | |
76 else f(prompt(shortText, deflt)); | |
77 } | |
78 | |
79 function confirmDialog(cm, text, shortText, fs) { | |
80 if (cm.openConfirm) cm.openConfirm(text, fs); | |
81 else if (confirm(shortText)) fs[0](); | |
82 } | |
83 | |
84 function parseString(string) { | |
85 return string.replace(/\\([nrt\\])/g, function(match, ch) { | |
86 if (ch == "n") return "\n" | |
87 if (ch == "r") return "\r" | |
88 if (ch == "t") return "\t" | |
89 if (ch == "\\") return "\\" | |
90 return match | |
91 }) | |
92 } | |
93 | |
94 function parseQuery(query) { | |
95 var isRE = query.match(/^\/(.*)\/([a-z]*)$/); | |
96 if (isRE) { | |
97 try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } | |
98 catch(e) {} // Not a regular expression after all, do a string search | |
99 } else { | |
100 query = parseString(query) | |
101 } | |
102 if (typeof query == "string" ? query == "" : query.test("")) | |
103 query = /x^/; | |
104 return query; | |
105 } | |
106 | |
107 function startSearch(cm, state, query) { | |
108 state.queryText = query; | |
109 state.query = parseQuery(query); | |
110 cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); | |
111 state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); | |
112 cm.addOverlay(state.overlay); | |
113 if (cm.showMatchesOnScrollbar) { | |
114 if (state.annotate) { state.annotate.clear(); state.annotate = null; } | |
115 state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); | |
116 } | |
117 } | |
118 | |
119 function doSearch(cm, rev, persistent, immediate) { | |
120 var state = getSearchState(cm); | |
121 if (state.query) return findNext(cm, rev); | |
122 var q = cm.getSelection() || state.lastQuery; | |
123 if (q instanceof RegExp && q.source == "x^") q = null | |
124 if (persistent && cm.openDialog) { | |
125 var hiding = null | |
126 var searchNext = function(query, event) { | |
127 CodeMirror.e_stop(event); | |
128 if (!query) return; | |
129 if (query != state.queryText) { | |
130 startSearch(cm, state, query); | |
131 state.posFrom = state.posTo = cm.getCursor(); | |
132 } | |
133 if (hiding) hiding.style.opacity = 1 | |
134 findNext(cm, event.shiftKey, function(_, to) { | |
135 var dialog | |
136 if (to.line < 3 && document.querySelector && | |
137 (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && | |
138 dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) | |
139 (hiding = dialog).style.opacity = .4 | |
140 }) | |
141 }; | |
142 persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) { | |
143 var keyName = CodeMirror.keyName(event) | |
144 var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName] | |
145 if (cmd == "findNext" || cmd == "findPrev" || | |
146 cmd == "findPersistentNext" || cmd == "findPersistentPrev") { | |
147 CodeMirror.e_stop(event); | |
148 startSearch(cm, getSearchState(cm), query); | |
149 cm.execCommand(cmd); | |
150 } else if (cmd == "find" || cmd == "findPersistent") { | |
151 CodeMirror.e_stop(event); | |
152 searchNext(query, event); | |
153 } | |
154 }); | |
155 if (immediate && q) { | |
156 startSearch(cm, state, q); | |
157 findNext(cm, rev); | |
158 } | |
159 } else { | |
160 dialog(cm, getQueryDialog(cm), "Search for:", q, function(query) { | |
161 if (query && !state.query) cm.operation(function() { | |
162 startSearch(cm, state, query); | |
163 state.posFrom = state.posTo = cm.getCursor(); | |
164 findNext(cm, rev); | |
165 }); | |
166 }); | |
167 } | |
168 } | |
169 | |
170 function findNext(cm, rev, callback) {cm.operation(function() { | |
171 var state = getSearchState(cm); | |
172 var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); | |
173 if (!cursor.find(rev)) { | |
174 cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); | |
175 if (!cursor.find(rev)) return; | |
176 } | |
177 cm.setSelection(cursor.from(), cursor.to()); | |
178 cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20); | |
179 state.posFrom = cursor.from(); state.posTo = cursor.to(); | |
180 if (callback) callback(cursor.from(), cursor.to()) | |
181 });} | |
182 | |
183 function clearSearch(cm) {cm.operation(function() { | |
184 var state = getSearchState(cm); | |
185 state.lastQuery = state.query; | |
186 if (!state.query) return; | |
187 state.query = state.queryText = null; | |
188 cm.removeOverlay(state.overlay); | |
189 if (state.annotate) { state.annotate.clear(); state.annotate = null; } | |
190 });} | |
191 | |
192 function el(tag, attrs) { | |
193 var element = tag ? document.createElement(tag) : document.createDocumentFragment(); | |
194 for (var key in attrs) { | |
195 element[key] = attrs[key]; | |
196 } | |
197 for (var i = 2; i < arguments.length; i++) { | |
198 var child = arguments[i] | |
199 element.appendChild(typeof child == "string" ? document.createTextNode(child) : child); | |
200 } | |
201 return element; | |
202 } | |
203 | |
204 function getQueryDialog(cm) { | |
205 var label = el("label", {className: "CodeMirror-search-label"}, | |
206 cm.phrase("Search:"), | |
207 el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field", | |
208 id: "CodeMirror-search-field"})); | |
209 label.setAttribute("for","CodeMirror-search-field"); | |
210 return el("", null, label, " ", | |
211 el("span", {style: "color: #666", className: "CodeMirror-search-hint"}, | |
212 cm.phrase("(Use /re/ syntax for regexp search)"))); | |
213 } | |
214 function getReplaceQueryDialog(cm) { | |
215 return el("", null, " ", | |
216 el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}), " ", | |
217 el("span", {style: "color: #666", className: "CodeMirror-search-hint"}, | |
218 cm.phrase("(Use /re/ syntax for regexp search)"))); | |
219 } | |
220 function getReplacementQueryDialog(cm) { | |
221 return el("", null, | |
222 el("span", {className: "CodeMirror-search-label"}, cm.phrase("With:")), " ", | |
223 el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"})); | |
224 } | |
225 function getDoReplaceConfirm(cm) { | |
226 return el("", null, | |
227 el("span", {className: "CodeMirror-search-label"}, cm.phrase("Replace?")), " ", | |
228 el("button", {}, cm.phrase("Yes")), " ", | |
229 el("button", {}, cm.phrase("No")), " ", | |
230 el("button", {}, cm.phrase("All")), " ", | |
231 el("button", {}, cm.phrase("Stop"))); | |
232 } | |
233 | |
234 function replaceAll(cm, query, text) { | |
235 cm.operation(function() { | |
236 for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { | |
237 if (typeof query != "string") { | |
238 var match = cm.getRange(cursor.from(), cursor.to()).match(query); | |
239 cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); | |
240 } else cursor.replace(text); | |
241 } | |
242 }); | |
243 } | |
244 | |
245 function replace(cm, all) { | |
246 if (cm.getOption("readOnly")) return; | |
247 var query = cm.getSelection() || getSearchState(cm).lastQuery; | |
248 var dialogText = all ? cm.phrase("Replace all:") : cm.phrase("Replace:") | |
249 var fragment = el("", null, | |
250 el("span", {className: "CodeMirror-search-label"}, dialogText), | |
251 getReplaceQueryDialog(cm)) | |
252 dialog(cm, fragment, dialogText, query, function(query) { | |
253 if (!query) return; | |
254 query = parseQuery(query); | |
255 dialog(cm, getReplacementQueryDialog(cm), cm.phrase("Replace with:"), "", function(text) { | |
256 text = parseString(text) | |
257 if (all) { | |
258 replaceAll(cm, query, text) | |
259 } else { | |
260 clearSearch(cm); | |
261 var cursor = getSearchCursor(cm, query, cm.getCursor("from")); | |
262 var advance = function() { | |
263 var start = cursor.from(), match; | |
264 if (!(match = cursor.findNext())) { | |
265 cursor = getSearchCursor(cm, query); | |
266 if (!(match = cursor.findNext()) || | |
267 (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; | |
268 } | |
269 cm.setSelection(cursor.from(), cursor.to()); | |
270 cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); | |
271 confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase("Replace?"), | |
272 [function() {doReplace(match);}, advance, | |
273 function() {replaceAll(cm, query, text)}]); | |
274 }; | |
275 var doReplace = function(match) { | |
276 cursor.replace(typeof query == "string" ? text : | |
277 text.replace(/\$(\d)/g, function(_, i) {return match[i];})); | |
278 advance(); | |
279 }; | |
280 advance(); | |
281 } | |
282 }); | |
283 }); | |
284 } | |
285 | |
286 CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; | |
287 CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);}; | |
288 CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);}; | |
289 CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);}; | |
290 CodeMirror.commands.findNext = doSearch; | |
291 CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; | |
292 CodeMirror.commands.clearSearch = clearSearch; | |
293 CodeMirror.commands.replace = replace; | |
294 CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; | |
295 }); |