diff options
author | Jody Bruchon <jody@jodybruchon.com> | 2014-04-02 13:49:26 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2014-04-02 13:49:26 +0200 |
commit | a8d6f9bee43aba077f7a3a9bcb6ac64e5d877278 (patch) | |
tree | 4e35b7616c463b9a6f9b9d27438eea8309683441 | |
parent | 7537406edd3e0adf11d117379cac4e519e746d35 (diff) | |
download | busybox-w32-a8d6f9bee43aba077f7a3a9bcb6ac64e5d877278.tar.gz busybox-w32-a8d6f9bee43aba077f7a3a9bcb6ac64e5d877278.tar.bz2 busybox-w32-a8d6f9bee43aba077f7a3a9bcb6ac64e5d877278.zip |
vi: undo support for vi with intermediate queuing
function old new delta
undo_push - 411 +411
undo_pop - 288 +288
do_cmd 4160 4426 +266
char_insert 363 483 +120
undo_queue_commit - 61 +61
text_hole_delete 108 163 +55
string_insert 94 127 +33
colon 2864 2882 +18
yank_delete 92 101 +9
vi_main 273 280 +7
dot_scroll 88 93 +5
dot_right 29 34 +5
dot_prev 20 25 +5
dot_next 20 25 +5
dot_left 24 29 +5
dot_end 20 25 +5
dot_begin 20 25 +5
init_text_buffer 154 156 +2
text_hole_make 145 142 -3
file_insert 333 318 -15
------------------------------------------------------------------------------
(add/remove: 3/0 grow/shrink: 15/2 up/down: 1305/-18) Total: 1287 bytes
(without queuing it's ~870 bytes)
Signed-off-by: Jody Bruchon <jody@jodybruchon.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | editors/vi.c | 502 |
1 files changed, 448 insertions, 54 deletions
diff --git a/editors/vi.c b/editors/vi.c index 097f309c8..e38667ad1 100644 --- a/editors/vi.c +++ b/editors/vi.c | |||
@@ -17,7 +17,6 @@ | |||
17 | * it would be easier to change the mark when add/delete lines | 17 | * it would be easier to change the mark when add/delete lines |
18 | * More intelligence in refresh() | 18 | * More intelligence in refresh() |
19 | * ":r !cmd" and "!cmd" to filter text through an external command | 19 | * ":r !cmd" and "!cmd" to filter text through an external command |
20 | * A true "undo" facility | ||
21 | * An "ex" line oriented mode- maybe using "cmdedit" | 20 | * An "ex" line oriented mode- maybe using "cmdedit" |
22 | */ | 21 | */ |
23 | 22 | ||
@@ -136,6 +135,36 @@ | |||
136 | //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. | 135 | //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. |
137 | //config: | 136 | //config: |
138 | //config: This is not clean but helps a lot on serial lines and such. | 137 | //config: This is not clean but helps a lot on serial lines and such. |
138 | //config:config FEATURE_VI_UNDO | ||
139 | //config: bool "Support undo command 'u'" | ||
140 | //config: default y | ||
141 | //config: depends on VI | ||
142 | //config: help | ||
143 | //config: Support the 'u' command to undo insertion, deletion, and replacement | ||
144 | //config: of text. | ||
145 | //config:config FEATURE_VI_UNDO_QUEUE | ||
146 | //config: bool "Enable undo operation queuing" | ||
147 | //config: default y | ||
148 | //config: depends on FEATURE_VI_UNDO | ||
149 | //config: help | ||
150 | //config: The vi undo functions can use an intermediate queue to greatly lower | ||
151 | //config: malloc() calls and overhead. When the maximum size of this queue is | ||
152 | //config: reached, the contents of the queue are committed to the undo stack. | ||
153 | //config: This increases the size of the undo code and allows some undo | ||
154 | //config: operations (especially un-typing/backspacing) to be far more useful. | ||
155 | //config:config FEATURE_VI_UNDO_QUEUE_MAX | ||
156 | //config: int "Maximum undo character queue size" | ||
157 | //config: default 256 | ||
158 | //config: range 32 65536 | ||
159 | //config: depends on FEATURE_VI_UNDO_QUEUE | ||
160 | //config: help | ||
161 | //config: This option sets the number of bytes used at runtime for the queue. | ||
162 | //config: Smaller values will create more undo objects and reduce the amount | ||
163 | //config: of typed or backspaced characters that are grouped into one undo | ||
164 | //config: operation; larger values increase the potential size of each undo | ||
165 | //config: and will generally malloc() larger objects and less frequently. | ||
166 | //config: Unless you want more (or less) frequent "undo points" while typing, | ||
167 | //config: you should probably leave this unchanged. | ||
139 | 168 | ||
140 | //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP)) | 169 | //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP)) |
141 | 170 | ||
@@ -347,6 +376,42 @@ struct globals { | |||
347 | char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ | 376 | char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ |
348 | 377 | ||
349 | char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; | 378 | char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; |
379 | #if ENABLE_FEATURE_VI_UNDO | ||
380 | // undo_push() operations | ||
381 | #define UNDO_INS 0 | ||
382 | #define UNDO_DEL 1 | ||
383 | #define UNDO_INS_CHAIN 2 | ||
384 | #define UNDO_DEL_CHAIN 3 | ||
385 | // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG | ||
386 | #define UNDO_QUEUED_FLAG 4 | ||
387 | #define UNDO_INS_QUEUED 4 | ||
388 | #define UNDO_DEL_QUEUED 5 | ||
389 | #define UNDO_USE_SPOS 32 | ||
390 | #define UNDO_EMPTY 64 | ||
391 | // Pass-through flags for functions that can be undone | ||
392 | #define NO_UNDO 0 | ||
393 | #define ALLOW_UNDO 1 | ||
394 | #define ALLOW_UNDO_CHAIN 2 | ||
395 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
396 | #define ALLOW_UNDO_QUEUED 3 | ||
397 | char undo_queue_state; | ||
398 | int undo_q; | ||
399 | char *undo_queue_spos; // Start position of queued operation | ||
400 | char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX]; | ||
401 | #else | ||
402 | // If undo queuing disabled, don't invoke the missing queue logic | ||
403 | #define ALLOW_UNDO_QUEUED 1 | ||
404 | #endif /* ENABLE_FEATURE_VI_UNDO_QUEUE */ | ||
405 | |||
406 | struct undo_object { | ||
407 | struct undo_object *prev; // Linking back avoids list traversal (LIFO) | ||
408 | int u_type; // 0=deleted, 1=inserted, 2=swapped | ||
409 | int start; // Offset where the data should be restored/deleted | ||
410 | int length; // total data size | ||
411 | char *undo_text; // ptr to text that will be inserted | ||
412 | } *undo_stack_tail; | ||
413 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
414 | |||
350 | }; | 415 | }; |
351 | #define G (*ptr_to_globals) | 416 | #define G (*ptr_to_globals) |
352 | #define text (G.text ) | 417 | #define text (G.text ) |
@@ -408,6 +473,16 @@ struct globals { | |||
408 | #define last_modifying_cmd (G.last_modifying_cmd ) | 473 | #define last_modifying_cmd (G.last_modifying_cmd ) |
409 | #define get_input_line__buf (G.get_input_line__buf) | 474 | #define get_input_line__buf (G.get_input_line__buf) |
410 | 475 | ||
476 | #if ENABLE_FEATURE_VI_UNDO | ||
477 | #define undo_stack_tail (G.undo_stack_tail ) | ||
478 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
479 | #define undo_queue_state (G.undo_queue_state) | ||
480 | #define undo_q (G.undo_q ) | ||
481 | #define undo_queue (G.undo_queue ) | ||
482 | #define undo_queue_spos (G.undo_queue_spos ) | ||
483 | # endif | ||
484 | #endif | ||
485 | |||
411 | #define INIT_G() do { \ | 486 | #define INIT_G() do { \ |
412 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | 487 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
413 | last_file_modified = -1; \ | 488 | last_file_modified = -1; \ |
@@ -437,10 +512,12 @@ static void dot_next(void); // move dot to next line B-o-l | |||
437 | static void dot_prev(void); // move dot to prev line B-o-l | 512 | static void dot_prev(void); // move dot to prev line B-o-l |
438 | static void dot_scroll(int, int); // move the screen up or down | 513 | static void dot_scroll(int, int); // move the screen up or down |
439 | static void dot_skip_over_ws(void); // move dot pat WS | 514 | static void dot_skip_over_ws(void); // move dot pat WS |
440 | static void dot_delete(void); // delete the char at 'dot' | ||
441 | static char *bound_dot(char *); // make sure text[0] <= P < "end" | 515 | static char *bound_dot(char *); // make sure text[0] <= P < "end" |
442 | static char *new_screen(int, int); // malloc virtual screen memory | 516 | static char *new_screen(int, int); // malloc virtual screen memory |
443 | static char *char_insert(char *, char); // insert the char c at 'p' | 517 | #if !ENABLE_FEATURE_VI_UNDO |
518 | #define char_insert(a,b,c) char_insert(a,b) | ||
519 | #endif | ||
520 | static char *char_insert(char *, char, int); // insert the char c at 'p' | ||
444 | // might reallocate text[]! use p += stupid_insert(p, ...), | 521 | // might reallocate text[]! use p += stupid_insert(p, ...), |
445 | // and be careful to not use pointers into potentially freed text[]! | 522 | // and be careful to not use pointers into potentially freed text[]! |
446 | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' | 523 | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' |
@@ -448,11 +525,17 @@ static int find_range(char **, char **, char); // return pointers for an object | |||
448 | static int st_test(char *, int, int, char *); // helper for skip_thing() | 525 | static int st_test(char *, int, int, char *); // helper for skip_thing() |
449 | static char *skip_thing(char *, int, int, int); // skip some object | 526 | static char *skip_thing(char *, int, int, int); // skip some object |
450 | static char *find_pair(char *, char); // find matching pair () [] {} | 527 | static char *find_pair(char *, char); // find matching pair () [] {} |
451 | static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole | 528 | #if !ENABLE_FEATURE_VI_UNDO |
529 | #define text_hole_delete(a,b,c) text_hole_delete(a,b) | ||
530 | #endif | ||
531 | static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole | ||
452 | // might reallocate text[]! use p += text_hole_make(p, ...), | 532 | // might reallocate text[]! use p += text_hole_make(p, ...), |
453 | // and be careful to not use pointers into potentially freed text[]! | 533 | // and be careful to not use pointers into potentially freed text[]! |
454 | static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole | 534 | static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole |
455 | static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete | 535 | #if !ENABLE_FEATURE_VI_UNDO |
536 | #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) | ||
537 | #endif | ||
538 | static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete | ||
456 | static void show_help(void); // display some help info | 539 | static void show_help(void); // display some help info |
457 | static void rawmode(void); // set "raw" mode on tty | 540 | static void rawmode(void); // set "raw" mode on tty |
458 | static void cookmode(void); // return to "cooked" mode on tty | 541 | static void cookmode(void); // return to "cooked" mode on tty |
@@ -514,20 +597,34 @@ static void showmatching(char *); // show the matching pair () [] {} | |||
514 | #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME | 597 | #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME |
515 | // might reallocate text[]! use p += string_insert(p, ...), | 598 | // might reallocate text[]! use p += string_insert(p, ...), |
516 | // and be careful to not use pointers into potentially freed text[]! | 599 | // and be careful to not use pointers into potentially freed text[]! |
517 | static uintptr_t string_insert(char *, const char *); // insert the string at 'p' | 600 | # if !ENABLE_FEATURE_VI_UNDO |
601 | #define string_insert(a,b,c) string_insert(a,b) | ||
602 | # endif | ||
603 | static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' | ||
518 | #endif | 604 | #endif |
519 | #if ENABLE_FEATURE_VI_YANKMARK | 605 | #if ENABLE_FEATURE_VI_YANKMARK |
520 | static char *text_yank(char *, char *, int); // save copy of "p" into a register | 606 | static char *text_yank(char *, char *, int); // save copy of "p" into a register |
521 | static char what_reg(void); // what is letter of current YDreg | 607 | static char what_reg(void); // what is letter of current YDreg |
522 | static void check_context(char); // remember context for '' command | 608 | static void check_context(char); // remember context for '' command |
523 | #endif | 609 | #endif |
610 | #if ENABLE_FEATURE_VI_UNDO | ||
611 | static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack | ||
612 | static void undo_pop(void); // Undo the last operation | ||
613 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
614 | static void undo_queue_commit(void); // Flush any queued objects to the undo stack | ||
615 | # else | ||
616 | # define undo_queue_commit() ((void)0) | ||
617 | # endif | ||
618 | #else | ||
619 | #define undo_queue_commit() ((void)0) | ||
620 | #endif | ||
621 | |||
524 | #if ENABLE_FEATURE_VI_CRASHME | 622 | #if ENABLE_FEATURE_VI_CRASHME |
525 | static void crash_dummy(); | 623 | static void crash_dummy(); |
526 | static void crash_test(); | 624 | static void crash_test(); |
527 | static int crashme = 0; | 625 | static int crashme = 0; |
528 | #endif | 626 | #endif |
529 | 627 | ||
530 | |||
531 | static void write1(const char *out) | 628 | static void write1(const char *out) |
532 | { | 629 | { |
533 | fputs(out, stdout); | 630 | fputs(out, stdout); |
@@ -540,6 +637,14 @@ int vi_main(int argc, char **argv) | |||
540 | 637 | ||
541 | INIT_G(); | 638 | INIT_G(); |
542 | 639 | ||
640 | #if ENABLE_FEATURE_VI_UNDO | ||
641 | /* undo_stack_tail = NULL; - already is */ | ||
642 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
643 | undo_queue_state = UNDO_EMPTY; | ||
644 | /* undo_q = 0; - already is */ | ||
645 | #endif | ||
646 | #endif | ||
647 | |||
543 | #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME | 648 | #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME |
544 | my_pid = getpid(); | 649 | my_pid = getpid(); |
545 | #endif | 650 | #endif |
@@ -631,7 +736,7 @@ static int init_text_buffer(char *fn) | |||
631 | } | 736 | } |
632 | if (size < 0) { | 737 | if (size < 0) { |
633 | // file dont exist. Start empty buf with dummy line | 738 | // file dont exist. Start empty buf with dummy line |
634 | char_insert(text, '\n'); | 739 | char_insert(text, '\n', NO_UNDO); |
635 | rc = 0; | 740 | rc = 0; |
636 | } else { | 741 | } else { |
637 | rc = file_insert(fn, text, 1); | 742 | rc = file_insert(fn, text, 1); |
@@ -756,7 +861,7 @@ static void edit_file(char *fn) | |||
756 | crash_dummy(); // generate a random command | 861 | crash_dummy(); // generate a random command |
757 | } else { | 862 | } else { |
758 | crashme = 0; | 863 | crashme = 0; |
759 | string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string | 864 | string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string |
760 | dot = text; | 865 | dot = text; |
761 | refresh(FALSE); | 866 | refresh(FALSE); |
762 | } | 867 | } |
@@ -1015,7 +1120,7 @@ static void colon(char *buf) | |||
1015 | q = begin_line(dot); // assume .,. for the range | 1120 | q = begin_line(dot); // assume .,. for the range |
1016 | r = end_line(dot); | 1121 | r = end_line(dot); |
1017 | } | 1122 | } |
1018 | dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines | 1123 | dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines |
1019 | dot_skip_over_ws(); | 1124 | dot_skip_over_ws(); |
1020 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file | 1125 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file |
1021 | // don't edit, if the current file has been modified | 1126 | // don't edit, if the current file has been modified |
@@ -1175,7 +1280,7 @@ static void colon(char *buf) | |||
1175 | // if the insert is before "dot" then we need to update | 1280 | // if the insert is before "dot" then we need to update |
1176 | if (q <= dot) | 1281 | if (q <= dot) |
1177 | dot += ch; | 1282 | dot += ch; |
1178 | /*file_modified++; - done by file_insert */ | 1283 | // file_modified++; |
1179 | } | 1284 | } |
1180 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args | 1285 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args |
1181 | if (file_modified && !useforce) { | 1286 | if (file_modified && !useforce) { |
@@ -1235,6 +1340,9 @@ static void colon(char *buf) | |||
1235 | char *F, *R, *flags; | 1340 | char *F, *R, *flags; |
1236 | size_t len_F, len_R; | 1341 | size_t len_F, len_R; |
1237 | int gflag; // global replace flag | 1342 | int gflag; // global replace flag |
1343 | #if ENABLE_FEATURE_VI_UNDO | ||
1344 | int dont_chain_first_item = ALLOW_UNDO; | ||
1345 | #endif | ||
1238 | 1346 | ||
1239 | // F points to the "find" pattern | 1347 | // F points to the "find" pattern |
1240 | // R points to the "replace" pattern | 1348 | // R points to the "replace" pattern |
@@ -1269,9 +1377,13 @@ static void colon(char *buf) | |||
1269 | if (found) { | 1377 | if (found) { |
1270 | uintptr_t bias; | 1378 | uintptr_t bias; |
1271 | // we found the "find" pattern - delete it | 1379 | // we found the "find" pattern - delete it |
1272 | text_hole_delete(found, found + len_F - 1); | 1380 | // For undo support, the first item should not be chained |
1273 | // inset the "replace" patern | 1381 | text_hole_delete(found, found + len_F - 1, dont_chain_first_item); |
1274 | bias = string_insert(found, R); // insert the string | 1382 | #if ENABLE_FEATURE_VI_UNDO |
1383 | dont_chain_first_item = ALLOW_UNDO_CHAIN; | ||
1384 | #endif | ||
1385 | // insert the "replace" patern | ||
1386 | bias = string_insert(found, R, ALLOW_UNDO_CHAIN); | ||
1275 | found += bias; | 1387 | found += bias; |
1276 | ls += bias; | 1388 | ls += bias; |
1277 | /*q += bias; - recalculated anyway */ | 1389 | /*q += bias; - recalculated anyway */ |
@@ -1572,23 +1684,27 @@ static char *find_line(int li) // find begining of line #li | |||
1572 | //----- Dot Movement Routines ---------------------------------- | 1684 | //----- Dot Movement Routines ---------------------------------- |
1573 | static void dot_left(void) | 1685 | static void dot_left(void) |
1574 | { | 1686 | { |
1687 | undo_queue_commit(); | ||
1575 | if (dot > text && dot[-1] != '\n') | 1688 | if (dot > text && dot[-1] != '\n') |
1576 | dot--; | 1689 | dot--; |
1577 | } | 1690 | } |
1578 | 1691 | ||
1579 | static void dot_right(void) | 1692 | static void dot_right(void) |
1580 | { | 1693 | { |
1694 | undo_queue_commit(); | ||
1581 | if (dot < end - 1 && *dot != '\n') | 1695 | if (dot < end - 1 && *dot != '\n') |
1582 | dot++; | 1696 | dot++; |
1583 | } | 1697 | } |
1584 | 1698 | ||
1585 | static void dot_begin(void) | 1699 | static void dot_begin(void) |
1586 | { | 1700 | { |
1701 | undo_queue_commit(); | ||
1587 | dot = begin_line(dot); // return pointer to first char cur line | 1702 | dot = begin_line(dot); // return pointer to first char cur line |
1588 | } | 1703 | } |
1589 | 1704 | ||
1590 | static void dot_end(void) | 1705 | static void dot_end(void) |
1591 | { | 1706 | { |
1707 | undo_queue_commit(); | ||
1592 | dot = end_line(dot); // return pointer to last char cur line | 1708 | dot = end_line(dot); // return pointer to last char cur line |
1593 | } | 1709 | } |
1594 | 1710 | ||
@@ -1614,11 +1730,13 @@ static char *move_to_col(char *p, int l) | |||
1614 | 1730 | ||
1615 | static void dot_next(void) | 1731 | static void dot_next(void) |
1616 | { | 1732 | { |
1733 | undo_queue_commit(); | ||
1617 | dot = next_line(dot); | 1734 | dot = next_line(dot); |
1618 | } | 1735 | } |
1619 | 1736 | ||
1620 | static void dot_prev(void) | 1737 | static void dot_prev(void) |
1621 | { | 1738 | { |
1739 | undo_queue_commit(); | ||
1622 | dot = prev_line(dot); | 1740 | dot = prev_line(dot); |
1623 | } | 1741 | } |
1624 | 1742 | ||
@@ -1626,6 +1744,7 @@ static void dot_scroll(int cnt, int dir) | |||
1626 | { | 1744 | { |
1627 | char *q; | 1745 | char *q; |
1628 | 1746 | ||
1747 | undo_queue_commit(); | ||
1629 | for (; cnt > 0; cnt--) { | 1748 | for (; cnt > 0; cnt--) { |
1630 | if (dir < 0) { | 1749 | if (dir < 0) { |
1631 | // scroll Backwards | 1750 | // scroll Backwards |
@@ -1653,11 +1772,6 @@ static void dot_skip_over_ws(void) | |||
1653 | dot++; | 1772 | dot++; |
1654 | } | 1773 | } |
1655 | 1774 | ||
1656 | static void dot_delete(void) // delete the char at 'dot' | ||
1657 | { | ||
1658 | text_hole_delete(dot, dot); | ||
1659 | } | ||
1660 | |||
1661 | static char *bound_dot(char *p) // make sure text[0] <= P < "end" | 1775 | static char *bound_dot(char *p) // make sure text[0] <= P < "end" |
1662 | { | 1776 | { |
1663 | if (p >= end && end > text) { | 1777 | if (p >= end && end > text) { |
@@ -1804,17 +1918,34 @@ static char *char_search(char *p, const char *pat, int dir, int range) | |||
1804 | 1918 | ||
1805 | #endif /* FEATURE_VI_SEARCH */ | 1919 | #endif /* FEATURE_VI_SEARCH */ |
1806 | 1920 | ||
1807 | static char *char_insert(char *p, char c) // insert the char c at 'p' | 1921 | static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' |
1808 | { | 1922 | { |
1809 | if (c == 22) { // Is this an ctrl-V? | 1923 | if (c == 22) { // Is this an ctrl-V? |
1810 | p += stupid_insert(p, '^'); // use ^ to indicate literal next | 1924 | p += stupid_insert(p, '^'); // use ^ to indicate literal next |
1811 | refresh(FALSE); // show the ^ | 1925 | refresh(FALSE); // show the ^ |
1812 | c = get_one_char(); | 1926 | c = get_one_char(); |
1813 | *p = c; | 1927 | *p = c; |
1814 | p++; | 1928 | #if ENABLE_FEATURE_VI_UNDO |
1929 | switch (undo) { | ||
1930 | case ALLOW_UNDO: | ||
1931 | undo_push(p, 1, UNDO_INS); | ||
1932 | break; | ||
1933 | case ALLOW_UNDO_CHAIN: | ||
1934 | undo_push(p, 1, UNDO_INS_CHAIN); | ||
1935 | break; | ||
1936 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
1937 | case ALLOW_UNDO_QUEUED: | ||
1938 | undo_push(p, 1, UNDO_INS_QUEUED); | ||
1939 | break; | ||
1940 | # endif | ||
1941 | } | ||
1942 | #else | ||
1815 | file_modified++; | 1943 | file_modified++; |
1944 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
1945 | p++; | ||
1816 | } else if (c == 27) { // Is this an ESC? | 1946 | } else if (c == 27) { // Is this an ESC? |
1817 | cmd_mode = 0; | 1947 | cmd_mode = 0; |
1948 | undo_queue_commit(); | ||
1818 | cmdcnt = 0; | 1949 | cmdcnt = 0; |
1819 | end_cmd_q(); // stop adding to q | 1950 | end_cmd_q(); // stop adding to q |
1820 | last_status_cksum = 0; // force status update | 1951 | last_status_cksum = 0; // force status update |
@@ -1825,7 +1956,7 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1825 | // 123456789 | 1956 | // 123456789 |
1826 | if ((p[-1] != '\n') && (dot>text)) { | 1957 | if ((p[-1] != '\n') && (dot>text)) { |
1827 | p--; | 1958 | p--; |
1828 | p = text_hole_delete(p, p); // shrink buffer 1 char | 1959 | p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char |
1829 | } | 1960 | } |
1830 | } else { | 1961 | } else { |
1831 | #if ENABLE_FEATURE_VI_SETOPTS | 1962 | #if ENABLE_FEATURE_VI_SETOPTS |
@@ -1838,6 +1969,27 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1838 | #if ENABLE_FEATURE_VI_SETOPTS | 1969 | #if ENABLE_FEATURE_VI_SETOPTS |
1839 | sp = p; // remember addr of insert | 1970 | sp = p; // remember addr of insert |
1840 | #endif | 1971 | #endif |
1972 | #if ENABLE_FEATURE_VI_UNDO | ||
1973 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
1974 | if (c == '\n') | ||
1975 | undo_queue_commit(); | ||
1976 | # endif | ||
1977 | switch (undo) { | ||
1978 | case ALLOW_UNDO: | ||
1979 | undo_push(p, 1, UNDO_INS); | ||
1980 | break; | ||
1981 | case ALLOW_UNDO_CHAIN: | ||
1982 | undo_push(p, 1, UNDO_INS_CHAIN); | ||
1983 | break; | ||
1984 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
1985 | case ALLOW_UNDO_QUEUED: | ||
1986 | undo_push(p, 1, UNDO_INS_QUEUED); | ||
1987 | break; | ||
1988 | # endif | ||
1989 | } | ||
1990 | #else | ||
1991 | file_modified++; | ||
1992 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
1841 | p += 1 + stupid_insert(p, c); // insert the char | 1993 | p += 1 + stupid_insert(p, c); // insert the char |
1842 | #if ENABLE_FEATURE_VI_SETOPTS | 1994 | #if ENABLE_FEATURE_VI_SETOPTS |
1843 | if (showmatch && strchr(")]}", *sp) != NULL) { | 1995 | if (showmatch && strchr(")]}", *sp) != NULL) { |
@@ -1853,6 +2005,9 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1853 | bias = text_hole_make(p, len); | 2005 | bias = text_hole_make(p, len); |
1854 | p += bias; | 2006 | p += bias; |
1855 | q += bias; | 2007 | q += bias; |
2008 | #if ENABLE_FEATURE_VI_UNDO | ||
2009 | undo_push(p, len, UNDO_INS); | ||
2010 | #endif | ||
1856 | memcpy(p, q, len); | 2011 | memcpy(p, q, len); |
1857 | p += len; | 2012 | p += len; |
1858 | } | 2013 | } |
@@ -1870,7 +2025,6 @@ static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at | |||
1870 | bias = text_hole_make(p, 1); | 2025 | bias = text_hole_make(p, 1); |
1871 | p += bias; | 2026 | p += bias; |
1872 | *p = c; | 2027 | *p = c; |
1873 | //file_modified++; - done by text_hole_make() | ||
1874 | return bias; | 2028 | return bias; |
1875 | } | 2029 | } |
1876 | 2030 | ||
@@ -2051,6 +2205,185 @@ static void showmatching(char *p) | |||
2051 | } | 2205 | } |
2052 | #endif /* FEATURE_VI_SETOPTS */ | 2206 | #endif /* FEATURE_VI_SETOPTS */ |
2053 | 2207 | ||
2208 | #if ENABLE_FEATURE_VI_UNDO | ||
2209 | // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com) | ||
2210 | static void undo_push(char *src, unsigned int length, unsigned char u_type) // Add to the undo stack | ||
2211 | { | ||
2212 | struct undo_object *undo_temp; | ||
2213 | // "u_type" values | ||
2214 | // UNDO_INS: insertion, undo will remove from buffer | ||
2215 | // UNDO_DEL: deleted text, undo will restore to buffer | ||
2216 | // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete | ||
2217 | // The CHAIN operations are for handling multiple operations that the user | ||
2218 | // performs with a single action, i.e. REPLACE mode or find-and-replace commands | ||
2219 | // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue | ||
2220 | // for the INS/DEL operation. The raw values should be equal to the values of | ||
2221 | // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG | ||
2222 | |||
2223 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2224 | // This undo queuing functionality groups multiple character typing or backspaces | ||
2225 | // into a single large undo object. This greatly reduces calls to malloc() for | ||
2226 | // single-character operations while typing and has the side benefit of letting | ||
2227 | // an undo operation remove chunks of text rather than a single character. | ||
2228 | switch (u_type) { | ||
2229 | case UNDO_EMPTY: // Just in case this ever happens... | ||
2230 | return; | ||
2231 | case UNDO_DEL_QUEUED: | ||
2232 | if (length != 1) | ||
2233 | return; // Only queue single characters | ||
2234 | switch (undo_queue_state) { | ||
2235 | case UNDO_EMPTY: | ||
2236 | undo_queue_state = UNDO_DEL; | ||
2237 | case UNDO_DEL: | ||
2238 | undo_queue_spos = src; | ||
2239 | undo_q++; | ||
2240 | undo_queue[(CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q)] = *src; | ||
2241 | // If queue is full, dump it into an object | ||
2242 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) | ||
2243 | undo_queue_commit(); | ||
2244 | return; | ||
2245 | case UNDO_INS: | ||
2246 | // Switch from storing inserted text to deleted text | ||
2247 | undo_queue_commit(); | ||
2248 | undo_push(src, length, UNDO_DEL_QUEUED); | ||
2249 | return; | ||
2250 | } | ||
2251 | break; | ||
2252 | case UNDO_INS_QUEUED: | ||
2253 | if (length != 1) | ||
2254 | return; | ||
2255 | switch (undo_queue_state) { | ||
2256 | case UNDO_EMPTY: | ||
2257 | undo_queue_state = UNDO_INS; | ||
2258 | undo_queue_spos = src; | ||
2259 | case UNDO_INS: | ||
2260 | undo_q++; // Don't need to save any data for insertions | ||
2261 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) | ||
2262 | undo_queue_commit(); | ||
2263 | return; | ||
2264 | case UNDO_DEL: | ||
2265 | // Switch from storing deleted text to inserted text | ||
2266 | undo_queue_commit(); | ||
2267 | undo_push(src, length, UNDO_INS_QUEUED); | ||
2268 | return; | ||
2269 | } | ||
2270 | break; | ||
2271 | } | ||
2272 | #else | ||
2273 | // If undo queuing is disabled, ignore the queuing flag entirely | ||
2274 | u_type = u_type & ~UNDO_QUEUED_FLAG; | ||
2275 | #endif | ||
2276 | |||
2277 | // Allocate a new undo object and use it as the stack tail | ||
2278 | undo_temp = undo_stack_tail; | ||
2279 | undo_stack_tail = xmalloc(sizeof(struct undo_object)); | ||
2280 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2281 | if ((u_type & UNDO_USE_SPOS) != 0) { | ||
2282 | undo_stack_tail->start = undo_queue_spos - text; // use start position from queue | ||
2283 | } else { | ||
2284 | undo_stack_tail->start = src - text; // use offset from start of text buffer | ||
2285 | } | ||
2286 | u_type = (u_type & ~UNDO_USE_SPOS); | ||
2287 | #else | ||
2288 | undo_stack_tail->start = src - text; | ||
2289 | #endif /* ENABLE_FEATURE_VI_UNDO_QUEUE */ | ||
2290 | // For UNDO_DEL objects, copy the deleted text somewhere | ||
2291 | switch (u_type) { | ||
2292 | case UNDO_DEL: | ||
2293 | case UNDO_DEL_CHAIN: | ||
2294 | if ((src + length) == end) | ||
2295 | length--; | ||
2296 | // If this deletion empties text[], strip the newline. When the buffer becomes | ||
2297 | // zero-length, a newline is added back, which requires this to compensate. | ||
2298 | undo_stack_tail->undo_text = xmalloc(length); | ||
2299 | memcpy(undo_stack_tail->undo_text, src, length); | ||
2300 | break; | ||
2301 | } | ||
2302 | undo_stack_tail->prev = undo_temp; | ||
2303 | undo_stack_tail->length = length; | ||
2304 | undo_stack_tail->u_type = u_type; | ||
2305 | file_modified++; | ||
2306 | } | ||
2307 | |||
2308 | static void undo_pop(void) // Undo the last operation | ||
2309 | { | ||
2310 | int repeat = 0; | ||
2311 | char *u_start, *u_end; | ||
2312 | struct undo_object *undo_temp; | ||
2313 | |||
2314 | // Commit pending undo queue before popping (should be unnecessary) | ||
2315 | undo_queue_commit(); | ||
2316 | |||
2317 | // Check for an empty undo stack | ||
2318 | if (undo_stack_tail == NULL) { | ||
2319 | status_line("Already at oldest change"); | ||
2320 | return; | ||
2321 | } | ||
2322 | |||
2323 | switch (undo_stack_tail->u_type) { | ||
2324 | case UNDO_DEL: | ||
2325 | case UNDO_DEL_CHAIN: | ||
2326 | // make hole and put in text that was deleted; deallocate text | ||
2327 | u_start = text + undo_stack_tail->start; | ||
2328 | text_hole_make(u_start, undo_stack_tail->length); | ||
2329 | memcpy(u_start, undo_stack_tail->undo_text, undo_stack_tail->length); | ||
2330 | free(undo_stack_tail->undo_text); | ||
2331 | status_line("Undo [%d] %s %d chars at position %d", | ||
2332 | file_modified, "restored", | ||
2333 | undo_stack_tail->length, undo_stack_tail->start); | ||
2334 | break; | ||
2335 | case UNDO_INS: | ||
2336 | case UNDO_INS_CHAIN: | ||
2337 | // delete what was inserted | ||
2338 | u_start = undo_stack_tail->start + text; | ||
2339 | u_end = u_start - 1 + undo_stack_tail->length; | ||
2340 | text_hole_delete(u_start, u_end, NO_UNDO); | ||
2341 | status_line("Undo [%d] %s %d chars at position %d", | ||
2342 | file_modified, "deleted", | ||
2343 | undo_stack_tail->length, undo_stack_tail->start); | ||
2344 | break; | ||
2345 | } | ||
2346 | // For chained operations, continue popping all the way down the chain. | ||
2347 | // If this is the end of a chain, lower modification count and refresh display | ||
2348 | switch (undo_stack_tail->u_type) { | ||
2349 | case UNDO_DEL: | ||
2350 | case UNDO_INS: | ||
2351 | dot = (text + undo_stack_tail->start); | ||
2352 | refresh(FALSE); | ||
2353 | break; | ||
2354 | case UNDO_DEL_CHAIN: | ||
2355 | case UNDO_INS_CHAIN: | ||
2356 | repeat = 1; | ||
2357 | break; | ||
2358 | } | ||
2359 | // Deallocate the undo object we just processed | ||
2360 | undo_temp = undo_stack_tail->prev; | ||
2361 | free(undo_stack_tail); | ||
2362 | undo_stack_tail = undo_temp; | ||
2363 | file_modified--; | ||
2364 | if (repeat == 1) { | ||
2365 | undo_pop(); // Follow the undo chain if one exists | ||
2366 | } | ||
2367 | } | ||
2368 | |||
2369 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2370 | static void undo_queue_commit(void) // Flush any queued objects to the undo stack | ||
2371 | { | ||
2372 | // Pushes the queue object onto the undo stack | ||
2373 | if (undo_q > 0) { | ||
2374 | // Deleted character undo events grow from the end | ||
2375 | undo_push((undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q), | ||
2376 | undo_q, | ||
2377 | (undo_queue_state | UNDO_USE_SPOS) | ||
2378 | ); | ||
2379 | undo_queue_state = UNDO_EMPTY; | ||
2380 | undo_q = 0; | ||
2381 | } | ||
2382 | } | ||
2383 | #endif | ||
2384 | |||
2385 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
2386 | |||
2054 | // open a hole in text[] | 2387 | // open a hole in text[] |
2055 | // might reallocate text[]! use p += text_hole_make(p, ...), | 2388 | // might reallocate text[]! use p += text_hole_make(p, ...), |
2056 | // and be careful to not use pointers into potentially freed text[]! | 2389 | // and be careful to not use pointers into potentially freed text[]! |
@@ -2082,12 +2415,12 @@ static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte | |||
2082 | } | 2415 | } |
2083 | memmove(p + size, p, end - size - p); | 2416 | memmove(p + size, p, end - size - p); |
2084 | memset(p, ' ', size); // clear new hole | 2417 | memset(p, ' ', size); // clear new hole |
2085 | file_modified++; | ||
2086 | return bias; | 2418 | return bias; |
2087 | } | 2419 | } |
2088 | 2420 | ||
2089 | // close a hole in text[] | 2421 | // close a hole in text[] |
2090 | static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive | 2422 | // "undo" value indicates if this operation should be undo-able |
2423 | static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive | ||
2091 | { | 2424 | { |
2092 | char *src, *dest; | 2425 | char *src, *dest; |
2093 | int cnt, hole_size; | 2426 | int cnt, hole_size; |
@@ -2102,10 +2435,29 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2102 | } | 2435 | } |
2103 | hole_size = q - p + 1; | 2436 | hole_size = q - p + 1; |
2104 | cnt = end - src; | 2437 | cnt = end - src; |
2438 | #if ENABLE_FEATURE_VI_UNDO | ||
2439 | switch (undo) { | ||
2440 | case NO_UNDO: | ||
2441 | break; | ||
2442 | case ALLOW_UNDO: | ||
2443 | undo_push(p, hole_size, UNDO_DEL); | ||
2444 | break; | ||
2445 | case ALLOW_UNDO_CHAIN: | ||
2446 | undo_push(p, hole_size, UNDO_DEL_CHAIN); | ||
2447 | break; | ||
2448 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2449 | case ALLOW_UNDO_QUEUED: | ||
2450 | undo_push(p, hole_size, UNDO_DEL_QUEUED); | ||
2451 | break; | ||
2452 | # endif | ||
2453 | } | ||
2454 | file_modified--; | ||
2455 | #endif | ||
2105 | if (src < text || src > end) | 2456 | if (src < text || src > end) |
2106 | goto thd0; | 2457 | goto thd0; |
2107 | if (dest < text || dest >= end) | 2458 | if (dest < text || dest >= end) |
2108 | goto thd0; | 2459 | goto thd0; |
2460 | file_modified++; | ||
2109 | if (src >= end) | 2461 | if (src >= end) |
2110 | goto thd_atend; // just delete the end of the buffer | 2462 | goto thd_atend; // just delete the end of the buffer |
2111 | memmove(dest, src, cnt); | 2463 | memmove(dest, src, cnt); |
@@ -2115,7 +2467,6 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2115 | dest = end - 1; // make sure dest in below end-1 | 2467 | dest = end - 1; // make sure dest in below end-1 |
2116 | if (end <= text) | 2468 | if (end <= text) |
2117 | dest = end = text; // keep pointers valid | 2469 | dest = end = text; // keep pointers valid |
2118 | file_modified++; | ||
2119 | thd0: | 2470 | thd0: |
2120 | return dest; | 2471 | return dest; |
2121 | } | 2472 | } |
@@ -2123,7 +2474,7 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2123 | // copy text into register, then delete text. | 2474 | // copy text into register, then delete text. |
2124 | // if dist <= 0, do not include, or go past, a NewLine | 2475 | // if dist <= 0, do not include, or go past, a NewLine |
2125 | // | 2476 | // |
2126 | static char *yank_delete(char *start, char *stop, int dist, int yf) | 2477 | static char *yank_delete(char *start, char *stop, int dist, int yf, int undo) |
2127 | { | 2478 | { |
2128 | char *p; | 2479 | char *p; |
2129 | 2480 | ||
@@ -2152,7 +2503,7 @@ static char *yank_delete(char *start, char *stop, int dist, int yf) | |||
2152 | text_yank(start, stop, YDreg); | 2503 | text_yank(start, stop, YDreg); |
2153 | #endif | 2504 | #endif |
2154 | if (yf == YANKDEL) { | 2505 | if (yf == YANKDEL) { |
2155 | p = text_hole_delete(start, stop); | 2506 | p = text_hole_delete(start, stop, undo); |
2156 | } // delete lines | 2507 | } // delete lines |
2157 | return p; | 2508 | return p; |
2158 | } | 2509 | } |
@@ -2218,12 +2569,22 @@ static void end_cmd_q(void) | |||
2218 | || ENABLE_FEATURE_VI_CRASHME | 2569 | || ENABLE_FEATURE_VI_CRASHME |
2219 | // might reallocate text[]! use p += string_insert(p, ...), | 2570 | // might reallocate text[]! use p += string_insert(p, ...), |
2220 | // and be careful to not use pointers into potentially freed text[]! | 2571 | // and be careful to not use pointers into potentially freed text[]! |
2221 | static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p' | 2572 | static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' |
2222 | { | 2573 | { |
2223 | uintptr_t bias; | 2574 | uintptr_t bias; |
2224 | int i; | 2575 | int i; |
2225 | 2576 | ||
2226 | i = strlen(s); | 2577 | i = strlen(s); |
2578 | #if ENABLE_FEATURE_VI_UNDO | ||
2579 | switch (undo) { | ||
2580 | case ALLOW_UNDO: | ||
2581 | undo_push(p, i, UNDO_INS); | ||
2582 | break; | ||
2583 | case ALLOW_UNDO_CHAIN: | ||
2584 | undo_push(p, i, UNDO_INS_CHAIN); | ||
2585 | break; | ||
2586 | } | ||
2587 | #endif | ||
2227 | bias = text_hole_make(p, i); | 2588 | bias = text_hole_make(p, i); |
2228 | p += bias; | 2589 | p += bias; |
2229 | memcpy(p, s, i); | 2590 | memcpy(p, s, i); |
@@ -2516,14 +2877,14 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2516 | cnt = safe_read(fd, p, size); | 2877 | cnt = safe_read(fd, p, size); |
2517 | if (cnt < 0) { | 2878 | if (cnt < 0) { |
2518 | status_line_bold_errno(fn); | 2879 | status_line_bold_errno(fn); |
2519 | p = text_hole_delete(p, p + size - 1); // un-do buffer insert | 2880 | p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert |
2520 | } else if (cnt < size) { | 2881 | } else if (cnt < size) { |
2521 | // There was a partial read, shrink unused space text[] | 2882 | // There was a partial read, shrink unused space text[] |
2522 | p = text_hole_delete(p + cnt, p + size - 1); // un-do buffer insert | 2883 | p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); // un-do buffer insert |
2523 | status_line_bold("can't read '%s'", fn); | 2884 | status_line_bold("can't read '%s'", fn); |
2524 | } | 2885 | } |
2525 | if (cnt >= size) | 2886 | // if (cnt >= size) |
2526 | file_modified++; | 2887 | // file_modified++; |
2527 | close(fd); | 2888 | close(fd); |
2528 | fi0: | 2889 | fi0: |
2529 | #if ENABLE_FEATURE_VI_READONLY | 2890 | #if ENABLE_FEATURE_VI_READONLY |
@@ -3048,11 +3409,12 @@ static void do_cmd(int c) | |||
3048 | if (*dot == '\n') { | 3409 | if (*dot == '\n') { |
3049 | // don't Replace past E-o-l | 3410 | // don't Replace past E-o-l |
3050 | cmd_mode = 1; // convert to insert | 3411 | cmd_mode = 1; // convert to insert |
3412 | undo_queue_commit(); | ||
3051 | } else { | 3413 | } else { |
3052 | if (1 <= c || Isprint(c)) { | 3414 | if (1 <= c || Isprint(c)) { |
3053 | if (c != 27) | 3415 | if (c != 27) |
3054 | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char | 3416 | dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char |
3055 | dot = char_insert(dot, c); // insert new char | 3417 | dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char |
3056 | } | 3418 | } |
3057 | goto dc1; | 3419 | goto dc1; |
3058 | } | 3420 | } |
@@ -3062,7 +3424,7 @@ static void do_cmd(int c) | |||
3062 | if (c == KEYCODE_INSERT) goto dc5; | 3424 | if (c == KEYCODE_INSERT) goto dc5; |
3063 | // insert the char c at "dot" | 3425 | // insert the char c at "dot" |
3064 | if (1 <= c || Isprint(c)) { | 3426 | if (1 <= c || Isprint(c)) { |
3065 | dot = char_insert(dot, c); | 3427 | dot = char_insert(dot, c, ALLOW_UNDO_QUEUED); |
3066 | } | 3428 | } |
3067 | goto dc1; | 3429 | goto dc1; |
3068 | } | 3430 | } |
@@ -3108,7 +3470,6 @@ static void do_cmd(int c) | |||
3108 | //case ']': // ]- | 3470 | //case ']': // ]- |
3109 | //case '_': // _- | 3471 | //case '_': // _- |
3110 | //case '`': // `- | 3472 | //case '`': // `- |
3111 | //case 'u': // u- FIXME- there is no undo | ||
3112 | //case 'v': // v- | 3473 | //case 'v': // v- |
3113 | default: // unrecognized command | 3474 | default: // unrecognized command |
3114 | buf[0] = c; | 3475 | buf[0] = c; |
@@ -3177,6 +3538,7 @@ static void do_cmd(int c) | |||
3177 | if (cmd_mode == 0) | 3538 | if (cmd_mode == 0) |
3178 | indicate_error(c); | 3539 | indicate_error(c); |
3179 | cmd_mode = 0; // stop insrting | 3540 | cmd_mode = 0; // stop insrting |
3541 | undo_queue_commit(); | ||
3180 | end_cmd_q(); | 3542 | end_cmd_q(); |
3181 | last_status_cksum = 0; // force status update | 3543 | last_status_cksum = 0; // force status update |
3182 | break; | 3544 | break; |
@@ -3251,15 +3613,20 @@ static void do_cmd(int c) | |||
3251 | if (c == 'p') | 3613 | if (c == 'p') |
3252 | dot_right(); // move to right, can move to NL | 3614 | dot_right(); // move to right, can move to NL |
3253 | } | 3615 | } |
3254 | string_insert(dot, p); // insert the string | 3616 | string_insert(dot, p, ALLOW_UNDO); // insert the string |
3255 | end_cmd_q(); // stop adding to q | 3617 | end_cmd_q(); // stop adding to q |
3256 | break; | 3618 | break; |
3619 | #if ENABLE_FEATURE_VI_UNDO | ||
3620 | case 'u': // u- undo last operation | ||
3621 | undo_pop(); | ||
3622 | break; | ||
3623 | #endif | ||
3257 | case 'U': // U- Undo; replace current line with original version | 3624 | case 'U': // U- Undo; replace current line with original version |
3258 | if (reg[Ureg] != NULL) { | 3625 | if (reg[Ureg] != NULL) { |
3259 | p = begin_line(dot); | 3626 | p = begin_line(dot); |
3260 | q = end_line(dot); | 3627 | q = end_line(dot); |
3261 | p = text_hole_delete(p, q); // delete cur line | 3628 | p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line |
3262 | p += string_insert(p, reg[Ureg]); // insert orig line | 3629 | p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line |
3263 | dot = p; | 3630 | dot = p; |
3264 | dot_skip_over_ws(); | 3631 | dot_skip_over_ws(); |
3265 | } | 3632 | } |
@@ -3485,7 +3852,7 @@ static void do_cmd(int c) | |||
3485 | cnt = count_lines(text, dot); // remember what line we are on | 3852 | cnt = count_lines(text, dot); // remember what line we are on |
3486 | c1 = get_one_char(); // get the type of thing to delete | 3853 | c1 = get_one_char(); // get the type of thing to delete |
3487 | find_range(&p, &q, c1); | 3854 | find_range(&p, &q, c1); |
3488 | yank_delete(p, q, 1, YANKONLY); // save copy before change | 3855 | yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change |
3489 | p = begin_line(p); | 3856 | p = begin_line(p); |
3490 | q = end_line(q); | 3857 | q = end_line(q); |
3491 | i = count_lines(p, q); // # of lines we are shifting | 3858 | i = count_lines(p, q); // # of lines we are shifting |
@@ -3494,16 +3861,16 @@ static void do_cmd(int c) | |||
3494 | // shift left- remove tab or 8 spaces | 3861 | // shift left- remove tab or 8 spaces |
3495 | if (*p == '\t') { | 3862 | if (*p == '\t') { |
3496 | // shrink buffer 1 char | 3863 | // shrink buffer 1 char |
3497 | text_hole_delete(p, p); | 3864 | text_hole_delete(p, p, NO_UNDO); |
3498 | } else if (*p == ' ') { | 3865 | } else if (*p == ' ') { |
3499 | // we should be calculating columns, not just SPACE | 3866 | // we should be calculating columns, not just SPACE |
3500 | for (j = 0; *p == ' ' && j < tabstop; j++) { | 3867 | for (j = 0; *p == ' ' && j < tabstop; j++) { |
3501 | text_hole_delete(p, p); | 3868 | text_hole_delete(p, p, NO_UNDO); |
3502 | } | 3869 | } |
3503 | } | 3870 | } |
3504 | } else if (c == '>') { | 3871 | } else if (c == '>') { |
3505 | // shift right -- add tab or 8 spaces | 3872 | // shift right -- add tab or 8 spaces |
3506 | char_insert(p, '\t'); | 3873 | char_insert(p, '\t', ALLOW_UNDO); |
3507 | } | 3874 | } |
3508 | } | 3875 | } |
3509 | dot = find_line(cnt); // what line were we on | 3876 | dot = find_line(cnt); // what line were we on |
@@ -3538,7 +3905,7 @@ static void do_cmd(int c) | |||
3538 | save_dot = dot; | 3905 | save_dot = dot; |
3539 | dot = dollar_line(dot); // move to before NL | 3906 | dot = dollar_line(dot); // move to before NL |
3540 | // copy text into a register and delete | 3907 | // copy text into a register and delete |
3541 | dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l | 3908 | dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l |
3542 | if (c == 'C') | 3909 | if (c == 'C') |
3543 | goto dc_i; // start inserting | 3910 | goto dc_i; // start inserting |
3544 | #if ENABLE_FEATURE_VI_DOT_CMD | 3911 | #if ENABLE_FEATURE_VI_DOT_CMD |
@@ -3583,15 +3950,22 @@ static void do_cmd(int c) | |||
3583 | case KEYCODE_INSERT: // Cursor Key Insert | 3950 | case KEYCODE_INSERT: // Cursor Key Insert |
3584 | dc_i: | 3951 | dc_i: |
3585 | cmd_mode = 1; // start inserting | 3952 | cmd_mode = 1; // start inserting |
3953 | undo_queue_commit(); // commit queue when cmd_mode changes | ||
3586 | break; | 3954 | break; |
3587 | case 'J': // J- join current and next lines together | 3955 | case 'J': // J- join current and next lines together |
3588 | do { | 3956 | do { |
3589 | dot_end(); // move to NL | 3957 | dot_end(); // move to NL |
3590 | if (dot < end - 1) { // make sure not last char in text[] | 3958 | if (dot < end - 1) { // make sure not last char in text[] |
3959 | #if ENABLE_FEATURE_VI_UNDO | ||
3960 | undo_push(dot, 1, UNDO_DEL); | ||
3591 | *dot++ = ' '; // replace NL with space | 3961 | *dot++ = ' '; // replace NL with space |
3962 | undo_push((dot - 1), 1, UNDO_INS_CHAIN); | ||
3963 | #else | ||
3964 | *dot++ = ' '; | ||
3592 | file_modified++; | 3965 | file_modified++; |
3966 | #endif | ||
3593 | while (isblank(*dot)) { // delete leading WS | 3967 | while (isblank(*dot)) { // delete leading WS |
3594 | dot_delete(); | 3968 | text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN); |
3595 | } | 3969 | } |
3596 | } | 3970 | } |
3597 | } while (--cmdcnt > 0); | 3971 | } while (--cmdcnt > 0); |
@@ -3620,10 +3994,10 @@ static void do_cmd(int c) | |||
3620 | dot_prev(); | 3994 | dot_prev(); |
3621 | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." | 3995 | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." |
3622 | dot_end(); | 3996 | dot_end(); |
3623 | dot = char_insert(dot, '\n'); | 3997 | dot = char_insert(dot, '\n', ALLOW_UNDO); |
3624 | } else { | 3998 | } else { |
3625 | dot_begin(); // 0 | 3999 | dot_begin(); // 0 |
3626 | dot = char_insert(dot, '\n'); // i\n ESC | 4000 | dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC |
3627 | dot_prev(); // - | 4001 | dot_prev(); // - |
3628 | } | 4002 | } |
3629 | goto dc_i; | 4003 | goto dc_i; |
@@ -3631,6 +4005,7 @@ static void do_cmd(int c) | |||
3631 | case 'R': // R- continuous Replace char | 4005 | case 'R': // R- continuous Replace char |
3632 | dc5: | 4006 | dc5: |
3633 | cmd_mode = 2; | 4007 | cmd_mode = 2; |
4008 | undo_queue_commit(); | ||
3634 | break; | 4009 | break; |
3635 | case KEYCODE_DELETE: | 4010 | case KEYCODE_DELETE: |
3636 | c = 'x'; | 4011 | c = 'x'; |
@@ -3645,7 +4020,7 @@ static void do_cmd(int c) | |||
3645 | if (dot[dir] != '\n') { | 4020 | if (dot[dir] != '\n') { |
3646 | if (c == 'X') | 4021 | if (c == 'X') |
3647 | dot--; // delete prev char | 4022 | dot--; // delete prev char |
3648 | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char | 4023 | dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char |
3649 | } | 4024 | } |
3650 | } while (--cmdcnt > 0); | 4025 | } while (--cmdcnt > 0); |
3651 | end_cmd_q(); // stop adding to q | 4026 | end_cmd_q(); // stop adding to q |
@@ -3716,6 +4091,7 @@ static void do_cmd(int c) | |||
3716 | c1 = get_one_char(); // get the type of thing to delete | 4091 | c1 = get_one_char(); // get the type of thing to delete |
3717 | // determine range, and whether it spans lines | 4092 | // determine range, and whether it spans lines |
3718 | ml = find_range(&p, &q, c1); | 4093 | ml = find_range(&p, &q, c1); |
4094 | place_cursor(0, 0); | ||
3719 | if (c1 == 27) { // ESC- user changed mind and wants out | 4095 | if (c1 == 27) { // ESC- user changed mind and wants out |
3720 | c = c1 = 27; // Escape- do nothing | 4096 | c = c1 = 27; // Escape- do nothing |
3721 | } else if (strchr("wW", c1)) { | 4097 | } else if (strchr("wW", c1)) { |
@@ -3727,13 +4103,13 @@ static void do_cmd(int c) | |||
3727 | q--; | 4103 | q--; |
3728 | } | 4104 | } |
3729 | } | 4105 | } |
3730 | dot = yank_delete(p, q, ml, yf); // delete word | 4106 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word |
3731 | } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { | 4107 | } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { |
3732 | // partial line copy text into a register and delete | 4108 | // partial line copy text into a register and delete |
3733 | dot = yank_delete(p, q, ml, yf); // delete word | 4109 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word |
3734 | } else if (strchr("cdykjHL+-{}\r\n", c1)) { | 4110 | } else if (strchr("cdykjHL+-{}\r\n", c1)) { |
3735 | // whole line copy text into a register and delete | 4111 | // whole line copy text into a register and delete |
3736 | dot = yank_delete(p, q, ml, yf); // delete lines | 4112 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines |
3737 | whole = 1; | 4113 | whole = 1; |
3738 | } else { | 4114 | } else { |
3739 | // could not recognize object | 4115 | // could not recognize object |
@@ -3743,7 +4119,7 @@ static void do_cmd(int c) | |||
3743 | } | 4119 | } |
3744 | if (ml && whole) { | 4120 | if (ml && whole) { |
3745 | if (c == 'c') { | 4121 | if (c == 'c') { |
3746 | dot = char_insert(dot, '\n'); | 4122 | dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN); |
3747 | // on the last line of file don't move to prev line | 4123 | // on the last line of file don't move to prev line |
3748 | if (whole && dot != (end-1)) { | 4124 | if (whole && dot != (end-1)) { |
3749 | dot_prev(); | 4125 | dot_prev(); |
@@ -3789,8 +4165,14 @@ static void do_cmd(int c) | |||
3789 | case 'r': // r- replace the current char with user input | 4165 | case 'r': // r- replace the current char with user input |
3790 | c1 = get_one_char(); // get the replacement char | 4166 | c1 = get_one_char(); // get the replacement char |
3791 | if (*dot != '\n') { | 4167 | if (*dot != '\n') { |
4168 | #if ENABLE_FEATURE_VI_UNDO | ||
4169 | undo_push(dot, 1, UNDO_DEL); | ||
4170 | *dot = c1; | ||
4171 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4172 | #else | ||
3792 | *dot = c1; | 4173 | *dot = c1; |
3793 | file_modified++; | 4174 | file_modified++; |
4175 | #endif | ||
3794 | } | 4176 | } |
3795 | end_cmd_q(); // stop adding to q | 4177 | end_cmd_q(); // stop adding to q |
3796 | break; | 4178 | break; |
@@ -3830,6 +4212,17 @@ static void do_cmd(int c) | |||
3830 | break; | 4212 | break; |
3831 | case '~': // ~- flip the case of letters a-z -> A-Z | 4213 | case '~': // ~- flip the case of letters a-z -> A-Z |
3832 | do { | 4214 | do { |
4215 | #if ENABLE_FEATURE_VI_UNDO | ||
4216 | if (islower(*dot)) { | ||
4217 | undo_push(dot, 1, UNDO_DEL); | ||
4218 | *dot = toupper(*dot); | ||
4219 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4220 | } else if (isupper(*dot)) { | ||
4221 | undo_push(dot, 1, UNDO_DEL); | ||
4222 | *dot = tolower(*dot); | ||
4223 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4224 | } | ||
4225 | #else | ||
3833 | if (islower(*dot)) { | 4226 | if (islower(*dot)) { |
3834 | *dot = toupper(*dot); | 4227 | *dot = toupper(*dot); |
3835 | file_modified++; | 4228 | file_modified++; |
@@ -3837,6 +4230,7 @@ static void do_cmd(int c) | |||
3837 | *dot = tolower(*dot); | 4230 | *dot = tolower(*dot); |
3838 | file_modified++; | 4231 | file_modified++; |
3839 | } | 4232 | } |
4233 | #endif | ||
3840 | dot_right(); | 4234 | dot_right(); |
3841 | } while (--cmdcnt > 0); | 4235 | } while (--cmdcnt > 0); |
3842 | end_cmd_q(); // stop adding to q | 4236 | end_cmd_q(); // stop adding to q |
@@ -3866,7 +4260,7 @@ static void do_cmd(int c) | |||
3866 | dc1: | 4260 | dc1: |
3867 | // if text[] just became empty, add back an empty line | 4261 | // if text[] just became empty, add back an empty line |
3868 | if (end == text) { | 4262 | if (end == text) { |
3869 | char_insert(text, '\n'); // start empty buf with dummy line | 4263 | char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line |
3870 | dot = text; | 4264 | dot = text; |
3871 | } | 4265 | } |
3872 | // it is OK for dot to exactly equal to end, otherwise check dot validity | 4266 | // it is OK for dot to exactly equal to end, otherwise check dot validity |