diff options
Diffstat (limited to 'editors')
-rw-r--r-- | editors/vi.c | 795 |
1 files changed, 606 insertions, 189 deletions
diff --git a/editors/vi.c b/editors/vi.c index ab49b3f7d..a6505e0bf 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 | ||
@@ -277,8 +306,8 @@ struct globals { | |||
277 | smallint editing; // >0 while we are editing a file | 306 | smallint editing; // >0 while we are editing a file |
278 | // [code audit says "can be 0, 1 or 2 only"] | 307 | // [code audit says "can be 0, 1 or 2 only"] |
279 | smallint cmd_mode; // 0=command 1=insert 2=replace | 308 | smallint cmd_mode; // 0=command 1=insert 2=replace |
280 | int file_modified; // buffer contents changed (counter, not flag!) | 309 | int modified_count; // buffer contents changed if !0 |
281 | int last_file_modified; // = -1; | 310 | int last_modified_count; // = -1; |
282 | int save_argc; // how many file names on cmd line | 311 | int save_argc; // how many file names on cmd line |
283 | int cmdcnt; // repetition count | 312 | int cmdcnt; // repetition count |
284 | unsigned rows, columns; // the terminal screen is this size | 313 | unsigned rows, columns; // the terminal screen is this size |
@@ -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 | ||
405 | |||
406 | struct undo_object { | ||
407 | struct undo_object *prev; // Linking back avoids list traversal (LIFO) | ||
408 | int start; // Offset where the data should be restored/deleted | ||
409 | int length; // total data size | ||
410 | uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped | ||
411 | char undo_text[1]; // text that was deleted (if deletion) | ||
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 ) |
@@ -358,8 +423,8 @@ struct globals { | |||
358 | #define vi_setops (G.vi_setops ) | 423 | #define vi_setops (G.vi_setops ) |
359 | #define editing (G.editing ) | 424 | #define editing (G.editing ) |
360 | #define cmd_mode (G.cmd_mode ) | 425 | #define cmd_mode (G.cmd_mode ) |
361 | #define file_modified (G.file_modified ) | 426 | #define modified_count (G.modified_count ) |
362 | #define last_file_modified (G.last_file_modified ) | 427 | #define last_modified_count (G.last_modified_count) |
363 | #define save_argc (G.save_argc ) | 428 | #define save_argc (G.save_argc ) |
364 | #define cmdcnt (G.cmdcnt ) | 429 | #define cmdcnt (G.cmdcnt ) |
365 | #define rows (G.rows ) | 430 | #define rows (G.rows ) |
@@ -408,15 +473,24 @@ 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_modified_count = -1; \ |
414 | /* "" but has space for 2 chars: */ \ | 489 | /* "" but has space for 2 chars: */ \ |
415 | IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \ | 490 | IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \ |
416 | } while (0) | 491 | } while (0) |
417 | 492 | ||
418 | 493 | ||
419 | static int init_text_buffer(char *); // init from file or create new | ||
420 | static void edit_file(char *); // edit one file | 494 | static void edit_file(char *); // edit one file |
421 | static void do_cmd(int); // execute a command | 495 | static void do_cmd(int); // execute a command |
422 | static int next_tabstop(int); | 496 | static int next_tabstop(int); |
@@ -437,10 +511,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 | 511 | 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 | 512 | static void dot_scroll(int, int); // move the screen up or down |
439 | static void dot_skip_over_ws(void); // move dot pat WS | 513 | 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" | 514 | static char *bound_dot(char *); // make sure text[0] <= P < "end" |
442 | static char *new_screen(int, int); // malloc virtual screen memory | 515 | static char *new_screen(int, int); // malloc virtual screen memory |
443 | static char *char_insert(char *, char); // insert the char c at 'p' | 516 | #if !ENABLE_FEATURE_VI_UNDO |
517 | #define char_insert(a,b,c) char_insert(a,b) | ||
518 | #endif | ||
519 | static char *char_insert(char *, char, int); // insert the char c at 'p' | ||
444 | // might reallocate text[]! use p += stupid_insert(p, ...), | 520 | // might reallocate text[]! use p += stupid_insert(p, ...), |
445 | // and be careful to not use pointers into potentially freed text[]! | 521 | // 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' | 522 | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' |
@@ -448,11 +524,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() | 524 | static int st_test(char *, int, int, char *); // helper for skip_thing() |
449 | static char *skip_thing(char *, int, int, int); // skip some object | 525 | static char *skip_thing(char *, int, int, int); // skip some object |
450 | static char *find_pair(char *, char); // find matching pair () [] {} | 526 | static char *find_pair(char *, char); // find matching pair () [] {} |
451 | static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole | 527 | #if !ENABLE_FEATURE_VI_UNDO |
528 | #define text_hole_delete(a,b,c) text_hole_delete(a,b) | ||
529 | #endif | ||
530 | 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, ...), | 531 | // might reallocate text[]! use p += text_hole_make(p, ...), |
453 | // and be careful to not use pointers into potentially freed text[]! | 532 | // 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 | 533 | 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 | 534 | #if !ENABLE_FEATURE_VI_UNDO |
535 | #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) | ||
536 | #endif | ||
537 | 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 | 538 | static void show_help(void); // display some help info |
457 | static void rawmode(void); // set "raw" mode on tty | 539 | static void rawmode(void); // set "raw" mode on tty |
458 | static void cookmode(void); // return to "cooked" mode on tty | 540 | static void cookmode(void); // return to "cooked" mode on tty |
@@ -460,7 +542,6 @@ static void cookmode(void); // return to "cooked" mode on tty | |||
460 | static int mysleep(int); | 542 | static int mysleep(int); |
461 | static int readit(void); // read (maybe cursor) key from stdin | 543 | static int readit(void); // read (maybe cursor) key from stdin |
462 | static int get_one_char(void); // read 1 char from stdin | 544 | static int get_one_char(void); // read 1 char from stdin |
463 | static int file_size(const char *); // what is the byte size of "fn" | ||
464 | #if !ENABLE_FEATURE_VI_READONLY | 545 | #if !ENABLE_FEATURE_VI_READONLY |
465 | #define file_insert(fn, p, update_ro_status) file_insert(fn, p) | 546 | #define file_insert(fn, p, update_ro_status) file_insert(fn, p) |
466 | #endif | 547 | #endif |
@@ -495,8 +576,8 @@ static char *char_search(char *, const char *, int, int); // search for pattern | |||
495 | #if ENABLE_FEATURE_VI_COLON | 576 | #if ENABLE_FEATURE_VI_COLON |
496 | static char *get_one_address(char *, int *); // get colon addr, if present | 577 | static char *get_one_address(char *, int *); // get colon addr, if present |
497 | static char *get_address(char *, int *, int *); // get two colon addrs, if present | 578 | static char *get_address(char *, int *, int *); // get two colon addrs, if present |
498 | static void colon(char *); // execute the "colon" mode cmds | ||
499 | #endif | 579 | #endif |
580 | static void colon(char *); // execute the "colon" mode cmds | ||
500 | #if ENABLE_FEATURE_VI_USE_SIGNALS | 581 | #if ENABLE_FEATURE_VI_USE_SIGNALS |
501 | static void winch_sig(int); // catch window size changes | 582 | static void winch_sig(int); // catch window size changes |
502 | static void suspend_sig(int); // catch ctrl-Z | 583 | static void suspend_sig(int); // catch ctrl-Z |
@@ -514,20 +595,36 @@ 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 | 595 | #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, ...), | 596 | // might reallocate text[]! use p += string_insert(p, ...), |
516 | // and be careful to not use pointers into potentially freed text[]! | 597 | // 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' | 598 | # if !ENABLE_FEATURE_VI_UNDO |
599 | #define string_insert(a,b,c) string_insert(a,b) | ||
600 | # endif | ||
601 | static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' | ||
518 | #endif | 602 | #endif |
519 | #if ENABLE_FEATURE_VI_YANKMARK | 603 | #if ENABLE_FEATURE_VI_YANKMARK |
520 | static char *text_yank(char *, char *, int); // save copy of "p" into a register | 604 | 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 | 605 | static char what_reg(void); // what is letter of current YDreg |
522 | static void check_context(char); // remember context for '' command | 606 | static void check_context(char); // remember context for '' command |
523 | #endif | 607 | #endif |
608 | #if ENABLE_FEATURE_VI_UNDO | ||
609 | static void flush_undo_data(void); | ||
610 | static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack | ||
611 | static void undo_pop(void); // Undo the last operation | ||
612 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
613 | static void undo_queue_commit(void); // Flush any queued objects to the undo stack | ||
614 | # else | ||
615 | # define undo_queue_commit() ((void)0) | ||
616 | # endif | ||
617 | #else | ||
618 | #define flush_undo_data() ((void)0) | ||
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 |
@@ -618,30 +723,29 @@ int vi_main(int argc, char **argv) | |||
618 | static int init_text_buffer(char *fn) | 723 | static int init_text_buffer(char *fn) |
619 | { | 724 | { |
620 | int rc; | 725 | int rc; |
621 | int size = file_size(fn); // file size. -1 means does not exist. | 726 | |
727 | flush_undo_data(); | ||
728 | modified_count = 0; | ||
729 | last_modified_count = -1; | ||
730 | #if ENABLE_FEATURE_VI_YANKMARK | ||
731 | /* init the marks */ | ||
732 | memset(mark, 0, sizeof(mark)); | ||
733 | #endif | ||
622 | 734 | ||
623 | /* allocate/reallocate text buffer */ | 735 | /* allocate/reallocate text buffer */ |
624 | free(text); | 736 | free(text); |
625 | text_size = size + 10240; | 737 | text_size = 10240; |
626 | screenbegin = dot = end = text = xzalloc(text_size); | 738 | screenbegin = dot = end = text = xzalloc(text_size); |
627 | 739 | ||
628 | if (fn != current_filename) { | 740 | if (fn != current_filename) { |
629 | free(current_filename); | 741 | free(current_filename); |
630 | current_filename = xstrdup(fn); | 742 | current_filename = xstrdup(fn); |
631 | } | 743 | } |
632 | if (size < 0) { | 744 | rc = file_insert(fn, text, 1); |
633 | // file dont exist. Start empty buf with dummy line | 745 | if (rc < 0) { |
634 | char_insert(text, '\n'); | 746 | // file doesnt exist. Start empty buf with dummy line |
635 | rc = 0; | 747 | char_insert(text, '\n', NO_UNDO); |
636 | } else { | ||
637 | rc = file_insert(fn, text, 1); | ||
638 | } | 748 | } |
639 | file_modified = 0; | ||
640 | last_file_modified = -1; | ||
641 | #if ENABLE_FEATURE_VI_YANKMARK | ||
642 | /* init the marks. */ | ||
643 | memset(mark, 0, sizeof(mark)); | ||
644 | #endif | ||
645 | return rc; | 749 | return rc; |
646 | } | 750 | } |
647 | 751 | ||
@@ -756,7 +860,7 @@ static void edit_file(char *fn) | |||
756 | crash_dummy(); // generate a random command | 860 | crash_dummy(); // generate a random command |
757 | } else { | 861 | } else { |
758 | crashme = 0; | 862 | crashme = 0; |
759 | string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string | 863 | string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string |
760 | dot = text; | 864 | dot = text; |
761 | refresh(FALSE); | 865 | refresh(FALSE); |
762 | } | 866 | } |
@@ -909,13 +1013,71 @@ static void setops(const char *args, const char *opname, int flg_no, | |||
909 | } | 1013 | } |
910 | #endif | 1014 | #endif |
911 | 1015 | ||
1016 | #endif /* FEATURE_VI_COLON */ | ||
1017 | |||
912 | // buf must be no longer than MAX_INPUT_LEN! | 1018 | // buf must be no longer than MAX_INPUT_LEN! |
913 | static void colon(char *buf) | 1019 | static void colon(char *buf) |
914 | { | 1020 | { |
1021 | #if !ENABLE_FEATURE_VI_COLON | ||
1022 | /* Simple ":cmd" handler with minimal set of commands */ | ||
1023 | char *p = buf; | ||
1024 | int cnt; | ||
1025 | |||
1026 | if (*p == ':') | ||
1027 | p++; | ||
1028 | cnt = strlen(p); | ||
1029 | if (cnt == 0) | ||
1030 | return; | ||
1031 | if (strncmp(p, "quit", cnt) == 0 | ||
1032 | || strncmp(p, "q!", cnt) == 0 | ||
1033 | ) { | ||
1034 | if (modified_count && p[1] != '!') { | ||
1035 | status_line_bold("No write since last change (:%s! overrides)", p); | ||
1036 | } else { | ||
1037 | editing = 0; | ||
1038 | } | ||
1039 | return; | ||
1040 | } | ||
1041 | if (strncmp(p, "write", cnt) == 0 | ||
1042 | || strncmp(p, "wq", cnt) == 0 | ||
1043 | || strncmp(p, "wn", cnt) == 0 | ||
1044 | || (p[0] == 'x' && !p[1]) | ||
1045 | ) { | ||
1046 | cnt = file_write(current_filename, text, end - 1); | ||
1047 | if (cnt < 0) { | ||
1048 | if (cnt == -1) | ||
1049 | status_line_bold("Write error: %s", strerror(errno)); | ||
1050 | } else { | ||
1051 | modified_count = 0; | ||
1052 | last_modified_count = -1; | ||
1053 | status_line("'%s' %dL, %dC", | ||
1054 | current_filename, | ||
1055 | count_lines(text, end - 1), cnt | ||
1056 | ); | ||
1057 | if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' | ||
1058 | || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N' | ||
1059 | ) { | ||
1060 | editing = 0; | ||
1061 | } | ||
1062 | } | ||
1063 | return; | ||
1064 | } | ||
1065 | if (strncmp(p, "file", cnt) == 0) { | ||
1066 | last_status_cksum = 0; // force status update | ||
1067 | return; | ||
1068 | } | ||
1069 | if (sscanf(p, "%d", &cnt) > 0) { | ||
1070 | dot = find_line(cnt); | ||
1071 | dot_skip_over_ws(); | ||
1072 | return; | ||
1073 | } | ||
1074 | not_implemented(p); | ||
1075 | #else | ||
1076 | |||
915 | char c, *orig_buf, *buf1, *q, *r; | 1077 | char c, *orig_buf, *buf1, *q, *r; |
916 | char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; | 1078 | char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; |
917 | int i, l, li, ch, b, e; | 1079 | int i, l, li, b, e; |
918 | int useforce, forced = FALSE; | 1080 | int useforce; |
919 | 1081 | ||
920 | // :3154 // if (-e line 3154) goto it else stay put | 1082 | // :3154 // if (-e line 3154) goto it else stay put |
921 | // :4,33w! foo // write a portion of buffer to file "foo" | 1083 | // :4,33w! foo // write a portion of buffer to file "foo" |
@@ -937,7 +1099,7 @@ static void colon(char *buf) | |||
937 | if (*buf == ':') | 1099 | if (*buf == ':') |
938 | buf++; // move past the ':' | 1100 | buf++; // move past the ':' |
939 | 1101 | ||
940 | li = ch = i = 0; | 1102 | li = i = 0; |
941 | b = e = -1; | 1103 | b = e = -1; |
942 | q = text; // assume 1,$ for the range | 1104 | q = text; // assume 1,$ for the range |
943 | r = end - 1; | 1105 | r = end - 1; |
@@ -1015,11 +1177,13 @@ static void colon(char *buf) | |||
1015 | q = begin_line(dot); // assume .,. for the range | 1177 | q = begin_line(dot); // assume .,. for the range |
1016 | r = end_line(dot); | 1178 | r = end_line(dot); |
1017 | } | 1179 | } |
1018 | dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines | 1180 | dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines |
1019 | dot_skip_over_ws(); | 1181 | dot_skip_over_ws(); |
1020 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file | 1182 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file |
1183 | int size; | ||
1184 | |||
1021 | // don't edit, if the current file has been modified | 1185 | // don't edit, if the current file has been modified |
1022 | if (file_modified && !useforce) { | 1186 | if (modified_count && !useforce) { |
1023 | status_line_bold("No write since last change (:%s! overrides)", cmd); | 1187 | status_line_bold("No write since last change (:%s! overrides)", cmd); |
1024 | goto ret; | 1188 | goto ret; |
1025 | } | 1189 | } |
@@ -1035,8 +1199,7 @@ static void colon(char *buf) | |||
1035 | goto ret; | 1199 | goto ret; |
1036 | } | 1200 | } |
1037 | 1201 | ||
1038 | if (init_text_buffer(fn) < 0) | 1202 | size = init_text_buffer(fn); |
1039 | goto ret; | ||
1040 | 1203 | ||
1041 | #if ENABLE_FEATURE_VI_YANKMARK | 1204 | #if ENABLE_FEATURE_VI_YANKMARK |
1042 | if (Ureg >= 0 && Ureg < 28) { | 1205 | if (Ureg >= 0 && Ureg < 28) { |
@@ -1052,12 +1215,14 @@ static void colon(char *buf) | |||
1052 | li = count_lines(text, end - 1); | 1215 | li = count_lines(text, end - 1); |
1053 | status_line("'%s'%s" | 1216 | status_line("'%s'%s" |
1054 | IF_FEATURE_VI_READONLY("%s") | 1217 | IF_FEATURE_VI_READONLY("%s") |
1055 | " %dL, %dC", current_filename, | 1218 | " %dL, %dC", |
1056 | (file_size(fn) < 0 ? " [New file]" : ""), | 1219 | current_filename, |
1220 | (size < 0 ? " [New file]" : ""), | ||
1057 | IF_FEATURE_VI_READONLY( | 1221 | IF_FEATURE_VI_READONLY( |
1058 | ((readonly_mode) ? " [Readonly]" : ""), | 1222 | ((readonly_mode) ? " [Readonly]" : ""), |
1059 | ) | 1223 | ) |
1060 | li, ch); | 1224 | li, (int)(end - text) |
1225 | ); | ||
1061 | } else if (strncmp(cmd, "file", i) == 0) { // what File is this | 1226 | } else if (strncmp(cmd, "file", i) == 0) { // what File is this |
1062 | if (b != -1 || e != -1) { | 1227 | if (b != -1 || e != -1) { |
1063 | status_line_bold("No address allowed on this command"); | 1228 | status_line_bold("No address allowed on this command"); |
@@ -1122,7 +1287,7 @@ static void colon(char *buf) | |||
1122 | goto ret; | 1287 | goto ret; |
1123 | } | 1288 | } |
1124 | // don't exit if the file been modified | 1289 | // don't exit if the file been modified |
1125 | if (file_modified) { | 1290 | if (modified_count) { |
1126 | status_line_bold("No write since last change (:%s! overrides)", cmd); | 1291 | status_line_bold("No write since last change (:%s! overrides)", cmd); |
1127 | goto ret; | 1292 | goto ret; |
1128 | } | 1293 | } |
@@ -1146,6 +1311,8 @@ static void colon(char *buf) | |||
1146 | } | 1311 | } |
1147 | editing = 0; | 1312 | editing = 0; |
1148 | } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] | 1313 | } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] |
1314 | int size; | ||
1315 | |||
1149 | fn = args; | 1316 | fn = args; |
1150 | if (!fn[0]) { | 1317 | if (!fn[0]) { |
1151 | status_line_bold("No filename given"); | 1318 | status_line_bold("No filename given"); |
@@ -1159,26 +1326,27 @@ static void colon(char *buf) | |||
1159 | q = next_line(q); | 1326 | q = next_line(q); |
1160 | { // dance around potentially-reallocated text[] | 1327 | { // dance around potentially-reallocated text[] |
1161 | uintptr_t ofs = q - text; | 1328 | uintptr_t ofs = q - text; |
1162 | ch = file_insert(fn, q, 0); | 1329 | size = file_insert(fn, q, /*update_ro:*/ 0); |
1163 | q = text + ofs; | 1330 | q = text + ofs; |
1164 | } | 1331 | } |
1165 | if (ch < 0) | 1332 | if (size < 0) |
1166 | goto ret; // nothing was inserted | 1333 | goto ret; // nothing was inserted |
1167 | // how many lines in text[]? | 1334 | // how many lines in text[]? |
1168 | li = count_lines(q, q + ch - 1); | 1335 | li = count_lines(q, q + size - 1); |
1169 | status_line("'%s'" | 1336 | status_line("'%s'" |
1170 | IF_FEATURE_VI_READONLY("%s") | 1337 | IF_FEATURE_VI_READONLY("%s") |
1171 | " %dL, %dC", fn, | 1338 | " %dL, %dC", |
1339 | fn, | ||
1172 | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) | 1340 | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) |
1173 | li, ch); | 1341 | li, size |
1174 | if (ch > 0) { | 1342 | ); |
1343 | if (size > 0) { | ||
1175 | // if the insert is before "dot" then we need to update | 1344 | // if the insert is before "dot" then we need to update |
1176 | if (q <= dot) | 1345 | if (q <= dot) |
1177 | dot += ch; | 1346 | dot += size; |
1178 | /*file_modified++; - done by file_insert */ | ||
1179 | } | 1347 | } |
1180 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args | 1348 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args |
1181 | if (file_modified && !useforce) { | 1349 | if (modified_count && !useforce) { |
1182 | status_line_bold("No write since last change (:%s! overrides)", cmd); | 1350 | status_line_bold("No write since last change (:%s! overrides)", cmd); |
1183 | } else { | 1351 | } else { |
1184 | // reset the filenames to edit | 1352 | // reset the filenames to edit |
@@ -1235,6 +1403,9 @@ static void colon(char *buf) | |||
1235 | char *F, *R, *flags; | 1403 | char *F, *R, *flags; |
1236 | size_t len_F, len_R; | 1404 | size_t len_F, len_R; |
1237 | int gflag; // global replace flag | 1405 | int gflag; // global replace flag |
1406 | #if ENABLE_FEATURE_VI_UNDO | ||
1407 | int dont_chain_first_item = ALLOW_UNDO; | ||
1408 | #endif | ||
1238 | 1409 | ||
1239 | // F points to the "find" pattern | 1410 | // F points to the "find" pattern |
1240 | // R points to the "replace" pattern | 1411 | // R points to the "replace" pattern |
@@ -1269,9 +1440,13 @@ static void colon(char *buf) | |||
1269 | if (found) { | 1440 | if (found) { |
1270 | uintptr_t bias; | 1441 | uintptr_t bias; |
1271 | // we found the "find" pattern - delete it | 1442 | // we found the "find" pattern - delete it |
1272 | text_hole_delete(found, found + len_F - 1); | 1443 | // For undo support, the first item should not be chained |
1273 | // inset the "replace" patern | 1444 | text_hole_delete(found, found + len_F - 1, dont_chain_first_item); |
1274 | bias = string_insert(found, R); // insert the string | 1445 | #if ENABLE_FEATURE_VI_UNDO |
1446 | dont_chain_first_item = ALLOW_UNDO_CHAIN; | ||
1447 | #endif | ||
1448 | // insert the "replace" patern | ||
1449 | bias = string_insert(found, R, ALLOW_UNDO_CHAIN); | ||
1275 | found += bias; | 1450 | found += bias; |
1276 | ls += bias; | 1451 | ls += bias; |
1277 | /*q += bias; - recalculated anyway */ | 1452 | /*q += bias; - recalculated anyway */ |
@@ -1293,6 +1468,9 @@ static void colon(char *buf) | |||
1293 | || strncmp(cmd, "wn", i) == 0 | 1468 | || strncmp(cmd, "wn", i) == 0 |
1294 | || (cmd[0] == 'x' && !cmd[1]) | 1469 | || (cmd[0] == 'x' && !cmd[1]) |
1295 | ) { | 1470 | ) { |
1471 | int size; | ||
1472 | //int forced = FALSE; | ||
1473 | |||
1296 | // is there a file name to write to? | 1474 | // is there a file name to write to? |
1297 | if (args[0]) { | 1475 | if (args[0]) { |
1298 | fn = args; | 1476 | fn = args; |
@@ -1305,34 +1483,33 @@ static void colon(char *buf) | |||
1305 | #endif | 1483 | #endif |
1306 | // how many lines in text[]? | 1484 | // how many lines in text[]? |
1307 | li = count_lines(q, r); | 1485 | li = count_lines(q, r); |
1308 | ch = r - q + 1; | 1486 | size = r - q + 1; |
1309 | // see if file exists- if not, its just a new file request | 1487 | //if (useforce) { |
1310 | if (useforce) { | ||
1311 | // if "fn" is not write-able, chmod u+w | 1488 | // if "fn" is not write-able, chmod u+w |
1312 | // sprintf(syscmd, "chmod u+w %s", fn); | 1489 | // sprintf(syscmd, "chmod u+w %s", fn); |
1313 | // system(syscmd); | 1490 | // system(syscmd); |
1314 | forced = TRUE; | 1491 | // forced = TRUE; |
1315 | } | 1492 | //} |
1316 | l = file_write(fn, q, r); | 1493 | l = file_write(fn, q, r); |
1317 | if (useforce && forced) { | 1494 | //if (useforce && forced) { |
1318 | // chmod u-w | 1495 | // chmod u-w |
1319 | // sprintf(syscmd, "chmod u-w %s", fn); | 1496 | // sprintf(syscmd, "chmod u-w %s", fn); |
1320 | // system(syscmd); | 1497 | // system(syscmd); |
1321 | forced = FALSE; | 1498 | // forced = FALSE; |
1322 | } | 1499 | //} |
1323 | if (l < 0) { | 1500 | if (l < 0) { |
1324 | if (l == -1) | 1501 | if (l == -1) |
1325 | status_line_bold_errno(fn); | 1502 | status_line_bold_errno(fn); |
1326 | } else { | 1503 | } else { |
1327 | status_line("'%s' %dL, %dC", fn, li, l); | 1504 | status_line("'%s' %dL, %dC", fn, li, l); |
1328 | if (q == text && r == end - 1 && l == ch) { | 1505 | if (q == text && r == end - 1 && l == size) { |
1329 | file_modified = 0; | 1506 | modified_count = 0; |
1330 | last_file_modified = -1; | 1507 | last_modified_count = -1; |
1331 | } | 1508 | } |
1332 | if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' | 1509 | if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' |
1333 | || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N' | 1510 | || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N' |
1334 | ) | 1511 | ) |
1335 | && l == ch | 1512 | && l == size |
1336 | ) { | 1513 | ) { |
1337 | editing = 0; | 1514 | editing = 0; |
1338 | } | 1515 | } |
@@ -1359,9 +1536,8 @@ static void colon(char *buf) | |||
1359 | colon_s_fail: | 1536 | colon_s_fail: |
1360 | status_line(":s expression missing delimiters"); | 1537 | status_line(":s expression missing delimiters"); |
1361 | #endif | 1538 | #endif |
1362 | } | ||
1363 | |||
1364 | #endif /* FEATURE_VI_COLON */ | 1539 | #endif /* FEATURE_VI_COLON */ |
1540 | } | ||
1365 | 1541 | ||
1366 | static void Hit_Return(void) | 1542 | static void Hit_Return(void) |
1367 | { | 1543 | { |
@@ -1572,23 +1748,27 @@ static char *find_line(int li) // find begining of line #li | |||
1572 | //----- Dot Movement Routines ---------------------------------- | 1748 | //----- Dot Movement Routines ---------------------------------- |
1573 | static void dot_left(void) | 1749 | static void dot_left(void) |
1574 | { | 1750 | { |
1751 | undo_queue_commit(); | ||
1575 | if (dot > text && dot[-1] != '\n') | 1752 | if (dot > text && dot[-1] != '\n') |
1576 | dot--; | 1753 | dot--; |
1577 | } | 1754 | } |
1578 | 1755 | ||
1579 | static void dot_right(void) | 1756 | static void dot_right(void) |
1580 | { | 1757 | { |
1758 | undo_queue_commit(); | ||
1581 | if (dot < end - 1 && *dot != '\n') | 1759 | if (dot < end - 1 && *dot != '\n') |
1582 | dot++; | 1760 | dot++; |
1583 | } | 1761 | } |
1584 | 1762 | ||
1585 | static void dot_begin(void) | 1763 | static void dot_begin(void) |
1586 | { | 1764 | { |
1765 | undo_queue_commit(); | ||
1587 | dot = begin_line(dot); // return pointer to first char cur line | 1766 | dot = begin_line(dot); // return pointer to first char cur line |
1588 | } | 1767 | } |
1589 | 1768 | ||
1590 | static void dot_end(void) | 1769 | static void dot_end(void) |
1591 | { | 1770 | { |
1771 | undo_queue_commit(); | ||
1592 | dot = end_line(dot); // return pointer to last char cur line | 1772 | dot = end_line(dot); // return pointer to last char cur line |
1593 | } | 1773 | } |
1594 | 1774 | ||
@@ -1614,11 +1794,13 @@ static char *move_to_col(char *p, int l) | |||
1614 | 1794 | ||
1615 | static void dot_next(void) | 1795 | static void dot_next(void) |
1616 | { | 1796 | { |
1797 | undo_queue_commit(); | ||
1617 | dot = next_line(dot); | 1798 | dot = next_line(dot); |
1618 | } | 1799 | } |
1619 | 1800 | ||
1620 | static void dot_prev(void) | 1801 | static void dot_prev(void) |
1621 | { | 1802 | { |
1803 | undo_queue_commit(); | ||
1622 | dot = prev_line(dot); | 1804 | dot = prev_line(dot); |
1623 | } | 1805 | } |
1624 | 1806 | ||
@@ -1626,6 +1808,7 @@ static void dot_scroll(int cnt, int dir) | |||
1626 | { | 1808 | { |
1627 | char *q; | 1809 | char *q; |
1628 | 1810 | ||
1811 | undo_queue_commit(); | ||
1629 | for (; cnt > 0; cnt--) { | 1812 | for (; cnt > 0; cnt--) { |
1630 | if (dir < 0) { | 1813 | if (dir < 0) { |
1631 | // scroll Backwards | 1814 | // scroll Backwards |
@@ -1653,11 +1836,6 @@ static void dot_skip_over_ws(void) | |||
1653 | dot++; | 1836 | dot++; |
1654 | } | 1837 | } |
1655 | 1838 | ||
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" | 1839 | static char *bound_dot(char *p) // make sure text[0] <= P < "end" |
1662 | { | 1840 | { |
1663 | if (p >= end && end > text) { | 1841 | if (p >= end && end > text) { |
@@ -1804,17 +1982,34 @@ static char *char_search(char *p, const char *pat, int dir, int range) | |||
1804 | 1982 | ||
1805 | #endif /* FEATURE_VI_SEARCH */ | 1983 | #endif /* FEATURE_VI_SEARCH */ |
1806 | 1984 | ||
1807 | static char *char_insert(char *p, char c) // insert the char c at 'p' | 1985 | static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' |
1808 | { | 1986 | { |
1809 | if (c == 22) { // Is this an ctrl-V? | 1987 | if (c == 22) { // Is this an ctrl-V? |
1810 | p += stupid_insert(p, '^'); // use ^ to indicate literal next | 1988 | p += stupid_insert(p, '^'); // use ^ to indicate literal next |
1811 | refresh(FALSE); // show the ^ | 1989 | refresh(FALSE); // show the ^ |
1812 | c = get_one_char(); | 1990 | c = get_one_char(); |
1813 | *p = c; | 1991 | *p = c; |
1992 | #if ENABLE_FEATURE_VI_UNDO | ||
1993 | switch (undo) { | ||
1994 | case ALLOW_UNDO: | ||
1995 | undo_push(p, 1, UNDO_INS); | ||
1996 | break; | ||
1997 | case ALLOW_UNDO_CHAIN: | ||
1998 | undo_push(p, 1, UNDO_INS_CHAIN); | ||
1999 | break; | ||
2000 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2001 | case ALLOW_UNDO_QUEUED: | ||
2002 | undo_push(p, 1, UNDO_INS_QUEUED); | ||
2003 | break; | ||
2004 | # endif | ||
2005 | } | ||
2006 | #else | ||
2007 | modified_count++; | ||
2008 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
1814 | p++; | 2009 | p++; |
1815 | file_modified++; | ||
1816 | } else if (c == 27) { // Is this an ESC? | 2010 | } else if (c == 27) { // Is this an ESC? |
1817 | cmd_mode = 0; | 2011 | cmd_mode = 0; |
2012 | undo_queue_commit(); | ||
1818 | cmdcnt = 0; | 2013 | cmdcnt = 0; |
1819 | end_cmd_q(); // stop adding to q | 2014 | end_cmd_q(); // stop adding to q |
1820 | last_status_cksum = 0; // force status update | 2015 | last_status_cksum = 0; // force status update |
@@ -1825,7 +2020,7 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1825 | // 123456789 | 2020 | // 123456789 |
1826 | if ((p[-1] != '\n') && (dot>text)) { | 2021 | if ((p[-1] != '\n') && (dot>text)) { |
1827 | p--; | 2022 | p--; |
1828 | p = text_hole_delete(p, p); // shrink buffer 1 char | 2023 | p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char |
1829 | } | 2024 | } |
1830 | } else { | 2025 | } else { |
1831 | #if ENABLE_FEATURE_VI_SETOPTS | 2026 | #if ENABLE_FEATURE_VI_SETOPTS |
@@ -1838,6 +2033,27 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1838 | #if ENABLE_FEATURE_VI_SETOPTS | 2033 | #if ENABLE_FEATURE_VI_SETOPTS |
1839 | sp = p; // remember addr of insert | 2034 | sp = p; // remember addr of insert |
1840 | #endif | 2035 | #endif |
2036 | #if ENABLE_FEATURE_VI_UNDO | ||
2037 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2038 | if (c == '\n') | ||
2039 | undo_queue_commit(); | ||
2040 | # endif | ||
2041 | switch (undo) { | ||
2042 | case ALLOW_UNDO: | ||
2043 | undo_push(p, 1, UNDO_INS); | ||
2044 | break; | ||
2045 | case ALLOW_UNDO_CHAIN: | ||
2046 | undo_push(p, 1, UNDO_INS_CHAIN); | ||
2047 | break; | ||
2048 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2049 | case ALLOW_UNDO_QUEUED: | ||
2050 | undo_push(p, 1, UNDO_INS_QUEUED); | ||
2051 | break; | ||
2052 | # endif | ||
2053 | } | ||
2054 | #else | ||
2055 | modified_count++; | ||
2056 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
1841 | p += 1 + stupid_insert(p, c); // insert the char | 2057 | p += 1 + stupid_insert(p, c); // insert the char |
1842 | #if ENABLE_FEATURE_VI_SETOPTS | 2058 | #if ENABLE_FEATURE_VI_SETOPTS |
1843 | if (showmatch && strchr(")]}", *sp) != NULL) { | 2059 | if (showmatch && strchr(")]}", *sp) != NULL) { |
@@ -1853,6 +2069,9 @@ static char *char_insert(char *p, char c) // insert the char c at 'p' | |||
1853 | bias = text_hole_make(p, len); | 2069 | bias = text_hole_make(p, len); |
1854 | p += bias; | 2070 | p += bias; |
1855 | q += bias; | 2071 | q += bias; |
2072 | #if ENABLE_FEATURE_VI_UNDO | ||
2073 | undo_push(p, len, UNDO_INS); | ||
2074 | #endif | ||
1856 | memcpy(p, q, len); | 2075 | memcpy(p, q, len); |
1857 | p += len; | 2076 | p += len; |
1858 | } | 2077 | } |
@@ -1870,7 +2089,6 @@ static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at | |||
1870 | bias = text_hole_make(p, 1); | 2089 | bias = text_hole_make(p, 1); |
1871 | p += bias; | 2090 | p += bias; |
1872 | *p = c; | 2091 | *p = c; |
1873 | //file_modified++; - done by text_hole_make() | ||
1874 | return bias; | 2092 | return bias; |
1875 | } | 2093 | } |
1876 | 2094 | ||
@@ -2051,6 +2269,199 @@ static void showmatching(char *p) | |||
2051 | } | 2269 | } |
2052 | #endif /* FEATURE_VI_SETOPTS */ | 2270 | #endif /* FEATURE_VI_SETOPTS */ |
2053 | 2271 | ||
2272 | #if ENABLE_FEATURE_VI_UNDO | ||
2273 | static void flush_undo_data(void) | ||
2274 | { | ||
2275 | struct undo_object *undo_entry; | ||
2276 | |||
2277 | while (undo_stack_tail) { | ||
2278 | undo_entry = undo_stack_tail; | ||
2279 | undo_stack_tail = undo_entry->prev; | ||
2280 | free(undo_entry); | ||
2281 | } | ||
2282 | } | ||
2283 | |||
2284 | // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com) | ||
2285 | static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack | ||
2286 | { | ||
2287 | struct undo_object *undo_entry; | ||
2288 | |||
2289 | // "u_type" values | ||
2290 | // UNDO_INS: insertion, undo will remove from buffer | ||
2291 | // UNDO_DEL: deleted text, undo will restore to buffer | ||
2292 | // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete | ||
2293 | // The CHAIN operations are for handling multiple operations that the user | ||
2294 | // performs with a single action, i.e. REPLACE mode or find-and-replace commands | ||
2295 | // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue | ||
2296 | // for the INS/DEL operation. The raw values should be equal to the values of | ||
2297 | // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG | ||
2298 | |||
2299 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2300 | // This undo queuing functionality groups multiple character typing or backspaces | ||
2301 | // into a single large undo object. This greatly reduces calls to malloc() for | ||
2302 | // single-character operations while typing and has the side benefit of letting | ||
2303 | // an undo operation remove chunks of text rather than a single character. | ||
2304 | switch (u_type) { | ||
2305 | case UNDO_EMPTY: // Just in case this ever happens... | ||
2306 | return; | ||
2307 | case UNDO_DEL_QUEUED: | ||
2308 | if (length != 1) | ||
2309 | return; // Only queue single characters | ||
2310 | switch (undo_queue_state) { | ||
2311 | case UNDO_EMPTY: | ||
2312 | undo_queue_state = UNDO_DEL; | ||
2313 | case UNDO_DEL: | ||
2314 | undo_queue_spos = src; | ||
2315 | undo_q++; | ||
2316 | undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src; | ||
2317 | // If queue is full, dump it into an object | ||
2318 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) | ||
2319 | undo_queue_commit(); | ||
2320 | return; | ||
2321 | case UNDO_INS: | ||
2322 | // Switch from storing inserted text to deleted text | ||
2323 | undo_queue_commit(); | ||
2324 | undo_push(src, length, UNDO_DEL_QUEUED); | ||
2325 | return; | ||
2326 | } | ||
2327 | break; | ||
2328 | case UNDO_INS_QUEUED: | ||
2329 | if (length != 1) | ||
2330 | return; | ||
2331 | switch (undo_queue_state) { | ||
2332 | case UNDO_EMPTY: | ||
2333 | undo_queue_state = UNDO_INS; | ||
2334 | undo_queue_spos = src; | ||
2335 | case UNDO_INS: | ||
2336 | undo_q++; // Don't need to save any data for insertions | ||
2337 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) | ||
2338 | undo_queue_commit(); | ||
2339 | return; | ||
2340 | case UNDO_DEL: | ||
2341 | // Switch from storing deleted text to inserted text | ||
2342 | undo_queue_commit(); | ||
2343 | undo_push(src, length, UNDO_INS_QUEUED); | ||
2344 | return; | ||
2345 | } | ||
2346 | break; | ||
2347 | } | ||
2348 | #else | ||
2349 | // If undo queuing is disabled, ignore the queuing flag entirely | ||
2350 | u_type = u_type & ~UNDO_QUEUED_FLAG; | ||
2351 | #endif | ||
2352 | |||
2353 | // Allocate a new undo object | ||
2354 | if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) { | ||
2355 | // For UNDO_DEL objects, save deleted text | ||
2356 | if ((src + length) == end) | ||
2357 | length--; | ||
2358 | // If this deletion empties text[], strip the newline. When the buffer becomes | ||
2359 | // zero-length, a newline is added back, which requires this to compensate. | ||
2360 | undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length); | ||
2361 | memcpy(undo_entry->undo_text, src, length); | ||
2362 | } else { | ||
2363 | undo_entry = xzalloc(sizeof(*undo_entry)); | ||
2364 | } | ||
2365 | undo_entry->length = length; | ||
2366 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2367 | if ((u_type & UNDO_USE_SPOS) != 0) { | ||
2368 | undo_entry->start = undo_queue_spos - text; // use start position from queue | ||
2369 | } else { | ||
2370 | undo_entry->start = src - text; // use offset from start of text buffer | ||
2371 | } | ||
2372 | u_type = (u_type & ~UNDO_USE_SPOS); | ||
2373 | #else | ||
2374 | undo_entry->start = src - text; | ||
2375 | #endif | ||
2376 | undo_entry->u_type = u_type; | ||
2377 | |||
2378 | // Push it on undo stack | ||
2379 | undo_entry->prev = undo_stack_tail; | ||
2380 | undo_stack_tail = undo_entry; | ||
2381 | modified_count++; | ||
2382 | } | ||
2383 | |||
2384 | static void undo_pop(void) // Undo the last operation | ||
2385 | { | ||
2386 | int repeat; | ||
2387 | char *u_start, *u_end; | ||
2388 | struct undo_object *undo_entry; | ||
2389 | |||
2390 | // Commit pending undo queue before popping (should be unnecessary) | ||
2391 | undo_queue_commit(); | ||
2392 | |||
2393 | undo_entry = undo_stack_tail; | ||
2394 | // Check for an empty undo stack | ||
2395 | if (!undo_entry) { | ||
2396 | status_line("Already at oldest change"); | ||
2397 | return; | ||
2398 | } | ||
2399 | |||
2400 | switch (undo_entry->u_type) { | ||
2401 | case UNDO_DEL: | ||
2402 | case UNDO_DEL_CHAIN: | ||
2403 | // make hole and put in text that was deleted; deallocate text | ||
2404 | u_start = text + undo_entry->start; | ||
2405 | text_hole_make(u_start, undo_entry->length); | ||
2406 | memcpy(u_start, undo_entry->undo_text, undo_entry->length); | ||
2407 | status_line("Undo [%d] %s %d chars at position %d", | ||
2408 | modified_count, "restored", | ||
2409 | undo_entry->length, undo_entry->start | ||
2410 | ); | ||
2411 | break; | ||
2412 | case UNDO_INS: | ||
2413 | case UNDO_INS_CHAIN: | ||
2414 | // delete what was inserted | ||
2415 | u_start = undo_entry->start + text; | ||
2416 | u_end = u_start - 1 + undo_entry->length; | ||
2417 | text_hole_delete(u_start, u_end, NO_UNDO); | ||
2418 | status_line("Undo [%d] %s %d chars at position %d", | ||
2419 | modified_count, "deleted", | ||
2420 | undo_entry->length, undo_entry->start | ||
2421 | ); | ||
2422 | break; | ||
2423 | } | ||
2424 | repeat = 0; | ||
2425 | switch (undo_entry->u_type) { | ||
2426 | // If this is the end of a chain, lower modification count and refresh display | ||
2427 | case UNDO_DEL: | ||
2428 | case UNDO_INS: | ||
2429 | dot = (text + undo_entry->start); | ||
2430 | refresh(FALSE); | ||
2431 | break; | ||
2432 | case UNDO_DEL_CHAIN: | ||
2433 | case UNDO_INS_CHAIN: | ||
2434 | repeat = 1; | ||
2435 | break; | ||
2436 | } | ||
2437 | // Deallocate the undo object we just processed | ||
2438 | undo_stack_tail = undo_entry->prev; | ||
2439 | free(undo_entry); | ||
2440 | modified_count--; | ||
2441 | // For chained operations, continue popping all the way down the chain. | ||
2442 | if (repeat) { | ||
2443 | undo_pop(); // Follow the undo chain if one exists | ||
2444 | } | ||
2445 | } | ||
2446 | |||
2447 | #if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2448 | static void undo_queue_commit(void) // Flush any queued objects to the undo stack | ||
2449 | { | ||
2450 | // Pushes the queue object onto the undo stack | ||
2451 | if (undo_q > 0) { | ||
2452 | // Deleted character undo events grow from the end | ||
2453 | undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q, | ||
2454 | undo_q, | ||
2455 | (undo_queue_state | UNDO_USE_SPOS) | ||
2456 | ); | ||
2457 | undo_queue_state = UNDO_EMPTY; | ||
2458 | undo_q = 0; | ||
2459 | } | ||
2460 | } | ||
2461 | #endif | ||
2462 | |||
2463 | #endif /* ENABLE_FEATURE_VI_UNDO */ | ||
2464 | |||
2054 | // open a hole in text[] | 2465 | // open a hole in text[] |
2055 | // might reallocate text[]! use p += text_hole_make(p, ...), | 2466 | // might reallocate text[]! use p += text_hole_make(p, ...), |
2056 | // and be careful to not use pointers into potentially freed text[]! | 2467 | // and be careful to not use pointers into potentially freed text[]! |
@@ -2082,12 +2493,12 @@ static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte | |||
2082 | } | 2493 | } |
2083 | memmove(p + size, p, end - size - p); | 2494 | memmove(p + size, p, end - size - p); |
2084 | memset(p, ' ', size); // clear new hole | 2495 | memset(p, ' ', size); // clear new hole |
2085 | file_modified++; | ||
2086 | return bias; | 2496 | return bias; |
2087 | } | 2497 | } |
2088 | 2498 | ||
2089 | // close a hole in text[] | 2499 | // close a hole in text[] |
2090 | static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive | 2500 | // "undo" value indicates if this operation should be undo-able |
2501 | static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive | ||
2091 | { | 2502 | { |
2092 | char *src, *dest; | 2503 | char *src, *dest; |
2093 | int cnt, hole_size; | 2504 | int cnt, hole_size; |
@@ -2102,10 +2513,29 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2102 | } | 2513 | } |
2103 | hole_size = q - p + 1; | 2514 | hole_size = q - p + 1; |
2104 | cnt = end - src; | 2515 | cnt = end - src; |
2516 | #if ENABLE_FEATURE_VI_UNDO | ||
2517 | switch (undo) { | ||
2518 | case NO_UNDO: | ||
2519 | break; | ||
2520 | case ALLOW_UNDO: | ||
2521 | undo_push(p, hole_size, UNDO_DEL); | ||
2522 | break; | ||
2523 | case ALLOW_UNDO_CHAIN: | ||
2524 | undo_push(p, hole_size, UNDO_DEL_CHAIN); | ||
2525 | break; | ||
2526 | # if ENABLE_FEATURE_VI_UNDO_QUEUE | ||
2527 | case ALLOW_UNDO_QUEUED: | ||
2528 | undo_push(p, hole_size, UNDO_DEL_QUEUED); | ||
2529 | break; | ||
2530 | # endif | ||
2531 | } | ||
2532 | modified_count--; | ||
2533 | #endif | ||
2105 | if (src < text || src > end) | 2534 | if (src < text || src > end) |
2106 | goto thd0; | 2535 | goto thd0; |
2107 | if (dest < text || dest >= end) | 2536 | if (dest < text || dest >= end) |
2108 | goto thd0; | 2537 | goto thd0; |
2538 | modified_count++; | ||
2109 | if (src >= end) | 2539 | if (src >= end) |
2110 | goto thd_atend; // just delete the end of the buffer | 2540 | goto thd_atend; // just delete the end of the buffer |
2111 | memmove(dest, src, cnt); | 2541 | memmove(dest, src, cnt); |
@@ -2115,7 +2545,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 | 2545 | dest = end - 1; // make sure dest in below end-1 |
2116 | if (end <= text) | 2546 | if (end <= text) |
2117 | dest = end = text; // keep pointers valid | 2547 | dest = end = text; // keep pointers valid |
2118 | file_modified++; | ||
2119 | thd0: | 2548 | thd0: |
2120 | return dest; | 2549 | return dest; |
2121 | } | 2550 | } |
@@ -2123,7 +2552,7 @@ static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclu | |||
2123 | // copy text into register, then delete text. | 2552 | // copy text into register, then delete text. |
2124 | // if dist <= 0, do not include, or go past, a NewLine | 2553 | // if dist <= 0, do not include, or go past, a NewLine |
2125 | // | 2554 | // |
2126 | static char *yank_delete(char *start, char *stop, int dist, int yf) | 2555 | static char *yank_delete(char *start, char *stop, int dist, int yf, int undo) |
2127 | { | 2556 | { |
2128 | char *p; | 2557 | char *p; |
2129 | 2558 | ||
@@ -2152,7 +2581,7 @@ static char *yank_delete(char *start, char *stop, int dist, int yf) | |||
2152 | text_yank(start, stop, YDreg); | 2581 | text_yank(start, stop, YDreg); |
2153 | #endif | 2582 | #endif |
2154 | if (yf == YANKDEL) { | 2583 | if (yf == YANKDEL) { |
2155 | p = text_hole_delete(start, stop); | 2584 | p = text_hole_delete(start, stop, undo); |
2156 | } // delete lines | 2585 | } // delete lines |
2157 | return p; | 2586 | return p; |
2158 | } | 2587 | } |
@@ -2218,12 +2647,22 @@ static void end_cmd_q(void) | |||
2218 | || ENABLE_FEATURE_VI_CRASHME | 2647 | || ENABLE_FEATURE_VI_CRASHME |
2219 | // might reallocate text[]! use p += string_insert(p, ...), | 2648 | // might reallocate text[]! use p += string_insert(p, ...), |
2220 | // and be careful to not use pointers into potentially freed text[]! | 2649 | // 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' | 2650 | static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' |
2222 | { | 2651 | { |
2223 | uintptr_t bias; | 2652 | uintptr_t bias; |
2224 | int i; | 2653 | int i; |
2225 | 2654 | ||
2226 | i = strlen(s); | 2655 | i = strlen(s); |
2656 | #if ENABLE_FEATURE_VI_UNDO | ||
2657 | switch (undo) { | ||
2658 | case ALLOW_UNDO: | ||
2659 | undo_push(p, i, UNDO_INS); | ||
2660 | break; | ||
2661 | case ALLOW_UNDO_CHAIN: | ||
2662 | undo_push(p, i, UNDO_INS_CHAIN); | ||
2663 | break; | ||
2664 | } | ||
2665 | #endif | ||
2227 | bias = text_hole_make(p, i); | 2666 | bias = text_hole_make(p, i); |
2228 | p += bias; | 2667 | p += bias; |
2229 | memcpy(p, s, i); | 2668 | memcpy(p, s, i); |
@@ -2481,17 +2920,6 @@ static char *get_input_line(const char *prompt) | |||
2481 | #undef buf | 2920 | #undef buf |
2482 | } | 2921 | } |
2483 | 2922 | ||
2484 | static int file_size(const char *fn) // what is the byte size of "fn" | ||
2485 | { | ||
2486 | struct stat st_buf; | ||
2487 | int cnt; | ||
2488 | |||
2489 | cnt = -1; | ||
2490 | if (fn && stat(fn, &st_buf) == 0) // see if file exists | ||
2491 | cnt = (int) st_buf.st_size; | ||
2492 | return cnt; | ||
2493 | } | ||
2494 | |||
2495 | // might reallocate text[]! | 2923 | // might reallocate text[]! |
2496 | static int file_insert(const char *fn, char *p, int update_ro_status) | 2924 | static int file_insert(const char *fn, char *p, int update_ro_status) |
2497 | { | 2925 | { |
@@ -2499,38 +2927,37 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2499 | int fd, size; | 2927 | int fd, size; |
2500 | struct stat statbuf; | 2928 | struct stat statbuf; |
2501 | 2929 | ||
2502 | /* Validate file */ | ||
2503 | if (stat(fn, &statbuf) < 0) { | ||
2504 | status_line_bold_errno(fn); | ||
2505 | goto fi0; | ||
2506 | } | ||
2507 | if (!S_ISREG(statbuf.st_mode)) { | ||
2508 | // This is not a regular file | ||
2509 | status_line_bold("'%s' is not a regular file", fn); | ||
2510 | goto fi0; | ||
2511 | } | ||
2512 | if (p < text || p > end) { | 2930 | if (p < text || p > end) { |
2513 | status_line_bold("Trying to insert file outside of memory"); | 2931 | status_line_bold("Trying to insert file outside of memory"); |
2514 | goto fi0; | 2932 | return cnt; |
2515 | } | 2933 | } |
2516 | 2934 | ||
2517 | // read file to buffer | ||
2518 | fd = open(fn, O_RDONLY); | 2935 | fd = open(fn, O_RDONLY); |
2519 | if (fd < 0) { | 2936 | if (fd < 0) { |
2520 | status_line_bold_errno(fn); | 2937 | status_line_bold_errno(fn); |
2521 | goto fi0; | 2938 | return cnt; |
2939 | } | ||
2940 | |||
2941 | /* Validate file */ | ||
2942 | if (fstat(fd, &statbuf) < 0) { | ||
2943 | status_line_bold_errno(fn); | ||
2944 | goto fi; | ||
2945 | } | ||
2946 | if (!S_ISREG(statbuf.st_mode)) { | ||
2947 | status_line_bold("'%s' is not a regular file", fn); | ||
2948 | goto fi; | ||
2522 | } | 2949 | } |
2523 | #if ENABLE_PLATFORM_MINGW32 | 2950 | #if ENABLE_PLATFORM_MINGW32 |
2524 | _setmode(fd, _O_TEXT); | 2951 | _setmode(fd, _O_TEXT); |
2525 | #endif | 2952 | #endif |
2526 | size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX); | 2953 | size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX); |
2527 | p += text_hole_make(p, size); | 2954 | p += text_hole_make(p, size); |
2528 | cnt = safe_read(fd, p, size); | 2955 | cnt = full_read(fd, p, size); |
2529 | if (cnt < 0) { | 2956 | if (cnt < 0) { |
2530 | status_line_bold_errno(fn); | 2957 | status_line_bold_errno(fn); |
2531 | p = text_hole_delete(p, p + size - 1); // un-do buffer insert | 2958 | p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert |
2532 | } else if (cnt < size) { | 2959 | } else if (cnt < size) { |
2533 | // There was a partial read, shrink unused space text[] | 2960 | // There was a partial read, shrink unused space |
2534 | #if ENABLE_PLATFORM_MINGW32 | 2961 | #if ENABLE_PLATFORM_MINGW32 |
2535 | int i, newline; | 2962 | int i, newline; |
2536 | 2963 | ||
@@ -2541,7 +2968,7 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2541 | } | 2968 | } |
2542 | } | 2969 | } |
2543 | #endif | 2970 | #endif |
2544 | p = text_hole_delete(p + cnt, p + size - 1); // un-do buffer insert | 2971 | p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); |
2545 | #if ENABLE_PLATFORM_MINGW32 | 2972 | #if ENABLE_PLATFORM_MINGW32 |
2546 | // on WIN32 a partial read might just mean CRs have been removed | 2973 | // on WIN32 a partial read might just mean CRs have been removed |
2547 | if ( cnt+newline != size ) { | 2974 | if ( cnt+newline != size ) { |
@@ -2551,10 +2978,9 @@ static int file_insert(const char *fn, char *p, int update_ro_status) | |||
2551 | status_line_bold("can't read '%s'", fn); | 2978 | status_line_bold("can't read '%s'", fn); |
2552 | #endif | 2979 | #endif |
2553 | } | 2980 | } |
2554 | if (cnt >= size) | 2981 | fi: |
2555 | file_modified++; | ||
2556 | close(fd); | 2982 | close(fd); |
2557 | fi0: | 2983 | |
2558 | #if ENABLE_FEATURE_VI_READONLY | 2984 | #if ENABLE_FEATURE_VI_READONLY |
2559 | if (update_ro_status | 2985 | if (update_ro_status |
2560 | && ((access(fn, W_OK) < 0) || | 2986 | && ((access(fn, W_OK) < 0) || |
@@ -2607,7 +3033,7 @@ static int file_write(char *fn, char *first, char *last) | |||
2607 | #endif | 3033 | #endif |
2608 | if (charcnt == cnt) { | 3034 | if (charcnt == cnt) { |
2609 | // good write | 3035 | // good write |
2610 | //file_modified = FALSE; | 3036 | //modified_count = FALSE; |
2611 | } else { | 3037 | } else { |
2612 | charcnt = 0; | 3038 | charcnt = 0; |
2613 | } | 3039 | } |
@@ -2829,7 +3255,7 @@ static int format_edit_status(void) | |||
2829 | 3255 | ||
2830 | int cur, percent, ret, trunc_at; | 3256 | int cur, percent, ret, trunc_at; |
2831 | 3257 | ||
2832 | // file_modified is now a counter rather than a flag. this | 3258 | // modified_count is now a counter rather than a flag. this |
2833 | // helps reduce the amount of line counting we need to do. | 3259 | // helps reduce the amount of line counting we need to do. |
2834 | // (this will cause a mis-reporting of modified status | 3260 | // (this will cause a mis-reporting of modified status |
2835 | // once every MAXINT editing operations.) | 3261 | // once every MAXINT editing operations.) |
@@ -2839,11 +3265,12 @@ static int format_edit_status(void) | |||
2839 | // we're on, then we shouldn't have to do this count_lines() | 3265 | // we're on, then we shouldn't have to do this count_lines() |
2840 | cur = count_lines(text, dot); | 3266 | cur = count_lines(text, dot); |
2841 | 3267 | ||
2842 | // reduce counting -- the total lines can't have | 3268 | // count_lines() is expensive. |
2843 | // changed if we haven't done any edits. | 3269 | // Call it only if something was changed since last time |
2844 | if (file_modified != last_file_modified) { | 3270 | // we were here: |
3271 | if (modified_count != last_modified_count) { | ||
2845 | tot = cur + count_lines(dot, end - 1) - 1; | 3272 | tot = cur + count_lines(dot, end - 1) - 1; |
2846 | last_file_modified = file_modified; | 3273 | last_modified_count = modified_count; |
2847 | } | 3274 | } |
2848 | 3275 | ||
2849 | // current line percent | 3276 | // current line percent |
@@ -2870,7 +3297,7 @@ static int format_edit_status(void) | |||
2870 | #if ENABLE_FEATURE_VI_READONLY | 3297 | #if ENABLE_FEATURE_VI_READONLY |
2871 | (readonly_mode ? " [Readonly]" : ""), | 3298 | (readonly_mode ? " [Readonly]" : ""), |
2872 | #endif | 3299 | #endif |
2873 | (file_modified ? " [Modified]" : ""), | 3300 | (modified_count ? " [Modified]" : ""), |
2874 | cur, tot, percent); | 3301 | cur, tot, percent); |
2875 | 3302 | ||
2876 | if (ret >= 0 && ret < trunc_at) | 3303 | if (ret >= 0 && ret < trunc_at) |
@@ -3095,11 +3522,12 @@ static void do_cmd(int c) | |||
3095 | if (*dot == '\n') { | 3522 | if (*dot == '\n') { |
3096 | // don't Replace past E-o-l | 3523 | // don't Replace past E-o-l |
3097 | cmd_mode = 1; // convert to insert | 3524 | cmd_mode = 1; // convert to insert |
3525 | undo_queue_commit(); | ||
3098 | } else { | 3526 | } else { |
3099 | if (1 <= c || Isprint(c)) { | 3527 | if (1 <= c || Isprint(c)) { |
3100 | if (c != 27) | 3528 | if (c != 27) |
3101 | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char | 3529 | dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char |
3102 | dot = char_insert(dot, c); // insert new char | 3530 | dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char |
3103 | } | 3531 | } |
3104 | goto dc1; | 3532 | goto dc1; |
3105 | } | 3533 | } |
@@ -3109,7 +3537,7 @@ static void do_cmd(int c) | |||
3109 | if (c == KEYCODE_INSERT) goto dc5; | 3537 | if (c == KEYCODE_INSERT) goto dc5; |
3110 | // insert the char c at "dot" | 3538 | // insert the char c at "dot" |
3111 | if (1 <= c || Isprint(c)) { | 3539 | if (1 <= c || Isprint(c)) { |
3112 | dot = char_insert(dot, c); | 3540 | dot = char_insert(dot, c, ALLOW_UNDO_QUEUED); |
3113 | } | 3541 | } |
3114 | goto dc1; | 3542 | goto dc1; |
3115 | } | 3543 | } |
@@ -3155,7 +3583,6 @@ static void do_cmd(int c) | |||
3155 | //case ']': // ]- | 3583 | //case ']': // ]- |
3156 | //case '_': // _- | 3584 | //case '_': // _- |
3157 | //case '`': // `- | 3585 | //case '`': // `- |
3158 | //case 'u': // u- FIXME- there is no undo | ||
3159 | //case 'v': // v- | 3586 | //case 'v': // v- |
3160 | default: // unrecognized command | 3587 | default: // unrecognized command |
3161 | buf[0] = c; | 3588 | buf[0] = c; |
@@ -3224,6 +3651,7 @@ static void do_cmd(int c) | |||
3224 | if (cmd_mode == 0) | 3651 | if (cmd_mode == 0) |
3225 | indicate_error(c); | 3652 | indicate_error(c); |
3226 | cmd_mode = 0; // stop insrting | 3653 | cmd_mode = 0; // stop insrting |
3654 | undo_queue_commit(); | ||
3227 | end_cmd_q(); | 3655 | end_cmd_q(); |
3228 | last_status_cksum = 0; // force status update | 3656 | last_status_cksum = 0; // force status update |
3229 | break; | 3657 | break; |
@@ -3298,15 +3726,20 @@ static void do_cmd(int c) | |||
3298 | if (c == 'p') | 3726 | if (c == 'p') |
3299 | dot_right(); // move to right, can move to NL | 3727 | dot_right(); // move to right, can move to NL |
3300 | } | 3728 | } |
3301 | string_insert(dot, p); // insert the string | 3729 | string_insert(dot, p, ALLOW_UNDO); // insert the string |
3302 | end_cmd_q(); // stop adding to q | 3730 | end_cmd_q(); // stop adding to q |
3303 | break; | 3731 | break; |
3732 | #if ENABLE_FEATURE_VI_UNDO | ||
3733 | case 'u': // u- undo last operation | ||
3734 | undo_pop(); | ||
3735 | break; | ||
3736 | #endif | ||
3304 | case 'U': // U- Undo; replace current line with original version | 3737 | case 'U': // U- Undo; replace current line with original version |
3305 | if (reg[Ureg] != NULL) { | 3738 | if (reg[Ureg] != NULL) { |
3306 | p = begin_line(dot); | 3739 | p = begin_line(dot); |
3307 | q = end_line(dot); | 3740 | q = end_line(dot); |
3308 | p = text_hole_delete(p, q); // delete cur line | 3741 | p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line |
3309 | p += string_insert(p, reg[Ureg]); // insert orig line | 3742 | p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line |
3310 | dot = p; | 3743 | dot = p; |
3311 | dot_skip_over_ws(); | 3744 | dot_skip_over_ws(); |
3312 | } | 3745 | } |
@@ -3482,57 +3915,14 @@ static void do_cmd(int c) | |||
3482 | break; | 3915 | break; |
3483 | case ':': // :- the colon mode commands | 3916 | case ':': // :- the colon mode commands |
3484 | p = get_input_line(":"); // get input line- use "status line" | 3917 | p = get_input_line(":"); // get input line- use "status line" |
3485 | #if ENABLE_FEATURE_VI_COLON | ||
3486 | colon(p); // execute the command | 3918 | colon(p); // execute the command |
3487 | #else | ||
3488 | if (*p == ':') | ||
3489 | p++; // move past the ':' | ||
3490 | cnt = strlen(p); | ||
3491 | if (cnt <= 0) | ||
3492 | break; | ||
3493 | if (strncmp(p, "quit", cnt) == 0 | ||
3494 | || strncmp(p, "q!", cnt) == 0 // delete lines | ||
3495 | ) { | ||
3496 | if (file_modified && p[1] != '!') { | ||
3497 | status_line_bold("No write since last change (:%s! overrides)", p); | ||
3498 | } else { | ||
3499 | editing = 0; | ||
3500 | } | ||
3501 | } else if (strncmp(p, "write", cnt) == 0 | ||
3502 | || strncmp(p, "wq", cnt) == 0 | ||
3503 | || strncmp(p, "wn", cnt) == 0 | ||
3504 | || (p[0] == 'x' && !p[1]) | ||
3505 | ) { | ||
3506 | cnt = file_write(current_filename, text, end - 1); | ||
3507 | if (cnt < 0) { | ||
3508 | if (cnt == -1) | ||
3509 | status_line_bold("Write error: %s", strerror(errno)); | ||
3510 | } else { | ||
3511 | file_modified = 0; | ||
3512 | last_file_modified = -1; | ||
3513 | status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt); | ||
3514 | if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' | ||
3515 | || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N' | ||
3516 | ) { | ||
3517 | editing = 0; | ||
3518 | } | ||
3519 | } | ||
3520 | } else if (strncmp(p, "file", cnt) == 0) { | ||
3521 | last_status_cksum = 0; // force status update | ||
3522 | } else if (sscanf(p, "%d", &j) > 0) { | ||
3523 | dot = find_line(j); // go to line # j | ||
3524 | dot_skip_over_ws(); | ||
3525 | } else { // unrecognized cmd | ||
3526 | not_implemented(p); | ||
3527 | } | ||
3528 | #endif /* !FEATURE_VI_COLON */ | ||
3529 | break; | 3919 | break; |
3530 | case '<': // <- Left shift something | 3920 | case '<': // <- Left shift something |
3531 | case '>': // >- Right shift something | 3921 | case '>': // >- Right shift something |
3532 | cnt = count_lines(text, dot); // remember what line we are on | 3922 | cnt = count_lines(text, dot); // remember what line we are on |
3533 | c1 = get_one_char(); // get the type of thing to delete | 3923 | c1 = get_one_char(); // get the type of thing to delete |
3534 | find_range(&p, &q, c1); | 3924 | find_range(&p, &q, c1); |
3535 | yank_delete(p, q, 1, YANKONLY); // save copy before change | 3925 | yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change |
3536 | p = begin_line(p); | 3926 | p = begin_line(p); |
3537 | q = end_line(q); | 3927 | q = end_line(q); |
3538 | i = count_lines(p, q); // # of lines we are shifting | 3928 | i = count_lines(p, q); // # of lines we are shifting |
@@ -3541,16 +3931,16 @@ static void do_cmd(int c) | |||
3541 | // shift left- remove tab or 8 spaces | 3931 | // shift left- remove tab or 8 spaces |
3542 | if (*p == '\t') { | 3932 | if (*p == '\t') { |
3543 | // shrink buffer 1 char | 3933 | // shrink buffer 1 char |
3544 | text_hole_delete(p, p); | 3934 | text_hole_delete(p, p, NO_UNDO); |
3545 | } else if (*p == ' ') { | 3935 | } else if (*p == ' ') { |
3546 | // we should be calculating columns, not just SPACE | 3936 | // we should be calculating columns, not just SPACE |
3547 | for (j = 0; *p == ' ' && j < tabstop; j++) { | 3937 | for (j = 0; *p == ' ' && j < tabstop; j++) { |
3548 | text_hole_delete(p, p); | 3938 | text_hole_delete(p, p, NO_UNDO); |
3549 | } | 3939 | } |
3550 | } | 3940 | } |
3551 | } else if (c == '>') { | 3941 | } else if (c == '>') { |
3552 | // shift right -- add tab or 8 spaces | 3942 | // shift right -- add tab or 8 spaces |
3553 | char_insert(p, '\t'); | 3943 | char_insert(p, '\t', ALLOW_UNDO); |
3554 | } | 3944 | } |
3555 | } | 3945 | } |
3556 | dot = find_line(cnt); // what line were we on | 3946 | dot = find_line(cnt); // what line were we on |
@@ -3585,7 +3975,7 @@ static void do_cmd(int c) | |||
3585 | save_dot = dot; | 3975 | save_dot = dot; |
3586 | dot = dollar_line(dot); // move to before NL | 3976 | dot = dollar_line(dot); // move to before NL |
3587 | // copy text into a register and delete | 3977 | // copy text into a register and delete |
3588 | dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l | 3978 | dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l |
3589 | if (c == 'C') | 3979 | if (c == 'C') |
3590 | goto dc_i; // start inserting | 3980 | goto dc_i; // start inserting |
3591 | #if ENABLE_FEATURE_VI_DOT_CMD | 3981 | #if ENABLE_FEATURE_VI_DOT_CMD |
@@ -3630,15 +4020,22 @@ static void do_cmd(int c) | |||
3630 | case KEYCODE_INSERT: // Cursor Key Insert | 4020 | case KEYCODE_INSERT: // Cursor Key Insert |
3631 | dc_i: | 4021 | dc_i: |
3632 | cmd_mode = 1; // start inserting | 4022 | cmd_mode = 1; // start inserting |
4023 | undo_queue_commit(); // commit queue when cmd_mode changes | ||
3633 | break; | 4024 | break; |
3634 | case 'J': // J- join current and next lines together | 4025 | case 'J': // J- join current and next lines together |
3635 | do { | 4026 | do { |
3636 | dot_end(); // move to NL | 4027 | dot_end(); // move to NL |
3637 | if (dot < end - 1) { // make sure not last char in text[] | 4028 | if (dot < end - 1) { // make sure not last char in text[] |
4029 | #if ENABLE_FEATURE_VI_UNDO | ||
4030 | undo_push(dot, 1, UNDO_DEL); | ||
3638 | *dot++ = ' '; // replace NL with space | 4031 | *dot++ = ' '; // replace NL with space |
3639 | file_modified++; | 4032 | undo_push((dot - 1), 1, UNDO_INS_CHAIN); |
4033 | #else | ||
4034 | *dot++ = ' '; | ||
4035 | modified_count++; | ||
4036 | #endif | ||
3640 | while (isblank(*dot)) { // delete leading WS | 4037 | while (isblank(*dot)) { // delete leading WS |
3641 | dot_delete(); | 4038 | text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN); |
3642 | } | 4039 | } |
3643 | } | 4040 | } |
3644 | } while (--cmdcnt > 0); | 4041 | } while (--cmdcnt > 0); |
@@ -3667,10 +4064,10 @@ static void do_cmd(int c) | |||
3667 | dot_prev(); | 4064 | dot_prev(); |
3668 | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." | 4065 | case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." |
3669 | dot_end(); | 4066 | dot_end(); |
3670 | dot = char_insert(dot, '\n'); | 4067 | dot = char_insert(dot, '\n', ALLOW_UNDO); |
3671 | } else { | 4068 | } else { |
3672 | dot_begin(); // 0 | 4069 | dot_begin(); // 0 |
3673 | dot = char_insert(dot, '\n'); // i\n ESC | 4070 | dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC |
3674 | dot_prev(); // - | 4071 | dot_prev(); // - |
3675 | } | 4072 | } |
3676 | goto dc_i; | 4073 | goto dc_i; |
@@ -3678,6 +4075,7 @@ static void do_cmd(int c) | |||
3678 | case 'R': // R- continuous Replace char | 4075 | case 'R': // R- continuous Replace char |
3679 | dc5: | 4076 | dc5: |
3680 | cmd_mode = 2; | 4077 | cmd_mode = 2; |
4078 | undo_queue_commit(); | ||
3681 | break; | 4079 | break; |
3682 | case KEYCODE_DELETE: | 4080 | case KEYCODE_DELETE: |
3683 | c = 'x'; | 4081 | c = 'x'; |
@@ -3692,7 +4090,7 @@ static void do_cmd(int c) | |||
3692 | if (dot[dir] != '\n') { | 4090 | if (dot[dir] != '\n') { |
3693 | if (c == 'X') | 4091 | if (c == 'X') |
3694 | dot--; // delete prev char | 4092 | dot--; // delete prev char |
3695 | dot = yank_delete(dot, dot, 0, YANKDEL); // delete char | 4093 | dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char |
3696 | } | 4094 | } |
3697 | } while (--cmdcnt > 0); | 4095 | } while (--cmdcnt > 0); |
3698 | end_cmd_q(); // stop adding to q | 4096 | end_cmd_q(); // stop adding to q |
@@ -3706,7 +4104,7 @@ static void do_cmd(int c) | |||
3706 | indicate_error(c); | 4104 | indicate_error(c); |
3707 | break; | 4105 | break; |
3708 | } | 4106 | } |
3709 | if (file_modified) { | 4107 | if (modified_count) { |
3710 | if (ENABLE_FEATURE_VI_READONLY && readonly_mode) { | 4108 | if (ENABLE_FEATURE_VI_READONLY && readonly_mode) { |
3711 | status_line_bold("'%s' is read only", current_filename); | 4109 | status_line_bold("'%s' is read only", current_filename); |
3712 | break; | 4110 | break; |
@@ -3763,6 +4161,7 @@ static void do_cmd(int c) | |||
3763 | c1 = get_one_char(); // get the type of thing to delete | 4161 | c1 = get_one_char(); // get the type of thing to delete |
3764 | // determine range, and whether it spans lines | 4162 | // determine range, and whether it spans lines |
3765 | ml = find_range(&p, &q, c1); | 4163 | ml = find_range(&p, &q, c1); |
4164 | place_cursor(0, 0); | ||
3766 | if (c1 == 27) { // ESC- user changed mind and wants out | 4165 | if (c1 == 27) { // ESC- user changed mind and wants out |
3767 | c = c1 = 27; // Escape- do nothing | 4166 | c = c1 = 27; // Escape- do nothing |
3768 | } else if (strchr("wW", c1)) { | 4167 | } else if (strchr("wW", c1)) { |
@@ -3774,13 +4173,13 @@ static void do_cmd(int c) | |||
3774 | q--; | 4173 | q--; |
3775 | } | 4174 | } |
3776 | } | 4175 | } |
3777 | dot = yank_delete(p, q, ml, yf); // delete word | 4176 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word |
3778 | } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { | 4177 | } else if (strchr("^0bBeEft%$ lh\b\177", c1)) { |
3779 | // partial line copy text into a register and delete | 4178 | // partial line copy text into a register and delete |
3780 | dot = yank_delete(p, q, ml, yf); // delete word | 4179 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word |
3781 | } else if (strchr("cdykjHL+-{}\r\n", c1)) { | 4180 | } else if (strchr("cdykjHL+-{}\r\n", c1)) { |
3782 | // whole line copy text into a register and delete | 4181 | // whole line copy text into a register and delete |
3783 | dot = yank_delete(p, q, ml, yf); // delete lines | 4182 | dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines |
3784 | whole = 1; | 4183 | whole = 1; |
3785 | } else { | 4184 | } else { |
3786 | // could not recognize object | 4185 | // could not recognize object |
@@ -3790,7 +4189,7 @@ static void do_cmd(int c) | |||
3790 | } | 4189 | } |
3791 | if (ml && whole) { | 4190 | if (ml && whole) { |
3792 | if (c == 'c') { | 4191 | if (c == 'c') { |
3793 | dot = char_insert(dot, '\n'); | 4192 | dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN); |
3794 | // on the last line of file don't move to prev line | 4193 | // on the last line of file don't move to prev line |
3795 | if (whole && dot != (end-1)) { | 4194 | if (whole && dot != (end-1)) { |
3796 | dot_prev(); | 4195 | dot_prev(); |
@@ -3836,8 +4235,14 @@ static void do_cmd(int c) | |||
3836 | case 'r': // r- replace the current char with user input | 4235 | case 'r': // r- replace the current char with user input |
3837 | c1 = get_one_char(); // get the replacement char | 4236 | c1 = get_one_char(); // get the replacement char |
3838 | if (*dot != '\n') { | 4237 | if (*dot != '\n') { |
4238 | #if ENABLE_FEATURE_VI_UNDO | ||
4239 | undo_push(dot, 1, UNDO_DEL); | ||
4240 | *dot = c1; | ||
4241 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4242 | #else | ||
3839 | *dot = c1; | 4243 | *dot = c1; |
3840 | file_modified++; | 4244 | modified_count++; |
4245 | #endif | ||
3841 | } | 4246 | } |
3842 | end_cmd_q(); // stop adding to q | 4247 | end_cmd_q(); // stop adding to q |
3843 | break; | 4248 | break; |
@@ -3877,13 +4282,25 @@ static void do_cmd(int c) | |||
3877 | break; | 4282 | break; |
3878 | case '~': // ~- flip the case of letters a-z -> A-Z | 4283 | case '~': // ~- flip the case of letters a-z -> A-Z |
3879 | do { | 4284 | do { |
4285 | #if ENABLE_FEATURE_VI_UNDO | ||
4286 | if (islower(*dot)) { | ||
4287 | undo_push(dot, 1, UNDO_DEL); | ||
4288 | *dot = toupper(*dot); | ||
4289 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4290 | } else if (isupper(*dot)) { | ||
4291 | undo_push(dot, 1, UNDO_DEL); | ||
4292 | *dot = tolower(*dot); | ||
4293 | undo_push(dot, 1, UNDO_INS_CHAIN); | ||
4294 | } | ||
4295 | #else | ||
3880 | if (islower(*dot)) { | 4296 | if (islower(*dot)) { |
3881 | *dot = toupper(*dot); | 4297 | *dot = toupper(*dot); |
3882 | file_modified++; | 4298 | modified_count++; |
3883 | } else if (isupper(*dot)) { | 4299 | } else if (isupper(*dot)) { |
3884 | *dot = tolower(*dot); | 4300 | *dot = tolower(*dot); |
3885 | file_modified++; | 4301 | modified_count++; |
3886 | } | 4302 | } |
4303 | #endif | ||
3887 | dot_right(); | 4304 | dot_right(); |
3888 | } while (--cmdcnt > 0); | 4305 | } while (--cmdcnt > 0); |
3889 | end_cmd_q(); // stop adding to q | 4306 | end_cmd_q(); // stop adding to q |
@@ -3913,7 +4330,7 @@ static void do_cmd(int c) | |||
3913 | dc1: | 4330 | dc1: |
3914 | // if text[] just became empty, add back an empty line | 4331 | // if text[] just became empty, add back an empty line |
3915 | if (end == text) { | 4332 | if (end == text) { |
3916 | char_insert(text, '\n'); // start empty buf with dummy line | 4333 | char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line |
3917 | dot = text; | 4334 | dot = text; |
3918 | } | 4335 | } |
3919 | // it is OK for dot to exactly equal to end, otherwise check dot validity | 4336 | // it is OK for dot to exactly equal to end, otherwise check dot validity |