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 /** editor actions **/
33 mp.actions['seek'] = sub (d) {
35 { 'label' => L("Text to seek:"),
37 'history' => 'search' },
38 { 'label' => L("Case sensitive") ~ ':',
40 'value' => mp.config.case_sensitive_search }
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."));
51 mp.actions['seek_next'] = sub (d) { mp.long_op(mp.search, d, NULL) ||
52 mp.alert(L("Text not found."));
55 mp.actions['seek_prev'] = sub (d) { mp.long_op(mp.search_back, d, NULL) ||
56 mp.alert(L("Text not found."));
59 mp.actions['replace'] = sub (d) {
62 { 'label' => L("Replace text:"),
64 'history' => 'search'},
65 { 'label' => L("Replace with:"),
67 'history' => 'replace'},
68 { 'label' => L("Case sensitive") ~ ':',
70 'value' => mp.config.case_sensitive_search },
71 { 'label' => L("Global replace:"),
73 'value' => mp.config.global_replace }
77 mp.config.case_sensitive_search = r[2];
78 mp.config.global_replace = r[3];
81 mp.long_op(mp.replace, d, mp.backslash_codes(r[0]), mp.backslash_codes(r[1]));
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) {
91 { 'label' => L("Text to seek:"),
93 'history' => 'search' },
94 { 'label' => L("Files to grep (empty, all):"),
96 'history' => 'grep' },
97 { 'label' => L("Base directory (empty, current):"),
99 'history' => 'grep_base' }
102 if (r != NULL && r[0] ne '') {
104 local t = '<grep ' ~ r[0] ~ ' ' ~ r[1] ~ '>';
106 if ((r = mp.long_op(mp.grep, '/' ~ r[0] ~ '/', r[1], r[2])) == NULL)
107 mp.alert(L("File(s) not found."));
110 mp.alert(L("Text not found."));
112 local l = mp.open(t);
118 mp.insert(l, sprintf("%s:%d: %s\n",
119 e[0], e[1] + 1, e[2]));
129 /** default key bindings **/
131 mp.keycodes['f3'] = 'seek_next';
132 mp.keycodes['ctrl-f3'] = 'seek_prev';
133 mp.keycodes['ctrl-f'] = 'seek';
134 mp.keycodes['ctrl-r'] = 'replace';
135 mp.keycodes['ctrl-page-down'] = 'seek_next_char';
136 mp.keycodes['ctrl-page-up'] = 'seek_prev_char';
138 /** action descriptions **/
140 mp.actdesc['seek'] = LL("Search text...");
141 mp.actdesc['seek_next'] = LL("Search next");
142 mp.actdesc['seek_prev'] = LL("Search previous");
143 mp.actdesc['replace'] = LL("Replace...");
144 mp.actdesc['seek_next_char'] = LL("Move to next instance of current char");
145 mp.actdesc['seek_prev_char'] = LL("Move to previous instance of current char");
146 mp.actdesc['grep'] = LL("Grep (find inside) files...");
150 sub mp.prefix_regex(str)
151 /* set str to be a valid regex */
156 /* surround with / for the regex */
157 str = '/' ~ str ~ '/';
159 /* add optional case insensitivity flag */
160 if (! mp.config.case_sensitive_search)
167 sub mp.search_dir(doc, str, dir)
168 /* search str and put the current position there, with direction */
170 local txt, r, l, lines;
171 local bx, by, ex, ey;
174 str = mp.last_search;
176 str = mp.prefix_regex(str);
177 mp.last_search = str;
186 /* search backwards */
189 ex = txt.x && txt.x - 1 || 0;
192 if (txt.mark && !txt.mark.incomplete) {
193 if (ey >= txt.mark.ey) {
196 if (ex > txt.mark.ex)
213 if (txt.mark && !txt.mark.incomplete) {
214 if (by <= txt.mark.by) {
217 if (bx < txt.mark.bx)
225 ex = size(txt.lines[-1]);
226 ey = size(txt.lines);
230 lines = mp.get_range(doc, bx, by, ex, ey, 0);
233 local n = (dir == -1) && (size(lines) - 1) || 0;
234 while ((l = lines[n]) != NULL && regex(str, l) == NULL)
240 /* if it was found in the first line, add offset */
241 r[0] += (n == 0 && bx);
243 mp.set_y(doc, by + n);
244 mp.set_x(doc, r[0] + r[1]);
246 /* set always to the same line */
247 if (mp.config.move_seek_to_line != NULL &&
248 (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
256 sub mp.search(doc, str)
257 /* search str and put the current position there, downwards */
259 mp.search_dir(doc, str, 1);
263 sub mp.search_back(doc, str)
264 /* search str and put the current position there, backwards */
266 mp.search_dir(doc, str, -1);
270 sub mp.replace_1(doc, this, that)
271 /* searches 'this' and replaces it with 'that', once */
275 if ((c = mp.search(doc, this)) != NULL) {
281 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
282 txt.lines[txt.y], that, c[0]);
284 /* move to correct position */
286 mp.set_x(doc, c[0] + c[1]);
295 sub mp.replace(doc, this, that)
296 /* replaces 'this' with 'that', may be globally */
300 while (mp.replace_1(doc, this, that)) {
303 if (!mp.config.global_replace)
308 'timeout' => time() + 4,
309 'string' => sprintf(L("%d replaces"), cnt)
314 sub mp.seek_prev_or_next_char(doc, func)
315 /* moves to next or previous occurent of current char */
319 /* get current char */
320 local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
322 /* move one char right */
325 /* search for it (mp.search() or mp.search_back()) */
326 local t = mp.last_search;
327 func(doc, '\' ~ w[1]);
335 sub mp.grep(rx, spec, base, r)
336 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
337 (or glob() is unsupported), an empty list if the string was not found or
338 an array with the matches, that are three-element arrays with the file name,
339 the line number and the line that matched */
343 /* if spec is empty, set as NULL (meaning "glob everything") */
347 all = glob(spec, base);
352 /* spec globs to NULL or empty; abort */
359 if ((f = open(fn, "r")) != NULL) {
362 /* file open; now grep */
363 while (l = read(f)) {
367 /* found; store line, filename and linenum */
368 push(r, [ fn, n, l ]);
378 /* glob again, trying subdirectories */
379 foreach (fn, glob(NULL, base)) {
380 if (regex('@/$@', fn)) {
381 r = mp.grep(rx, spec, base ~ fn, r);