Updated RELEASE_NOTES.
[mp-5.x.git] / mp_edit.mpsl
blobe64630ebb8f770f00f5d805fd563032a0434edf2
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Editing.
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
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)
36         mp.detect_syntax(d);
38     return d;
41 mp.actions['delete_line']       = sub (d) { mp.store_undo(d); mp.delete_line(d); };
42 mp.actions['insert_space']      = sub (d) { mp.store_undo(d); mp.insert_space(d); };
43 mp.actions['insert_tab']        = sub (d) { mp.store_undo(d); mp.insert_tab(d); };
44 mp.actions['insert_real_tab']   = sub (d) { mp.store_undo(d); mp.insert(d, "\t"); };
45 mp.actions['delete']            = sub (d) { mp.store_undo(d); mp.delete_char(d); };
47 mp.actions['delete_left']       = sub (d) {
48     if (d.txt.x + d.txt.y) {
49         mp.store_undo(d);
50         mp.move_left(d);
51         mp.delete_char(d);
52     }
54     return d;
57 mp.actions['indent_block'] = sub (d) {
58     mp.store_undo(d);
60     if (d.txt.mark == NULL) {
61         mp.move(d, mp.move_bol);
62         mp.insert_tab(d);
63     }
64         else {
65         local currentY = d.txt.y;
66         local startY   = d.txt.mark.by;
67         local endY     = d.txt.mark.ey;
68         local times    = endY - startY;
69         
70         mp.unmark(d);
71         
72         mp.set_y(d, startY);
74         /* use to be while d.txt.y <= endY, but that entered an endless loop when
75             you were indenting a block including the very last line in the file */
76         while (times >= 0) {
77             mp.move(d, mp.move_bol);
78             mp.insert_tab(d);
79             mp.move(d, mp.move_down);
80                 
81             times--;
82         }
83         
84         mp.set_y(d, startY);
85         mp.move(d, mp.move_bol);
86         mp.mark(d);
87         
88         mp.set_y(d, endY);
89         mp.move(d.mp_move_eol);
90         mp.mark(d);
91         
92         mp.set_y(d, currentY);
93     }
95     return d;
98 mp.actions['unindent_block'] = sub(d) {
99     mp.store_undo(d);
101     if (d.txt.mark == NULL) {
102         mp.unindent_line(d);
103     }
104     else {
105         local currentY = d.txt.y;
106         local startY   = d.txt.mark.by;
107         local endY     = d.txt.mark.ey;
108         local times    = endY - startY;
109         
110         mp.unmark(d);
111         
112         mp.set_y(d, startY);
113         
114         /* use to be while d.txt.y <= endY, but that entered an endless loop when
115             you were unindenting a block including the very last line in the file */
116         while (times >= 0) {
117             mp.unindent_line(d);
118             mp.move(d, mp.move_down);
119                 
120             times--;
121         }
122         
123         mp.set_y(d, startY);
124         mp.move(d, mp.move_bol);
125         mp.mark(d);
126         
127         mp.set_y(d, endY);
128         mp.move(d.mp_move_eol);
129         mp.mark(d);
130         
131         mp.set_y(d, currentY);
132     }
134     return d;
137 mp.actions['undo']              = sub (d) { mp.undo(d); };
138 mp.actions['redo']              = sub (d) { mp.redo(d); };
140 mp.actions['join_paragraph'] = sub (d) {
141     mp.store_undo(d);
143     if (d.txt.mark) {
144         mp.busy(1);
145         mp.cut(d);
147         /* create a working document */
148         local p = mp.create('<wrk>', mp.clipboard);
150         /* while not at EOF, word wrap everything */
151         while (p.txt.y < size(p.txt.lines) - 1) {
152             mp.join_paragraph(p);
153             mp.move_down(p);
154             mp.move_down(p);
155         }
157         /* insert the content */
158         mp.insert(d, p.txt.lines);
159         mp.busy(0);
160     }
161     else
162         mp.join_paragraph(d);
164     return d;
167 mp.actions['word_wrap_paragraph'] = sub (d) {
169     if(mp.config.word_wrap == 0)
170         mp.alert(L("Word wrapping must be set"));
171     else {
172         mp.store_undo(d);
174         if (d.txt.mark) {
175             mp.busy(1);
176             mp.cut(d);
178             /* create a working document */
179             local p = mp.create('<wrk>', mp.clipboard);
181             /* while not at EOF, word wrap everything */
182             while (p.txt.y < size(p.txt.lines) - 1) {
183                 mp.word_wrap_paragraph(p);
184                 mp.move_down(p);
185                 mp.move_down(p);
186             }
188             /* insert the content */
189             mp.insert(d, p.txt.lines);
190             mp.busy(0);
191         }
192         else
193             mp.word_wrap_paragraph(d);
194     }
196     return d;
199 mp.actions['line_options']      = sub (d) {
201     /* convert special characters on end of line */
202     local lt = mp.backslash_codes(mp.config.eol, 1);
204     local t = mp.form(
205         [
206             {
207                 label:      L("Word wrap on column (0, no word wrap):"),
208                 type:       'text',
209                 value:      mp.config.word_wrap,
210                 history:    'wordwrap'
211             },
212             {
213                 label:      L("Automatic indentation") ~ ':',
214                 type:       'checkbox',
215                 value:      mp.config.auto_indent
216             },
217             {
218                 label:      L("Line termination") ~ ':',
219                 value:      lt,
220                 type:       'text'
221             },
222             {
223                 label:      L("Keep original end of lines") ~ ':',
224                 value:      mp.config.keep_eol,
225                 type:       'checkbox'
226             },
227             {
228                 label:      L("Mark end of lines") ~ ':',
229                 value:      mp.config.mark_eol,
230                 type:       'checkbox'
231             },
232             {
233                 label:      L("'Smart' move to beginning of line:"),
234                 value:      mp.config.smart_bol,
235                 type:       'checkbox'
236             }
237         ]
238     );
240     if (t != NULL) {
241         mp.config.word_wrap     = t[0];
242         mp.config.auto_indent   = t[1];
243         mp.config.eol           = mp.backslash_codes(t[2], 0);
244         mp.config.keep_eol      = t[3];
245         mp.config.mark_eol      = t[4];
246         mp.config.smart_bol     = t[5];
247     }
250 mp.actions['tab_options']       = sub (d) {
252         local t = mp.form( [
253                 { 'label'       => L("Tab size") ~ ':',
254                   'type'        => 'text',
255                   'value'       => mp.config.tab_size,
256                   'history'     => 'tabsize' },
257                 { 'label'       => L("Convert tabs to spaces") ~ ':',
258                   'type'        => 'checkbox',
259                   'value'       => mp.config.tabs_as_spaces },
260                 { 'label'       => L("Use previous line for tab columns") ~ ':',
261                   'type'        => 'checkbox',
262                   'value'       => mp.config.dynamic_tabs }
263         ] );
265         if (t != NULL) {
266                 mp.config.tab_size       = t[0];
267                 mp.config.tabs_as_spaces = t[1];
268                 mp.config.dynamic_tabs   = t[2];
269         }
272 mp.actions['toggle_insert'] = sub (d) { mp.config.insert = !mp.config.insert; };
274 /** default key bindings **/
276 mp.keycodes['enter']            = "insert_line";
277 mp.keycodes['tab']              = "insert_tab";
278 mp.keycodes['shift-tab']        = "insert_real_tab";
279 mp.keycodes['space']            = "insert_space";
280 mp.keycodes['delete']           = "delete";
281 mp.keycodes['backspace']        = "delete_left";
282 mp.keycodes['ctrl-i']           = "insert_tab";
283 mp.keycodes['ctrl-m']           = "insert_line";
284 mp.keycodes['ctrl-y']           = "delete_line";
285 mp.keycodes['alt-cursor-right'] = "indent_block";
286 mp.keycodes['alt-cursor-left']  = "unindent_block";
287 mp.keycodes['ctrl-z']           = "undo";
288 mp.keycodes['f4']               = "word_wrap_paragraph";
289 mp.keycodes['insert']           = "toggle_insert";
291 /** action descriptions **/
293 mp.actdesc['insert_line']           = LL("Insert line");
294 mp.actdesc['delete_line']           = LL("Delete line");
295 mp.actdesc['insert_space']          = LL("Insert space");
296 mp.actdesc['insert_tab']            = LL("Insert tab");
297 mp.actdesc['insert_real_tab']       = LL("Insert real tab character");
298 mp.actdesc['delete']                = LL("Delete character");
299 mp.actdesc['delete_left']           = LL("Delete character to the left");
300 mp.actdesc['indent_block']          = LL("Indent block");
301 mp.actdesc['unindent_block']        = LL("Unindent block");
302 mp.actdesc['undo']                  = LL("Undo");
303 mp.actdesc['redo']                  = LL("Redo");
304 mp.actdesc['join_paragraph']        = LL("Join a paragraph in one line");
305 mp.actdesc['word_wrap_paragraph']   = LL("Word-wrap a paragraph");
306 mp.actdesc['line_options']          = LL("Line options...");
307 mp.actdesc['tab_options']           = LL("Tab options...");
308 mp.actdesc['toggle_insert']         = LL("Toggle insert/overwrite mode");
310 /** code **/
313  * mp.break_line - Breaks current line in two (inserts a newline).
314  * @doc: the document
315  * @col: column where the newline will be inserted 
317  * Breaks current line in two by inserting a newline character in between.
318  * If @col is not NULL, the newline will be inserted in that column; otherwise,
319  * the current x position will be used.
320  */
321 sub mp.break_line(doc, col)
323         local txt = doc.txt;
324         local c, w;
326         /* if col is NULL, set it to be the x cursor */
327         if (col == NULL)
328                 col = txt.x;
330         /* gets line where cursor is */
331         c = txt.lines[txt.y];
333         /* deletes from col to the end of line */
334         w = splice(c, NULL, col, -1);
336         /* set first part as current line */
337         txt.lines[txt.y] = w[0];
339         /* move to next line */
340         txt.y++;
342         /* insert a new line here */
343         expand(txt.lines, txt.y, 1);
345         /* fix the x cursor position */
346         txt.x -= col;
348         /* if autoindenting... */
349         if (mp.config.auto_indent) {
350                 /* extract leading blanks in the original line
351                    to prepend them to the line to be inserted */
352                 local i = regex(c, "/^[ \t]*[-\+\*]?[ \t]+/", 0);
354                 /* substitute all non-tab characters with spaces */
355                 i = sregex(i, "/[^\t]/g", " ");
357                 /* delete any blank in the new line */
358                 w[1] = sregex(w[1], "/^[ \t]*/");
360                 /* concatenate */
361                 w[1] = i ~ w[1];
363                 /* the x position is further the length of that */
364                 txt.x += size(i);
365         }
367         /* put second part there (or an empty string if NULL) */
368         txt.lines[txt.y] = w[1] || '';
370         txt.mod++;
372         return doc;
376 sub mp.join_line(doc)
377 /* joins the current line with the next one */
379         local txt = doc.txt;
381         if (txt.y < size(txt.lines)) {
382                 /* concats current line with the next one */
383                 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
385                 /* delete it */
386                 adel(txt.lines, txt.y + 1);
388                 txt.mod++;
389         }
391         return doc;
395 sub mp.delete_line(doc)
396 /* deletes the current line */
398         local txt = doc.txt;
399         local vx;
401         /* take current position */
402         vx = mp.x2vx(txt.lines[txt.y], txt.x);
404         /* if it's the only line, just replace it */
405         if (size(txt.lines) == 1)
406                 txt.lines[0] = '';
407         else {
408                 /* destroy the line */
409                 adel(txt.lines, txt.y);
410         }
412         /* fix if it was the last line */
413         if (txt.y >= size(txt.lines))
414                 txt.y = size(txt.lines) - 1;
416         /* move to previous x position */
417         txt.x = mp.vx2x(txt.lines[txt.y], vx);
419         txt.mod++;
421         return doc;
425 sub mp.delete_char(doc)
426 /* deletes the current char */
428         local txt = doc.txt;
430         if (txt.mark != NULL) {
431                 mp.delete_mark(doc);
432                 return;
433         }
435         /* is it over the end of line? */
436         if (txt.x == size(txt.lines[txt.y]))
437                 mp.join_line(doc);
438         else {
439                 local w;
441                 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
442                 txt.lines[txt.y] = w[0];
443         }
445         txt.mod++;
447         return doc;
451 sub mp.delete_range(doc, bx, by, ex, ey, v)
452 /* deletes a range of characters from a document */
454         local txt = doc.txt;
456         /* move to the start of the range */
457         txt.x = bx;
458         txt.y = by;
460         if (by == ey) {
461                 local w;
463                 /* block is just one line; delete the middle part */
464                 w = splice(txt.lines[by], NULL, bx, ex - bx);
466                 txt.lines[by] = w[0];
467         }
468         else {
469                 /* block has more than one line */
470                 local w;
472                 if (v == 0) {
473                         /* delete using normal selection block */
475                         /* delete from the beginning to the end of the first line */
476                         w = splice(txt.lines[by], NULL, bx, -1);
477                         txt.lines[by] = w[0];
479                         /* delete from the beginning of the last line to
480                            the end of the block */
481                         w = splice(txt.lines[ey], NULL, 0, ex);
482                         txt.lines[ey] = w[0];
484                         /* collapse the lines in between */
485                         collapse(txt.lines, by + 1, ey - by - 1);
487                         /* finally join both lines */
488                         mp.join_line(doc);
489                 }
490                 else {
491                         /* delete using vertical selection block */
492                         while (by <= ey) {
493                                 w = splice(txt.lines[by], NULL, bx, ex - bx);
494                                 txt.lines[by] = w[0];
495                                 by++;
496                         }
497                 }
498         }
500         txt.mod++;
502         return doc;
506 sub mp.insert_string(doc, str)
507 /* inserts a string into the cursor position */
509         local txt = doc.txt;
510         local w;
512         mp.delete_mark(doc);
514         /* splice and change */
515         w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
516         txt.lines[txt.y] = w[0];
518         /* move right */
519         txt.x += size(str);
521         txt.mod++;
523         return doc;
527 sub mp.insert(doc, a)
528 /* inserts an array of text into a document */
530         local txt = doc.txt;
531         local s;
533         /* if a is not an array, split it */
534         if (!is_array(a))
535                 a = split(a, "\n");
537         /* empty array? return */
538         if ((s = size(a)) == 0)
539                 return doc;
541         /* paste first line into current position */
542         mp.insert_string(doc, a[0]);
544         /* more than just one line? */
545         if (s > 1) {
546                 /* break current line in two */
547                 mp.break_line(doc);
549                 /* insert last line */
550                 mp.insert_string(doc, a[s - 1]);
551         }
553         /* more than two lines? */
554         if (s > 2) {
555                 local n = 1;
557                 /* open room */
558                 expand(txt.lines, txt.y, s - 2);
560                 /* transfer middle lines */
561                 while (n < s - 1)
562                         txt.lines[txt.y++] = a[n++];
563         }
565         return doc;
569 sub mp.wrap_words(doc)
570 /* do the word wrapping */
572         local txt = doc.txt;
574         if (mp.config.word_wrap == 0)
575                 return doc;
577         /* take the column where the cursor is */
578         local c = mp.x2vx(txt.lines[txt.y], txt.x);
580         if (c >= mp.config.word_wrap &&
581                 regex(txt.lines[txt.y], "/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/")) {
582                 local w;
584                 /* take the coordinates */
585                 w = regex();
587                 /* break the line there */
588                 mp.break_line(doc, w[1]);
590                 /* delete the space at the end of the line */
591                 txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
592         }
594         return doc;
598 sub mp.insert_space(doc)
599 /* inserts a space, taking wordwrapping into account */
601         mp.wrap_words(doc);
602         mp.insert(doc, ' ');
606 sub mp.insert_tab(doc)
607 /* inserts a tab */
609         if (doc.txt.y && mp.config.dynamic_tabs) {
610                 local pl = doc.txt.lines[doc.txt.y - 1];
612                 if ((pl = regex(pl, "/[^ \t]*[ \t]+/", doc.txt.x)) != NULL) {
613                         pl = sregex(pl, "/[^\t]/g", ' ');
614                         mp.insert(doc, pl);
615                         return doc;
616                 }
617         }
619         if (mp.config.tabs_as_spaces) {
620                 /* number of spaces to insert */
621                 local n = mp.config.tab_size -
622                         ((doc.txt.x) % mp.config.tab_size);
624                 while(n--) mp.insert(doc, ' ');
625         }
626         else
627                 mp.insert(doc, "\t");
629         return doc;
633 sub mp.insert_newline(doc)
634 /* inserts a newline */
636         mp.wrap_words(doc);
637         mp.break_line(doc);
641 sub mp.insert_keystroke(doc, key)
642 /* inserts from a keystroke (with undo) */
644         if (size(key) == 1) {
645                 mp.store_undo(doc);
646                 mp.insert(doc, key);
647         }
648         else
649         if (key != NULL) {
650                 mp.message = {
651                         'timeout'       => time() + 2,
652                         'string'        => sprintf(L("Unbound keystroke '%s'"), key)
653                 };
654         }
656         return doc;
660 /** undo **/
662 sub mp.store_undo(doc)
663 /* stores the current txt in the undo queue */
665         queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
666         doc.redo = [];
668         return doc;
672 sub mp.undo(doc)
673 /* undoes last operation */
675         local txt;
677         if (txt = pop(doc.undo)) {
678                 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
679                 doc.txt = txt;
680         }
682         return doc;
686 sub mp.redo(doc)
687 /* redoes last undid operation */
689         local txt;
691         if (txt = pop(doc.redo)) {
692                 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
693                 doc.txt = txt;
694         }
696         return doc;
700 /** paragraphs **/
702 sub mp.join_paragraph(doc)
703 /* joins current paragraph in just one line */
705         local txt = doc.txt;
706         local l;
708         while ((l = txt.lines[txt.y + 1]) && size(l)) {
709                 /* delete all leading blanks in the next line */
710                 txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");
712                 /* move to end of line and add a space separator */
713                 mp.move_eol(doc);
714                 mp.insert(doc, ' ');
716                 /* really join */
717                 mp.join_line(doc);
718         }
720         return doc;
724 sub mp.word_wrap_paragraph(doc)
725 /* word wraps current paragraph */
727         local txt = doc.txt;
729         if (mp.config.word_wrap == 0)
730                 return doc;
732         mp.join_paragraph(doc);
734         mp.move_eol(doc);
736         while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
737                 mp.insert_space(doc);
738                 mp.move_left(doc);
739                 mp.delete_char(doc);
740         }
742         return doc;
745 /* indent/unindent support functions */
747 sub mp.unindent_line(d)
748 /* Unindent the current line by 1 tab or the indent size */
750         local l = split(d.txt.lines[d.txt.y]);
751         
752         mp.move(d, mp.move_bol);
753         
754         if (cmp(l[0], "\t") == 0) {
755                 mp.delete_char(d);
756         } else {
757                 local i = 0;
758                 while (i < mp.config.tab_size) {
759                         if (cmp(l[i], " ") == 0) {
760                                 mp.delete_char(d);
761                         } else {
762                                 break;
763                         }
764                         
765                         i++;
766                 }
767         }
769     return d;