4 A Programmer's Text Editor
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) {
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) {
52 mp.actions['indent_block'] = sub (d) {
55 if (d.txt.mark == NULL) {
56 mp.move(d, mp.move_bol);
61 local currentY = d.txt.y;
62 local startY = d.txt.mark.by;
63 local endY = d.txt.mark.ey;
64 local times = endY - startY;
69 /* use to be while d.txt.y <= endY, but that entered an endless loop when
70 you were indenting a block including the very last line in the file */
72 mp.move(d, mp.move_bol);
74 mp.move(d, mp.move_down);
80 mp.move(d, mp.move_bol);
84 mp.move(d.mp_move_eol);
87 mp.set_y(d, currentY);
90 mp.actions['unindent_block'] = sub(d) {
93 if (d.txt.mark == NULL) {
98 local currentY = d.txt.y;
99 local startY = d.txt.mark.by;
100 local endY = d.txt.mark.ey;
101 local times = endY - startY;
107 /* use to be while d.txt.y <= endY, but that entered an endless loop when
108 you were unindenting a block including the very last line in the file */
111 mp.move(d, mp.move_down);
117 mp.move(d, mp.move_bol);
121 mp.move(d.mp_move_eol);
124 mp.set_y(d, currentY);
127 mp.actions['undo'] = sub (d) { mp.undo(d); };
128 mp.actions['redo'] = sub (d) { mp.redo(d); };
130 mp.actions['join_paragraph'] = sub (d) {
137 /* create a working document */
138 local p = mp.create('<wrk>', mp.clipboard);
140 /* while not at EOF, word wrap everything */
141 while (p.txt.y < size(p.txt.lines) - 1) {
142 mp.join_paragraph(p);
147 /* insert the content */
148 mp.insert(d, p.txt.lines);
152 mp.join_paragraph(d);
155 mp.actions['word_wrap_paragraph'] = sub (d) {
157 if(mp.config.word_wrap == 0)
158 mp.alert(L("Word wrapping must be set"));
166 /* create a working document */
167 local p = mp.create('<wrk>', mp.clipboard);
169 /* while not at EOF, word wrap everything */
170 while (p.txt.y < size(p.txt.lines) - 1) {
171 mp.word_wrap_paragraph(p);
176 /* insert the content */
177 mp.insert(d, p.txt.lines);
181 mp.word_wrap_paragraph(d);
185 mp.actions['line_options'] = sub (d) {
187 /* convert special characters on end of line */
188 local lt = mp.backslash_codes(mp.config.eol, 1);
191 { 'label' => L("Word wrap on column (0, no word wrap):"),
193 'value' => mp.config.word_wrap,
194 'history' => 'wordwrap' },
195 { 'label' => L("Automatic indentation") ~ ':',
196 'type' => 'checkbox',
197 'value' => mp.config.auto_indent },
198 { 'label' => L("Line termination") ~ ':',
201 { 'label' => L("Keep original end of lines") ~ ':',
202 'value' => mp.config.keep_eol,
203 'type' => 'checkbox' },
204 { 'label' => L("Mark end of lines") ~ ':',
205 'value' => mp.config.mark_eol,
206 'type' => 'checkbox' }
210 mp.config.word_wrap = t[0];
211 mp.config.auto_indent = t[1];
212 mp.config.eol = mp.backslash_codes(t[2], 0);
213 mp.config.keep_eol = t[3];
214 mp.config.mark_eol = t[4];
218 mp.actions['tab_options'] = sub (d) {
221 { 'label' => L("Tab size") ~ ':',
223 'value' => mp.config.tab_size,
224 'history' => 'tabsize' },
225 { 'label' => L("Convert tabs to spaces") ~ ':',
226 'type' => 'checkbox',
227 'value' => mp.config.tabs_as_spaces },
228 { 'label' => L("Use previous line for tab columns") ~ ':',
229 'type' => 'checkbox',
230 'value' => mp.config.dynamic_tabs }
234 mp.config.tab_size = t[0];
235 mp.config.tabs_as_spaces = t[1];
236 mp.config.dynamic_tabs = t[2];
240 mp.actions['toggle_insert'] = sub (d) { mp.config.insert = !mp.config.insert; };
242 /** default key bindings **/
244 mp.keycodes['enter'] = "insert_line";
245 mp.keycodes['tab'] = "insert_tab";
246 mp.keycodes['shift-tab'] = "insert_real_tab";
247 mp.keycodes['space'] = "insert_space";
248 mp.keycodes['delete'] = "delete";
249 mp.keycodes['backspace'] = "delete_left";
250 mp.keycodes['ctrl-i'] = "insert_tab";
251 mp.keycodes['ctrl-m'] = "insert_line";
252 mp.keycodes['ctrl-y'] = "delete_line";
253 mp.keycodes['alt-cursor-right'] = "indent_block";
254 mp.keycodes['alt-cursor-left'] = "unindent_block";
255 mp.keycodes['ctrl-z'] = "undo";
256 mp.keycodes['f4'] = "word_wrap_paragraph";
257 mp.keycodes['insert'] = "toggle_insert";
259 /** action descriptions **/
261 mp.actdesc['insert_line'] = LL("Insert line");
262 mp.actdesc['delete_line'] = LL("Delete line");
263 mp.actdesc['insert_space'] = LL("Insert space");
264 mp.actdesc['insert_tab'] = LL("Insert tab");
265 mp.actdesc['insert_real_tab'] = LL("Insert real tab character");
266 mp.actdesc['delete'] = LL("Delete character");
267 mp.actdesc['delete_left'] = LL("Delete character to the left");
268 mp.actdesc['indent_block'] = LL("Indent block");
269 mp.actdesc['unindent_block'] = LL("Unindent block");
270 mp.actdesc['undo'] = LL("Undo");
271 mp.actdesc['redo'] = LL("Redo");
272 mp.actdesc['join_paragraph'] = LL("Join a paragraph in one line");
273 mp.actdesc['word_wrap_paragraph'] = LL("Word-wrap a paragraph");
274 mp.actdesc['line_options'] = LL("Line options...");
275 mp.actdesc['tab_options'] = LL("Tab options...");
276 mp.actdesc['toggle_insert'] = LL("Toggle insert/overwrite mode");
281 * mp.break_line - Breaks current line in two (inserts a newline).
283 * @col: column where the newline will be inserted
285 * Breaks current line in two by inserting a newline character in between.
286 * If @col is not NULL, the newline will be inserted in that column; otherwise,
287 * the current x position will be used.
289 sub mp.break_line(doc, col)
294 /* if col is NULL, set it to be the x cursor */
298 /* gets line where cursor is */
299 c = txt.lines[txt.y];
301 /* deletes from col to the end of line */
302 w = splice(c, NULL, col, -1);
304 /* set first part as current line */
305 txt.lines[txt.y] = w[0];
307 /* move to next line */
310 /* insert a new line here */
311 expand(txt.lines, txt.y, 1);
313 /* fix the x cursor position */
316 /* if autoindenting... */
317 if (mp.config.auto_indent) {
318 /* extract leading blanks in the original line
319 to prepend them to the line to be inserted */
320 local i = regex(c, "/^[ \t]*[-\+\*]?[ \t]+/", 0);
322 /* substitute all non-tab characters with spaces */
323 i = sregex(i, "/[^\t]/g", " ");
325 /* delete any blank in the new line */
326 w[1] = sregex(w[1], "/^[ \t]*/");
331 /* the x position is further the length of that */
335 /* put second part there (or an empty string if NULL) */
336 txt.lines[txt.y] = w[1] || '';
344 sub mp.join_line(doc)
345 /* joins the current line with the next one */
349 if (txt.y < size(txt.lines)) {
350 /* concats current line with the next one */
351 txt.lines[txt.y] = txt.lines[txt.y] ~ txt.lines[txt.y + 1];
354 adel(txt.lines, txt.y + 1);
363 sub mp.delete_line(doc)
364 /* deletes the current line */
369 /* take current position */
370 vx = mp.x2vx(txt.lines[txt.y], txt.x);
372 /* if it's the only line, just replace it */
373 if (size(txt.lines) == 1)
376 /* destroy the line */
377 adel(txt.lines, txt.y);
380 /* fix if it was the last line */
381 if (txt.y >= size(txt.lines))
382 txt.y = size(txt.lines) - 1;
384 /* move to previous x position */
385 txt.x = mp.vx2x(txt.lines[txt.y], vx);
393 sub mp.delete_char(doc)
394 /* deletes the current char */
398 if (txt.mark != NULL) {
403 /* is it over the end of line? */
404 if (txt.x == size(txt.lines[txt.y]))
409 w = splice(txt.lines[txt.y], NULL, txt.x, 1);
410 txt.lines[txt.y] = w[0];
419 sub mp.delete_range(doc, bx, by, ex, ey, v)
420 /* deletes a range of characters from a document */
424 /* move to the start of the range */
431 /* block is just one line; delete the middle part */
432 w = splice(txt.lines[by], NULL, bx, ex - bx);
434 txt.lines[by] = w[0];
437 /* block has more than one line */
441 /* delete using normal selection block */
443 /* delete from the beginning to the end of the first line */
444 w = splice(txt.lines[by], NULL, bx, -1);
445 txt.lines[by] = w[0];
447 /* delete from the beginning of the last line to
448 the end of the block */
449 w = splice(txt.lines[ey], NULL, 0, ex);
450 txt.lines[ey] = w[0];
452 /* collapse the lines in between */
453 collapse(txt.lines, by + 1, ey - by - 1);
455 /* finally join both lines */
459 /* delete using vertical selection block */
461 w = splice(txt.lines[by], NULL, bx, ex - bx);
462 txt.lines[by] = w[0];
474 sub mp.insert_string(doc, str)
475 /* inserts a string into the cursor position */
482 /* splice and change */
483 w = splice(txt.lines[txt.y], str, txt.x, mp.config.insert && size(str) || 0);
484 txt.lines[txt.y] = w[0];
495 sub mp.insert(doc, a)
496 /* inserts an array of text into a document */
501 /* if a is not an array, split it */
505 /* empty array? return */
506 if ((s = size(a)) == 0)
509 /* paste first line into current position */
510 mp.insert_string(doc, a[0]);
512 /* more than just one line? */
514 /* break current line in two */
517 /* insert last line */
518 mp.insert_string(doc, a[s - 1]);
521 /* more than two lines? */
526 expand(txt.lines, txt.y, s - 2);
528 /* transfer middle lines */
530 txt.lines[txt.y++] = a[n++];
537 sub mp.wrap_words(doc)
538 /* do the word wrapping */
542 if (mp.config.word_wrap == 0)
545 /* take the column where the cursor is */
546 local c = mp.x2vx(txt.lines[txt.y], txt.x);
548 if (c >= mp.config.word_wrap &&
549 regex(txt.lines[txt.y], "/^.{1," ~ mp.config.word_wrap ~ "}[ \t]/")) {
552 /* take the coordinates */
555 /* break the line there */
556 mp.break_line(doc, w[1]);
558 /* delete the space at the end of the line */
559 txt.lines[txt.y - 1] = sregex(txt.lines[txt.y - 1], "/[ \t]$/", NULL);
566 sub mp.insert_space(doc)
567 /* inserts a space, taking wordwrapping into account */
574 sub mp.insert_tab(doc)
577 if (doc.txt.y && mp.config.dynamic_tabs) {
578 local pl = doc.txt.lines[doc.txt.y - 1];
580 if ((pl = regex(pl, "/[^ \t]*[ \t]+/", doc.txt.x)) != NULL) {
581 pl = sregex(pl, "/[^\t]/g", ' ');
587 if (mp.config.tabs_as_spaces) {
588 /* number of spaces to insert */
589 local n = mp.config.tab_size -
590 ((doc.txt.x) % mp.config.tab_size);
592 while(n--) mp.insert(doc, ' ');
595 mp.insert(doc, "\t");
601 sub mp.insert_newline(doc)
602 /* inserts a newline */
609 sub mp.insert_keystroke(doc, key)
610 /* inserts from a keystroke (with undo) */
612 if (size(key) == 1) {
619 'timeout' => time() + 2,
620 'string' => sprintf(L("Unbound keystroke '%s'"), key)
630 sub mp.store_undo(doc)
631 /* stores the current txt in the undo queue */
633 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
641 /* undoes last operation */
645 if (txt = pop(doc.undo)) {
646 queue(doc.redo, clone(doc.txt), mp.config.undo_levels);
655 /* redoes last undid operation */
659 if (txt = pop(doc.redo)) {
660 queue(doc.undo, clone(doc.txt), mp.config.undo_levels);
670 sub mp.join_paragraph(doc)
671 /* joins current paragraph in just one line */
676 while ((l = txt.lines[txt.y + 1]) && size(l)) {
677 /* delete all leading blanks in the next line */
678 txt.lines[txt.y + 1] = sregex(txt.lines[txt.y + 1], "/^[ \t]+/");
680 /* move to end of line and add a space separator */
692 sub mp.word_wrap_paragraph(doc)
693 /* word wraps current paragraph */
697 if (mp.config.word_wrap == 0)
700 mp.join_paragraph(doc);
704 while (size(txt.lines[txt.y]) > mp.config.word_wrap) {
705 mp.insert_space(doc);
713 /* indent/unindent support functions */
715 sub mp.unindent_line(d)
716 /* Unindent the current line by 1 tab or the indent size */
718 local l = split(d.txt.lines[d.txt.y]);
720 mp.move(d, mp.move_bol);
722 if (cmp(l[0], "\t") == 0) {
726 while (i < mp.config.tab_size) {
727 if (cmp(l[i], " ") == 0) {