The hex viewer is no longer multithreaded (Closes: #1281).
[mp-5.x.git] / mp_edit.mpsl
blobd770f91e25afb8788717e5aef8b4a57e6545924c
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                 { 'label'       => L("Word wrap on column (0, no word wrap):"),
206                   'type'        => 'text',
207                   'value'       => mp.config.word_wrap,
208                   'history'     => 'wordwrap' },
209                 { 'label'       => L("Automatic indentation") ~ ':',
210                   'type'        => 'checkbox',
211                   'value'       => mp.config.auto_indent },
212                 { 'label'       => L("Line termination") ~ ':',
213                   'value'       => lt,
214                   'type'        => 'text' },
215                 { 'label'       => L("Keep original end of lines") ~ ':',
216                   'value'       => mp.config.keep_eol,
217                   'type'        => 'checkbox' },
218                 { 'label'       => L("Mark end of lines") ~ ':',
219                   'value'       => mp.config.mark_eol,
220                   'type'        => 'checkbox' }
221         ] );
223         if (t != NULL) {
224                 mp.config.word_wrap = t[0];
225                 mp.config.auto_indent = t[1];
226                 mp.config.eol = mp.backslash_codes(t[2], 0);
227                 mp.config.keep_eol = t[3];
228                 mp.config.mark_eol = t[4];
229         }
232 mp.actions['tab_options']       = sub (d) {
234         local t = mp.form( [
235                 { 'label'       => L("Tab size") ~ ':',
236                   'type'        => 'text',
237                   'value'       => mp.config.tab_size,
238                   'history'     => 'tabsize' },
239                 { 'label'       => L("Convert tabs to spaces") ~ ':',
240                   'type'        => 'checkbox',
241                   'value'       => mp.config.tabs_as_spaces },
242                 { 'label'       => L("Use previous line for tab columns") ~ ':',
243                   'type'        => 'checkbox',
244                   'value'       => mp.config.dynamic_tabs }
245         ] );
247         if (t != NULL) {
248                 mp.config.tab_size       = t[0];
249                 mp.config.tabs_as_spaces = t[1];
250                 mp.config.dynamic_tabs   = t[2];
251         }
254 mp.actions['toggle_insert'] = sub (d) { mp.config.insert = !mp.config.insert; };
256 /** default key bindings **/
258 mp.keycodes['enter']            = "insert_line";
259 mp.keycodes['tab']              = "insert_tab";
260 mp.keycodes['shift-tab']        = "insert_real_tab";
261 mp.keycodes['space']            = "insert_space";
262 mp.keycodes['delete']           = "delete";
263 mp.keycodes['backspace']        = "delete_left";
264 mp.keycodes['ctrl-i']           = "insert_tab";
265 mp.keycodes['ctrl-m']           = "insert_line";
266 mp.keycodes['ctrl-y']           = "delete_line";
267 mp.keycodes['alt-cursor-right'] = "indent_block";
268 mp.keycodes['alt-cursor-left']  = "unindent_block";
269 mp.keycodes['ctrl-z']           = "undo";
270 mp.keycodes['f4']               = "word_wrap_paragraph";
271 mp.keycodes['insert']           = "toggle_insert";
273 /** action descriptions **/
275 mp.actdesc['insert_line']           = LL("Insert line");
276 mp.actdesc['delete_line']           = LL("Delete line");
277 mp.actdesc['insert_space']          = LL("Insert space");
278 mp.actdesc['insert_tab']            = LL("Insert tab");
279 mp.actdesc['insert_real_tab']       = LL("Insert real tab character");
280 mp.actdesc['delete']                = LL("Delete character");
281 mp.actdesc['delete_left']           = LL("Delete character to the left");
282 mp.actdesc['indent_block']          = LL("Indent block");
283 mp.actdesc['unindent_block']        = LL("Unindent block");
284 mp.actdesc['undo']                  = LL("Undo");
285 mp.actdesc['redo']                  = LL("Redo");
286 mp.actdesc['join_paragraph']        = LL("Join a paragraph in one line");
287 mp.actdesc['word_wrap_paragraph']   = LL("Word-wrap a paragraph");
288 mp.actdesc['line_options']          = LL("Line options...");
289 mp.actdesc['tab_options']           = LL("Tab options...");
290 mp.actdesc['toggle_insert']         = LL("Toggle insert/overwrite mode");
292 /** code **/
295  * mp.break_line - Breaks current line in two (inserts a newline).
296  * @doc: the document
297  * @col: column where the newline will be inserted 
299  * Breaks current line in two by inserting a newline character in between.
300  * If @col is not NULL, the newline will be inserted in that column; otherwise,
301  * the current x position will be used.
302  */
303 sub mp.break_line(doc, col)
305         local txt = doc.txt;
306         local c, w;
308         /* if col is NULL, set it to be the x cursor */
309         if (col == NULL)
310                 col = txt.x;
312         /* gets line where cursor is */
313         c = txt.lines[txt.y];
315         /* deletes from col to the end of line */
316         w = splice(c, NULL, col, -1);
318         /* set first part as current line */
319         txt.lines[txt.y] = w[0];
321         /* move to next line */
322         txt.y++;
324         /* insert a new line here */
325         expand(txt.lines, txt.y, 1);
327         /* fix the x cursor position */
328         txt.x -= col;
330         /* if autoindenting... */
331         if (mp.config.auto_indent) {
332                 /* extract leading blanks in the original line
333                    to prepend them to the line to be inserted */
334                 local i = regex(c, "/^[ \t]*[-\+\*]?[ \t]+/", 0);
336                 /* substitute all non-tab characters with spaces */
337                 i = sregex(i, "/[^\t]/g", " ");
339                 /* delete any blank in the new line */
340                 w[1] = sregex(w[1], "/^[ \t]*/");
342                 /* concatenate */
343                 w[1] = i ~ w[1];
345                 /* the x position is further the length of that */
346                 txt.x += size(i);
347         }
349         /* put second part there (or an empty string if NULL) */
350         txt.lines[txt.y] = w[1] || '';
352         txt.mod++;
354         return doc;
358 sub mp.join_line(doc)
359 /* joins the current line with the next one */
361         local txt = doc.txt;
363         if (txt.y < size(txt.lines)) {
364                 /* concats current line with the next one */
365                 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
367                 /* delete it */
368                 adel(txt.lines, txt.y + 1);
370                 txt.mod++;
371         }
373         return doc;
377 sub mp.delete_line(doc)
378 /* deletes the current line */
380         local txt = doc.txt;
381         local vx;
383         /* take current position */
384         vx = mp.x2vx(txt.lines[txt.y], txt.x);
386         /* if it's the only line, just replace it */
387         if (size(txt.lines) == 1)
388                 txt.lines[0] = '';
389         else {
390                 /* destroy the line */
391                 adel(txt.lines, txt.y);
392         }
394         /* fix if it was the last line */
395         if (txt.y >= size(txt.lines))
396                 txt.y = size(txt.lines) - 1;
398         /* move to previous x position */
399         txt.x = mp.vx2x(txt.lines[txt.y], vx);
401         txt.mod++;
403         return doc;
407 sub mp.delete_char(doc)
408 /* deletes the current char */
410         local txt = doc.txt;
412         if (txt.mark != NULL) {
413                 mp.delete_mark(doc);
414                 return;
415         }
417         /* is it over the end of line? */
418         if (txt.x == size(txt.lines[txt.y]))
419                 mp.join_line(doc);
420         else {
421                 local w;
423                 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
424                 txt.lines[txt.y] = w[0];
425         }
427         txt.mod++;
429         return doc;
433 sub mp.delete_range(doc, bx, by, ex, ey, v)
434 /* deletes a range of characters from a document */
436         local txt = doc.txt;
438         /* move to the start of the range */
439         txt.x = bx;
440         txt.y = by;
442         if (by == ey) {
443                 local w;
445                 /* block is just one line; delete the middle part */
446                 w = splice(txt.lines[by], NULL, bx, ex - bx);
448                 txt.lines[by] = w[0];
449         }
450         else {
451                 /* block has more than one line */
452                 local w;
454                 if (v == 0) {
455                         /* delete using normal selection block */
457                         /* delete from the beginning to the end of the first line */
458                         w = splice(txt.lines[by], NULL, bx, -1);
459                         txt.lines[by] = w[0];
461                         /* delete from the beginning of the last line to
462                            the end of the block */
463                         w = splice(txt.lines[ey], NULL, 0, ex);
464                         txt.lines[ey] = w[0];
466                         /* collapse the lines in between */
467                         collapse(txt.lines, by + 1, ey - by - 1);
469                         /* finally join both lines */
470                         mp.join_line(doc);
471                 }
472                 else {
473                         /* delete using vertical selection block */
474                         while (by <= ey) {
475                                 w = splice(txt.lines[by], NULL, bx, ex - bx);
476                                 txt.lines[by] = w[0];
477                                 by++;
478                         }
479                 }
480         }
482         txt.mod++;
484         return doc;
488 sub mp.insert_string(doc, str)
489 /* inserts a string into the cursor position */
491         local txt = doc.txt;
492         local w;
494         mp.delete_mark(doc);
496         /* splice and change */
497         w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
498         txt.lines[txt.y] = w[0];
500         /* move right */
501         txt.x += size(str);
503         txt.mod++;
505         return doc;
509 sub mp.insert(doc, a)
510 /* inserts an array of text into a document */
512         local txt = doc.txt;
513         local s;
515         /* if a is not an array, split it */
516         if (!is_array(a))
517                 a = split(a, "\n");
519         /* empty array? return */
520         if ((s = size(a)) == 0)
521                 return doc;
523         /* paste first line into current position */
524         mp.insert_string(doc, a[0]);
526         /* more than just one line? */
527         if (s > 1) {
528                 /* break current line in two */
529                 mp.break_line(doc);
531                 /* insert last line */
532                 mp.insert_string(doc, a[s - 1]);
533         }
535         /* more than two lines? */
536         if (s > 2) {
537                 local n = 1;
539                 /* open room */
540                 expand(txt.lines, txt.y, s - 2);
542                 /* transfer middle lines */
543                 while (n < s - 1)
544                         txt.lines[txt.y++] = a[n++];
545         }
547         return doc;
551 sub mp.wrap_words(doc)
552 /* do the word wrapping */
554         local txt = doc.txt;
556         if (mp.config.word_wrap == 0)
557                 return doc;
559         /* take the column where the cursor is */
560         local c = mp.x2vx(txt.lines[txt.y], txt.x);
562         if (c >= mp.config.word_wrap &&
563                 regex(txt.lines[txt.y], "/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/")) {
564                 local w;
566                 /* take the coordinates */
567                 w = regex();
569                 /* break the line there */
570                 mp.break_line(doc, w[1]);
572                 /* delete the space at the end of the line */
573                 txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
574         }
576         return doc;
580 sub mp.insert_space(doc)
581 /* inserts a space, taking wordwrapping into account */
583         mp.wrap_words(doc);
584         mp.insert(doc, ' ');
588 sub mp.insert_tab(doc)
589 /* inserts a tab */
591         if (doc.txt.y && mp.config.dynamic_tabs) {
592                 local pl = doc.txt.lines[doc.txt.y - 1];
594                 if ((pl = regex(pl, "/[^ \t]*[ \t]+/", doc.txt.x)) != NULL) {
595                         pl = sregex(pl, "/[^\t]/g", ' ');
596                         mp.insert(doc, pl);
597                         return doc;
598                 }
599         }
601         if (mp.config.tabs_as_spaces) {
602                 /* number of spaces to insert */
603                 local n = mp.config.tab_size -
604                         ((doc.txt.x) % mp.config.tab_size);
606                 while(n--) mp.insert(doc, ' ');
607         }
608         else
609                 mp.insert(doc, "\t");
611         return doc;
615 sub mp.insert_newline(doc)
616 /* inserts a newline */
618         mp.wrap_words(doc);
619         mp.break_line(doc);
623 sub mp.insert_keystroke(doc, key)
624 /* inserts from a keystroke (with undo) */
626         if (size(key) == 1) {
627                 mp.store_undo(doc);
628                 mp.insert(doc, key);
629         }
630         else
631         if (key != NULL) {
632                 mp.message = {
633                         'timeout'       => time() + 2,
634                         'string'        => sprintf(L("Unbound keystroke '%s'"), key)
635                 };
636         }
638         return doc;
642 /** undo **/
644 sub mp.store_undo(doc)
645 /* stores the current txt in the undo queue */
647         queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
648         doc.redo = [];
650         return doc;
654 sub mp.undo(doc)
655 /* undoes last operation */
657         local txt;
659         if (txt = pop(doc.undo)) {
660                 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
661                 doc.txt = txt;
662         }
664         return doc;
668 sub mp.redo(doc)
669 /* redoes last undid operation */
671         local txt;
673         if (txt = pop(doc.redo)) {
674                 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
675                 doc.txt = txt;
676         }
678         return doc;
682 /** paragraphs **/
684 sub mp.join_paragraph(doc)
685 /* joins current paragraph in just one line */
687         local txt = doc.txt;
688         local l;
690         while ((l = txt.lines[txt.y + 1]) && size(l)) {
691                 /* delete all leading blanks in the next line */
692                 txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");
694                 /* move to end of line and add a space separator */
695                 mp.move_eol(doc);
696                 mp.insert(doc, ' ');
698                 /* really join */
699                 mp.join_line(doc);
700         }
702         return doc;
706 sub mp.word_wrap_paragraph(doc)
707 /* word wraps current paragraph */
709         local txt = doc.txt;
711         if (mp.config.word_wrap == 0)
712                 return doc;
714         mp.join_paragraph(doc);
716         mp.move_eol(doc);
718         while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
719                 mp.insert_space(doc);
720                 mp.move_left(doc);
721                 mp.delete_char(doc);
722         }
724         return doc;
727 /* indent/unindent support functions */
729 sub mp.unindent_line(d)
730 /* Unindent the current line by 1 tab or the indent size */
732         local l = split(d.txt.lines[d.txt.y]);
733         
734         mp.move(d, mp.move_bol);
735         
736         if (cmp(l[0], "\t") == 0) {
737                 mp.delete_char(d);
738         } else {
739                 local i = 0;
740                 while (i < mp.config.tab_size) {
741                         if (cmp(l[i], " ") == 0) {
742                                 mp.delete_char(d);
743                         } else {
744                                 break;
745                         }
746                         
747                         i++;
748                 }
749         }
751     return d;