4 A Programmer's Text Editor
8 Copyright (C) 1991-2011 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>"));
43 mp.actions['next'] = sub (d) { mp.next(); };
44 mp.actions['prev'] = sub (d) { mp.prev(); };
46 mp.actions['save_as'] = sub (d, newname) {
49 newname = mp.savefile(L("Save file as:"));
51 if (newname != NULL) {
55 if (mp.long_op(mp.save, d) == -1)
56 mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
64 mp.actions['save'] = sub (d) {
66 /* name is <unnamed> or something similar; ask for one */
67 if (regex(d.name, "/^<.+>$/"))
68 mp.actions.save_as(d);
70 if (mp.long_op(mp.save, d) == -1)
71 mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
76 mp.actions['close'] = sub (d) {
78 /* call all 'on_close' functions */
79 foreach (f, d.on_close) {
81 /* if it returns 0, cancel close */
89 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));
108 mp.actions['revert'] = sub (d) {
109 /* save current name */
114 r = mp.confirm(L("File has changed. Are you sure?"));
116 /* cancel? don't close */
117 if (r == 0 || r == 2)
122 if (mp.long_op(mp.open, p) == NULL && ERRNO != NULL)
123 mp.alert(sprintf("Error opening '%s': %s", p, ERRNO));
128 mp.actions['open_config_file'] = sub (d) {
130 mp.open(HOMEDIR ~ ".mp.mpsl");
133 mp.actions['sync'] = sub (d) {
135 /* save all modified documents */
136 foreach (d, grep(mp.docs, sub (e) { e.txt.mod; }))
142 mp.actions['exec_command'] = sub (d, cmd) {
148 'label' => L("System command:"),
150 'history' => 'system'
160 /* does it start with a pipe? */
161 if (regex(cmd, '/^\|/')) {
164 /* yes; current document should be fed to it */
165 cmd = sregex(cmd, '/^\|/');
167 if ((p = popen(cmd, "w")) != NULL) {
170 foreach (l, mp.get_active_area(d))
171 write(p, l ~ mp.config.eol);
178 sprintf(L("Error writing to command '%s'"), cmd));
181 /* no; execute command and insert into cursor */
184 if ((p = popen(cmd, "r")) != NULL) {
190 while ((l = read(p)) != NULL) {
191 mp.insert(d, mp.chomp(l));
192 mp.insert_newline(d);
200 sprintf(L("Error reading from command '%s'"), cmd));
207 mp.actions['filter_selection'] = sub (d, cmd) {
213 'label' => L("System command:"),
215 'history' => 'system2'
227 /* if there is no selection, take full document */
228 if (d.txt.mark == NULL) {
239 /* now feed it to the command */
240 local p = popen2(cmd);
243 write(p[1], join(mp.clipboard, "\n"));
247 while ((l = read(p[0])) != NULL)
257 mp.actions['close_all'] = sub {
261 while (s = size(mp.docs)) {
262 local doc = mp.docs[mp.active_i];
264 /* close current document */
265 mp.actions.close(doc);
267 /* if the size of the list hasn't changed,
268 action was cancelled, so don't exit */
269 if (s == size(mp.docs))
276 mp.actions['open_under_cursor'] = sub (d) {
279 /* is the word under cursor file:line: ? */
280 if ((w = mp.get_word(d, '/[a-z\._0-9\/ -]+:[0-9]+:/i')) != NULL) {
287 local l = pop(w) - 1;
289 /* open the file, rejoining with : */
290 local n = mp.open(join(w, ':'));
292 /* now move to the line */
293 mp.search_set_y(n, l);
298 if ((w = mp.get_word(d, '/[a-z\._0-9\/:-]+/i')) != NULL) {
305 mp.actions['hex_view'] = sub (d) {
308 if ((n = mp.openfile(L("File to open:"))) != NULL && n ne "")
309 if (mp.long_op(mp.hex_view, n) == NULL && ERRNO != NULL)
310 mp.alert(sprintf("Error opening '%s': %s", n, ERRNO));
317 mp.actions['open_dropped_files'] = sub (d) {
318 while (size(mp.dropped_files))
319 mp.open(shift(mp.dropped_files));
323 /** default key bindings **/
325 mp.keycodes['ctrl-n'] = 'next';
326 mp.keycodes['ctrl-o'] = 'open';
327 mp.keycodes['ctrl-q'] = 'exit';
328 mp.keycodes['ctrl-s'] = 'save';
329 mp.keycodes['ctrl-w'] = 'close';
330 mp.keycodes['ctrl-enter'] = 'open_under_cursor';
331 mp.keycodes['alt-enter'] = 'open_under_cursor';
332 mp.keycodes['dropped-files'] = 'open_dropped_files';
334 mp.keycodes['close-window'] = 'exit';
336 /** action descriptions **/
338 mp.actdesc['new'] = LL("New");
339 mp.actdesc['save'] = LL("Save...");
340 mp.actdesc['save_as'] = LL("Save as...");
341 mp.actdesc['next'] = LL("Next");
342 mp.actdesc['prev'] = LL("Previous");
343 mp.actdesc['open'] = LL("Open...");
344 mp.actdesc['exit'] = LL("Exit");
345 mp.actdesc['close'] = LL("Close");
346 mp.actdesc['revert'] = LL("Revert");
347 mp.actdesc['close_all'] = LL("Close all");
349 mp.actdesc['open_config_file'] = LL("Edit configuration file");
350 mp.actdesc['sync'] = LL("Save modified texts");
351 mp.actdesc['exec_command'] = LL("Run system command...");
352 mp.actdesc['filter_selection'] = LL("Filter selection through system command...");
353 mp.actdesc['open_under_cursor'] = LL("Open file under cursor");
354 mp.actdesc['hex_view'] = LL("Hexadecimal viewer...");
355 mp.actdesc['open_dropped_files'] = LL("Open dropped files");
360 /* chomps the end of file chars from a string */
362 sregex(str, "/\r*\n*$/");
366 sub mp.save_th(f, doc)
367 /* mp.save() helper */
370 local eol = doc.eol || mp.config.eol;
374 /* save as a plain text file */
375 foreach (l, doc.txt.lines) {
376 /* write a line separator if it's not the first line */
399 /* if unlink before write is desired, do it */
400 if (mp.config.unlink && (s = stat(doc.name)) != NULL)
403 /* set the encoding for this file opening */
404 TEMP_ENCODING = doc.encoding;
406 if ((f = open(doc.name, "wb")) == NULL) {
407 /* can't write? delete name */
408 doc.name = L("<unnamed>");
414 /* if the document has a password, save it encrypted */
416 mp.crypt1_save(f, doc.txt.lines, doc.password);
422 /* set back the permissions and ownership, if available */
424 chmod(doc.name, s[2]);
425 chown(doc.name, s[4], s[5]);
433 sub mp.save_on_close(doc)
434 /* on_close function to save modified files */
439 r = mp.confirm(L("File has changed. Save changes?"));
450 sub mp.create(filename, lines)
451 /* creates a document */
460 lines: lines || [ '' ]
462 name: filename || L("<unnamed>"),
466 on_close: [ mp.save_on_close ]
473 sub mp.new(filename, lines)
474 /* creates a new document */
476 local doc = mp.create(filename, lines);
478 /* store in the list and set as active */
479 ins(mp.docs, doc, mp.active_i);
481 mp.detect_syntax(doc);
488 /* rotates through the document list */
490 if (++mp.active_i == size(mp.docs))
498 /* rotates through the document list, backwards */
500 if (--mp.active_i == -1)
501 mp.active_i = size(mp.docs) - 1;
508 /* closes the active document */
510 local k = mp.active_i;
512 /* delete from the list */
513 adel(mp.docs, mp.active_i);
515 /* rotate if it was the last one */
516 if (mp.active_i == size(mp.docs))
519 /* cannot call mp.active() */
523 sub mp.find_file_by_name(filename)
524 /* finds an open file by its name */
536 sub mp.open(filename)
537 /* opens a new document (uses UI) */
541 /* looks first if the file is already open */
542 if ((s = mp.find_file_by_name(filename)) != -1) {
547 if ((s = stat(filename)) == NULL) {
549 'timeout' => time() + 2,
550 'string' => sprintf(L("New file '%s'"), filename)
553 return mp.new(filename);
556 /* canonicalize, if possible */
560 /* look again for this filename in the open files */
561 if ((s = mp.find_file_by_name(filename)) != -1) {
569 if ((f = open(filename, "rb")) == NULL)
572 if (mp.crypt1_detect(f)) {
573 /* password needed; ask for it */
577 { 'label' => L("Password:"),
578 'type' => 'password' }
580 /* cancel? fail, but not on error */
584 /* get the password */
587 /* an empty password is equal to cancellation */
591 /* and load the file */
592 d = mp.new(filename, mp.crypt1_load(f, p));
596 /* close file (needed for rewinding AND
597 possible encoding autodetection) */
600 /* reopen and read */
601 f = open(filename, "rb");
602 d = mp.new(filename, mp.plain_load(f));
608 /* store the encoding */
609 d.encoding = DETECTED_ENCODING || ENCODING || '';
611 /* if original EOL is to be kept, store it */
612 if (mp.config.keep_eol)
613 d.eol = mp.last_seen_eol;
619 sub mp.hex_view_th(filename, d)
620 /* mp.hex_view() helper */
626 local lines = d.txt.lines;
628 local f = open(filename, 'rb');
631 if ((c = getchar(f)) != NULL)
634 if (size(l) == 16 || c == NULL) {
640 h = h ~ sprintf(' %02X', ord(v));
645 if (ord(v) < 32 || ord(v) > 126)
651 local n = 16 - size(l);
659 push(lines, join([ sprintf('| %06X', offset), h, a, ''], ' |'));
674 sub mp.hex_view(filename)
675 /* shows a file as an hex dump */
680 if ((f = open(filename, "rb")) != NULL) {
683 d = mp.new('<' ~ filename ~ ' hex view>', []);
685 d.syntax = mp.syntax.hex_view;
688 mp.hex_view_th(filename, d);