4 A Programmer's Text Editor
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) {
38 { 'label' => L("Text to seek:"),
40 'history' => 'search' },
41 { 'label' => L("Case sensitive") ~ ':',
43 'value' => mp.config.case_sensitive_search }
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."));
54 mp.actions['seek_next'] = sub (d) { mp.long_op(mp.search, d, NULL) ||
55 mp.alert(L("Text not found."));
58 mp.actions['seek_prev'] = sub (d) { mp.long_op(mp.search_back, d, NULL) ||
59 mp.alert(L("Text not found."));
62 mp.actions['replace'] = sub (d) {
65 { 'label' => L("Replace text:"),
67 'history' => 'search'},
68 { 'label' => L("Replace with:"),
70 'history' => 'replace'},
71 { 'label' => L("Case sensitive") ~ ':',
73 'value' => mp.config.case_sensitive_search },
74 { 'label' => L("Global replace:"),
76 'value' => mp.config.global_replace }
80 mp.config.case_sensitive_search = r[2];
81 mp.config.global_replace = r[3];
84 mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
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) {
94 { 'label' => L("Text to seek:"),
96 'history' => 'search' },
97 { 'label' => L("Files to grep (empty, all):"),
99 'history' => 'grep' },
100 { 'label' => L("Base directory (empty, current):"),
102 'history' => 'grep_base' },
103 { 'label' => L("Recursive?"),
104 'type' => 'checkbox',
105 'value' => mp.config.recursive_grep }
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."));
119 mp.alert(L("Text not found."));
121 local l = mp.open(t);
127 mp.insert(l, sprintf("%s:%d: %s\n",
128 e[0], e[1] + 1, e[2]));
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...");
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.
165 sub mp.search_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)
176 sub mp.prefix_regex(str)
177 /* set str to be a valid regex */
182 /* surround with / for the regex */
183 str = '/' ~ str ~ '/';
185 /* add optional case insensitivity flag */
186 if (! mp.config.case_sensitive_search)
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;
200 str = mp.last_search;
202 str = mp.prefix_regex(str);
203 mp.last_search = str;
212 /* search backwards */
215 ex = txt.x && txt.x - 1 || 0;
218 if (txt.mark && !txt.mark.incomplete) {
219 if (ey >= txt.mark.ey) {
222 if (ex > txt.mark.ex)
239 if (txt.mark && !txt.mark.incomplete) {
240 if (by <= txt.mark.by) {
243 if (bx < txt.mark.bx)
251 ex = size(txt.lines[-1]);
252 ey = size(txt.lines);
256 lines = mp.get_range(doc, bx, by, ex, ey, 0);
259 local n = (dir == -1) && (size(lines) - 1) || 0;
260 while ((l = lines[n]) != NULL && regex(str, l) == NULL)
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]);
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 */
296 if ((c = mp.search(doc, this)) != NULL) {
302 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
303 txt.lines[txt.y], that, c[0]);
305 /* move to correct position */
307 mp.set_x(doc, c[0] + c[1]);
316 sub mp.replace(doc, this, that)
317 /* replaces 'this' with 'that', may be globally */
321 while (mp.replace_1(doc, this, that)) {
324 if (!mp.config.global_replace)
329 'timeout' => time() + 4,
330 'string' => sprintf(L("%d replaces"), cnt)
335 sub mp.seek_prev_or_next_char(doc, func)
336 /* moves to next or previous occurent of current char */
340 /* get current char */
341 local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
343 /* move one char right */
346 /* search for it (mp.search() or mp.search_back()) */
347 local t = mp.last_search;
348 func(doc, '\' ~ w[1]);
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 */
364 /* if spec is empty, set as NULL (meaning "glob everything") */
368 all = glob(spec, base);
373 /* spec globs to NULL or empty; abort */
380 if ((f = open(fn, "r")) != NULL) {
383 /* file open; now grep */
384 while (l = read(f)) {
388 /* found; store line, filename and linenum */
389 push(r, [ fn, n, l ]);
400 /* glob again, trying subdirectories */
401 foreach (fn, glob(NULL, base)) {
402 if (regex('@/$@', fn)) {
403 r = mp.grep(rx, spec, fn, rec, r);