4 A Programmer's Text Editor
8 Copyright (C) 1991-2011 Angel Ortega <angel@triptico.com>
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 http://www.triptico.com
28 /* successful searches will always be shown in this line */
29 mp.config.move_seek_to_line = 5;
31 /* grep recursivity */
32 mp.config.recursive_grep = 0;
34 /** editor actions **/
36 mp.actions['seek'] = sub (d) {
40 label: L("Text to seek:"),
45 label: L("Case sensitive") ~ ':',
47 value: mp.config.case_sensitive_search
53 mp.config.case_sensitive_search = t[1];
55 mp.long_op(mp.search, d, mp.backslash_codes(t[0])) ||
56 mp.alert(L("Text not found."));
62 mp.actions['seek_next'] = sub (d) {
63 mp.long_op(mp.search, d, NULL) || mp.alert(L("Text not found."));
68 mp.actions['seek_prev'] = sub (d) {
69 mp.long_op(mp.search_back, d, NULL) || mp.alert(L("Text not found."));
74 mp.actions['replace'] = sub (d) {
78 'label' => L("Replace text:"),
83 'label' => L("Replace with:"),
85 'history' => 'replace'
88 'label' => L("Case sensitive") ~ ':',
90 'value' => mp.config.case_sensitive_search
93 'label' => L("Global replace:"),
95 'value' => mp.config.global_replace
101 mp.config.case_sensitive_search = r[2];
102 mp.config.global_replace = r[3];
105 mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
111 mp.actions['seek_next_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search); };
112 mp.actions['seek_prev_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search_back); };
114 mp.actions['grep'] = sub (d) {
118 'label' => L("Text to seek:"),
120 'history' => 'search'
123 'label' => L("Files to grep (empty, all):"),
128 'label' => L("Base directory (empty, current):"),
130 'history' => 'grep_base'
133 'label' => L("Recursive?"),
134 'type' => 'checkbox',
135 'value' => mp.config.recursive_grep
140 if (r != NULL && r[0] ne '') {
141 mp.config.recursive_grep = r[3];
143 local t = '<grep ' ~ r[0] ~ ' ' ~ r[1] ~ '>';
145 if ((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/', r[1], r[2], r[3])) == NULL)
146 mp.alert(L("File(s) not found."));
149 mp.alert(L("Text not found."));
151 local l = mp.open(t);
157 mp.insert(l, sprintf("%s:%d: %s\n", e[0], e[1] + 1, e[2]));
169 /** default key bindings **/
171 mp.keycodes['f3'] = 'seek_next';
172 mp.keycodes['ctrl-f3'] = 'seek_prev';
173 mp.keycodes['ctrl-f'] = 'seek';
174 mp.keycodes['ctrl-r'] = 'replace';
175 mp.keycodes['ctrl-page-down'] = 'seek_next_char';
176 mp.keycodes['ctrl-page-up'] = 'seek_prev_char';
178 /** action descriptions **/
180 mp.actdesc['seek'] = LL("Search text...");
181 mp.actdesc['seek_next'] = LL("Search next");
182 mp.actdesc['seek_prev'] = LL("Search previous");
183 mp.actdesc['replace'] = LL("Replace...");
184 mp.actdesc['seek_next_char'] = LL("Move to next instance of current char");
185 mp.actdesc['seek_prev_char'] = LL("Move to previous instance of current char");
186 mp.actdesc['grep'] = LL("Grep (find inside) files...");
191 * mp.search_set_y - Sets the y position after a successful search.
193 * Sets the y position after a successful search, setting the
194 * visual line to that defined in mp.config.move_seek_to_line.
196 sub mp.search_set_y(doc, y)
200 /* set always to the same line */
201 if (mp.config.move_seek_to_line != NULL &&
202 (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
209 sub mp.prefix_regex(str)
210 /* set str to be a valid regex */
215 /* surround with / for the regex */
216 str = '/' ~ str ~ '/';
218 /* add optional case insensitivity flag */
219 if (! mp.config.case_sensitive_search)
226 sub mp.search_dir(doc, str, dir)
227 /* search str and put the current position there, with direction */
229 local txt, r, l, lines;
230 local bx, by, ex, ey;
233 str = mp.last_search;
235 str = mp.prefix_regex(str);
236 mp.last_search = str;
245 /* search backwards */
248 ex = txt.x && txt.x - 1 || 0;
251 if (txt.mark && !txt.mark.incomplete) {
252 if (ey >= txt.mark.ey) {
255 if (ex > txt.mark.ex)
272 if (txt.mark && !txt.mark.incomplete) {
273 if (by <= txt.mark.by) {
276 if (bx < txt.mark.bx)
284 ex = size(txt.lines[-1]);
285 ey = size(txt.lines);
289 lines = mp.get_range(doc, bx, by, ex, ey, 0);
292 local n = (dir == -1) && (size(lines) - 1) || 0;
293 while ((l = lines[n]) != NULL && regex(l, str) == NULL)
299 /* if it was found in the first line, add offset */
300 r[0] += (n == 0 && bx);
302 mp.search_set_y(doc, by + n);
303 mp.set_x(doc, r[0] + r[1]);
310 sub mp.search(doc, str)
311 /* search str and put the current position there, downwards */
313 mp.search_dir(doc, str, 1);
317 sub mp.search_back(doc, str)
318 /* search str and put the current position there, backwards */
320 mp.search_dir(doc, str, -1);
324 sub mp.replace_1(doc, org, dst)
325 /* searches 'org' and replaces it with 'dst', once */
329 if ((c = mp.search(doc, org)) != NULL) {
335 txt.lines[txt.y] = sregex(txt.lines[txt.y],
336 mp.prefix_regex(org),
340 /* move to correct position */
342 mp.set_x(doc, c[0] + c[1]);
351 sub mp.replace(doc, org, dst)
352 /* replaces 'org' with 'that', may be globally */
356 while (mp.replace_1(doc, org, dst)) {
359 if (!mp.config.global_replace)
364 'timeout' => time() + 4,
365 'string' => sprintf(L("%d replaces"), cnt)
372 sub mp.seek_prev_or_next_char(doc, func)
373 /* moves to next or previous occurence of current char */
377 /* get current char */
378 local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
380 /* move one char right */
383 /* search for it (mp.search() or mp.search_back()) */
384 local t = mp.last_search;
385 func(doc, '\' ~ w[1]);
395 sub mp.grep(rx, spec, base, rec, r)
396 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
397 (or glob() is unsupported), an empty list if the string was not found or
398 an array with the matches, that are three-element arrays with the file name,
399 the line number and the line that matched */
403 /* if spec is empty, set as NULL (meaning "glob everything") */
407 all = glob(spec, base);
412 /* spec globs to NULL or empty; abort */
419 if ((f = open(fn, "r")) != NULL) {
422 /* file open; now grep */
423 while (l = read(f)) {
427 /* found; store line, filename and linenum */
428 push(r, [ fn, n, l ]);
439 /* glob again, trying subdirectories */
440 foreach (fn, glob(NULL, base)) {
441 if (regex(fn, '@/$@')) {
442 r = mp.grep(rx, spec, fn, rec, r);