1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/sourceeditor/codemirror/search/search.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,167 @@ 1.4 +// Define search commands. Depends on dialog.js or another 1.5 +// implementation of the openDialog method. 1.6 + 1.7 +// Replace works a little oddly -- it will do the replace on the next 1.8 +// Ctrl-G (or whatever is bound to findNext) press. You prevent a 1.9 +// replace by making sure the match is no longer selected when hitting 1.10 +// Ctrl-G. 1.11 + 1.12 +(function(mod) { 1.13 + if (typeof exports == "object" && typeof module == "object") // CommonJS 1.14 + mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); 1.15 + else if (typeof define == "function" && define.amd) // AMD 1.16 + define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); 1.17 + else // Plain browser env 1.18 + mod(CodeMirror); 1.19 +})(function(CodeMirror) { 1.20 + "use strict"; 1.21 + function searchOverlay(query, caseInsensitive) { 1.22 + var startChar; 1.23 + if (typeof query == "string") { 1.24 + startChar = query.charAt(0); 1.25 + query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), 1.26 + caseInsensitive ? "i" : ""); 1.27 + } else { 1.28 + query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : ""); 1.29 + } 1.30 + return {token: function(stream) { 1.31 + if (stream.match(query)) return "searching"; 1.32 + while (!stream.eol()) { 1.33 + stream.next(); 1.34 + if (startChar && !caseInsensitive) 1.35 + stream.skipTo(startChar) || stream.skipToEnd(); 1.36 + if (stream.match(query, false)) break; 1.37 + } 1.38 + }}; 1.39 + } 1.40 + 1.41 + function SearchState() { 1.42 + this.posFrom = this.posTo = this.query = null; 1.43 + this.overlay = null; 1.44 + } 1.45 + function getSearchState(cm) { 1.46 + return cm.state.search || (cm.state.search = new SearchState()); 1.47 + } 1.48 + function queryCaseInsensitive(query) { 1.49 + return typeof query == "string" && query == query.toLowerCase(); 1.50 + } 1.51 + function getSearchCursor(cm, query, pos) { 1.52 + // Heuristic: if the query string is all lowercase, do a case insensitive search. 1.53 + return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); 1.54 + } 1.55 + function dialog(cm, text, shortText, deflt, f) { 1.56 + if (cm.openDialog) cm.openDialog(text, f, {value: deflt}); 1.57 + else f(prompt(shortText, deflt)); 1.58 + } 1.59 + function confirmDialog(cm, text, shortText, fs) { 1.60 + if (cm.openConfirm) cm.openConfirm(text, fs); 1.61 + else if (confirm(shortText)) fs[0](); 1.62 + } 1.63 + function parseQuery(query) { 1.64 + var isRE = query.match(/^\/(.*)\/([a-z]*)$/); 1.65 + if (isRE) { 1.66 + query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); 1.67 + if (query.test("")) query = /x^/; 1.68 + } else if (query == "") { 1.69 + query = /x^/; 1.70 + } 1.71 + return query; 1.72 + } 1.73 + var queryDialog; 1.74 + function doSearch(cm, rev) { 1.75 + if (!queryDialog) { 1.76 + let doc = cm.getWrapperElement().ownerDocument; 1.77 + let inp = doc.createElement("input"); 1.78 + let txt = doc.createTextNode(cm.l10n("findCmd.promptMessage")); 1.79 + 1.80 + inp.type = "text"; 1.81 + inp.style.width = "10em"; 1.82 + inp.style.MozMarginStart = "1em"; 1.83 + 1.84 + queryDialog = doc.createElement("div"); 1.85 + queryDialog.appendChild(txt); 1.86 + queryDialog.appendChild(inp); 1.87 + } 1.88 + var state = getSearchState(cm); 1.89 + if (state.query) return findNext(cm, rev); 1.90 + dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) { 1.91 + cm.operation(function() { 1.92 + if (!query || state.query) return; 1.93 + state.query = parseQuery(query); 1.94 + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); 1.95 + state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); 1.96 + cm.addOverlay(state.overlay); 1.97 + state.posFrom = state.posTo = cm.getCursor(); 1.98 + findNext(cm, rev); 1.99 + }); 1.100 + }); 1.101 + } 1.102 + function findNext(cm, rev) {cm.operation(function() { 1.103 + var state = getSearchState(cm); 1.104 + var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); 1.105 + if (!cursor.find(rev)) { 1.106 + cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); 1.107 + if (!cursor.find(rev)) return; 1.108 + } 1.109 + cm.setSelection(cursor.from(), cursor.to()); 1.110 + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); 1.111 + state.posFrom = cursor.from(); state.posTo = cursor.to(); 1.112 + });} 1.113 + function clearSearch(cm) {cm.operation(function() { 1.114 + var state = getSearchState(cm); 1.115 + if (!state.query) return; 1.116 + state.query = null; 1.117 + cm.removeOverlay(state.overlay); 1.118 + });} 1.119 + 1.120 + var replaceQueryDialog = 1.121 + 'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>'; 1.122 + var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>'; 1.123 + var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>"; 1.124 + function replace(cm, all) { 1.125 + dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { 1.126 + if (!query) return; 1.127 + query = parseQuery(query); 1.128 + dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { 1.129 + if (all) { 1.130 + cm.operation(function() { 1.131 + for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { 1.132 + if (typeof query != "string") { 1.133 + var match = cm.getRange(cursor.from(), cursor.to()).match(query); 1.134 + cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); 1.135 + } else cursor.replace(text); 1.136 + } 1.137 + }); 1.138 + } else { 1.139 + clearSearch(cm); 1.140 + var cursor = getSearchCursor(cm, query, cm.getCursor()); 1.141 + var advance = function() { 1.142 + var start = cursor.from(), match; 1.143 + if (!(match = cursor.findNext())) { 1.144 + cursor = getSearchCursor(cm, query); 1.145 + if (!(match = cursor.findNext()) || 1.146 + (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; 1.147 + } 1.148 + cm.setSelection(cursor.from(), cursor.to()); 1.149 + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); 1.150 + confirmDialog(cm, doReplaceConfirm, "Replace?", 1.151 + [function() {doReplace(match);}, advance]); 1.152 + }; 1.153 + var doReplace = function(match) { 1.154 + cursor.replace(typeof query == "string" ? text : 1.155 + text.replace(/\$(\d)/g, function(_, i) {return match[i];})); 1.156 + advance(); 1.157 + }; 1.158 + advance(); 1.159 + } 1.160 + }); 1.161 + }); 1.162 + } 1.163 + 1.164 + CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; 1.165 + CodeMirror.commands.findNext = doSearch; 1.166 + CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; 1.167 + CodeMirror.commands.clearSearch = clearSearch; 1.168 + CodeMirror.commands.replace = replace; 1.169 + CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; 1.170 +});