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 /** editor actions **/
30 mp.actions['new'] = sub (d) {
31 d = mp.find_file_by_name(L("<unnamed>"));
41 mp.actions['next'] = sub (d) { mp.next(); };
42 mp.actions['prev'] = sub (d) { mp.prev(); };
44 mp.actions['save_as'] = sub (d) {
48 if ((t = mp.savefile(L("Save file as:"))) == NULL)
54 if (mp.long_op(mp.save, d) == -1)
55 mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
60 mp.actions['save'] = sub (d) {
62 /* name is <unnamed> or something similar; ask for one */
63 if (regex("/^<.+>$/", d.name))
64 mp.actions.save_as(d);
66 if (mp.long_op(mp.save, d) == -1)
67 mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
70 mp.actions['close'] = sub (d) {
74 r = mp.confirm(L("File has changed. Save changes?"));
76 /* cancel? don't close */
86 mp.actions['exit'] = sub (d) {
90 if (mp.config.auto_sessions)
93 if (mp.actions.close_all())
97 mp.actions['open'] = sub (d) {
101 if ((n = mp.openfile(L("File to open:"))) != NULL && n ne "")
102 if (mp.long_op(mp.open, n) == NULL && ERRNO != NULL)
103 mp.alert(sprintf("Error opening '%s': %s", n, ERRNO));
106 mp.actions['revert'] = sub (d) {
107 /* save current name */
112 r = mp.confirm(L("File has changed. Are you sure?"));
114 /* cancel? don't close */
115 if (r == 0 || r == 2)
120 if (mp.long_op(mp.open, p) == NULL && ERRNO != NULL)
121 mp.alert(sprintf("Error opening '%s': %s", p, ERRNO));
124 mp.actions['open_config_file'] = sub (d) {
126 mp.open(HOMEDIR ~ ".mp.mpsl");
129 mp.actions['sync'] = sub (d) {
131 /* save all modified documents */
132 foreach (d, grep(sub (e) { e.txt.mod; }, mp.docs))
136 mp.actions['exec_command'] = sub (d) {
139 { 'label' => L("System command:"),
141 'history' => 'system' }
147 /* does it start with a pipe? */
148 if (regex('/^\|/', cmd)) {
151 /* yes; current document should be fed to it */
152 cmd = sregex('/^\|/', cmd, NULL);
154 if ((p = popen(cmd, "w")) != NULL) {
155 foreach (l, mp.get_active_area(d))
156 write(p, l ~ mp.config.eol);
162 sprintf(L("Error writing to command '%s'"), cmd));
165 /* no; execute command and insert into cursor */
168 if ((p = popen(cmd, "r")) != NULL) {
173 while ((l = read(p)) != NULL) {
174 mp.insert(d, mp.chomp(l));
175 mp.insert_newline(d);
182 sprintf(L("Error reading from command '%s'"), cmd));
187 mp.actions['close_all'] = sub {
191 while (s = size(mp.docs)) {
192 local doc = mp.docs[mp.active_i];
194 /* close current document */
195 mp.actions.close(doc);
197 /* if the size of the list hasn't changed,
198 action was cancelled, so don't exit */
199 if (s == size(mp.docs))
206 mp.actions['open_under_cursor'] = sub (d) {
209 /* is the word under cursor file:line: ? */
210 if ((w = mp.get_word(d, '/[a-z\._0-9\/ :-]+:[0-9]+: ?/i')) != NULL) {
217 local l = pop(w) - 1;
219 /* open the file, rejoining with : */
220 local n = mp.open(join(':', w));
222 /* now move to the line */
223 mp.search_set_y(n, l);
226 if ((w = mp.get_word(d, '/[a-z\._0-9\/ :-]+/i')) != NULL) {
231 mp.actions['hex_view'] = sub (d) {
235 if ((n = mp.openfile(L("File to open:"))) != NULL && n ne "")
236 if (mp.long_op(mp.hex_view, n) == NULL && ERRNO != NULL)
237 mp.alert(sprintf("Error opening '%s': %s", n, ERRNO));
240 /** default key bindings **/
242 mp.keycodes['ctrl-n'] = 'next';
243 mp.keycodes['ctrl-o'] = 'open';
244 mp.keycodes['ctrl-q'] = 'exit';
245 mp.keycodes['ctrl-s'] = 'save';
246 mp.keycodes['ctrl-w'] = 'close';
247 mp.keycodes['ctrl-enter'] = 'open_under_cursor';
249 mp.keycodes['close-window'] = 'exit';
251 /** action descriptions **/
253 mp.actdesc['new'] = LL("New");
254 mp.actdesc['save'] = LL("Save...");
255 mp.actdesc['save_as'] = LL("Save as...");
256 mp.actdesc['next'] = LL("Next");
257 mp.actdesc['prev'] = LL("Previous");
258 mp.actdesc['open'] = LL("Open...");
259 mp.actdesc['exit'] = LL("Exit");
260 mp.actdesc['close'] = LL("Close");
261 mp.actdesc['revert'] = LL("Revert");
262 mp.actdesc['close_all'] = LL("Close all");
264 mp.actdesc['open_config_file'] = LL("Edit configuration file");
265 mp.actdesc['sync'] = LL("Save modified texts");
266 mp.actdesc['exec_command'] = LL("Run system command...");
267 mp.actdesc['open_under_cursor'] = LL("Open file under cursor");
268 mp.actdesc['hex_view'] = LL("Hexadecimal viewer...");
273 /* chomps the end of file chars from a string */
275 sregex("/\r*\n*$/", str, NULL);
286 /* if unlink before write is desired, do it */
287 if (mp.config.unlink && (s = stat(doc.name)) != NULL)
290 /* set the encoding for this file opening */
291 TEMP_ENCODING = doc.encoding;
293 if ((f = open(doc.name, "wb")) == NULL) {
294 /* can't write? delete name */
295 doc.name = L("<unnamed>");
299 /* if the document has a password, save it encrypted */
301 nl = mp.crypt1_save(f, doc.txt.lines, doc.password);
303 /* save as a plain text file */
304 foreach (l, doc.txt.lines) {
305 /* write a line separator if it's not the first line */
307 write(f, mp.config.eol);
318 /* set back the permissions and ownership, if available */
320 chmod(doc.name, s[2]);
321 chown(doc.name, s[4], s[5]);
328 sub mp.new(filename, lines)
329 /* creates a new document */
338 txt.lines = lines || [ '' ];
342 doc.name = filename || L("<unnamed>");
350 /* store in the list and set as active */
352 mp.active_i = size(mp.docs) - 1;
354 /* autodetect syntax */
355 mp.detect_syntax(doc);
362 /* rotates through the document list */
364 if (++mp.active_i == size(mp.docs))
370 /* rotates through the document list, backwards */
372 if (--mp.active_i == -1)
373 mp.active_i = size(mp.docs) - 1;
378 /* closes the active document */
380 local k = mp.active_i;
382 /* delete from the list */
383 adel(mp.docs, mp.active_i);
385 /* rotate if it was the last one */
386 if (mp.active_i == size(mp.docs))
391 sub mp.find_file_by_name(filename)
392 /* finds an open file by its name */
394 seek(map(sub(d) { d.name; }, mp.docs), filename);
398 sub mp.open(filename)
399 /* opens a new document (uses UI) */
403 /* looks first if the file is already open */
404 if ((s = mp.find_file_by_name(filename)) != -1) {
409 if ((s = stat(filename)) == NULL) {
411 'timeout' => time() + 2,
412 'string' => sprintf(L("New file '%s'"), filename)
415 return mp.new(filename);
418 /* canonicalize, if possible */
422 /* look again for this filename in the open files */
423 if ((s = mp.find_file_by_name(filename)) != -1) {
431 if ((f = open(filename, "rb")) == NULL)
434 if (mp.crypt1_detect(f)) {
435 /* password needed; ask for it */
439 { 'label' => L("Password:"),
440 'type' => 'password' }
442 /* cancel? fail, but not on error */
446 /* get the password */
449 /* an empty password is equal to cancellation */
453 /* and load the file */
454 d = mp.new(filename, mp.crypt1_load(f, p));
458 /* close file (needed for rewinding AND
459 possible encoding autodetection) */
462 /* reopen and read */
463 f = open(filename, "rb");
464 d = mp.new(filename, mp.plain_load(f));
470 /* store the encoding */
471 d.encoding = DETECTED_ENCODING || ENCODING || '';
477 sub mp.hex_view(filename)
478 /* shows a file as an hex dump */
482 if ((f = open(filename, "rb")) == NULL)
490 if ((c = getchar(f)) != NULL)
493 if (size(l) == 16 || c == NULL) {
499 h = h ~ sprintf(' %02X', ord(v));
504 if (ord(v) < 32 || ord(v) > 126)
510 local n = 16 - size(l);
518 push(lines, join(' |', [ sprintf('| %06X', offset), h, a, '']));
529 local d = mp.new('<' ~ filename ~ ' hex view>', lines);
531 d.syntax = mp.syntax.hex_view;