1 // Define search commands. Depends on dialog.js or another
2 // implementation of the openDialog method.
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
10 function searchOverlay(query) {
11 if (typeof query == "string") return {token: function(stream) {
12 if (stream.match(query)) return "searching";
14 stream.skipTo(query.charAt(0)) || stream.skipToEnd();
16 return {token: function(stream) {
17 if (stream.match(query)) return "searching";
18 while (!stream.eol()) {
20 if (stream.match(query, false)) break;
25 function SearchState() {
26 this.posFrom = this.posTo = this.query = null;
29 function getSearchState(cm) {
30 return cm.state.search || (cm.state.search = new SearchState());
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());
36 function dialog(cm, text, shortText, f) {
37 if (cm.openDialog) cm.openDialog(text, f);
38 else f(prompt(shortText, ""));
40 function confirmDialog(cm, text, shortText, fs) {
41 if (cm.openConfirm) cm.openConfirm(text, fs);
42 else if (confirm(shortText)) fs[0]();
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;
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();
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;
72 cm.setSelection(cursor.from(), cursor.to());
73 state.posFrom = cursor.from(); state.posTo = cursor.to();
75 function clearSearch(cm) {cm.operation(function() {
76 var state = getSearchState(cm);
77 if (!state.query) return;
79 cm.removeOverlay(state.overlay);
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) {
89 query = parseQuery(query);
90 dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
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);
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;
110 cm.setSelection(cursor.from(), cursor.to());
111 confirmDialog(cm, doReplaceConfirm, "Replace?",
112 [function() {doReplace(match);}, advance]);
114 var doReplace = function(match) {
115 cursor.replace(typeof query == "string" ? text :
116 text.replace(/\$(\d)/, function(_, i) {return match[i];}));
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);};