Updated RELEASE_NOTES.
[mp-5.x.git] / mp_search.mpsl
blob98b5733560ede1873d125d45660dd70f970d8f4f
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Search and replace.
8     Copyright (C) 1991-2007 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         {
45                 mp.config.case_sensitive_search = t[1];
47                 mp.long_op(mp.search, d, mp.backslash_codes(t[0])) ||
48                         mp.alert(L("Text not found."));
49         }
52 mp.actions['seek_next'] = sub (d) { mp.long_op(mp.search, d, NULL) ||
53                 mp.alert(L("Text not found."));
54         };
56 mp.actions['seek_prev'] = sub (d) { mp.long_op(mp.search_back, d, NULL) ||
57                 mp.alert(L("Text not found."));
58         };
60 mp.actions['replace']   = sub (d) {
62         local r = mp.form( [
63                 { 'label'       => L("Replace text:"),
64                   'type'        => 'text',
65                   'history'     => 'search'},
66                 { 'label'       => L("Replace with:"),
67                   'type'        => 'text',
68                   'history'     => 'replace'},
69                 { 'label'       => L("Case sensitive") ~ ':',
70                   'type'        => 'checkbox',
71                   'value'       => mp.config.case_sensitive_search },
72                 { 'label'       => L("Global replace:"),
73                   'type'        => 'checkbox',
74                   'value'       => mp.config.global_replace }
75         ] );
77         if(r != NULL)
78         {
79                 mp.config.case_sensitive_search = r[2];
80                 mp.config.global_replace = r[3];
82                 mp.store_undo(d);
83                 mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
84         }
87 mp.actions['seek_next_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search); };
88 mp.actions['seek_prev_char'] = sub (d) { mp.seek_prev_or_next_char(d, mp.search_back); };
90 mp.actions['grep']      = sub (d) {
92         local r = mp.form( [
93                 { 'label'       => L("Text to seek:"),
94                   'type'        => 'text',
95                   'history'     => 'search'},
96                 { 'label'       => L("Files to grep (empty, all):"),
97                   'type'        => 'text',
98                   'history'     => 'grep'}
99         ] );
101         if(r != NULL && r[0] ne '')
102         {
103                 local i = 0;
105                 if((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/', r[1])) == NULL)
106                         mp.alert(L("File(s) not found."));
108                 if(size(r) == 0)
109                         mp.alert(L("Text not found."));
110                 else
111                 if(size(r) > 1)
112                 {
113                         /* more than one; ask for it */
114                         local l = mp.form( [
115                                 { 'label'       => L("Grep"),
116                                   'type'        => 'list',
117                                   'list'        => map(sub (e) {
118                                         sprintf("%s (%d): %s", e[0], e[1] + 1, e[2]); }, r)
119                                  }
120                                 ]);
122                         if(l == NULL)
123                                 return;
125                         /* get index of the desired item */
126                         i = l[0];
127                 }
129                 if(size(r))
130                 {
131                         /* now open the document and move there */
132                         d = mp.long_op(mp.open, r[i][0]);
133                         d.txt.x = 0;
134                         mp.set_y(d, r[i][1]);
135                 }
136         }
139 /* default key bindings */
141 mp.keycodes['f3']               = 'seek_next';
142 mp.keycodes['ctrl-f3']          = 'seek_prev';
143 mp.keycodes['ctrl-f']           = 'seek';
144 mp.keycodes['ctrl-r']           = 'replace';
145 mp.keycodes['ctrl-page-down']   = 'seek_next_char';
146 mp.keycodes['ctrl-page-up']     = 'seek_prev_char';
148 /* 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 */
159 sub mp.prefix_regex(str)
160 /* set str to be a valid regex */
162         /* surround with / for the regex */
163         str = '/' ~ str ~ '/';
165         /* add optional case insensitivity flag */
166         if(! mp.config.case_sensitive_search)
167                 str = str ~ 'i';
169         return(str);
173 sub mp.search_dir(doc, str, dir)
174 /* search str and put the current position there, with direction */
176         local txt, x, y, r;
178         if(str == NULL)
179                 str = mp.last_search;
180         else
181                 mp.last_search = str;
183         if(str == NULL)
184                 return(NULL);
186         str = mp.prefix_regex(str);
188         txt = doc.txt;
189         x = txt.x;
190         y = txt.y;
192         if(dir == -1)
193         {
194                 local l;
195                 str = str ~ 'l';
197                 /* search backwards */
198                 if(txt.x)
199                 {
200                         l = splice(txt.lines[y], NULL, 0, txt.x - 1);
201                         l = l[1];
202                 }
204                 while(regex(str, l, 0) == NULL && y > 0)
205                         l = txt.lines[--y];
206         }
207         else
208         {
209                 /* search forward */
210                 while(regex(str, txt.lines[y], x) == NULL && y < size(txt.lines))
211                 {
212                         x = 0; y++;
213                 }
214         }
216         /* get last coords */
217         r = regex();
219         if(r)
220         {
221                 mp.set_y(doc, y);
222                 mp.set_x(doc, r[0] + r[1]);
224                 /* set always to the same line */
225                 if(mp.config.move_seek_to_line != NULL &&
226                    (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
227                         doc.txt.vy = 0;
228         }
230         return(r);
234 sub mp.search(doc, str)
235 /* search str and put the current position there, downwards */
237         mp.search_dir(doc, str, 1);
241 sub mp.search_back(doc, str)
242 /* search str and put the current position there, backwards */
244         mp.search_dir(doc, str, -1);
248 sub mp.replace_1(doc, this, that)
249 /* searches 'this' and replaces it with 'that', once */
251         local c;
253         if((c = mp.search(doc, this)) != NULL)
254         {
255                 local txt, l;
257                 txt = doc.txt;
259                 /* substitute */
260                 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
261                         txt.lines[txt.y], that, c[0]);
263                 /* move to correct position */
264                 c = regex();
265                 mp.set_x(doc, c[0] + c[1]);
267                 txt.mod++;
268         }
270         return(c);
274 sub mp.replace(doc, this, that)
275 /* replaces 'this' with 'that', may be globally */
277         while(mp.replace_1(doc, this, that))
278         {
279                 if(!mp.config.global_replace) break;
280         }
284 sub mp.seek_prev_or_next_char(doc, func)
285 /* moves to next or previous occurent of current char */
287         local txt = doc.txt;
289         /* get current char */
290         local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
292         /* move one char right */
293         mp.move_right(doc);
295         /* search for it (mp.search() or mp.search_back()) */
296         func(doc, '\' ~ w[1]);
298         /* move back */
299         mp.move_left(doc);
303 sub mp.grep(rx, spec)
304 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
305    (or glob() is unsupported), an empty list if the string was not found or
306    an array with the matches, that are three-element arrays with the file name,
307    the line number and the line that matched */
309         local r, all;
311         /* if spec is empty, set as NULL (meaning "glob everything") */
312         if(spec eq '') spec = NULL;
314         all = glob(spec);
316         /* spec globs to NULL or empty; abort */
317         if(size(all) == 0) { return(NULL); }
319         r = [];
321         foreach(local fn, all)
322         {
323                 local f;
325                 if((f = open(fn, "r")) != NULL)
326                 {
327                         local l, n;
329                         /* file open; now grep */
330                         while(l = read(f))
331                         {
332                                 l = mp.chomp(l);
334                                 if(regex(rx, l))
335                                 {
336                                         /* found; store line, filename and linenum */
337                                         push(r, [ fn, n, l ]);
338                                 }
340                                 n++;
341                         }
343                         close(f);
344                 }
345         }
347         return(r);