If mark_eol is set, also mark tabs as small dots.
[mp-5.x.git] / mp_edit.mpsl
blob0214b0ee77eb3d39e979f0093b8e3cc7670564d8
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['undo']              = sub (d) { mp.undo(d); };
53 mp.actions['redo']              = sub (d) { mp.redo(d); };
55 mp.actions['join_paragraph'] = sub (d) {
56     mp.store_undo(d);
58     if (d.txt.mark) {
59         mp.busy(1);
60         mp.cut(d);
62         /* create a working document */
63         local p = mp.create('<wrk>', mp.clipboard);
65         /* while not at EOF, word wrap everything */
66         while (p.txt.y < size(p.txt.lines) - 1) {
67             mp.join_paragraph(p);
68             mp.move_down(p);
69             mp.move_down(p);
70         }
72         /* insert the content */
73         mp.insert(d, p.txt.lines);
74         mp.busy(0);
75     }
76     else
77         mp.join_paragraph(d);
80 mp.actions['word_wrap_paragraph'] = sub (d) {
82     if(mp.config.word_wrap == 0)
83         mp.alert(L("Word wrapping must be set"));
84     else {
85         mp.store_undo(d);
87         if (d.txt.mark) {
88             mp.busy(1);
89             mp.cut(d);
91             /* create a working document */
92             local p = mp.create('<wrk>', mp.clipboard);
94             /* while not at EOF, word wrap everything */
95             while (p.txt.y < size(p.txt.lines) - 1) {
96                 mp.word_wrap_paragraph(p);
97                 mp.move_down(p);
98                 mp.move_down(p);
99             }
101             /* insert the content */
102             mp.insert(d, p.txt.lines);
103             mp.busy(0);
104         }
105         else
106             mp.word_wrap_paragraph(d);
107     }
110 mp.actions['line_options']      = sub (d) {
112         /* convert special characters on end of line */
113         local lt = mp.backslash_codes(mp.config.eol, 1);
115         local t = mp.form( [
116                 { 'label'       => L("Word wrap on column (0, no word wrap):"),
117                   'type'        => 'text',
118                   'value'       => mp.config.word_wrap,
119                   'history'     => 'wordwrap' },
120                 { 'label'       => L("Automatic indentation") ~ ':',
121                   'type'        => 'checkbox',
122                   'value'       => mp.config.auto_indent },
123                 { 'label'       => L("Line termination") ~ ':',
124                   'value'       => lt,
125                   'type'        => 'text' },
126                 { 'label'       => L("Keep original end of lines") ~ ':',
127                   'value'       => mp.config.keep_eol,
128                   'type'        => 'checkbox' },
129                 { 'label'       => L("Mark end of lines") ~ ':',
130                   'value'       => mp.config.mark_eol,
131                   'type'        => 'checkbox' }
132         ] );
134         if (t != NULL) {
135                 mp.config.word_wrap = t[0];
136                 mp.config.auto_indent = t[1];
137                 mp.config.eol = mp.backslash_codes(t[2], 0);
138                 mp.config.keep_eol = t[3];
139                 mp.config.mark_eol = t[4];
140         }
143 mp.actions['tab_options']       = sub (d) {
145         local t = mp.form( [
146                 { 'label'       => L("Tab size") ~ ':',
147                   'type'        => 'text',
148                   'value'       => mp.config.tab_size,
149                   'history'     => 'tabsize' },
150                 { 'label'       => L("Convert tabs to spaces") ~ ':',
151                   'type'        => 'checkbox',
152                   'value'       => mp.config.tabs_as_spaces },
153                 { 'label'       => L("Use previous line for tab columns") ~ ':',
154                   'type'        => 'checkbox',
155                   'value'       => mp.config.dynamic_tabs }
156         ] );
158         if (t != NULL) {
159                 mp.config.tab_size       = t[0];
160                 mp.config.tabs_as_spaces = t[1];
161                 mp.config.dynamic_tabs   = t[2];
162         }
165 mp.actions['toggle_insert'] = sub (d) { mp.config.insert = !mp.config.insert; };
167 /** default key bindings **/
169 mp.keycodes['enter']     = "insert_line";
170 mp.keycodes['tab']       = "insert_tab";
171 mp.keycodes['shift-tab'] = "insert_real_tab";
172 mp.keycodes['space']     = "insert_space";
173 mp.keycodes['delete']    = "delete";
174 mp.keycodes['backspace'] = "delete_left";
175 mp.keycodes['ctrl-i']    = "insert_tab";
176 mp.keycodes['ctrl-m']    = "insert_line";
177 mp.keycodes['ctrl-y']    = "delete_line";
178 mp.keycodes['ctrl-z']    = "undo";
179 mp.keycodes['f4']        = "word_wrap_paragraph";
180 mp.keycodes['insert']    = "toggle_insert";
182 /** action descriptions **/
184 mp.actdesc['insert_line']           = LL("Insert line");
185 mp.actdesc['delete_line']           = LL("Delete line");
186 mp.actdesc['insert_space']          = LL("Insert space");
187 mp.actdesc['insert_tab']            = LL("Insert tab");
188 mp.actdesc['insert_real_tab']       = LL("Insert real tab character");
189 mp.actdesc['delete']                = LL("Delete character");
190 mp.actdesc['delete_left']           = LL("Delete character to the left");
191 mp.actdesc['undo']                  = LL("Undo");
192 mp.actdesc['redo']                  = LL("Redo");
193 mp.actdesc['join_paragraph']        = LL("Join a paragraph in one line");
194 mp.actdesc['word_wrap_paragraph']   = LL("Word-wrap a paragraph");
195 mp.actdesc['line_options']          = LL("Line options...");
196 mp.actdesc['tab_options']           = LL("Tab options...");
197 mp.actdesc['toggle_insert']         = LL("Toggle insert/overwrite mode");
199 /** code **/
202  * mp.break_line - Breaks current line in two (inserts a newline).
203  * @doc: the document
204  * @col: column where the newline will be inserted 
206  * Breaks current line in two by inserting a newline character in between.
207  * If @col is not NULL, the newline will be inserted in that column; otherwise,
208  * the current x position will be used.
209  */
210 sub mp.break_line(doc, col)
212         local txt = doc.txt;
213         local c, w;
215         /* if col is NULL, set it to be the x cursor */
216         if (col == NULL)
217                 col = txt.x;
219         /* gets line where cursor is */
220         c = txt.lines[txt.y];
222         /* deletes from col to the end of line */
223         w = splice(c, NULL, col, -1);
225         /* set first part as current line */
226         txt.lines[txt.y] = w[0];
228         /* move to next line */
229         txt.y++;
231         /* insert a new line here */
232         expand(txt.lines, txt.y, 1);
234         /* fix the x cursor position */
235         txt.x -= col;
237         /* if autoindenting... */
238         if (mp.config.auto_indent) {
239                 /* extract leading blanks in the original line
240                    to prepend them to the line to be inserted */
241                 local i = regex(c, "/^[ \t]*[-\+\*]?[ \t]+/", 0);
243                 /* substitute all non-tab characters with spaces */
244                 i = sregex(i, "/[^\t]/g", " ");
246                 /* delete any blank in the new line */
247                 w[1] = sregex(w[1], "/^[ \t]*/");
249                 /* concatenate */
250                 w[1] = i ~ w[1];
252                 /* the x position is further the length of that */
253                 txt.x += size(i);
254         }
256         /* put second part there (or an empty string if NULL) */
257         txt.lines[txt.y] = w[1] || '';
259         txt.mod++;
261         return doc;
265 sub mp.join_line(doc)
266 /* joins the current line with the next one */
268         local txt = doc.txt;
270         if (txt.y < size(txt.lines)) {
271                 /* concats current line with the next one */
272                 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
274                 /* delete it */
275                 adel(txt.lines, txt.y + 1);
277                 txt.mod++;
278         }
280         return doc;
284 sub mp.delete_line(doc)
285 /* deletes the current line */
287         local txt = doc.txt;
288         local vx;
290         /* take current position */
291         vx = mp.x2vx(txt.lines[txt.y], txt.x);
293         /* if it's the only line, just replace it */
294         if (size(txt.lines) == 1)
295                 txt.lines[0] = '';
296         else {
297                 /* destroy the line */
298                 adel(txt.lines, txt.y);
299         }
301         /* fix if it was the last line */
302         if (txt.y >= size(txt.lines))
303                 txt.y = size(txt.lines) - 1;
305         /* move to previous x position */
306         txt.x = mp.vx2x(txt.lines[txt.y], vx);
308         txt.mod++;
310         return doc;
314 sub mp.delete_char(doc)
315 /* deletes the current char */
317         local txt = doc.txt;
319         if (txt.mark != NULL) {
320                 mp.delete_mark(doc);
321                 return;
322         }
324         /* is it over the end of line? */
325         if (txt.x == size(txt.lines[txt.y]))
326                 mp.join_line(doc);
327         else {
328                 local w;
330                 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
331                 txt.lines[txt.y] = w[0];
332         }
334         txt.mod++;
336         return doc;
340 sub mp.delete_range(doc, bx, by, ex, ey, v)
341 /* deletes a range of characters from a document */
343         local txt = doc.txt;
345         /* move to the start of the range */
346         txt.x = bx;
347         txt.y = by;
349         if (by == ey) {
350                 local w;
352                 /* block is just one line; delete the middle part */
353                 w = splice(txt.lines[by], NULL, bx, ex - bx);
355                 txt.lines[by] = w[0];
356         }
357         else {
358                 /* block has more than one line */
359                 local w;
361                 if (v == 0) {
362                         /* delete using normal selection block */
364                         /* delete from the beginning to the end of the first line */
365                         w = splice(txt.lines[by], NULL, bx, -1);
366                         txt.lines[by] = w[0];
368                         /* delete from the beginning of the last line to
369                            the end of the block */
370                         w = splice(txt.lines[ey], NULL, 0, ex);
371                         txt.lines[ey] = w[0];
373                         /* collapse the lines in between */
374                         collapse(txt.lines, by + 1, ey - by - 1);
376                         /* finally join both lines */
377                         mp.join_line(doc);
378                 }
379                 else {
380                         /* delete using vertical selection block */
381                         while (by <= ey) {
382                                 w = splice(txt.lines[by], NULL, bx, ex - bx);
383                                 txt.lines[by] = w[0];
384                                 by++;
385                         }
386                 }
387         }
389         txt.mod++;
391         return doc;
395 sub mp.insert_string(doc, str)
396 /* inserts a string into the cursor position */
398         local txt = doc.txt;
399         local w;
401         mp.delete_mark(doc);
403         /* splice and change */
404         w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
405         txt.lines[txt.y] = w[0];
407         /* move right */
408         txt.x += size(str);
410         txt.mod++;
412         return doc;
416 sub mp.insert(doc, a)
417 /* inserts an array of text into a document */
419         local txt = doc.txt;
420         local s;
422         /* if a is not an array, split it */
423         if (!is_array(a))
424                 a = split(a, "\n");
426         /* empty array? return */
427         if ((s = size(a)) == 0)
428                 return doc;
430         /* paste first line into current position */
431         mp.insert_string(doc, a[0]);
433         /* more than just one line? */
434         if (s > 1) {
435                 /* break current line in two */
436                 mp.break_line(doc);
438                 /* insert last line */
439                 mp.insert_string(doc, a[s - 1]);
440         }
442         /* more than two lines? */
443         if (s > 2) {
444                 local n = 1;
446                 /* open room */
447                 expand(txt.lines, txt.y, s - 2);
449                 /* transfer middle lines */
450                 while (n < s - 1)
451                         txt.lines[txt.y++] = a[n++];
452         }
454         return doc;
458 sub mp.wrap_words(doc)
459 /* do the word wrapping */
461         local txt = doc.txt;
463         if (mp.config.word_wrap == 0)
464                 return doc;
466         /* take the column where the cursor is */
467         local c = mp.x2vx(txt.lines[txt.y], txt.x);
469         if (c >= mp.config.word_wrap &&
470                 regex(txt.lines[txt.y], "/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/")) {
471                 local w;
473                 /* take the coordinates */
474                 w = regex();
476                 /* break the line there */
477                 mp.break_line(doc, w[1]);
479                 /* delete the space at the end of the line */
480                 txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
481         }
483         return doc;
487 sub mp.insert_space(doc)
488 /* inserts a space, taking wordwrapping into account */
490         mp.wrap_words(doc);
491         mp.insert(doc, ' ');
495 sub mp.insert_tab(doc)
496 /* inserts a tab */
498         if (doc.txt.y && mp.config.dynamic_tabs) {
499                 local pl = doc.txt.lines[doc.txt.y - 1];
501                 if ((pl = regex(pl, "/[^ \t]*[ \t]+/", doc.txt.x)) != NULL) {
502                         pl = sregex(pl, "/[^\t]/g", ' ');
503                         mp.insert(doc, pl);
504                         return doc;
505                 }
506         }
508         if (mp.config.tabs_as_spaces) {
509                 /* number of spaces to insert */
510                 local n = mp.config.tab_size -
511                         ((doc.txt.x) % mp.config.tab_size);
513                 while(n--) mp.insert(doc, ' ');
514         }
515         else
516                 mp.insert(doc, "\t");
518         return doc;
522 sub mp.insert_newline(doc)
523 /* inserts a newline */
525         mp.wrap_words(doc);
526         mp.break_line(doc);
530 sub mp.insert_keystroke(doc, key)
531 /* inserts from a keystroke (with undo) */
533         if (size(key) == 1) {
534                 mp.store_undo(doc);
535                 mp.insert(doc, key);
536         }
537         else
538         if (key != NULL) {
539                 mp.message = {
540                         'timeout'       => time() + 2,
541                         'string'        => sprintf(L("Unbound keystroke '%s'"), key)
542                 };
543         }
545         return doc;
549 /** undo **/
551 sub mp.store_undo(doc)
552 /* stores the current txt in the undo queue */
554         queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
555         doc.redo = [];
557         return doc;
561 sub mp.undo(doc)
562 /* undoes last operation */
564         local txt;
566         if (txt = pop(doc.undo)) {
567                 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
568                 doc.txt = txt;
569         }
571         return doc;
575 sub mp.redo(doc)
576 /* redoes last undid operation */
578         local txt;
580         if (txt = pop(doc.redo)) {
581                 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
582                 doc.txt = txt;
583         }
585         return doc;
589 /** paragraphs **/
591 sub mp.join_paragraph(doc)
592 /* joins current paragraph in just one line */
594         local txt = doc.txt;
595         local l;
597         while ((l = txt.lines[txt.y + 1]) && size(l)) {
598                 /* delete all leading blanks in the next line */
599                 txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");
601                 /* move to end of line and add a space separator */
602                 mp.move_eol(doc);
603                 mp.insert(doc, ' ');
605                 /* really join */
606                 mp.join_line(doc);
607         }
609         return doc;
613 sub mp.word_wrap_paragraph(doc)
614 /* word wraps current paragraph */
616         local txt = doc.txt;
618         if (mp.config.word_wrap == 0)
619                 return doc;
621         mp.join_paragraph(doc);
623         mp.move_eol(doc);
625         while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
626                 mp.insert_space(doc);
627                 mp.move_left(doc);
628                 mp.delete_char(doc);
629         }
631         return doc;