Fix: merge conflict
[myslice.git] / third-party / codemirror-3.15 / addon / search / search.js
1 // Define search commands. Depends on dialog.js or another
2 // implementation of the openDialog method.
3
4 // Replace works a little oddly -- it will do the replace on the next
5 // Ctrl-G (or whatever is bound to findNext) press. You prevent a
6 // replace by making sure the match is no longer selected when hitting
7 // Ctrl-G.
8
9 (function() {
10   function searchOverlay(query) {
11     if (typeof query == "string") return {token: function(stream) {
12       if (stream.match(query)) return "searching";
13       stream.next();
14       stream.skipTo(query.charAt(0)) || stream.skipToEnd();
15     }};
16     return {token: function(stream) {
17       if (stream.match(query)) return "searching";
18       while (!stream.eol()) {
19         stream.next();
20         if (stream.match(query, false)) break;
21       }
22     }};
23   }
24
25   function SearchState() {
26     this.posFrom = this.posTo = this.query = null;
27     this.overlay = null;
28   }
29   function getSearchState(cm) {
30     return cm.state.search || (cm.state.search = new SearchState());
31   }
32   function getSearchCursor(cm, query, pos) {
33     // Heuristic: if the query string is all lowercase, do a case insensitive search.
34     return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
35   }
36   function dialog(cm, text, shortText, f) {
37     if (cm.openDialog) cm.openDialog(text, f);
38     else f(prompt(shortText, ""));
39   }
40   function confirmDialog(cm, text, shortText, fs) {
41     if (cm.openConfirm) cm.openConfirm(text, fs);
42     else if (confirm(shortText)) fs[0]();
43   }
44   function parseQuery(query) {
45     var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
46     return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
47   }
48   var queryDialog =
49     'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
50   function doSearch(cm, rev) {
51     var state = getSearchState(cm);
52     if (state.query) return findNext(cm, rev);
53     dialog(cm, queryDialog, "Search for:", function(query) {
54       cm.operation(function() {
55         if (!query || state.query) return;
56         state.query = parseQuery(query);
57         cm.removeOverlay(state.overlay);
58         state.overlay = searchOverlay(state.query);
59         cm.addOverlay(state.overlay);
60         state.posFrom = state.posTo = cm.getCursor();
61         findNext(cm, rev);
62       });
63     });
64   }
65   function findNext(cm, rev) {cm.operation(function() {
66     var state = getSearchState(cm);
67     var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
68     if (!cursor.find(rev)) {
69       cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
70       if (!cursor.find(rev)) return;
71     }
72     cm.setSelection(cursor.from(), cursor.to());
73     state.posFrom = cursor.from(); state.posTo = cursor.to();
74   });}
75   function clearSearch(cm) {cm.operation(function() {
76     var state = getSearchState(cm);
77     if (!state.query) return;
78     state.query = null;
79     cm.removeOverlay(state.overlay);
80   });}
81
82   var replaceQueryDialog =
83     'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
84   var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
85   var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
86   function replace(cm, all) {
87     dialog(cm, replaceQueryDialog, "Replace:", function(query) {
88       if (!query) return;
89       query = parseQuery(query);
90       dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
91         if (all) {
92           cm.operation(function() {
93             for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
94               if (typeof query != "string") {
95                 var match = cm.getRange(cursor.from(), cursor.to()).match(query);
96                 cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
97               } else cursor.replace(text);
98             }
99           });
100         } else {
101           clearSearch(cm);
102           var cursor = getSearchCursor(cm, query, cm.getCursor());
103           var advance = function() {
104             var start = cursor.from(), match;
105             if (!(match = cursor.findNext())) {
106               cursor = getSearchCursor(cm, query);
107               if (!(match = cursor.findNext()) ||
108                   (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
109             }
110             cm.setSelection(cursor.from(), cursor.to());
111             confirmDialog(cm, doReplaceConfirm, "Replace?",
112                           [function() {doReplace(match);}, advance]);
113           };
114           var doReplace = function(match) {
115             cursor.replace(typeof query == "string" ? text :
116                            text.replace(/\$(\d)/, function(_, i) {return match[i];}));
117             advance();
118           };
119           advance();
120         }
121       });
122     });
123   }
124
125   CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
126   CodeMirror.commands.findNext = doSearch;
127   CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
128   CodeMirror.commands.clearSearch = clearSearch;
129   CodeMirror.commands.replace = replace;
130   CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
131 })();