Changed sregex().
[mp-5.x.git] / mp_edit.mpsl
blob71ce8551fe79cdb29dd5245b828e979ad2bd7ba3
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['delete']            = sub (d) { mp.store_undo(d); mp.delete_char(d); };
43 mp.actions['delete_left']       = sub (d) {
44         if (d.txt.x + d.txt.y) {
45                 mp.store_undo(d);
46                 mp.move_left(d);
47                 mp.delete_char(d);
48         }
51 mp.actions['undo']              = sub (d) { mp.undo(d); };
52 mp.actions['redo']              = sub (d) { mp.redo(d); };
54 mp.actions['join_paragraph'] = sub (d) {
55     mp.store_undo(d);
57     if (d.txt.mark) {
58         mp.busy(1);
59         mp.cut(d);
61         /* create a working document */
62         local p = mp.create('<wrk>', mp.clipboard);
64         /* while not at EOF, word wrap everything */
65         while (p.txt.y < size(p.txt.lines) - 1) {
66             mp.join_paragraph(p);
67             mp.move_down(p);
68             mp.move_down(p);
69         }
71         /* insert the content */
72         mp.insert(d, p.txt.lines);
73         mp.busy(0);
74     }
75     else
76         mp.join_paragraph(d);
79 mp.actions['word_wrap_paragraph'] = sub (d) {
81     if(mp.config.word_wrap == 0)
82         mp.alert(L("Word wrapping must be set"));
83     else {
84         mp.store_undo(d);
86         if (d.txt.mark) {
87             mp.busy(1);
88             mp.cut(d);
90             /* create a working document */
91             local p = mp.create('<wrk>', mp.clipboard);
93             /* while not at EOF, word wrap everything */
94             while (p.txt.y < size(p.txt.lines) - 1) {
95                 mp.word_wrap_paragraph(p);
96                 mp.move_down(p);
97                 mp.move_down(p);
98             }
100             /* insert the content */
101             mp.insert(d, p.txt.lines);
102             mp.busy(0);
103         }
104         else
105             mp.word_wrap_paragraph(d);
106     }
109 mp.actions['line_options']      = sub (d) {
111         /* convert special characters on end of line */
112         local lt = mp.backslash_codes(mp.config.eol, 1);
114         local t = mp.form( [
115                 { 'label'       => L("Word wrap on column (0, no word wrap):"),
116                   'type'        => 'text',
117                   'value'       => mp.config.word_wrap,
118                   'history'     => 'wordwrap' },
119                 { 'label'       => L("Automatic indentation") ~ ':',
120                   'type'        => 'checkbox',
121                   'value'       => mp.config.auto_indent },
122                 { 'label'       => L("Line termination") ~ ':',
123                   'value'       => lt,
124                   'type'        => 'text' },
125                 { 'label'       => L("Keep original end of lines") ~ ':',
126                   'value'       => mp.config.keep_eol,
127                   'type'        => 'checkbox' },
128                 { 'label'       => L("Mark end of lines") ~ ':',
129                   'value'       => mp.config.mark_eol,
130                   'type'        => 'checkbox' }
131         ] );
133         if (t != NULL) {
134                 mp.config.word_wrap = t[0];
135                 mp.config.auto_indent = t[1];
136                 mp.config.eol = mp.backslash_codes(t[2], 0);
137                 mp.config.keep_eol = t[3];
138                 mp.config.mark_eol = t[4];
139         }
142 mp.actions['tab_options']       = sub (d) {
144         local t = mp.form( [
145                 { 'label'       => L("Tab size") ~ ':',
146                   'type'        => 'text',
147                   'value'       => mp.config.tab_size,
148                   'history'     => 'tabsize' },
149                 { 'label'       => L("Convert tabs to spaces") ~ ':',
150                   'type'        => 'checkbox',
151                   'value'       => mp.config.tabs_as_spaces },
152                 { 'label'       => L("Use previous line for tab columns") ~ ':',
153                   'type'        => 'checkbox',
154                   'value'       => mp.config.dynamic_tabs }
155         ] );
157         if (t != NULL) {
158                 mp.config.tab_size       = t[0];
159                 mp.config.tabs_as_spaces = t[1];
160                 mp.config.dynamic_tabs   = t[2];
161         }
164 mp.actions['toggle_insert'] = sub (d) { mp.config.insert = !mp.config.insert; };
166 /** default key bindings **/
168 mp.keycodes['enter']    = "insert_line";
169 mp.keycodes['tab']      = "insert_tab";
170 mp.keycodes['space']    = "insert_space";
171 mp.keycodes['delete']   = "delete";
172 mp.keycodes['backspace']= "delete_left";
173 mp.keycodes['ctrl-i']   = "insert_tab";
174 mp.keycodes['ctrl-m']   = "insert_line";
175 mp.keycodes['ctrl-y']   = "delete_line";
176 mp.keycodes['ctrl-z']   = "undo";
177 mp.keycodes['f4']       = "word_wrap_paragraph";
178 mp.keycodes['insert']   = "toggle_insert";
180 /** action descriptions **/
182 mp.actdesc['insert_line']       = LL("Insert line");
183 mp.actdesc['delete_line']       = LL("Delete line");
184 mp.actdesc['insert_space']      = LL("Insert space");
185 mp.actdesc['insert_tab']        = LL("Insert tab");
186 mp.actdesc['delete']            = LL("Delete character");
187 mp.actdesc['delete_left']       = LL("Delete character to the left");
188 mp.actdesc['undo']              = LL("Undo");
189 mp.actdesc['redo']              = LL("Redo");
190 mp.actdesc['join_paragraph']    = LL("Join a paragraph in one line");
191 mp.actdesc['word_wrap_paragraph'] = LL("Word-wrap a paragraph");
192 mp.actdesc['line_options']      = LL("Line options...");
193 mp.actdesc['tab_options']       = LL("Tab options...");
194 mp.actdesc['toggle_insert'] = LL("Toggle insert/overwrite mode");
196 /** code **/
199  * mp.break_line - Breaks current line in two (inserts a newline).
200  * @doc: the document
201  * @col: column where the newline will be inserted 
203  * Breaks current line in two by inserting a newline character in between.
204  * If @col is not NULL, the newline will be inserted in that column; otherwise,
205  * the current x position will be used.
206  */
207 sub mp.break_line(doc, col)
209         local txt = doc.txt;
210         local c, w;
212         /* if col is NULL, set it to be the x cursor */
213         if (col == NULL)
214                 col = txt.x;
216         /* gets line where cursor is */
217         c = txt.lines[txt.y];
219         /* deletes from col to the end of line */
220         w = splice(c, NULL, col, -1);
222         /* set first part as current line */
223         txt.lines[txt.y] = w[0];
225         /* move to next line */
226         txt.y++;
228         /* insert a new line here */
229         expand(txt.lines, txt.y, 1);
231         /* fix the x cursor position */
232         txt.x -= col;
234         /* if autoindenting... */
235         if (mp.config.auto_indent) {
236                 /* extract leading blanks in the original line
237                    to prepend them to the line to be inserted */
238                 local i = regex("/^[ \t]*[-\+\*]?[ \t]+/", c, 0);
240                 /* substitute all non-tab characters with spaces */
241                 i = sregex(i, "/[^\t]/g", " ");
243                 /* delete any blank in the new line */
244                 w[1] = sregex(w[1], "/^[ \t]*/");
246                 /* concatenate */
247                 w[1] = i ~ w[1];
249                 /* the x position is further the length of that */
250                 txt.x += size(i);
251         }
253         /* put second part there (or an empty string if NULL) */
254         txt.lines[txt.y] = w[1] || '';
256         txt.mod++;
258         return doc;
262 sub mp.join_line(doc)
263 /* joins the current line with the next one */
265         local txt = doc.txt;
267         if (txt.y < size(txt.lines)) {
268                 /* concats current line with the next one */
269                 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
271                 /* delete it */
272                 adel(txt.lines, txt.y + 1);
274                 txt.mod++;
275         }
277         return doc;
281 sub mp.delete_line(doc)
282 /* deletes the current line */
284         local txt = doc.txt;
285         local vx;
287         /* take current position */
288         vx = mp.x2vx(txt.lines[txt.y], txt.x);
290         /* if it's the only line, just replace it */
291         if (size(txt.lines) == 1)
292                 txt.lines[0] = '';
293         else {
294                 /* destroy the line */
295                 adel(txt.lines, txt.y);
296         }
298         /* fix if it was the last line */
299         if (txt.y >= size(txt.lines))
300                 txt.y = size(txt.lines) - 1;
302         /* move to previous x position */
303         txt.x = mp.vx2x(txt.lines[txt.y], vx);
305         txt.mod++;
307         return doc;
311 sub mp.delete_char(doc)
312 /* deletes the current char */
314         local txt = doc.txt;
316         if (txt.mark != NULL) {
317                 mp.delete_mark(doc);
318                 return;
319         }
321         /* is it over the end of line? */
322         if (txt.x == size(txt.lines[txt.y]))
323                 mp.join_line(doc);
324         else {
325                 local w;
327                 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
328                 txt.lines[txt.y] = w[0];
329         }
331         txt.mod++;
333         return doc;
337 sub mp.delete_range(doc, bx, by, ex, ey, v)
338 /* deletes a range of characters from a document */
340         local txt = doc.txt;
342         /* move to the start of the range */
343         txt.x = bx;
344         txt.y = by;
346         if (by == ey) {
347                 local w;
349                 /* block is just one line; delete the middle part */
350                 w = splice(txt.lines[by], NULL, bx, ex - bx);
352                 txt.lines[by] = w[0];
353         }
354         else {
355                 /* block has more than one line */
356                 local w;
358                 if (v == 0) {
359                         /* delete using normal selection block */
361                         /* delete from the beginning to the end of the first line */
362                         w = splice(txt.lines[by], NULL, bx, -1);
363                         txt.lines[by] = w[0];
365                         /* delete from the beginning of the last line to
366                            the end of the block */
367                         w = splice(txt.lines[ey], NULL, 0, ex);
368                         txt.lines[ey] = w[0];
370                         /* collapse the lines in between */
371                         collapse(txt.lines, by + 1, ey - by - 1);
373                         /* finally join both lines */
374                         mp.join_line(doc);
375                 }
376                 else {
377                         /* delete using vertical selection block */
378                         while (by <= ey) {
379                                 w = splice(txt.lines[by], NULL, bx, ex - bx);
380                                 txt.lines[by] = w[0];
381                                 by++;
382                         }
383                 }
384         }
386         txt.mod++;
388         return doc;
392 sub mp.insert_string(doc, str)
393 /* inserts a string into the cursor position */
395         local txt = doc.txt;
396         local w;
398         mp.delete_mark(doc);
400         /* splice and change */
401         w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
402         txt.lines[txt.y] = w[0];
404         /* move right */
405         txt.x += size(str);
407         txt.mod++;
409         return doc;
413 sub mp.insert(doc, a)
414 /* inserts an array of text into a document */
416         local txt = doc.txt;
417         local s;
419         /* if a is not an array, split it */
420         if (!is_array(a))
421                 a = split(a, "\n");
423         /* empty array? return */
424         if ((s = size(a)) == 0)
425                 return doc;
427         /* paste first line into current position */
428         mp.insert_string(doc, a[0]);
430         /* more than just one line? */
431         if (s > 1) {
432                 /* break current line in two */
433                 mp.break_line(doc);
435                 /* insert last line */
436                 mp.insert_string(doc, a[s - 1]);
437         }
439         /* more than two lines? */
440         if (s > 2) {
441                 local n = 1;
443                 /* open room */
444                 expand(txt.lines, txt.y, s - 2);
446                 /* transfer middle lines */
447                 while (n < s - 1)
448                         txt.lines[txt.y++] = a[n++];
449         }
451         return doc;
455 sub mp.wrap_words(doc)
456 /* do the word wrapping */
458         local txt = doc.txt;
460         if (mp.config.word_wrap == 0)
461                 return doc;
463         /* take the column where the cursor is */
464         local c = mp.x2vx(txt.lines[txt.y], txt.x);
466         if (c >= mp.config.word_wrap &&
467                 regex("/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/", txt.lines[txt.y])) {
468                 local w;
470                 /* take the coordinates */
471                 w = regex();
473                 /* break the line there */
474                 mp.break_line(doc, w[1]);
476                 /* delete the space at the end of the line */
477                 txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
478         }
480         return doc;
484 sub mp.insert_space(doc)
485 /* inserts a space, taking wordwrapping into account */
487         mp.wrap_words(doc);
488         mp.insert(doc, ' ');
492 sub mp.insert_tab(doc)
493 /* inserts a tab */
495         if (doc.txt.y && mp.config.dynamic_tabs) {
496                 local pl = doc.txt.lines[doc.txt.y - 1];
498                 if ((pl = regex("/[^ \t]*[ \t]+/", pl, doc.txt.x)) != NULL) {
499                         pl = sregex(pl, "/[^\t]/g", ' ');
500                         mp.insert(doc, pl);
501                         return doc;
502                 }
503         }
505         if (mp.config.tabs_as_spaces) {
506                 /* number of spaces to insert */
507                 local n = mp.config.tab_size -
508                         ((doc.txt.x) % mp.config.tab_size);
510                 while(n--) mp.insert(doc, ' ');
511         }
512         else
513                 mp.insert(doc, "\t");
515         return doc;
519 sub mp.insert_newline(doc)
520 /* inserts a newline */
522         mp.wrap_words(doc);
523         mp.break_line(doc);
527 sub mp.insert_keystroke(doc, key)
528 /* inserts from a keystroke (with undo) */
530         if (size(key) == 1) {
531                 mp.store_undo(doc);
532                 mp.insert(doc, key);
533         }
534         else
535         if (key != NULL) {
536                 mp.message = {
537                         'timeout'       => time() + 2,
538                         'string'        => sprintf(L("Unbound keystroke '%s'"), key)
539                 };
540         }
542         return doc;
546 /** undo **/
548 sub mp.store_undo(doc)
549 /* stores the current txt in the undo queue */
551         queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
552         doc.redo = [];
554         return doc;
558 sub mp.undo(doc)
559 /* undoes last operation */
561         local txt;
563         if (txt = pop(doc.undo)) {
564                 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
565                 doc.txt = txt;
566         }
568         return doc;
572 sub mp.redo(doc)
573 /* redoes last undid operation */
575         local txt;
577         if (txt = pop(doc.redo)) {
578                 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
579                 doc.txt = txt;
580         }
582         return doc;
586 /** paragraphs **/
588 sub mp.join_paragraph(doc)
589 /* joins current paragraph in just one line */
591         local txt = doc.txt;
592         local l;
594         while ((l = txt.lines[txt.y + 1]) && size(l)) {
595                 /* delete all leading blanks in the next line */
596                 txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");
598                 /* move to end of line and add a space separator */
599                 mp.move_eol(doc);
600                 mp.insert(doc, ' ');
602                 /* really join */
603                 mp.join_line(doc);
604         }
606         return doc;
610 sub mp.word_wrap_paragraph(doc)
611 /* word wraps current paragraph */
613         local txt = doc.txt;
615         if (mp.config.word_wrap == 0)
616                 return doc;
618         mp.join_paragraph(doc);
620         mp.move_eol(doc);
622         while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
623                 mp.insert_space(doc);
624                 mp.move_left(doc);
625                 mp.delete_char(doc);
626         }
628         return doc;