mp.grep() now works recursively (more tests needed).
[mp-5.x.git] / mp_search.mpsl
blobe23123b0c3f4c2dcb50022800297c97461132e71
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Search and replace.
8     Copyright (C) 1991-2009 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 /** editor actions **/
33 mp.actions['seek']      = sub (d) {
34         local t = mp.form( [
35                 { 'label'       => L("Text to seek:"),
36                   'type'        => 'text',
37                   'history'     => 'search' },
38                 { 'label'       => L("Case sensitive") ~ ':',
39                   'type'        => 'checkbox',
40                   'value'       => mp.config.case_sensitive_search }
41         ] );
43         if (t != NULL) {
44                 mp.config.case_sensitive_search = t[1];
46                 mp.long_op(mp.search, d, mp.backslash_codes(t[0])) ||
47                         mp.alert(L("Text not found."));
48         }
51 mp.actions['seek_next'] = sub (d) { mp.long_op(mp.search, d, NULL) ||
52                 mp.alert(L("Text not found."));
53         };
55 mp.actions['seek_prev'] = sub (d) { mp.long_op(mp.search_back, d, NULL) ||
56                 mp.alert(L("Text not found."));
57         };
59 mp.actions['replace']   = sub (d) {
61         local r = mp.form( [
62                 { 'label'       => L("Replace text:"),
63                   'type'        => 'text',
64                   'history'     => 'search'},
65                 { 'label'       => L("Replace with:"),
66                   'type'        => 'text',
67                   'history'     => 'replace'},
68                 { 'label'       => L("Case sensitive") ~ ':',
69                   'type'        => 'checkbox',
70                   'value'       => mp.config.case_sensitive_search },
71                 { 'label'       => L("Global replace:"),
72                   'type'        => 'checkbox',
73                   'value'       => mp.config.global_replace }
74         ] );
76         if (r != NULL) {
77                 mp.config.case_sensitive_search = r[2];
78                 mp.config.global_replace = r[3];
80                 mp.store_undo(d);
81                 mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
82         }
85 mp.actions['seek_next_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search); };
86 mp.actions['seek_prev_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search_back); };
88 mp.actions['grep']      = sub (d) {
90         local r = mp.form( [
91                 { 'label'       => L("Text to seek:"),
92                   'type'        => 'text',
93                   'history'     => 'search' },
94                 { 'label'       => L("Files to grep (empty, all):"),
95                   'type'        => 'text',
96                   'history'     => 'grep' },
97                 { 'label'       => L("Base directory (empty, current):"),
98                   'type'        => 'text',
99                   'history'     => 'grep_base' }
100         ] );
102         if (r != NULL && r[0] ne '') {
104                 local t = '<grep ' ~ r[0] ~ ' ' ~ r[1] ~ '>';
106                 if ((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/', r[1], r[2])) == NULL)
107                         mp.alert(L("File(s) not found."));
108                 else
109                 if (size(r) == 0)
110                         mp.alert(L("Text not found."));
111                 else {
112                         local l = mp.open(t);
114                         l.txt.lines = [];
115                         mp.move_bof(l);
117                         foreach (e, r)
118                                 mp.insert(l, sprintf("%s:%d: %s\n",
119                                                 e[0], e[1] + 1, e[2]));
121                         mp.move_bof(l);
123                         l.txt.mod = 0;
124                         l.read_only = 1;
125                 }
126         }
129 /** default key bindings **/
131 mp.keycodes['f3']               = 'seek_next';
132 mp.keycodes['ctrl-f3']          = 'seek_prev';
133 mp.keycodes['ctrl-f']           = 'seek';
134 mp.keycodes['ctrl-r']           = 'replace';
135 mp.keycodes['ctrl-page-down']   = 'seek_next_char';
136 mp.keycodes['ctrl-page-up']     = 'seek_prev_char';
138 /** action descriptions **/
140 mp.actdesc['seek']              = LL("Search text...");
141 mp.actdesc['seek_next']         = LL("Search next");
142 mp.actdesc['seek_prev']         = LL("Search previous");
143 mp.actdesc['replace']           = LL("Replace...");
144 mp.actdesc['seek_next_char']    = LL("Move to next instance of current char");
145 mp.actdesc['seek_prev_char']    = LL("Move to previous instance of current char");
146 mp.actdesc['grep']              = LL("Grep (find inside) files...");
148 /** code **/
150 sub mp.prefix_regex(str)
151 /* set str to be a valid regex */
153         if (!str)
154                 return NULL;
156         /* surround with / for the regex */
157         str = '/' ~ str ~ '/';
159         /* add optional case insensitivity flag */
160         if (! mp.config.case_sensitive_search)
161                 str = str ~ 'i';
163         return str;
167 sub mp.search_dir(doc, str, dir)
168 /* search str and put the current position there, with direction */
170         local txt, r, l, lines;
171         local bx, by, ex, ey;
173         if (str == NULL)
174                 str = mp.last_search;
175         else {
176                 str = mp.prefix_regex(str);
177                 mp.last_search = str;
178         }
180         if (str == NULL)
181                 return NULL;
183         txt = doc.txt;
185         if (dir == -1) {
186                 /* search backwards */
187                 str = str ~ 'l';
189                 ex = txt.x && txt.x - 1 || 0;
190                 ey = txt.y;
192                 if (txt.mark && !txt.mark.incomplete) {
193                         if (ey >= txt.mark.ey) {
194                                 ey = txt.mark.ey;
196                                 if (ex > txt.mark.ex)
197                                         ex = txt.mark.ex;
198                         }
200                         bx = txt.mark.bx;
201                         by = txt.mark.by;
202                 }
203                 else {
204                         bx = 0;
205                         by = 0;
206                 }
207         }
208         else {
209                 /* search forward */
210                 bx = txt.x;
211                 by = txt.y;
213                 if (txt.mark && !txt.mark.incomplete) {
214                         if (by <= txt.mark.by) {
215                                 by = txt.mark.by;
217                                 if (bx < txt.mark.bx)
218                                         bx = txt.mark.bx;
219                         }
221                         ex = txt.mark.ex;
222                         ey = txt.mark.ey;
223                 }
224                 else {
225                         ex = size(txt.lines[-1]);
226                         ey = size(txt.lines);
227                 }
228         }
230         lines = mp.get_range(doc, bx, by, ex, ey, 0);
232         /* do the search */
233         local n = (dir == -1) && (size(lines) - 1) || 0;
234         while ((l = lines[n]) != NULL && regex(str, l) == NULL)
235                 n += dir;
237         r = regex();
239         if (r) {
240                 /* if it was found in the first line, add offset */
241                 r[0] += (n == 0 && bx);
243                 mp.set_y(doc, by + n);
244                 mp.set_x(doc, r[0] + r[1]);
246                 /* set always to the same line */
247                 if (mp.config.move_seek_to_line != NULL &&
248                    (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
249                         doc.txt.vy = 0;
250         }
252         return r;
256 sub mp.search(doc, str)
257 /* search str and put the current position there, downwards */
259         mp.search_dir(doc, str, 1);
263 sub mp.search_back(doc, str)
264 /* search str and put the current position there, backwards */
266         mp.search_dir(doc, str, -1);
270 sub mp.replace_1(doc, this, that)
271 /* searches 'this' and replaces it with 'that', once */
273         local c;
275         if ((c = mp.search(doc, this)) != NULL) {
276                 local txt, l;
278                 txt = doc.txt;
280                 /* substitute */
281                 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
282                         txt.lines[txt.y], that, c[0]);
284                 /* move to correct position */
285                 c = regex();
286                 mp.set_x(doc, c[0] + c[1]);
288                 txt.mod++;
289         }
291         return c;
295 sub mp.replace(doc, this, that)
296 /* replaces 'this' with 'that', may be globally */
298         local cnt = 0;
300         while (mp.replace_1(doc, this, that)) {
301                 cnt++;
303                 if (!mp.config.global_replace)
304                         break;
305         }
307         mp.message = {
308                 'timeout' => time() + 4,
309                 'string'  => sprintf(L("%d replaces"), cnt)
310         };
314 sub mp.seek_prev_or_next_char(doc, func)
315 /* moves to next or previous occurent of current char */
317         local txt = doc.txt;
319         /* get current char */
320         local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
322         /* move one char right */
323         mp.move_right(doc);
325         /* search for it (mp.search() or mp.search_back()) */
326         local t = mp.last_search;
327         func(doc, '\' ~ w[1]);
328         mp.last_search = t;
330         /* move back */
331         mp.move_left(doc);
335 sub mp.grep(rx, spec, base, r)
336 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
337    (or glob() is unsupported), an empty list if the string was not found or
338    an array with the matches, that are three-element arrays with the file name,
339    the line number and the line that matched */
341         local all;
343         /* if spec is empty, set as NULL (meaning "glob everything") */
344         if (spec eq '')
345                 spec = NULL;
347         all = glob(spec, base);
349         if (r == NULL)
350                 r = [];
352         /* spec globs to NULL or empty; abort */
353         if (size(all) == 0)
354                 return r;
356         foreach (fn, all) {
357                 local f;
359                 if ((f = open(fn, "r")) != NULL) {
360                         local l, n;
362                         /* file open; now grep */
363                         while (l = read(f)) {
364                                 l = mp.chomp(l);
366                                 if (regex(rx, l)) {
367                                         /* found; store line, filename and linenum */
368                                         push(r, [ fn, n, l ]);
369                                 }
371                                 n++;
372                         }
374                         close(f);
375                 }
376         }
378         /* glob again, trying subdirectories */
379         foreach (fn, glob(NULL, base)) {
380                 if (regex('@/$@', fn)) {
381                         r = mp.grep(rx, spec, base ~ fn, r);
382                 }
383         }
385         return r;