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)
178 sub mp.prefix_regex(str)
179 /* set str to be a valid regex */
184 /* surround with / for the regex */
185 str = '/' ~ str ~ '/';
187 /* add optional case insensitivity flag */
188 if (! mp.config.case_sensitive_search)
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;
202 str = mp.last_search;
204 str = mp.prefix_regex(str);
205 mp.last_search = str;
214 /* search backwards */
217 ex = txt.x && txt.x - 1 || 0;
220 if (txt.mark && !txt.mark.incomplete) {
221 if (ey >= txt.mark.ey) {
224 if (ex > txt.mark.ex)
241 if (txt.mark && !txt.mark.incomplete) {
242 if (by <= txt.mark.by) {
245 if (bx < txt.mark.bx)
253 ex = size(txt.lines[-1]);
254 ey = size(txt.lines);
258 lines = mp.get_range(doc, bx, by, ex, ey, 0);
261 local n = (dir == -1) && (size(lines) - 1) || 0;
262 while ((l = lines[n]) != NULL && regex(str, l) == NULL)
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]);
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 */
298 if ((c = mp.search(doc, this)) != NULL) {
304 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
305 txt.lines[txt.y], that, c[0]);
307 /* move to correct position */
309 mp.set_x(doc, c[0] + c[1]);
318 sub mp.replace(doc, this, that)
319 /* replaces 'this' with 'that', may be globally */
323 while (mp.replace_1(doc, this, that)) {
326 if (!mp.config.global_replace)
331 'timeout' => time() + 4,
332 'string' => sprintf(L("%d replaces"), cnt)
339 sub mp.seek_prev_or_next_char(doc, func)
340 /* moves to next or previous occurence of current char */
344 /* get current char */
345 local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
347 /* move one char right */
350 /* search for it (mp.search() or mp.search_back()) */
351 local t = mp.last_search;
352 func(doc, '\' ~ w[1]);
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 */
370 /* if spec is empty, set as NULL (meaning "glob everything") */
374 all = glob(spec, base);
379 /* spec globs to NULL or empty; abort */
386 if ((f = open(fn, "r")) != NULL) {
389 /* file open; now grep */
390 while (l = read(f)) {
394 /* found; store line, filename and linenum */
395 push(r, [ fn, n, l ]);
406 /* glob again, trying subdirectories */
407 foreach (fn, glob(NULL, base)) {
408 if (regex('@/$@', fn)) {
409 r = mp.grep(rx, spec, fn, rec, r);