The 'save_as' action accepts additional arguments.
[mp-5.x.git] / mp_edit.mpsl
blobb7ae2af24ad8aa66d294992fcfb39b4d71b92574
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Editing.
8     Copyright (C) 1991-2010 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
29 /** editor actions **/
31 mp.actions['insert_line']       = sub (d) {
32         mp.store_undo(d);
33         mp.insert_newline(d);
35         if (d.syntax == NULL) mp.detect_syntax(d);
38 mp.actions['delete_line']       = sub (d) { mp.store_undo(d); mp.delete_line(d); };
39 mp.actions['insert_space']      = sub (d) { mp.store_undo(d); mp.insert_space(d); };
40 mp.actions['insert_tab']        = sub (d) { mp.store_undo(d); mp.insert_tab(d); };
41 mp.actions['insert_real_tab']   = sub (d) { mp.store_undo(d); mp.insert(d, "\t"); };
42 mp.actions['delete']            = sub (d) { mp.store_undo(d); mp.delete_char(d); };
44 mp.actions['delete_left']       = sub (d) {
45         if (d.txt.x + d.txt.y) {
46                 mp.store_undo(d);
47                 mp.move_left(d);
48                 mp.delete_char(d);
49         }
52 mp.actions['indent_block'] = sub (d) {
53         mp.store_undo(d);
55         if (d.txt.mark == NULL) {
56                 mp.move(d, mp.move_bol);
57                 mp.insert_tab(d);
58                 return;
59         }
60         
61         local currentY = d.txt.y;
62         local startY   = d.txt.mark.by;
63         local endY     = d.txt.mark.ey;
64         local times    = endY - startY;
65         
66         mp.unmark(d);
67         
68         mp.set_y(d, startY);
69         /* use to be while d.txt.y <= endY, but that entered an endless loop when
70            you were indenting a block including the very last line in the file */
71         while (times >= 0) {
72                 mp.move(d, mp.move_bol);
73                 mp.insert_tab(d);
74                 mp.move(d, mp.move_down);
75                 
76                 times--;
77         }
78         
79         mp.set_y(d, startY);
80         mp.move(d, mp.move_bol);
81         mp.mark(d);
82         
83         mp.set_y(d, endY);
84         mp.move(d.mp_move_eol);
85         mp.mark(d);
86         
87         mp.set_y(d, currentY);
90 mp.actions['unindent_block'] = sub(d) {
91         mp.store_undo(d);
93         if (d.txt.mark == NULL) {
94                 mp.unindent_line(d);
95                 return;
96         }
97         
98         local currentY = d.txt.y;
99         local startY   = d.txt.mark.by;
100         local endY     = d.txt.mark.ey;
101         local times    = endY - startY;
102         
103         mp.unmark(d);
104         
105         mp.set_y(d, startY);
106         
107         /* use to be while d.txt.y <= endY, but that entered an endless loop when
108            you were unindenting a block including the very last line in the file */
109         while (times >= 0) {
110                 mp.unindent_line(d);
111                 mp.move(d, mp.move_down);
112                 
113                 times--;
114         }
115         
116         mp.set_y(d, startY);
117         mp.move(d, mp.move_bol);
118         mp.mark(d);
119         
120         mp.set_y(d, endY);
121         mp.move(d.mp_move_eol);
122         mp.mark(d);
123         
124         mp.set_y(d, currentY);
127 mp.actions['undo']              = sub (d) { mp.undo(d); };
128 mp.actions['redo']              = sub (d) { mp.redo(d); };
130 mp.actions['join_paragraph'] = sub (d) {
131     mp.store_undo(d);
133     if (d.txt.mark) {
134         mp.busy(1);
135         mp.cut(d);
137         /* create a working document */
138         local p = mp.create('<wrk>', mp.clipboard);
140         /* while not at EOF, word wrap everything */
141         while (p.txt.y < size(p.txt.lines) - 1) {
142             mp.join_paragraph(p);
143             mp.move_down(p);
144             mp.move_down(p);
145         }
147         /* insert the content */
148         mp.insert(d, p.txt.lines);
149         mp.busy(0);
150     }
151     else
152         mp.join_paragraph(d);
155 mp.actions['word_wrap_paragraph'] = sub (d) {
157     if(mp.config.word_wrap == 0)
158         mp.alert(L("Word wrapping must be set"));
159     else {
160         mp.store_undo(d);
162         if (d.txt.mark) {
163             mp.busy(1);
164             mp.cut(d);
166             /* create a working document */
167             local p = mp.create('<wrk>', mp.clipboard);
169             /* while not at EOF, word wrap everything */
170             while (p.txt.y < size(p.txt.lines) - 1) {
171                 mp.word_wrap_paragraph(p);
172                 mp.move_down(p);
173                 mp.move_down(p);
174             }
176             /* insert the content */
177             mp.insert(d, p.txt.lines);
178             mp.busy(0);
179         }
180         else
181             mp.word_wrap_paragraph(d);
182     }
185 mp.actions['line_options']      = sub (d) {
187         /* convert special characters on end of line */
188         local lt = mp.backslash_codes(mp.config.eol, 1);
190         local t = mp.form( [
191                 { 'label'       => L("Word wrap on column (0, no word wrap):"),
192                   'type'        => 'text',
193                   'value'       => mp.config.word_wrap,
194                   'history'     => 'wordwrap' },
195                 { 'label'       => L("Automatic indentation") ~ ':',
196                   'type'        => 'checkbox',
197                   'value'       => mp.config.auto_indent },
198                 { 'label'       => L("Line termination") ~ ':',
199                   'value'       => lt,
200                   'type'        => 'text' },
201                 { 'label'       => L("Keep original end of lines") ~ ':',
202                   'value'       => mp.config.keep_eol,
203                   'type'        => 'checkbox' },
204                 { 'label'       => L("Mark end of lines") ~ ':',
205                   'value'       => mp.config.mark_eol,
206                   'type'        => 'checkbox' }
207         ] );
209         if (t != NULL) {
210                 mp.config.word_wrap = t[0];
211                 mp.config.auto_indent = t[1];
212                 mp.config.eol = mp.backslash_codes(t[2], 0);
213                 mp.config.keep_eol = t[3];
214                 mp.config.mark_eol = t[4];
215         }
218 mp.actions['tab_options']       = sub (d) {
220         local t = mp.form( [
221                 { 'label'       => L("Tab size") ~ ':',
222                   'type'        => 'text',
223                   'value'       => mp.config.tab_size,
224                   'history'     => 'tabsize' },
225                 { 'label'       => L("Convert tabs to spaces") ~ ':',
226                   'type'        => 'checkbox',
227                   'value'       => mp.config.tabs_as_spaces },
228                 { 'label'       => L("Use previous line for tab columns") ~ ':',
229                   'type'        => 'checkbox',
230                   'value'       => mp.config.dynamic_tabs }
231         ] );
233         if (t != NULL) {
234                 mp.config.tab_size       = t[0];
235                 mp.config.tabs_as_spaces = t[1];
236                 mp.config.dynamic_tabs   = t[2];
237         }
240 mp.actions['toggle_insert'] = sub (d) { mp.config.insert = !mp.config.insert; };
242 /** default key bindings **/
244 mp.keycodes['enter']            = "insert_line";
245 mp.keycodes['tab']              = "insert_tab";
246 mp.keycodes['shift-tab']        = "insert_real_tab";
247 mp.keycodes['space']            = "insert_space";
248 mp.keycodes['delete']           = "delete";
249 mp.keycodes['backspace']        = "delete_left";
250 mp.keycodes['ctrl-i']           = "insert_tab";
251 mp.keycodes['ctrl-m']           = "insert_line";
252 mp.keycodes['ctrl-y']           = "delete_line";
253 mp.keycodes['alt-cursor-right'] = "indent_block";
254 mp.keycodes['alt-cursor-left']  = "unindent_block";
255 mp.keycodes['ctrl-z']           = "undo";
256 mp.keycodes['f4']               = "word_wrap_paragraph";
257 mp.keycodes['insert']           = "toggle_insert";
259 /** action descriptions **/
261 mp.actdesc['insert_line']           = LL("Insert line");
262 mp.actdesc['delete_line']           = LL("Delete line");
263 mp.actdesc['insert_space']          = LL("Insert space");
264 mp.actdesc['insert_tab']            = LL("Insert tab");
265 mp.actdesc['insert_real_tab']       = LL("Insert real tab character");
266 mp.actdesc['delete']                = LL("Delete character");
267 mp.actdesc['delete_left']           = LL("Delete character to the left");
268 mp.actdesc['indent_block']          = LL("Indent block");
269 mp.actdesc['unindent_block']        = LL("Unindent block");
270 mp.actdesc['undo']                  = LL("Undo");
271 mp.actdesc['redo']                  = LL("Redo");
272 mp.actdesc['join_paragraph']        = LL("Join a paragraph in one line");
273 mp.actdesc['word_wrap_paragraph']   = LL("Word-wrap a paragraph");
274 mp.actdesc['line_options']          = LL("Line options...");
275 mp.actdesc['tab_options']           = LL("Tab options...");
276 mp.actdesc['toggle_insert']         = LL("Toggle insert/overwrite mode");
278 /** code **/
281  * mp.break_line - Breaks current line in two (inserts a newline).
282  * @doc: the document
283  * @col: column where the newline will be inserted 
285  * Breaks current line in two by inserting a newline character in between.
286  * If @col is not NULL, the newline will be inserted in that column; otherwise,
287  * the current x position will be used.
288  */
289 sub mp.break_line(doc, col)
291         local txt = doc.txt;
292         local c, w;
294         /* if col is NULL, set it to be the x cursor */
295         if (col == NULL)
296                 col = txt.x;
298         /* gets line where cursor is */
299         c = txt.lines[txt.y];
301         /* deletes from col to the end of line */
302         w = splice(c, NULL, col, -1);
304         /* set first part as current line */
305         txt.lines[txt.y] = w[0];
307         /* move to next line */
308         txt.y++;
310         /* insert a new line here */
311         expand(txt.lines, txt.y, 1);
313         /* fix the x cursor position */
314         txt.x -= col;
316         /* if autoindenting... */
317         if (mp.config.auto_indent) {
318                 /* extract leading blanks in the original line
319                    to prepend them to the line to be inserted */
320                 local i = regex(c, "/^[ \t]*[-\+\*]?[ \t]+/", 0);
322                 /* substitute all non-tab characters with spaces */
323                 i = sregex(i, "/[^\t]/g", " ");
325                 /* delete any blank in the new line */
326                 w[1] = sregex(w[1], "/^[ \t]*/");
328                 /* concatenate */
329                 w[1] = i ~ w[1];
331                 /* the x position is further the length of that */
332                 txt.x += size(i);
333         }
335         /* put second part there (or an empty string if NULL) */
336         txt.lines[txt.y] = w[1] || '';
338         txt.mod++;
340         return doc;
344 sub mp.join_line(doc)
345 /* joins the current line with the next one */
347         local txt = doc.txt;
349         if (txt.y < size(txt.lines)) {
350                 /* concats current line with the next one */
351                 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
353                 /* delete it */
354                 adel(txt.lines, txt.y + 1);
356                 txt.mod++;
357         }
359         return doc;
363 sub mp.delete_line(doc)
364 /* deletes the current line */
366         local txt = doc.txt;
367         local vx;
369         /* take current position */
370         vx = mp.x2vx(txt.lines[txt.y], txt.x);
372         /* if it's the only line, just replace it */
373         if (size(txt.lines) == 1)
374                 txt.lines[0] = '';
375         else {
376                 /* destroy the line */
377                 adel(txt.lines, txt.y);
378         }
380         /* fix if it was the last line */
381         if (txt.y >= size(txt.lines))
382                 txt.y = size(txt.lines) - 1;
384         /* move to previous x position */
385         txt.x = mp.vx2x(txt.lines[txt.y], vx);
387         txt.mod++;
389         return doc;
393 sub mp.delete_char(doc)
394 /* deletes the current char */
396         local txt = doc.txt;
398         if (txt.mark != NULL) {
399                 mp.delete_mark(doc);
400                 return;
401         }
403         /* is it over the end of line? */
404         if (txt.x == size(txt.lines[txt.y]))
405                 mp.join_line(doc);
406         else {
407                 local w;
409                 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
410                 txt.lines[txt.y] = w[0];
411         }
413         txt.mod++;
415         return doc;
419 sub mp.delete_range(doc, bx, by, ex, ey, v)
420 /* deletes a range of characters from a document */
422         local txt = doc.txt;
424         /* move to the start of the range */
425         txt.x = bx;
426         txt.y = by;
428         if (by == ey) {
429                 local w;
431                 /* block is just one line; delete the middle part */
432                 w = splice(txt.lines[by], NULL, bx, ex - bx);
434                 txt.lines[by] = w[0];
435         }
436         else {
437                 /* block has more than one line */
438                 local w;
440                 if (v == 0) {
441                         /* delete using normal selection block */
443                         /* delete from the beginning to the end of the first line */
444                         w = splice(txt.lines[by], NULL, bx, -1);
445                         txt.lines[by] = w[0];
447                         /* delete from the beginning of the last line to
448                            the end of the block */
449                         w = splice(txt.lines[ey], NULL, 0, ex);
450                         txt.lines[ey] = w[0];
452                         /* collapse the lines in between */
453                         collapse(txt.lines, by + 1, ey - by - 1);
455                         /* finally join both lines */
456                         mp.join_line(doc);
457                 }
458                 else {
459                         /* delete using vertical selection block */
460                         while (by <= ey) {
461                                 w = splice(txt.lines[by], NULL, bx, ex - bx);
462                                 txt.lines[by] = w[0];
463                                 by++;
464                         }
465                 }
466         }
468         txt.mod++;
470         return doc;
474 sub mp.insert_string(doc, str)
475 /* inserts a string into the cursor position */
477         local txt = doc.txt;
478         local w;
480         mp.delete_mark(doc);
482         /* splice and change */
483         w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
484         txt.lines[txt.y] = w[0];
486         /* move right */
487         txt.x += size(str);
489         txt.mod++;
491         return doc;
495 sub mp.insert(doc, a)
496 /* inserts an array of text into a document */
498         local txt = doc.txt;
499         local s;
501         /* if a is not an array, split it */
502         if (!is_array(a))
503                 a = split(a, "\n");
505         /* empty array? return */
506         if ((s = size(a)) == 0)
507                 return doc;
509         /* paste first line into current position */
510         mp.insert_string(doc, a[0]);
512         /* more than just one line? */
513         if (s > 1) {
514                 /* break current line in two */
515                 mp.break_line(doc);
517                 /* insert last line */
518                 mp.insert_string(doc, a[s - 1]);
519         }
521         /* more than two lines? */
522         if (s > 2) {
523                 local n = 1;
525                 /* open room */
526                 expand(txt.lines, txt.y, s - 2);
528                 /* transfer middle lines */
529                 while (n < s - 1)
530                         txt.lines[txt.y++] = a[n++];
531         }
533         return doc;
537 sub mp.wrap_words(doc)
538 /* do the word wrapping */
540         local txt = doc.txt;
542         if (mp.config.word_wrap == 0)
543                 return doc;
545         /* take the column where the cursor is */
546         local c = mp.x2vx(txt.lines[txt.y], txt.x);
548         if (c >= mp.config.word_wrap &&
549                 regex(txt.lines[txt.y], "/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/")) {
550                 local w;
552                 /* take the coordinates */
553                 w = regex();
555                 /* break the line there */
556                 mp.break_line(doc, w[1]);
558                 /* delete the space at the end of the line */
559                 txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
560         }
562         return doc;
566 sub mp.insert_space(doc)
567 /* inserts a space, taking wordwrapping into account */
569         mp.wrap_words(doc);
570         mp.insert(doc, ' ');
574 sub mp.insert_tab(doc)
575 /* inserts a tab */
577         if (doc.txt.y && mp.config.dynamic_tabs) {
578                 local pl = doc.txt.lines[doc.txt.y - 1];
580                 if ((pl = regex(pl, "/[^ \t]*[ \t]+/", doc.txt.x)) != NULL) {
581                         pl = sregex(pl, "/[^\t]/g", ' ');
582                         mp.insert(doc, pl);
583                         return doc;
584                 }
585         }
587         if (mp.config.tabs_as_spaces) {
588                 /* number of spaces to insert */
589                 local n = mp.config.tab_size -
590                         ((doc.txt.x) % mp.config.tab_size);
592                 while(n--) mp.insert(doc, ' ');
593         }
594         else
595                 mp.insert(doc, "\t");
597         return doc;
601 sub mp.insert_newline(doc)
602 /* inserts a newline */
604         mp.wrap_words(doc);
605         mp.break_line(doc);
609 sub mp.insert_keystroke(doc, key)
610 /* inserts from a keystroke (with undo) */
612         if (size(key) == 1) {
613                 mp.store_undo(doc);
614                 mp.insert(doc, key);
615         }
616         else
617         if (key != NULL) {
618                 mp.message = {
619                         'timeout'       => time() + 2,
620                         'string'        => sprintf(L("Unbound keystroke '%s'"), key)
621                 };
622         }
624         return doc;
628 /** undo **/
630 sub mp.store_undo(doc)
631 /* stores the current txt in the undo queue */
633         queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
634         doc.redo = [];
636         return doc;
640 sub mp.undo(doc)
641 /* undoes last operation */
643         local txt;
645         if (txt = pop(doc.undo)) {
646                 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
647                 doc.txt = txt;
648         }
650         return doc;
654 sub mp.redo(doc)
655 /* redoes last undid operation */
657         local txt;
659         if (txt = pop(doc.redo)) {
660                 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
661                 doc.txt = txt;
662         }
664         return doc;
668 /** paragraphs **/
670 sub mp.join_paragraph(doc)
671 /* joins current paragraph in just one line */
673         local txt = doc.txt;
674         local l;
676         while ((l = txt.lines[txt.y + 1]) && size(l)) {
677                 /* delete all leading blanks in the next line */
678                 txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");
680                 /* move to end of line and add a space separator */
681                 mp.move_eol(doc);
682                 mp.insert(doc, ' ');
684                 /* really join */
685                 mp.join_line(doc);
686         }
688         return doc;
692 sub mp.word_wrap_paragraph(doc)
693 /* word wraps current paragraph */
695         local txt = doc.txt;
697         if (mp.config.word_wrap == 0)
698                 return doc;
700         mp.join_paragraph(doc);
702         mp.move_eol(doc);
704         while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
705                 mp.insert_space(doc);
706                 mp.move_left(doc);
707                 mp.delete_char(doc);
708         }
710         return doc;
713 /* indent/unindent support functions */
715 sub mp.unindent_line(d)
716 /* Unindent the current line by 1 tab or the indent size */
718         local l = split(d.txt.lines[d.txt.y]);
719         
720         mp.move(d, mp.move_bol);
721         
722         if (cmp(l[0], "\t") == 0) {
723                 mp.delete_char(d);
724         } else {
725                 local i = 0;
726                 while (i < mp.config.tab_size) {
727                         if (cmp(l[i], " ") == 0) {
728                                 mp.delete_char(d);
729                         } else {
730                                 break;
731                         }
732                         
733                         i++;
734                 }
735         }