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">&nbsp;' + toolbox_lang.casematch +
30        '</label><br />' +
31
32        '<label>' +
33        '<input type="checkbox" class="regexp" value="1">&nbsp;' + toolbox_lang.regexp +
34        '</label><br />' +
35
36        '<label>' +
37        '<input type="checkbox" class="words" value="1">&nbsp;' + 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