4 A Programmer's Text Editor
6 Copyright (C) 1991-2009 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 */
37 mp.config.undo_levels = 100;
38 mp.config.word_wrap = 0;
39 mp.config.auto_indent = 0;
40 mp.config.tab_size = 8;
41 mp.config.tabs_as_spaces = 0;
43 mp.config.case_sensitive_search = 1;
44 mp.config.global_replace = 0;
45 mp.config.preread_lines = 60;
46 mp.config.mark_eol = 0;
47 mp.config.maximize = 0;
48 mp.config.keep_eol = 1;
50 /* default end of line, system dependent */
51 if (mp.drv.id eq 'win32')
52 mp.config.eol = "\r\n";
57 mp.config.status_format = "%m%n %x,%y [%l] %R%O %s %e %t";
58 mp.status_line_info = {
60 '%m' => sub { mp.active.txt.mod && '*' || ''; },
61 '%x' => sub { mp.active.txt.x + 1; },
62 '%y' => sub { mp.active.txt.y + 1; },
63 '%l' => sub { size(mp.active.txt.lines); },
64 '%R' => sub { mp.macro.process_event && 'R' || ''; },
66 '%s' => sub { mp.active.syntax.name; },
67 '%t' => sub { mp.tags[mp.get_word(mp.active())].label; },
68 '%n' => sub { mp.active.name; },
69 '%w' => sub { mp.word_count(mp.active()); },
70 '%e' => sub { mp.active.encoding || ''; },
74 /* a regex for selecting words */
75 mp.word_regex = "/[[:alnum:]_]+/i";
77 /* if it does not work (i.e. not GNU regex), fall back */
78 if (regex(mp.word_regex, "test") == NULL)
79 mp.word_regex = '/[A-Z_][A-Z0-9_]*/i';
85 /* allowed color names (order matters, match the Unix curses one) */
86 mp.color_names = [ "default", "black", "red", "green",
87 "yellow", "blue", "magenta", "cyan", "white" ];
89 /* color definitions */
91 'normal' => { 'text' => [ 'default', 'default' ],
92 'gui' => [ 0x000000, 0xffffff ] },
93 'cursor' => { 'text' => [ 'default', 'default' ],
94 'gui' => [ 0x000000, 0xffffff ],
95 'flags' => [ 'reverse' ] },
96 'selection' => { 'text' => [ 'red', 'default' ],
97 'gui' => [ 0xff0000, 0xffffff ],
98 'flags' => [ 'reverse'] },
99 'comments' => { 'text' => [ 'green', 'default' ],
100 'gui' => [ 0x00cc77, 0xffffff ] },
101 'documentation' => { 'text' => [ 'cyan', 'default' ],
102 'gui' => [ 0x8888ff, 0xffffff ] },
103 'quotes' => { 'text' => [ 'blue', 'default' ],
104 'gui' => [ 0x0000ff, 0xffffff ],
105 'flags' => [ 'bright' ] },
106 'matching' => { 'text' => [ 'black', 'cyan' ],
107 'gui' => [ 0x000000, 0xffff00 ] },
108 'word1' => { 'text' => [ 'green', 'default' ],
109 'gui' => [ 0x00aa00, 0xffffff ],
110 'flags' => [ 'bright' ] },
111 'word2' => { 'text' => [ 'red', 'default' ],
112 'gui' => [ 0xff6666, 0xffffff ],
113 'flags' => [ 'bright' ] },
114 'tag' => { 'text' => [ 'cyan', 'default' ],
115 'gui' => [ 0x8888ff, 0xffffff ],
116 'flags' => [ 'underline' ] },
117 'spell' => { 'text' => [ 'red', 'default' ],
118 'gui' => [ 0xff8888, 0xffffff ],
119 'flags' => [ 'bright', 'underline' ] },
120 'search' => { 'text' => [ 'black', 'green' ],
121 'gui' => [ 0x000000, 0x00cc77 ] }
124 /* hash of specially coloured words */
140 [ 'new', 'open', 'save', 'save_as', 'close', 'revert',
144 '-', 'open_config_file', 'open_templates_file',
146 '-', 'save_session', 'load_session',
152 [ 'undo', 'redo', '-',
153 'cut_mark', 'copy_mark', 'paste_mark', 'delete_mark',
155 'mark', 'mark_vertical', 'unmark', '-',
156 'insert_template', '-',
157 'word_wrap_paragraph', 'join_paragraph', '-',
164 [ 'seek', 'seek_next', 'seek_prev', 'replace', '-',
166 'seek_misspelled', 'ignore_last_misspell', '-',
167 'seek_repeated_word', '-',
168 'find_tag', 'complete_symbol', '-', 'grep'
174 'move_bof', 'move_eof', 'move_bol', 'move_eol',
175 'goto', 'move_word_right', 'move_word_left',
182 [ 'record_macro', 'play_macro', '-',
183 'encoding', 'tab_options', 'line_options', 'repeated_words_options',
184 'toggle_spellcheck', '-',
186 'zoom_in', 'zoom_out', '-',
192 mp.actions_by_menu_label = {};
197 * mp.redraw - Triggers a redraw on the next cycle.
199 * Triggers a full document redraw in the next cycle.
203 /* just increment the redraw trigger */
209 /* returns the active document */
213 /* empty document list? create a new, empty one */
214 if (size(mp.docs) == 0)
217 /* get active document */
218 d = mp.docs[mp.active_i];
220 /* if it's read only but has modifications, revert them */
221 if (d.read_only && size(d.undo)) {
226 'timeout' => time() + 2,
227 'string' => '*' ~ L("Read-only document") ~ '*'
235 sub mp.process_action(a)
236 /* processes an action */
242 if ((f = mp.actions[a]) != NULL)
246 'timeout' => time() + 2,
247 'string' => sprintf(L("Unknown action '%s'"), a)
253 sub mp.process_event(k)
254 /* processes a key event */
258 /* empty document list? do nothing */
259 if (size(mp.docs) == 0)
264 if (mp.keycodes_t == NULL)
265 mp.keycodes_t = mp.keycodes;
267 /* get the action asociated to the keycode */
268 if ((a = mp.keycodes_t[k]) != NULL) {
270 /* if it's a hash, store for further testing */
274 /* if it's executable, run it */
278 /* if it's an array, process it sequentially */
281 mp.process_action(l);
283 mp.process_action(a);
285 mp.keycodes_t = NULL;
289 mp.insert_keystroke(d, k);
290 mp.keycodes_t = NULL;
293 mp.shift_pressed = NULL;
297 sub mp.build_status_line()
298 /* returns the string to be drawn in the status line */
301 /* is the message still active? */
302 if (mp.message.timeout > time())
303 return mp.message.string;
308 return sregex("/%./g", mp.config.status_format, mp.status_line_info);
312 sub mp.backslash_codes(s, d)
313 /* encodes (d == 0) or decodes (d == 1) backslash codes
314 (like \n, \r, etc.) */
316 d && sregex("/[\r\n\t]/g", s, { "\r" => '\r', "\n" => '\n', "\t" => '\t'}) ||
317 sregex("/\\\\[rnt]/g", s, { '\r' => "\r", '\n' => "\n", '\t' => "\t"});
321 sub mp.long_op(func, a1, a2, a3, a4)
322 /* executes a potentially long function */
327 r = func(a1, a2, a3, a4);
334 sub mp.get_history(key)
335 /* returns a history for the specified key */
339 if (mp.history == NULL)
341 if (mp.history[key] == NULL)
342 mp.history[key] = [];
344 return mp.history[key];
348 sub mp.menu_label(action)
349 /* returns a label for the menu for an action */
353 /* if action is '-', it's a menu separator */
357 /* no recognized action? return */
358 if (!exists(mp.actions, action))
361 /* get the translated description */
362 l = L(mp.actdesc[action]) || action;
364 /* is there a keycode that generates this action? */
365 foreach (i, sort(keys(mp.keycodes))) {
366 if (mp.keycodes[i] eq action) {
367 /* avoid mouse and window pseudo-keycodes */
368 if (!regex("/window/", i) && !regex("/mouse/", i)) {
369 l = l ~ ' [' ~ i ~ ']';
375 mp.actions_by_menu_label[l] = action;
381 sub mp.trim_with_ellipsis(str, max)
382 /* trims the string to the last max characters, adding ellipsis if done */
384 local v = regex('/.{' ~ max ~ '}$/', str);
385 return v && '...' ~ v || str;
389 sub mp.get_doc_names(max)
390 /* returns an array with the trimmed names of the documents */
393 (e.txt.mod && '* ' || '') ~ mp.trim_with_ellipsis(e.name, (max || 24));
399 /* set mp.exit_message with an usage message (--help) */
403 "Minimum Profit %s - Programmer Text Editor\n"\
404 "Copyright (C) Angel Ortega <angel@triptico.com>\n"\
405 "This software is covered by the GPL license. NO WARRANTY.\n"\
407 "Usage: mp-5 [options] [files...]\n"\
411 " -t {tag} Edits the file where tag is defined\n"\
412 " -e {mpsl_code} Executes MPSL code\n"\
413 " -f {mpsl_script} Executes MPSL script file\n"\
414 " -d {directory} Set current directory\n"\
415 " +NNN Moves to line number NNN of last file\n"\
417 "Homepage: http://www.triptico.com/software/mp.html\n"\
418 "Mailing list: mp-subscribe@lists.triptico.com\n"
423 sub mp.process_cmdline()
424 /* process the command line arguments (ARGV) */
433 while (o = shift(ARGV)) {
434 if (o eq '-h' || o eq '--help') {
442 local c = shift(ARGV);
444 if (! regex('/;\s*$/', c))
452 local s = shift(ARGV);
455 ERROR = sprintf(L("Cannot open '%s'"), s);
458 eval(join("\n", mp.active.txt.lines));
467 mp.open_tag(shift(ARGV));
470 local s = shift(ARGV);
472 if (mp.hex_view(s) == NULL)
473 ERROR = sprintf(L("Cannot open '%s'"), s);
476 if (regex('/^\+/', o)) {
485 mp.exit_message = ERROR ~ "\n";
491 /* if no files are loaded, try a session */
492 if (size(mp.docs) == 0 && mp.config.auto_sessions) {
496 /* set the first as the active one */
502 /* if there is a line defined, move there */
504 mp.set_y(mp.active(), line);
508 sub mp.load_profile()
509 /* loads ~/.mp.mpsl */
511 /* if /etc/mp.mpsl exists, execute it */
512 if (stat('/etc/mp.mpsl') != NULL) {
514 local INC = [ '/etc' ];
519 /* if ~/.mp.mpsl exists, execute it */
520 if (ERROR == NULL && stat(HOMEDIR ~ '.mp.mpsl') != NULL) {
522 local INC = [ HOMEDIR ];
527 /* errors? show in a message */
530 'timeout' => time() + 20,
539 sub mp.setup_language()
540 /* sets up the language */
542 /* set gettext() domain */
543 gettext_domain('minimum-profit', APPDIR ~ 'locale');
545 /* test if gettext() can do a basic translation */
546 if (gettext('&File') eq '&File' && ENV.LANG) {
547 /* no; try alternatives using the LANG variable */
548 local v = [ sregex('!/!g', ENV.LANG) ]; /* es_ES.UTF-8 */
549 push(v, shift(split('.', v[-1]))); /* es_ES */
550 push(v, shift(split('_', v[-1]))); /* es */
553 eval('load("lang/' ~ l ~ '.mpsl");');
564 sub mp.normalize_version(vs)
565 /* converts a version string to something usable with cmp() */
567 map(sub(e) { sprintf("%03d", e); },
569 sregex('/-.+$/', vs)));
573 sub mp.assert_version(found, minimal, package)
574 /* asserts that 'found' version of 'package' is at least 'minimal',
575 or generate a warning otherwise */
577 if (cmp(mp.normalize_version(found),
578 mp.normalize_version(minimal)) < 0) {
579 mp.alert(sprintf(L("WARNING: %s version found is %s, but %s is needed"),
580 package, found, minimal));
585 sub mp.test_versions()
586 /* tests component versions */
590 mp.assert_version(mpdm.version, '1.0.7', 'MPDM');
591 mp.assert_version(MPSL.VERSION, '1.0.7', 'MPSL');
598 load("mp_move.mpsl");
599 load("mp_edit.mpsl");
600 load("mp_file.mpsl");
601 load("mp_clipboard.mpsl");
602 load("mp_search.mpsl");
603 load("mp_tags.mpsl");
604 load("mp_syntax.mpsl");
605 load("mp_macro.mpsl");
606 load("mp_templates.mpsl");
607 load("mp_spell.mpsl");
608 load("mp_misc.mpsl");
609 load("mp_crypt.mpsl");
610 load("mp_keyseq.mpsl");
611 load("mp_session.mpsl");
612 load("mp_build.mpsl");
613 load("mp_writing.mpsl");
618 mp.process_cmdline();