4 A Programmer's Text Editor
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;
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):"),
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."));
107 mp.alert(L("Text not found."));
109 local l = mp.open(t);
115 mp.insert(l, sprintf("%s:%d: %s\n",
116 e[0], e[1] + 1, e[2]));
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...");
146 sub mp.prefix_regex(str)
147 /* set str to be a valid regex */
152 /* surround with / for the regex */
153 str = '/' ~ str ~ '/';
155 /* add optional case insensitivity flag */
156 if (! mp.config.case_sensitive_search)
163 sub mp.search_dir(doc, str, dir)
164 /* search str and put the current position there, with direction */
169 str = mp.last_search;
171 str = mp.prefix_regex(str);
172 mp.last_search = str;
186 /* search backwards */
188 l = splice(txt.lines[y], NULL, 0, txt.x - 1);
192 while (regex(str, l, 0) == NULL && y > 0)
197 while (regex(str, txt.lines[y], x) == NULL && y < size(txt.lines)) {
202 /* get last coords */
207 mp.set_x(doc, r[0] + r[1]);
209 /* set always to the same line */
210 if (mp.config.move_seek_to_line != NULL &&
211 (doc.txt.vy = doc.txt.y - mp.config.move_seek_to_line) < 0)
219 sub mp.search(doc, str)
220 /* search str and put the current position there, downwards */
222 mp.search_dir(doc, str, 1);
226 sub mp.search_back(doc, str)
227 /* search str and put the current position there, backwards */
229 mp.search_dir(doc, str, -1);
233 sub mp.replace_1(doc, this, that)
234 /* searches 'this' and replaces it with 'that', once */
238 if ((c = mp.search(doc, this)) != NULL) {
244 txt.lines[txt.y] = sregex(mp.prefix_regex(this),
245 txt.lines[txt.y], that, c[0]);
247 /* move to correct position */
249 mp.set_x(doc, c[0] + c[1]);
258 sub mp.replace(doc, this, that)
259 /* replaces 'this' with 'that', may be globally */
263 while (mp.replace_1(doc, this, that)) {
266 if (!mp.config.global_replace)
271 'timeout' => time() + 4,
272 'string' => sprintf(L("%d replaces"), cnt)
277 sub mp.seek_prev_or_next_char(doc, func)
278 /* moves to next or previous occurent of current char */
282 /* get current char */
283 local w = splice(txt.lines[txt.y], NULL, txt.x, 1);
285 /* move one char right */
288 /* search for it (mp.search() or mp.search_back()) */
289 local t = mp.last_search;
290 func(doc, '\' ~ w[1]);
298 sub mp.grep(rx, spec)
299 /* Greps str in the files in spec. Returns NULL if no file matched the glob()
300 (or glob() is unsupported), an empty list if the string was not found or
301 an array with the matches, that are three-element arrays with the file name,
302 the line number and the line that matched */
306 /* if spec is empty, set as NULL (meaning "glob everything") */
312 /* spec globs to NULL or empty; abort */
321 if ((f = open(fn, "r")) != NULL) {
324 /* file open; now grep */
325 while (l = read(f)) {
329 /* found; store line, filename and linenum */
330 push(r, [ fn, n, l ]);