4 A Programmer's Text Editor
6 Copyright (C) 1991-2012 Angel Ortega <angel@triptico.com>
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 http://www.triptico.com
28 /* L(x) is the same as gettext(x) */
31 /* LL(x) is the same as x */
44 case_sensitive_search: 1,
53 /* default end of line, system dependent */
54 if (mp.drv.id eq 'win32')
55 mp.config.eol = "\r\n";
60 mp.config.status_format = "%m%n %x,%y [%l] %R%O %s %e %t";
61 mp.status_line_info = {
63 '%m' => sub { mp.active.disk_op && '!' || (mp.active.txt.mod && '*' || ''); },
64 '%x' => sub { mp.active.txt.x + 1; },
65 '%y' => sub { mp.active.txt.y + 1; },
66 '%l' => sub { size(mp.active.txt.lines); },
67 '%R' => sub { mp.macro.process_event && 'R' || ''; },
68 '%O' => sub { mp.config.insert && 'O' || ''; },
69 '%s' => sub { mp.active.syntax.name; },
70 '%t' => sub { mp.tags[mp.get_word(mp.active())].label; },
71 '%n' => sub { mp.active.name; },
72 '%w' => sub { mp.word_count(mp.active()); },
73 '%e' => sub { mp.active.encoding || ''; },
77 /* a regex for selecting words */
78 mp.word_regex = "/[[:alnum:]_]+/i";
80 /* if it does not work (i.e. not GNU regex), fall back */
81 if (regex("test", mp.word_regex) == NULL)
82 mp.word_regex = '/[A-Z_][A-Z0-9_]*/i';
88 /* allowed color names (order matters, match the Unix curses one) */
91 "black", "red", "green", "yellow",
92 "blue", "magenta", "cyan", "white"
95 /* color definitions */
98 text: [ 'default', 'default' ],
99 gui: [ 0x000000, 0xffffff ]
102 text: [ 'default', 'default' ],
103 gui: [ 0x000000, 0xffffff ],
107 text: [ 'red', 'default' ],
108 gui: [ 0xff0000, 0xffffff ],
112 text: [ 'green', 'default' ],
113 gui: [ 0x00cc77, 0xffffff ]
116 text: [ 'cyan', 'default' ],
117 gui: [ 0x8888ff, 0xffffff ]
120 text: [ 'blue', 'default' ],
121 gui: [ 0x0000ff, 0xffffff ],
125 text: [ 'black', 'cyan' ],
126 gui: [ 0x000000, 0xffff00 ]
129 text: [ 'green', 'default' ],
130 gui: [ 0x00aa00, 0xffffff ],
134 text: [ 'red', 'default' ],
135 gui: [ 0xff6666, 0xffffff ],
139 text: [ 'cyan', 'default' ],
140 gui: [ 0x8888ff, 0xffffff ],
141 flags: [ 'underline' ]
144 text: [ 'red', 'default' ],
145 gui: [ 0xff8888, 0xffffff ],
146 flags: [ 'bright', 'underline' ]
149 text: [ 'black', 'green' ],
150 gui: [ 0x000000, 0x00cc77 ]
154 /* hash of specially coloured words */
170 'new', 'open', 'save', 'save_as', 'close', 'revert',
174 '-', 'open_config_file', 'open_templates_file',
176 '-', 'save_session', 'load_session',
183 'cut_mark', 'copy_mark', 'paste_mark', 'delete_mark',
184 'delete_line', 'cut_lines_with_string', '-',
185 'mark', 'mark_vertical', 'unmark', 'mark_all', '-',
186 'insert_template', 'insert_next_item', '-',
187 'word_wrap_paragraph', 'join_paragraph', '-',
188 'exec_command', 'filter_selection', '-',
189 'exec_action', 'eval', 'eval_doc'
194 'seek', 'seek_next', 'seek_prev', 'replace', '-',
196 'seek_misspelled', 'ignore_last_misspell', '-',
197 'seek_repeated_word', '-',
198 'find_tag', 'complete_symbol', '-', 'grep'
204 'move_bof', 'move_eof', 'move_bol', 'move_eol',
205 'goto', 'move_word_right', 'move_word_left',
212 'record_macro', 'play_macro', '-',
213 'encoding', 'tab_options', 'line_options',
214 'repeated_words_options', 'toggle_spellcheck', '-',
216 'zoom_in', 'zoom_out', '-',
222 mp.actions_by_menu_label = {};
227 * mp.redraw - Triggers a redraw on the next cycle.
229 * Triggers a full document redraw in the next cycle.
233 /* just increment the redraw trigger */
239 /* returns the active document */
243 /* empty document list? create a new, empty one */
244 if (size(mp.docs) == 0)
247 /* get active document */
248 d = mp.docs[mp.active_i];
250 /* if it's read only but has modifications, revert them */
251 if (d.read_only && size(d.undo)) {
256 'timeout' => time() + 2,
257 'string' => '*' ~ L("Read-only document") ~ '*'
265 sub mp.process_action(a)
266 /* processes an action */
272 if ((f = mp.actions[a]) != NULL)
277 string: sprintf(L("Unknown action '%s'"), a)
285 sub mp.test_mtime(doc)
286 /* tests for a file's mtime (uses GUI) */
288 if (doc.mtime && time() > mp.mtime_test) {
291 if ((s = stat(doc.name)) != NULL && s[9] > doc.mtime) {
292 if (mp.confirm(L("File was changed externally. Reload?")) == 1)
293 mp.actions.revert(doc);
298 mp.mtime_test = time() + 10;
305 sub mp.process_event(k)
306 /* processes a key event */
315 if (mp.keycodes_t == NULL)
316 mp.keycodes_t = mp.keycodes;
318 /* get the action asociated to the keycode */
319 if ((a = mp.keycodes_t[k]) != NULL) {
321 /* if it's a hash, store for further testing */
325 /* if it's executable, run it */
329 /* if it's an array, process it sequentially */
332 mp.process_action(l);
334 mp.process_action(a);
336 mp.keycodes_t = NULL;
340 mp.insert_keystroke(d, k);
341 mp.keycodes_t = NULL;
344 mp.shift_pressed = NULL;
346 /* if there is a keypress notifier function, call it */
347 if (is_exec(d.keypress))
355 sub mp.build_status_line()
356 /* returns the string to be drawn in the status line */
359 /* is the message still active? */
360 if (mp.message.timeout > time())
361 return mp.message.string;
366 return sregex(mp.config.status_format, "/%./g", mp.status_line_info);
370 sub mp.backslash_codes(s, d)
371 /* encodes (d == 0) or decodes (d == 1) backslash codes
372 (like \n, \r, etc.) */
374 d && sregex(s, "/[\r\n\t]/g", { "\r" => '\r', "\n" => '\n', "\t" => '\t'}) ||
375 sregex(s, "/\\\\[rnt]/g", { '\r' => "\r", '\n' => "\n", '\t' => "\t"});
379 sub mp.long_op(func, a1, a2, a3, a4)
380 /* executes a potentially long function */
385 r = func(a1, a2, a3, a4);
392 sub mp.get_history(key)
393 /* returns a history for the specified key */
397 if (mp.history == NULL)
399 if (mp.history[key] == NULL)
400 mp.history[key] = [];
402 return mp.history[key];
406 sub mp.menu_label(action)
407 /* returns a label for the menu for an action */
411 /* if action is '-', it's a menu separator */
415 /* no recognized action? return */
416 if (!exists(mp.actions, action))
419 /* get the translated description */
420 l = L(mp.actdesc[action]) || action;
422 /* is there a keycode that generates this action? */
423 foreach (i, sort(keys(mp.keycodes))) {
424 if (mp.keycodes[i] eq action) {
425 /* avoid mouse and window pseudo-keycodes */
426 if (!regex(i, "/window/") && !regex(i, "/mouse/")) {
427 l = l ~ ' [' ~ i ~ ']';
433 mp.actions_by_menu_label[l] = action;
439 sub mp.trim_with_ellipsis(str, max)
440 /* trims the string to the last max characters, adding ellipsis if done */
442 local v = regex(str, '/.{' ~ max ~ '}$/');
443 return v && '...' ~ v || str;
447 sub mp.get_doc_names(max)
448 /* returns an array with the trimmed names of the documents */
453 (e.txt.mod && '* ' || '') ~ mp.trim_with_ellipsis(e.name, (max || 24));
460 /* set mp.exit_message with an usage message (--help) */
464 "Minimum Profit %s - Programmer Text Editor\n"\
465 "Copyright (C) Angel Ortega <angel@triptico.com>\n"\
466 "This software is covered by the GPL license. NO WARRANTY.\n"\
468 "Usage: mp-5 [options] [files...]\n"\
472 " -t {tag} Edits the file where tag is defined\n"\
473 " -e {mpsl_code} Executes MPSL code\n"\
474 " -f {mpsl_script} Executes MPSL script file\n"\
475 " -d {directory} Set current directory\n"\
476 " -x {file} Open file in the hexadecimal viewer\n"\
477 " -txt Use text mode instead of GUI\n"\
478 " +NNN Moves to line number NNN of last file\n"\
480 "Homepage: http://triptico.com/software/mp.html\n"\
481 "Mailing list: mp-subscribe@lists.triptico.com\n"
486 sub mp.process_cmdline()
487 /* process the command line arguments (ARGV) */
496 while (o = shift(ARGV)) {
497 if (o eq '-h' || o eq '--help') {
505 local c = shift(ARGV);
507 if (! regex(c, '/;\s*$/'))
515 local s = shift(ARGV);
518 ERROR = sprintf(L("Cannot open '%s'"), s);
521 eval(join(mp.active.txt.lines, "\n"));
530 mp.open_tag(shift(ARGV));
533 local s = shift(ARGV);
535 if (mp.hex_view(s) == NULL)
536 ERROR = sprintf(L("Cannot open '%s'"), s);
540 mp.config.text_mode = 1;
542 if (regex(o, '/^\+/')) {
551 mp.exit_message = ERROR ~ "\n";
557 /* if no files are loaded, try a session */
558 if (size(mp.docs) == 0 && mp.config.auto_sessions) {
562 /* set the first as the active one */
568 /* if there is a line defined, move there */
570 mp.set_y(mp.active(), line);
574 sub mp.load_profile()
575 /* loads ~/.mp.mpsl */
577 /* if /etc/mp.mpsl exists, execute it */
578 if (stat('/etc/mp.mpsl') != NULL) {
580 local INC = [ '/etc' ];
585 /* if ~/.mp.mpsl exists, execute it */
586 if (ERROR == NULL && stat(HOMEDIR ~ '.mp.mpsl') != NULL) {
588 local INC = [ HOMEDIR ];
593 /* errors? show in a message */
596 'timeout' => time() + 20,
605 sub mp.setup_language()
606 /* sets up the language */
608 /* set gettext() domain */
609 gettext_domain('minimum-profit', APPDIR ~ 'locale');
611 /* test if gettext() can do a basic translation */
612 if (gettext('&File') eq '&File' && ENV.LANG) {
613 /* no; try alternatives using the LANG variable */
614 local v = [ sregex(ENV.LANG, '!/!g') ]; /* es_ES.UTF-8 */
615 push(v, shift(split(v[-1], '.'))); /* es_ES */
616 push(v, shift(split(v[-1], '_'))); /* es */
619 eval('load("lang/' ~ l ~ '.mpsl");');
630 sub mp.normalize_version(vs)
631 /* converts a version string to something usable with cmp() */
633 vs->sregex('/-.+$/')->split('.')->map(sub(e) { sprintf("%03d", e); });
637 sub mp.assert_version(found, minimal, package)
638 /* asserts that 'found' version of 'package' is at least 'minimal',
639 or generate a warning otherwise */
641 if (cmp(mp.normalize_version(found),
642 mp.normalize_version(minimal)) < 0) {
643 mp.alert(sprintf(L("WARNING: %s version found is %s, but %s is needed"),
644 package, found, minimal));
649 sub mp.test_versions()
650 /* tests component versions */
654 mp.assert_version(mpdm.version, '2.0.2', 'MPDM');
655 mp.assert_version(MPSL.VERSION, '2.0.2', 'MPSL');
683 foreach (m, mp_modules) {
684 eval('load("mp_' ~ m ~ '.mpsl");');
687 mp.exit_message = mp.exit_message ~ ERROR ~ "\n";
697 mp.process_cmdline();