1/** 2 * Implements the Find and Replace dialog 3 * 4 * @param {string} edid 5 * @constructor 6 */ 7var ToolboxFindAndReplace = function (edid) { 8 9 var textarea = jQuery('#' + edid)[0]; 10 11 // the dialog HTML 12 var $dialog = jQuery( 13 '<div>' + 14 '<label>' + 15 '<input type="text" class="find" />' + 16 '<button class="find">' + toolbox_lang.find + '</button>' + 17 '</label>' + 18 19 '<label>' + 20 '<input type="text" class="replace" />' + 21 '<button class="replace">' + toolbox_lang.replace + '</button>' + 22 '</label><br />' + 23 24 '<button class="find_replace">' + toolbox_lang.find_replace + '</button>' + 25 '<button class="replace_all">' + toolbox_lang.replace_all + '</button><br />' + 26 27 28 '<label>' + 29 '<input type="checkbox" class="casematch" value="1"> ' + toolbox_lang.casematch + 30 '</label><br />' + 31 32 '<label>' + 33 '<input type="checkbox" class="regexp" value="1"> ' + toolbox_lang.regexp + 34 '</label><br />' + 35 36 '<label>' + 37 '<input type="checkbox" class="words" value="1"> ' + toolbox_lang.wordmatch + 38 '</label>' + 39 40 '</div>' 41 ); 42 43 // pointers to the various elements in the dialog 44 $dialog.components = { 45 in_find: $dialog.find('input.find'), 46 btn_find: $dialog.find('button.find'), 47 chk_casematch: $dialog.find('input.casematch'), 48 chk_regexp: $dialog.find('input.regexp'), 49 chk_words: $dialog.find('input.words'), 50 in_replace: $dialog.find('input.replace'), 51 btn_replace: $dialog.find('button.replace'), 52 btn_find_replace: $dialog.find('button.find_replace'), 53 btn_replace_all: $dialog.find('button.replace_all') 54 }; 55 56 // register handlers 57 $dialog.components.btn_find.click(handle_find); 58 $dialog.components.btn_replace.click(handle_replace); 59 $dialog.components.btn_find_replace.click(handle_find_replace); 60 $dialog.components.btn_replace_all.click(handle_replace_all); 61 62 63 /** 64 * Initialize the dialog 65 */ 66 $dialog.dialog({ 67 title: toolbox_lang.f_r, 68 resizable: false, 69 // show outside the textarea 70 position: { 71 my: 'right+25 bottom-25', 72 at: 'right top', 73 of: textarea 74 }, 75 // clean up on close 76 close: function () { 77 $dialog.dialog('destroy'); 78 $dialog.remove(); 79 } 80 }); 81 82 83 /** 84 * Refocus the textarea after interaction with the dialog 85 * 86 * except for input fields. this makes sure selections are visible 87 */ 88 $dialog.dialog('widget').mouseup(function (e) { 89 if (e.target.nodeName == 'INPUT') return; 90 window.setTimeout(function () { 91 textarea.focus(); 92 }, 1); 93 }); 94 95 96 /** 97 * Handle find 98 * 99 * It highlights the next place where the current search term 100 * can be found. Looking from the cursor position onward. 101 * 102 * @return {boolean} true when the word was found 103 */ 104 function handle_find() { 105 var sel = DWgetSelection(textarea); 106 107 var term = $dialog.components.in_find.val(); 108 if (term == '') return false; 109 110 var found = findNextPosition(term, sel.end); 111 if (found[0] === -1) return false; 112 selectWord(found[0], found[1].length); 113 return true; 114 } 115 116 /** 117 * Handle replace 118 * 119 * It replaces the current selection with the replacement 120 * 121 * @return {boolean} true when the selection was replaced 122 */ 123 function handle_replace() { 124 var sel = DWgetSelection(textarea); 125 if (sel.start === sel.end) { 126 window.alert(toolbox_lang.notext); 127 return false; 128 } 129 var text = $dialog.components.in_replace.val(); 130 pasteText(sel, text, {startofs: 0, endofs: 0, nosel: true}); 131 return true; 132 } 133 134 /** 135 * Handle find&replace 136 * 137 * Look for the next match and replace it 138 * 139 * @return {boolean} 140 */ 141 function handle_find_replace() { 142 return handle_find() && handle_replace(); 143 } 144 145 /** 146 * Handle replace all 147 * 148 * Counts matches first and asks for confirmation 149 * 150 * @return {boolean} 151 */ 152 function handle_replace_all() { 153 var term = $dialog.components.in_find.val(); 154 if (term == '') return false; 155 var text = textarea.value; 156 var repl = $dialog.components.in_replace.val(); 157 158 var re = makeRegexp(term, 'g'); 159 160 // count the matches 161 var m; 162 var found = 0; 163 while (m = re.exec(text)) { 164 found++; 165 } 166 167 if (!found) { 168 window.alert(toolbox_lang.nothing); 169 return false; 170 } 171 172 if (window.confirm(toolbox_lang.really.replace('%d', found))) { 173 textarea.value = text.replace(re, repl); 174 return true; 175 } 176 return false; 177 } 178 179 /** 180 * Select the given range in the textarea and scrolls to it 181 * 182 * @param start 183 * @param len 184 */ 185 function selectWord(start, len) { 186 var sel = DWgetSelection(textarea); 187 188 // first move cursor only, defocus for a moment to make the area scroll 189 sel.start = start; 190 sel.end = start; 191 sel.scroll = undefined; 192 DWsetSelection(sel); 193 textarea.blur(); 194 textarea.focus(); 195 196 // then select the found word 197 sel.end = start + len; 198 DWsetSelection(sel); 199 } 200 201 /** 202 * Find the postion of the term right of pos 203 * 204 * @param {string} term 205 * @param {int} pos 206 * @returns {[{int}, {string}]} the position and the matched term 207 */ 208 function findNextPosition(term, pos) { 209 var text = textarea.value.substr(pos); 210 var re = makeRegexp(term); 211 var idx = text.search(re); 212 if (idx === -1) { 213 if (pos !== 0) { 214 if (window.confirm(toolbox_lang.fromtop)) { 215 return findNextPosition(term, 0); 216 } else { 217 return [-1, term]; 218 } 219 } else { 220 window.alert(toolbox_lang.nothing); 221 return [-1, term]; 222 } 223 } 224 var match = text.match(re); 225 return [pos + idx, match[0]]; 226 } 227 228 /** 229 * create the proper regexp to search for the given term 230 * 231 * It checks the current settings and sets the proper flags 232 * for the regular expression. It also escapes the given term 233 * if needed 234 * 235 * @param term 236 * @param {string} [flags] initial regexp flags to set 237 * @returns {RegExp} 238 */ 239 function makeRegexp(term, flags) { 240 if (!flags) flags = ''; 241 242 if (!$dialog.components.chk_regexp.prop('checked')) { 243 term = quoteRE(term); 244 } 245 if (!$dialog.components.chk_casematch.prop('checked')) { 246 flags += 'i'; 247 } 248 if ($dialog.components.chk_words.prop('checked')) { 249 term = '(?:\\b)' + term + '(?:\\b)'; 250 } 251 252 console.log(term); 253 254 try { 255 return new RegExp(term, flags); 256 } catch (e) { 257 window.alert(toolbox_lang.reerror + '\n' + e.message); 258 return null; 259 } 260 } 261 262 /** 263 * Escapes characters in the string that are not safe to use in a RegExp. 264 * 265 * @param {*} s The string to escape. If not a string, it will be casted to one. 266 * @return {string} A RegExp safe, escaped copy of {@code s}. 267 * @link http://stackoverflow.com/a/18151038/172068 268 */ 269 function quoteRE(s) { 270 return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').replace(/\x08/g, '\\x08'); 271 } 272}; 273