All functions with a doc as first argument returns it in mp_search.mpsl (except for...
[mp-5.x.git] / mp_search.mpsl
blob1095bc794e5c3474790b4e8561aecf9a3c11b11b
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 /* grep recursivity */
32 mp.config.recursive_grep = 0;
34 /** editor actions **/
36 mp.actions['seek']      = sub (d) {
37         local t = mp.form( [
38                 { 'label'       => L("Text to seek:"),
39                   'type'        => 'text',
40                   'history'     => 'search' },
41                 { 'label'       => L("Case sensitive") ~ ':',
42                   'type'        => 'checkbox',
43                   'value'       => mp.config.case_sensitive_search }
44         ] );
46         if (t != NULL) {
47                 mp.config.case_sensitive_search = t[1];
49                 mp.long_op(mp.search, d, mp.backslash_codes(t[0])) ||
50                         mp.alert(L("Text not found."));
51         }
54 mp.actions['seek_next'] = sub (d) { mp.long_op(mp.search, d, NULL) ||
55                 mp.alert(L("Text not found."));
56         };
58 mp.actions['seek_prev'] = sub (d) { mp.long_op(mp.search_back, d, NULL) ||
59                 mp.alert(L("Text not found."));
60         };
62 mp.actions['replace']   = sub (d) {
64         local r = mp.form( [
65                 { 'label'       => L("Replace text:"),
66                   'type'        => 'text',
67                   'history'     => 'search'},
68                 { 'label'       => L("Replace with:"),
69                   'type'        => 'text',
70                   'history'     => 'replace'},
71                 { 'label'       => L("Case sensitive") ~ ':',
72                   'type'        => 'checkbox',
73                   'value'       => mp.config.case_sensitive_search },
74                 { 'label'       => L("Global replace:"),
75                   'type'        => 'checkbox',
76                   'value'       => mp.config.global_replace }
77         ] );
79         if (r != NULL) {
80                 mp.config.case_sensitive_search = r[2];
81                 mp.config.global_replace = r[3];
83                 mp.store_undo(d);
84                 mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
85         }
88 mp.actions['seek_next_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search); };
89 mp.actions['seek_prev_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search_back); };
91 mp.actions['grep']      = sub (d) {
93         local r = mp.form( [
94                 { 'label'       => L("Text to seek:"),
95                   'type'        => 'text',
96                   'history'     => 'search' },
97                 { 'label'       => L("Files to grep (empty, all):"),
98                   'type'        => 'text',
99                   'history'     => 'grep' },
100                 { 'label'       => L("Base directory (empty, current):"),
101                   'type'        => 'text',
102                   'history'     => 'grep_base' },
103                 { 'label'       => L("Recursive?"),
104                   'type'        => 'checkbox',
105                   'value'       => mp.config.recursive_grep }
106         ] );
108         if (r != NULL && r[0] ne '') {
110                 mp.config.recursive_grep = r[3];
112                 local t = '<grep ' ~ r[0] ~ ' ' ~ r[1] ~ '>';
114                 if ((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/',
115                         r[1], r[2], r[3])) == NULL)
116                         mp.alert(L("File(s) not found."));
117                 else
118                 if (size(r) == 0)
119                         mp.alert(L("Text not found."));
120                 else {
121                         local l = mp.open(t);
123                         l.txt.lines = [];
124                         mp.move_bof(l);
126                         foreach (e, r)
127                                 mp.insert(l, sprintf("%s:%d: %s\n",
128                                                 e[0], e[1] + 1, e[2]));
130                         mp.move_bof(l);
132                         l.txt.mod = 0;
133                         l.read_only = 1;
134                 }
135         }
138 /** default key bindings **/
140 mp.keycodes['f3']               = 'seek_next';
141 mp.keycodes['ctrl-f3']          = 'seek_prev';
142 mp.keycodes['ctrl-f']           = 'seek';
143 mp.keycodes['ctrl-r']           = 'replace';
144 mp.keycodes['ctrl-page-down']   = 'seek_next_char';
145 mp.keycodes['ctrl-page-up']     = 'seek_prev_char';
147 /** action descriptions **/
149 mp.actdesc['seek']              = LL("Search text...");
150 mp.actdesc['seek_next']         = LL("Search next");
151 mp.actdesc['seek_prev']         = LL("Search previous");
152 mp.actdesc['replace']           = LL("Replace...");
153 mp.actdesc['seek_next_char']    = LL("Move to next instance of current char");
154 mp.actdesc['seek_prev_char']    = LL("Move to previous instance of current char");
155 mp.actdesc['grep']              = LL("Grep (find inside) files...");
157 /** code **/
160  * mp.search_set_y - Sets the y position after a successful search.
162  * Sets the y position after a successful search, setting the
163  * visual line to that defined in mp.config.move_seek_to_line.
164  */
165 sub mp.search_set_y(doc, y)
167         mp.set_y(doc, y);
169         /* set always to the same line */
170         if (mp.config.move_seek_to_line != NULL &&
171            (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
172                 doc.txt.vy = 0;
174         return doc;
178 sub mp.prefix_regex(str)
179 /* set str to be a valid regex */
181         if (!str)
182                 return NULL;
184         /* surround with / for the regex */
185         str = '/' ~ str ~ '/';
187         /* add optional case insensitivity flag */
188         if (! mp.config.case_sensitive_search)
189                 str = str ~ 'i';
191         return str;
195 sub mp.search_dir(doc, str, dir)
196 /* search str and put the current position there, with direction */
198         local txt, r, l, lines;
199         local bx, by, ex, ey;
201         if (str == NULL)
202                 str = mp.last_search;
203         else {
204                 str = mp.prefix_regex(str);
205                 mp.last_search = str;
206         }
208         if (str == NULL)
209                 return NULL;
211         txt = doc.txt;
213         if (dir == -1) {
214                 /* search backwards */
215                 str = str ~ 'l';
217                 ex = txt.x && txt.x - 1 || 0;
218                 ey = txt.y;
220                 if (txt.mark && !txt.mark.incomplete) {
221                         if (ey >= txt.mark.ey) {
222                                 ey = txt.mark.ey;
224                                 if (ex > txt.mark.ex)
225                                         ex = txt.mark.ex;
226                         }
228                         bx = txt.mark.bx;
229                         by = txt.mark.by;
230                 }
231                 else {
232                         bx = 0;
233                         by = 0;
234                 }
235         }
236         else {
237                 /* search forward */
238                 bx = txt.x;
239                 by = txt.y;
241                 if (txt.mark && !txt.mark.incomplete) {
242                         if (by <= txt.mark.by) {
243                                 by = txt.mark.by;
245                                 if (bx < txt.mark.bx)
246                                         bx = txt.mark.bx;
247                         }
249                         ex = txt.mark.ex;
250                         ey = txt.mark.ey;
251                 }
252                 else {
253                         ex = size(txt.lines[-1]);
254                         ey = size(txt.lines);
255                 }
256         }
258         lines = mp.get_range(doc, bx, by, ex, ey, 0);
260         /* do the search */
261         local n = (dir == -1) && (size(lines) - 1) || 0;
262         while ((l = lines[n]) != NULL && regex(str, l) == NULL)
263                 n += dir;
265         r = regex();
267         if (r) {
268                 /* if it was found in the first line, add offset */
269                 r[0] += (n == 0 && bx);
271                 mp.search_set_y(doc, by + n);
272                 mp.set_x(doc, r[0] + r[1]);
273         }
275         return r;
279 sub mp.search(doc, str)
280 /* search str and put the current position there, downwards */
282         mp.search_dir(doc, str, 1);
286 sub mp.search_back(doc, str)
287 /* search str and put the current position there, backwards */
289         mp.search_dir(doc, str, -1);
293 sub mp.replace_1(doc, this, that)
294 /* searches 'this' and replaces it with 'that', once */
296         local c;
298         if ((c = mp.search(doc, this)) != NULL) {
299                 local txt, l;
301                 txt = doc.txt;
303                 /* substitute */
304                 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
305                         txt.lines[txt.y], that, c[0]);
307                 /* move to correct position */
308                 c = regex();
309                 mp.set_x(doc, c[0] + c[1]);
311                 txt.mod++;
312         }
314         return c;
318 sub mp.replace(doc, this, that)
319 /* replaces 'this' with 'that', may be globally */
321         local cnt = 0;
323         while (mp.replace_1(doc, this, that)) {
324                 cnt++;
326                 if (!mp.config.global_replace)
327                         break;
328         }
330         mp.message = {
331                 'timeout' => time() + 4,
332                 'string'  => sprintf(L("%d replaces"), cnt)
333         };
335         return doc;
339 sub mp.seek_prev_or_next_char(doc, func)
340 /* moves to next or previous occurence of current char */
342         local txt = doc.txt;
344         /* get current char */
345         local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
347         /* move one char right */
348         mp.move_right(doc);
350         /* search for it (mp.search() or mp.search_back()) */
351         local t = mp.last_search;
352         func(doc, '\' ~ w[1]);
353         mp.last_search = t;
355         /* move back */
356         mp.move_left(doc);
358         return doc;
362 sub mp.grep(rx, spec, base, rec, r)
363 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
364    (or glob() is unsupported), an empty list if the string was not found or
365    an array with the matches, that are three-element arrays with the file name,
366    the line number and the line that matched */
368         local all;
370         /* if spec is empty, set as NULL (meaning "glob everything") */
371         if (spec eq '')
372                 spec = NULL;
374         all = glob(spec, base);
376         if (r == NULL)
377                 r = [];
379         /* spec globs to NULL or empty; abort */
380         if (size(all) == 0)
381                 return r;
383         foreach (fn, all) {
384                 local f;
386                 if ((f = open(fn, "r")) != NULL) {
387                         local l, n;
389                         /* file open; now grep */
390                         while (l = read(f)) {
391                                 l = mp.chomp(l);
393                                 if (regex(rx, l)) {
394                                         /* found; store line, filename and linenum */
395                                         push(r, [ fn, n, l ]);
396                                 }
398                                 n++;
399                         }
401                         close(f);
402                 }
403         }
405         if (rec) {
406                 /* glob again, trying subdirectories */
407                 foreach (fn, glob(NULL, base)) {
408                         if (regex('@/$@', fn)) {
409                                 r = mp.grep(rx, spec, fn, rec, r);
410                         }
411                 }
412         }
414         return r;