All functions with a doc as first argument returns it in mp_file.mpsl (except for...
[mp-5.x.git] / mp_edit.mpsl
blob5a57c63612ae4437e6f0e9f451824865b516e523
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Editing.
8     Copyright (C) 1991-2009 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); };
42 mp.actions['delete_left']       = sub (d) { mp.store_undo(d); mp.move_left(d) &&
43                                         mp.delete_char(d); };
44 mp.actions['undo']              = sub (d) { mp.undo(d); };
45 mp.actions['redo']              = sub (d) { mp.redo(d); };
46 mp.actions['join_paragraph']    = sub (d) { mp.store_undo(d); mp.join_paragraph(d); };
48 mp.actions['word_wrap_paragraph'] = sub (d) {
50         if(mp.config.word_wrap == 0)
51                 mp.alert(L("Word wrapping must be set"));
52         else {
53                 mp.store_undo(d);
54                 mp.word_wrap_paragraph(d);
55         }
58 mp.actions['line_options']      = sub (d) {
60         /* convert special characters on end of line */
61         local lt = mp.backslash_codes(mp.config.eol, 1);
63         local t = mp.form( [
64                 { 'label'       => L("Word wrap on column (0, no word wrap):"),
65                   'type'        => 'text',
66                   'value'       => mp.config.word_wrap,
67                   'history'     => 'wordwrap' },
68                 { 'label'       => L("Automatic indentation") ~ ':',
69                   'type'        => 'checkbox',
70                   'value'       => mp.config.auto_indent },
71                 { 'label'       => L("Line termination") ~ ':',
72                   'value'       => lt,
73                   'type'        => 'text' },
74                 { 'label'       => L("Keep original end of lines") ~ ':',
75                   'value'       => mp.config.keep_eol,
76                   'type'        => 'checkbox' },
77                 { 'label'       => L("Mark end of lines") ~ ':',
78                   'value'       => mp.config.mark_eol,
79                   'type'        => 'checkbox' }
80         ] );
82         if (t != NULL) {
83                 mp.config.word_wrap = t[0];
84                 mp.config.auto_indent = t[1];
85                 mp.config.eol = mp.backslash_codes(t[2], 0);
86                 mp.config.keep_eol = t[3];
87                 mp.config.mark_eol = t[4];
88         }
91 mp.actions['tab_options']       = sub (d) {
93         local t = mp.form( [
94                 { 'label'       => L("Tab size") ~ ':',
95                   'type'        => 'text',
96                   'value'       => mp.config.tab_size,
97                   'history'     => 'tabsize' },
98                 { 'label'       => L("Convert tabs to spaces") ~ ':',
99                   'type'        => 'checkbox',
100                   'value'       => mp.config.tabs_as_spaces }
101         ] );
103         if (t != NULL) {
104                 mp.config.tab_size = t[0];
105                 mp.config.tabs_as_spaces = t[1];
106         }
109 /** default key bindings **/
111 mp.keycodes['enter']    = "insert_line";
112 mp.keycodes['tab']      = "insert_tab";
113 mp.keycodes['space']    = "insert_space";
114 mp.keycodes['delete']   = "delete";
115 mp.keycodes['backspace']= "delete_left";
116 mp.keycodes['ctrl-i']   = "insert_tab";
117 mp.keycodes['ctrl-m']   = "insert_line";
118 mp.keycodes['ctrl-y']   = "delete_line";
119 mp.keycodes['ctrl-z']   = "undo";
120 mp.keycodes['f4']       = "word_wrap_paragraph";
122 /** action descriptions **/
124 mp.actdesc['insert_line']       = LL("Insert line");
125 mp.actdesc['delete_line']       = LL("Delete line");
126 mp.actdesc['insert_space']      = LL("Insert space");
127 mp.actdesc['insert_tab']        = LL("Insert tab");
128 mp.actdesc['delete']            = LL("Delete character");
129 mp.actdesc['delete_left']       = LL("Delete character to the left");
130 mp.actdesc['undo']              = LL("Undo");
131 mp.actdesc['redo']              = LL("Redo");
132 mp.actdesc['join_paragraph']    = LL("Join a paragraph in one line");
133 mp.actdesc['word_wrap_paragraph'] = LL("Word-wrap a paragraph");
134 mp.actdesc['line_options']      = LL("Line options...");
135 mp.actdesc['tab_options']       = LL("Tab options...");
137 /** code **/
140  * mp.break_line - Breaks current line in two (inserts a newline).
141  * @doc: the document
142  * @col: column where the newline will be inserted 
144  * Breaks current line in two by inserting a newline character in between.
145  * If @col is not NULL, the newline will be inserted in that column; otherwise,
146  * the current x position will be used.
147  */
148 sub mp.break_line(doc, col)
150         local txt = doc.txt;
151         local c, w;
153         /* if col is NULL, set it to be the x cursor */
154         if (col == NULL)
155                 col = txt.x;
157         /* gets line where cursor is */
158         c = txt.lines[txt.y];
160         /* deletes from col to the end of line */
161         w = splice(c, NULL, col, -1);
163         /* set first part as current line */
164         txt.lines[txt.y] = w[0];
166         /* move to next line */
167         txt.y++;
169         /* insert a new line here */
170         expand(txt.lines, txt.y, 1);
172         /* fix the x cursor position */
173         txt.x -= col;
175         /* if autoindenting... */
176         if (mp.config.auto_indent) {
177                 /* extract leading blanks in the original line
178                    to prepend them to the line to be inserted */
179                 local i = regex("/^[ \t]*[-\+\*]?[ \t]+/", c, 0);
181                 /* substitute all non-tab characters with spaces */
182                 i = sregex("/[^\t]/g", i, " ");
184                 /* delete any blank in the new line */
185                 w[1] = sregex("/^[ \t]*/", w[1]);
187                 /* concatenate */
188                 w[1] = i ~ w[1];
190                 /* the x position is further the length of that */
191                 txt.x += size(i);
192         }
194         /* put second part there (or an empty string if NULL) */
195         txt.lines[txt.y] = w[1] || '';
197         txt.mod++;
199         return doc;
203 sub mp.join_line(doc)
204 /* joins the current line with the next one */
206         local txt = doc.txt;
208         if (txt.y < size(txt.lines)) {
209                 /* concats current line with the next one */
210                 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
212                 /* delete it */
213                 adel(txt.lines, txt.y + 1);
215                 txt.mod++;
216         }
218         return doc;
222 sub mp.delete_line(doc)
223 /* deletes the current line */
225         local txt = doc.txt;
226         local vx;
228         /* take current position */
229         vx = mp.x2vx(txt.lines[txt.y], txt.x);
231         /* if it's the only line, just replace it */
232         if (size(txt.lines) == 1)
233                 txt.lines[0] = '';
234         else {
235                 /* destroy the line */
236                 adel(txt.lines, txt.y);
237         }
239         /* fix if it was the last line */
240         if (txt.y >= size(txt.lines))
241                 txt.y = size(txt.lines) - 1;
243         /* move to previous x position */
244         txt.x = mp.vx2x(txt.lines[txt.y], vx);
246         txt.mod++;
248         return doc;
252 sub mp.delete_char(doc)
253 /* deletes the current char */
255         local txt = doc.txt;
257         /* is it over the end of line? */
258         if (txt.x == size(txt.lines[txt.y]))
259                 mp.join_line(doc);
260         else {
261                 local w;
263                 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
264                 txt.lines[txt.y] = w[0];
265         }
267         txt.mod++;
269         return doc;
273 sub mp.delete_range(doc, bx, by, ex, ey, v)
274 /* deletes a range of characters from a document */
276         local txt = doc.txt;
278         /* move to the start of the range */
279         txt.x = bx;
280         txt.y = by;
282         if (by == ey) {
283                 local w;
285                 /* block is just one line; delete the middle part */
286                 w = splice(txt.lines[by], NULL, bx, ex - bx);
288                 txt.lines[by] = w[0];
289         }
290         else {
291                 /* block has more than one line */
292                 local w;
294                 if (v == 0) {
295                         /* delete using normal selection block */
297                         /* delete from the beginning to the end of the first line */
298                         w = splice(txt.lines[by], NULL, bx, -1);
299                         txt.lines[by] = w[0];
301                         /* delete from the beginning of the last line to
302                            the end of the block */
303                         w = splice(txt.lines[ey], NULL, 0, ex);
304                         txt.lines[ey] = w[0];
306                         /* collapse the lines in between */
307                         collapse(txt.lines, by + 1, ey - by - 1);
309                         /* finally join both lines */
310                         mp.join_line(doc);
311                 }
312                 else {
313                         /* delete using vertical selection block */
314                         while (by <= ey) {
315                                 w = splice(txt.lines[by], NULL, bx, ex - bx);
316                                 txt.lines[by] = w[0];
317                                 by++;
318                         }
319                 }
320         }
322         txt.mod++;
324         return doc;
328 sub mp.insert_string(doc, str)
329 /* inserts a string into the cursor position */
331         local txt = doc.txt;
332         local w;
334         /* splice and change */
335         w = splice(txt.lines[txt.y], str, txt.x, 0);
336         txt.lines[txt.y] = w[0];
338         /* move right */
339         txt.x += size(str);
341         txt.mod++;
343         return doc;
347 sub mp.insert(doc, a)
348 /* inserts an array of text into a document */
350         local txt = doc.txt;
351         local s;
353         /* if a is not an array, split it */
354         if (!is_array(a))
355                 a = split("\n", a);
357         /* empty array? return */
358         if ((s = size(a)) == 0)
359                 return doc;
361         /* paste first line into current position */
362         mp.insert_string(doc, a[0]);
364         /* more than just one line? */
365         if (s > 1) {
366                 /* break current line in two */
367                 mp.break_line(doc);
369                 /* insert last line */
370                 mp.insert_string(doc, a[s - 1]);
371         }
373         /* more than two lines? */
374         if (s > 2) {
375                 local n = 1;
377                 /* open room */
378                 expand(txt.lines, txt.y, s - 2);
380                 /* transfer middle lines */
381                 while (n < s - 1)
382                         txt.lines[txt.y++] = a[n++];
383         }
385         return doc;
389 sub mp.wrap_words(doc)
390 /* do the word wrapping */
392         local txt = doc.txt;
394         if (mp.config.word_wrap == 0)
395                 return doc;
397         /* take the column where the cursor is */
398         local c = mp.x2vx(txt.lines[txt.y], txt.x);
400         if (c >= mp.config.word_wrap &&
401                 regex("/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/", txt.lines[txt.y])) {
402                 local w;
404                 /* take the coordinates */
405                 w = regex();
407                 /* break the line there */
408                 mp.break_line(doc, w[1]);
410                 /* delete the space at the end of the line */
411                 txt.lines[txt.y - 1] = sregex("/[ \t]$/", txt.lines[txt.y - 1], NULL);
412         }
414         return doc;
418 sub mp.insert_space(doc)
419 /* inserts a space, taking wordwrapping into account */
421         mp.wrap_words(doc);
422         mp.insert(doc, ' ');
426 sub mp.insert_tab(doc)
427 /* inserts a tab */
429         if (mp.config.tabs_as_spaces) {
430                 /* number of spaces to insert */
431                 local n = mp.config.tab_size -
432                         ((doc.txt.x) % mp.config.tab_size);
434                 while(n--) mp.insert(doc, ' ');
435         }
436         else
437                 mp.insert(doc, "\t");
439         return doc;
443 sub mp.insert_newline(doc)
444 /* inserts a newline */
446         mp.wrap_words(doc);
447         mp.break_line(doc);
451 sub mp.insert_keystroke(doc, key)
452 /* inserts from a keystroke (with undo) */
454         if (size(key) == 1) {
455                 mp.store_undo(doc);
457                 mp.insert(doc, key);
458         }
459         else
460         if (key != NULL) {
461                 mp.message = {
462                         'timeout'       => time() + 2,
463                         'string'        => sprintf(L("Unbound keystroke '%s'"), key)
464                 };
465         }
467         return doc;
471 /** undo **/
473 sub mp.store_undo(doc)
474 /* stores the current txt in the undo queue */
476         queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
477         doc.redo = [];
479         return doc;
483 sub mp.undo(doc)
484 /* undoes last operation */
486         local txt;
488         if (txt = pop(doc.undo)) {
489                 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
490                 doc.txt = txt;
491         }
493         return doc;
497 sub mp.redo(doc)
498 /* redoes last undid operation */
500         local txt;
502         if (txt = pop(doc.redo)) {
503                 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
504                 doc.txt = txt;
505         }
507         return doc;
511 /** paragraphs **/
513 sub mp.join_paragraph(doc)
514 /* joins current paragraph in just one line */
516         local txt = doc.txt;
517         local l;
519         while ((l = txt.lines[txt.y + 1]) && size(l)) {
520                 /* delete all leading blanks in the next line */
521                 txt.lines[txt.y + 1] = sregex("/^[ \t]+/", txt.lines[txt.y + 1]);
523                 /* move to end of line and add a space separator */
524                 mp.move_eol(doc);
525                 mp.insert(doc, ' ');
527                 /* really join */
528                 mp.join_line(doc);
529         }
531         return doc;
535 sub mp.word_wrap_paragraph(doc)
536 /* word wraps current paragraph */
538         local txt = doc.txt;
540         if (mp.config.word_wrap == 0)
541                 return doc;
543         mp.join_paragraph(doc);
545         mp.move_eol(doc);
547         while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
548                 mp.insert_space(doc);
549                 mp.move_left(doc);
550                 mp.delete_char(doc);
551         }
553         return doc;