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 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
11 mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
12 else if (typeof define
== "function" && define
.amd
) // AMD
13 define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod
);
14 else // Plain browser env
16 })(function(CodeMirror
) {
18 function searchOverlay(query
, caseInsensitive
) {
20 if (typeof query
== "string") {
21 startChar
= query
.charAt(0);
22 query
= new RegExp("^" + query
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"),
23 caseInsensitive
? "i" : "");
25 query
= new RegExp("^(?:" + query
.source
+ ")", query
.ignoreCase
? "i" : "");
27 return {token: function(stream
) {
28 if (stream
.match(query
)) return "searching";
29 while (!stream
.eol()) {
31 if (startChar
&& !caseInsensitive
)
32 stream
.skipTo(startChar
) || stream
.skipToEnd();
33 if (stream
.match(query
, false)) break;
38 function SearchState() {
39 this.posFrom
= this.posTo
= this.query
= null;
42 function getSearchState(cm
) {
43 return cm
.state
.search
|| (cm
.state
.search
= new SearchState());
45 function queryCaseInsensitive(query
) {
46 return typeof query
== "string" && query
== query
.toLowerCase();
48 function getSearchCursor(cm
, query
, pos
) {
49 // Heuristic: if the query string is all lowercase, do a case insensitive search.
50 return cm
.getSearchCursor(query
, pos
, queryCaseInsensitive(query
));
52 function dialog(cm
, text
, shortText
, deflt
, f
) {
53 if (cm
.openDialog
) cm
.openDialog(text
, f
, {value
: deflt
});
54 else f(prompt(shortText
, deflt
));
56 function confirmDialog(cm
, text
, shortText
, fs
) {
57 if (cm
.openConfirm
) cm
.openConfirm(text
, fs
);
58 else if (confirm(shortText
)) fs
[0]();
60 function parseQuery(query
) {
61 var isRE
= query
.match(/^\/(.*)\/([a-z]*)$/);
63 query
= new RegExp(isRE
[1], isRE
[2].indexOf("i") == -1 ? "" : "i");
64 if (query
.test("")) query
= /x^/;
65 } else if (query
== "") {
71 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
72 function doSearch(cm
, rev
) {
73 var state
= getSearchState(cm
);
74 if (state
.query
) return findNext(cm
, rev
);
75 dialog(cm
, queryDialog
, "Search for:", cm
.getSelection(), function(query
) {
76 cm
.operation(function() {
77 if (!query
|| state
.query
) return;
78 state
.query
= parseQuery(query
);
79 cm
.removeOverlay(state
.overlay
, queryCaseInsensitive(state
.query
));
80 state
.overlay
= searchOverlay(state
.query
, queryCaseInsensitive(state
.query
));
81 cm
.addOverlay(state
.overlay
);
82 state
.posFrom
= state
.posTo
= cm
.getCursor();
87 function findNext(cm
, rev
) {cm
.operation(function() {
88 var state
= getSearchState(cm
);
89 var cursor
= getSearchCursor(cm
, state
.query
, rev
? state
.posFrom
: state
.posTo
);
90 if (!cursor
.find(rev
)) {
91 cursor
= getSearchCursor(cm
, state
.query
, rev
? CodeMirror
.Pos(cm
.lastLine()) : CodeMirror
.Pos(cm
.firstLine(), 0));
92 if (!cursor
.find(rev
)) return;
94 cm
.setSelection(cursor
.from(), cursor
.to());
95 cm
.scrollIntoView({from: cursor
.from(), to
: cursor
.to()});
96 state
.posFrom
= cursor
.from(); state
.posTo
= cursor
.to();
98 function clearSearch(cm
) {cm
.operation(function() {
99 var state
= getSearchState(cm
);
100 if (!state
.query
) return;
102 cm
.removeOverlay(state
.overlay
);
105 var replaceQueryDialog
=
106 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
107 var replacementQueryDialog
= 'With: <input type="text" style="width: 10em"/>';
108 var doReplaceConfirm
= "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
109 function replace(cm
, all
) {
110 dialog(cm
, replaceQueryDialog
, "Replace:", cm
.getSelection(), function(query
) {
112 query
= parseQuery(query
);
113 dialog(cm
, replacementQueryDialog
, "Replace with:", "", function(text
) {
115 cm
.operation(function() {
116 for (var cursor
= getSearchCursor(cm
, query
); cursor
.findNext();) {
117 if (typeof query
!= "string") {
118 var match
= cm
.getRange(cursor
.from(), cursor
.to()).match(query
);
119 cursor
.replace(text
.replace(/\$(\d)/g, function(_
, i
) {return match
[i
];}));
120 } else cursor
.replace(text
);
125 var cursor
= getSearchCursor(cm
, query
, cm
.getCursor());
126 var advance = function() {
127 var start
= cursor
.from(), match
;
128 if (!(match
= cursor
.findNext())) {
129 cursor
= getSearchCursor(cm
, query
);
130 if (!(match
= cursor
.findNext()) ||
131 (start
&& cursor
.from().line
== start
.line
&& cursor
.from().ch
== start
.ch
)) return;
133 cm
.setSelection(cursor
.from(), cursor
.to());
134 cm
.scrollIntoView({from: cursor
.from(), to
: cursor
.to()});
135 confirmDialog(cm
, doReplaceConfirm
, "Replace?",
136 [function() {doReplace(match
);}, advance
]);
138 var doReplace = function(match
) {
139 cursor
.replace(typeof query
== "string" ? text
:
140 text
.replace(/\$(\d)/g, function(_
, i
) {return match
[i
];}));
149 CodeMirror
.commands
.find = function(cm
) {clearSearch(cm
); doSearch(cm
);};
150 CodeMirror
.commands
.findNext
= doSearch
;
151 CodeMirror
.commands
.findPrev = function(cm
) {doSearch(cm
, true);};
152 CodeMirror
.commands
.clearSearch
= clearSearch
;
153 CodeMirror
.commands
.replace
= replace
;
154 CodeMirror
.commands
.replaceAll = function(cm
) {replace(cm
, true);};