When searching backwards, move one character left.
[mp-5.x.git] / mp_search.mpsl
blob48e2b8d19cabc99ed894dd262c2d2ea8f2e4a3d2
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Search and replace.
8     Copyright (C) 1991-2008 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         ] );
99         if (r != NULL && r[0] ne '') {
101                 local t = '<grep ' ~ r[0] ~ ' ' ~ r[1] ~ '>';
103                 if ((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/', r[1])) == NULL)
104                         mp.alert(L("File(s) not found."));
105                 else
106                 if (size(r) == 0)
107                         mp.alert(L("Text not found."));
108                 else {
109                         local l = mp.open(t);
111                         l.txt.lines = [];
112                         mp.move_bof(l);
114                         foreach (e, r)
115                                 mp.insert(l, sprintf("%s:%d: %s\n",
116                                                 e[0], e[1] + 1, e[2]));
118                         mp.move_bof(l);
120                         l.txt.mod = 0;
121                         l.read_only = 1;
122                 }
123         }
126 /* default key bindings */
128 mp.keycodes['f3']               = 'seek_next';
129 mp.keycodes['ctrl-f3']          = 'seek_prev';
130 mp.keycodes['ctrl-f']           = 'seek';
131 mp.keycodes['ctrl-r']           = 'replace';
132 mp.keycodes['ctrl-page-down']   = 'seek_next_char';
133 mp.keycodes['ctrl-page-up']     = 'seek_prev_char';
135 /* action descriptions */
136 mp.actdesc['seek']              = LL("Search text...");
137 mp.actdesc['seek_next']         = LL("Search next");
138 mp.actdesc['seek_prev']         = LL("Search previous");
139 mp.actdesc['replace']           = LL("Replace...");
140 mp.actdesc['seek_next_char']    = LL("Move to next instance of current char");
141 mp.actdesc['seek_prev_char']    = LL("Move to previous instance of current char");
142 mp.actdesc['grep']              = LL("Grep (find inside) files...");
144 /* code */
146 sub mp.prefix_regex(str)
147 /* set str to be a valid regex */
149         if (!str)
150                 return NULL;
152         /* surround with / for the regex */
153         str = '/' ~ str ~ '/';
155         /* add optional case insensitivity flag */
156         if (! mp.config.case_sensitive_search)
157                 str = str ~ 'i';
159         return str;
163 sub mp.search_dir(doc, str, dir)
164 /* search str and put the current position there, with direction */
166         local txt, r, l, lines;
167         local bx, by, ex, ey;
169         if (str == NULL)
170                 str = mp.last_search;
171         else {
172                 str = mp.prefix_regex(str);
173                 mp.last_search = str;
174         }
176         if (str == NULL)
177                 return NULL;
179         txt = doc.txt;
181         if (dir == -1) {
182                 /* search backwards */
183                 str = str ~ 'l';
185                 ex = txt.x && txt.x - 1 || 0;
186                 ey = txt.y;
188                 if (txt.mark) {
189                         if (ey >= txt.mark.ey) {
190                                 ey = txt.mark.ey;
192                                 if (ex > txt.mark.ex)
193                                         ex = txt.mark.ex;
194                         }
196                         bx = txt.mark.bx;
197                         by = txt.mark.by;
198                 }
199                 else {
200                         bx = 0;
201                         by = 0;
202                 }
203         }
204         else {
205                 /* search forward */
206                 bx = txt.x;
207                 by = txt.y;
209                 if (txt.mark) {
210                         if (by <= txt.mark.by) {
211                                 by = txt.mark.by;
213                                 if (bx < txt.mark.bx)
214                                         bx = txt.mark.bx;
215                         }
217                         ex = txt.mark.ex;
218                         ey = txt.mark.ey;
219                 }
220                 else {
221                         ex = size(txt.lines[-1]);
222                         ey = size(txt.lines);
223                 }
224         }
226         lines = mp.get_range(doc, bx, by, ex, ey, 0);
228         /* do the search */
229         local n = (dir == -1) && (size(lines) - 1) || 0;
230         while ((l = lines[n]) != NULL && regex(str, l) == NULL)
231                 n += dir;
233         r = regex();
235         if (r) {
236                 local x = r[0] + r[1];
238                 if (n == 0)
239                         x += bx;
241                 mp.set_y(doc, by + n);
242                 mp.set_x(doc, x);
244                 /* set always to the same line */
245                 if (mp.config.move_seek_to_line != NULL &&
246                    (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
247                         doc.txt.vy = 0;
248         }
250         return r;
254 sub mp.search(doc, str)
255 /* search str and put the current position there, downwards */
257         mp.search_dir(doc, str, 1);
261 sub mp.search_back(doc, str)
262 /* search str and put the current position there, backwards */
264         mp.search_dir(doc, str, -1);
268 sub mp.replace_1(doc, this, that)
269 /* searches 'this' and replaces it with 'that', once */
271         local c;
273         if ((c = mp.search(doc, this)) != NULL) {
274                 local txt, l;
276                 txt = doc.txt;
278                 /* substitute */
279                 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
280                         txt.lines[txt.y], that, c[0]);
282                 /* move to correct position */
283                 c = regex();
284                 mp.set_x(doc, c[0] + c[1]);
286                 txt.mod++;
287         }
289         return c;
293 sub mp.replace(doc, this, that)
294 /* replaces 'this' with 'that', may be globally */
296         local cnt = 0;
298         while (mp.replace_1(doc, this, that)) {
299                 cnt++;
301                 if (!mp.config.global_replace)
302                         break;
303         }
305         mp.message = {
306                 'timeout' => time() + 4,
307                 'string'  => sprintf(L("%d replaces"), cnt)
308         };
312 sub mp.seek_prev_or_next_char(doc, func)
313 /* moves to next or previous occurent of current char */
315         local txt = doc.txt;
317         /* get current char */
318         local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
320         /* move one char right */
321         mp.move_right(doc);
323         /* search for it (mp.search() or mp.search_back()) */
324         local t = mp.last_search;
325         func(doc, '\' ~ w[1]);
326         mp.last_search = t;
328         /* move back */
329         mp.move_left(doc);
333 sub mp.grep(rx, spec)
334 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
335    (or glob() is unsupported), an empty list if the string was not found or
336    an array with the matches, that are three-element arrays with the file name,
337    the line number and the line that matched */
339         local r, all;
341         /* if spec is empty, set as NULL (meaning "glob everything") */
342         if (spec eq '')
343                 spec = NULL;
345         all = glob(spec);
347         /* spec globs to NULL or empty; abort */
348         if (size(all) == 0)
349                 return NULL;
351         r = [];
353         foreach (fn, all) {
354                 local f;
356                 if ((f = open(fn, "r")) != NULL) {
357                         local l, n;
359                         /* file open; now grep */
360                         while (l = read(f)) {
361                                 l = mp.chomp(l);
363                                 if (regex(rx, l)) {
364                                         /* found; store line, filename and linenum */
365                                         push(r, [ fn, n, l ]);
366                                 }
368                                 n++;
369                         }
371                         close(f);
372                 }
373         }
375         return r;