Updated RELEASE_NOTES.
[mp-5.x.git] / mp_search.mpsl
blobc893d5cab084886774bdd15843b5f10bcc8ec9c9
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;
176 sub mp.prefix_regex(str)
177 /* set str to be a valid regex */
179         if (!str)
180                 return NULL;
182         /* surround with / for the regex */
183         str = '/' ~ str ~ '/';
185         /* add optional case insensitivity flag */
186         if (! mp.config.case_sensitive_search)
187                 str = str ~ 'i';
189         return str;
193 sub mp.search_dir(doc, str, dir)
194 /* search str and put the current position there, with direction */
196         local txt, r, l, lines;
197         local bx, by, ex, ey;
199         if (str == NULL)
200                 str = mp.last_search;
201         else {
202                 str = mp.prefix_regex(str);
203                 mp.last_search = str;
204         }
206         if (str == NULL)
207                 return NULL;
209         txt = doc.txt;
211         if (dir == -1) {
212                 /* search backwards */
213                 str = str ~ 'l';
215                 ex = txt.x && txt.x - 1 || 0;
216                 ey = txt.y;
218                 if (txt.mark && !txt.mark.incomplete) {
219                         if (ey >= txt.mark.ey) {
220                                 ey = txt.mark.ey;
222                                 if (ex > txt.mark.ex)
223                                         ex = txt.mark.ex;
224                         }
226                         bx = txt.mark.bx;
227                         by = txt.mark.by;
228                 }
229                 else {
230                         bx = 0;
231                         by = 0;
232                 }
233         }
234         else {
235                 /* search forward */
236                 bx = txt.x;
237                 by = txt.y;
239                 if (txt.mark && !txt.mark.incomplete) {
240                         if (by <= txt.mark.by) {
241                                 by = txt.mark.by;
243                                 if (bx < txt.mark.bx)
244                                         bx = txt.mark.bx;
245                         }
247                         ex = txt.mark.ex;
248                         ey = txt.mark.ey;
249                 }
250                 else {
251                         ex = size(txt.lines[-1]);
252                         ey = size(txt.lines);
253                 }
254         }
256         lines = mp.get_range(doc, bx, by, ex, ey, 0);
258         /* do the search */
259         local n = (dir == -1) && (size(lines) - 1) || 0;
260         while ((l = lines[n]) != NULL && regex(str, l) == NULL)
261                 n += dir;
263         r = regex();
265         if (r) {
266                 /* if it was found in the first line, add offset */
267                 r[0] += (n == 0 && bx);
269                 mp.search_set_y(doc, by + n);
270                 mp.set_x(doc, r[0] + r[1]);
271         }
273         return r;
277 sub mp.search(doc, str)
278 /* search str and put the current position there, downwards */
280         mp.search_dir(doc, str, 1);
284 sub mp.search_back(doc, str)
285 /* search str and put the current position there, backwards */
287         mp.search_dir(doc, str, -1);
291 sub mp.replace_1(doc, this, that)
292 /* searches 'this' and replaces it with 'that', once */
294         local c;
296         if ((c = mp.search(doc, this)) != NULL) {
297                 local txt, l;
299                 txt = doc.txt;
301                 /* substitute */
302                 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
303                         txt.lines[txt.y], that, c[0]);
305                 /* move to correct position */
306                 c = regex();
307                 mp.set_x(doc, c[0] + c[1]);
309                 txt.mod++;
310         }
312         return c;
316 sub mp.replace(doc, this, that)
317 /* replaces 'this' with 'that', may be globally */
319         local cnt = 0;
321         while (mp.replace_1(doc, this, that)) {
322                 cnt++;
324                 if (!mp.config.global_replace)
325                         break;
326         }
328         mp.message = {
329                 'timeout' => time() + 4,
330                 'string'  => sprintf(L("%d replaces"), cnt)
331         };
335 sub mp.seek_prev_or_next_char(doc, func)
336 /* moves to next or previous occurent of current char */
338         local txt = doc.txt;
340         /* get current char */
341         local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
343         /* move one char right */
344         mp.move_right(doc);
346         /* search for it (mp.search() or mp.search_back()) */
347         local t = mp.last_search;
348         func(doc, '\' ~ w[1]);
349         mp.last_search = t;
351         /* move back */
352         mp.move_left(doc);
356 sub mp.grep(rx, spec, base, rec, r)
357 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
358    (or glob() is unsupported), an empty list if the string was not found or
359    an array with the matches, that are three-element arrays with the file name,
360    the line number and the line that matched */
362         local all;
364         /* if spec is empty, set as NULL (meaning "glob everything") */
365         if (spec eq '')
366                 spec = NULL;
368         all = glob(spec, base);
370         if (r == NULL)
371                 r = [];
373         /* spec globs to NULL or empty; abort */
374         if (size(all) == 0)
375                 return r;
377         foreach (fn, all) {
378                 local f;
380                 if ((f = open(fn, "r")) != NULL) {
381                         local l, n;
383                         /* file open; now grep */
384                         while (l = read(f)) {
385                                 l = mp.chomp(l);
387                                 if (regex(rx, l)) {
388                                         /* found; store line, filename and linenum */
389                                         push(r, [ fn, n, l ]);
390                                 }
392                                 n++;
393                         }
395                         close(f);
396                 }
397         }
399         if (rec) {
400                 /* glob again, trying subdirectories */
401                 foreach (fn, glob(NULL, base)) {
402                         if (regex('@/$@', fn)) {
403                                 r = mp.grep(rx, spec, fn, rec, r);
404                         }
405                 }
406         }
408         return r;