search.js
118 lines
| 4.9 KiB
| application/javascript
|
JavascriptLexer
Brian Granger
|
r5941 | // Define search commands. Depends on dialog.js or another | ||
// implementation of the openDialog method. | ||||
// Replace works a little oddly -- it will do the replace on the next | ||||
// Ctrl-G (or whatever is bound to findNext) press. You prevent a | ||||
// replace by making sure the match is no longer selected when hitting | ||||
// Ctrl-G. | ||||
(function() { | ||||
function SearchState() { | ||||
this.posFrom = this.posTo = this.query = null; | ||||
this.marked = []; | ||||
} | ||||
function getSearchState(cm) { | ||||
return cm._searchState || (cm._searchState = new SearchState()); | ||||
} | ||||
Matthias BUSSONNIER
|
r8053 | function getSearchCursor(cm, query, pos) { | ||
// Heuristic: if the query string is all lowercase, do a case insensitive search. | ||||
return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase()); | ||||
} | ||||
Brian Granger
|
r5941 | function dialog(cm, text, shortText, f) { | ||
if (cm.openDialog) cm.openDialog(text, f); | ||||
else f(prompt(shortText, "")); | ||||
} | ||||
function confirmDialog(cm, text, shortText, fs) { | ||||
if (cm.openConfirm) cm.openConfirm(text, fs); | ||||
else if (confirm(shortText)) fs[0](); | ||||
} | ||||
function parseQuery(query) { | ||||
Matthias BUSSONNIER
|
r8053 | var isRE = query.match(/^\/(.*)\/([a-z]*)$/); | ||
return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query; | ||||
Brian Granger
|
r5941 | } | ||
var queryDialog = | ||||
Matthias BUSSONNIER
|
r8053 | 'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>'; | ||
Brian Granger
|
r5941 | function doSearch(cm, rev) { | ||
var state = getSearchState(cm); | ||||
if (state.query) return findNext(cm, rev); | ||||
dialog(cm, queryDialog, "Search for:", function(query) { | ||||
cm.operation(function() { | ||||
if (!query || state.query) return; | ||||
state.query = parseQuery(query); | ||||
if (cm.lineCount() < 2000) { // This is too expensive on big documents. | ||||
Matthias BUSSONNIER
|
r8053 | for (var cursor = getSearchCursor(cm, query); cursor.findNext();) | ||
Brian Granger
|
r5941 | state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching")); | ||
} | ||||
state.posFrom = state.posTo = cm.getCursor(); | ||||
findNext(cm, rev); | ||||
}); | ||||
}); | ||||
} | ||||
function findNext(cm, rev) {cm.operation(function() { | ||||
var state = getSearchState(cm); | ||||
Matthias BUSSONNIER
|
r8053 | var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); | ||
Brian Granger
|
r5941 | if (!cursor.find(rev)) { | ||
Matthias BUSSONNIER
|
r8053 | cursor = getSearchCursor(cm, state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0}); | ||
Brian Granger
|
r5941 | if (!cursor.find(rev)) return; | ||
} | ||||
cm.setSelection(cursor.from(), cursor.to()); | ||||
state.posFrom = cursor.from(); state.posTo = cursor.to(); | ||||
})} | ||||
function clearSearch(cm) {cm.operation(function() { | ||||
var state = getSearchState(cm); | ||||
if (!state.query) return; | ||||
state.query = null; | ||||
for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear(); | ||||
state.marked.length = 0; | ||||
})} | ||||
var replaceQueryDialog = | ||||
Matthias BUSSONNIER
|
r8053 | 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>'; | ||
var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>'; | ||||
Brian Granger
|
r5941 | var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>"; | ||
function replace(cm, all) { | ||||
dialog(cm, replaceQueryDialog, "Replace:", function(query) { | ||||
if (!query) return; | ||||
query = parseQuery(query); | ||||
dialog(cm, replacementQueryDialog, "Replace with:", function(text) { | ||||
if (all) { | ||||
Matthias BUSSONNIER
|
r8053 | cm.compoundChange(function() { cm.operation(function() { | ||
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { | ||||
Brian Granger
|
r5941 | if (typeof query != "string") { | ||
var match = cm.getRange(cursor.from(), cursor.to()).match(query); | ||||
cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];})); | ||||
} else cursor.replace(text); | ||||
} | ||||
Matthias BUSSONNIER
|
r8053 | })}); | ||
Brian Granger
|
r5941 | } else { | ||
clearSearch(cm); | ||||
Matthias BUSSONNIER
|
r8053 | var cursor = getSearchCursor(cm, query, cm.getCursor()); | ||
Brian Granger
|
r5941 | function advance() { | ||
var start = cursor.from(), match; | ||||
if (!(match = cursor.findNext())) { | ||||
Matthias BUSSONNIER
|
r8053 | cursor = getSearchCursor(cm, query); | ||
Brian Granger
|
r5941 | if (!(match = cursor.findNext()) || | ||
Matthias BUSSONNIER
|
r8053 | (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; | ||
Brian Granger
|
r5941 | } | ||
cm.setSelection(cursor.from(), cursor.to()); | ||||
confirmDialog(cm, doReplaceConfirm, "Replace?", | ||||
[function() {doReplace(match);}, advance]); | ||||
} | ||||
function doReplace(match) { | ||||
cursor.replace(typeof query == "string" ? text : | ||||
text.replace(/\$(\d)/, function(w, i) {return match[i];})); | ||||
advance(); | ||||
} | ||||
advance(); | ||||
} | ||||
}); | ||||
}); | ||||
} | ||||
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; | ||||
CodeMirror.commands.findNext = doSearch; | ||||
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; | ||||
CodeMirror.commands.clearSearch = clearSearch; | ||||
CodeMirror.commands.replace = replace; | ||||
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; | ||||
})(); | ||||