aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-rw-r--r--editors/vi.c795
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
419static int init_text_buffer(char *); // init from file or create new
420static void edit_file(char *); // edit one file 494static void edit_file(char *); // edit one file
421static void do_cmd(int); // execute a command 495static void do_cmd(int); // execute a command
422static int next_tabstop(int); 496static int next_tabstop(int);
@@ -437,10 +511,12 @@ static void dot_next(void); // move dot to next line B-o-l
437static void dot_prev(void); // move dot to prev line B-o-l 511static void dot_prev(void); // move dot to prev line B-o-l
438static void dot_scroll(int, int); // move the screen up or down 512static void dot_scroll(int, int); // move the screen up or down
439static void dot_skip_over_ws(void); // move dot pat WS 513static void dot_skip_over_ws(void); // move dot pat WS
440static void dot_delete(void); // delete the char at 'dot'
441static char *bound_dot(char *); // make sure text[0] <= P < "end" 514static char *bound_dot(char *); // make sure text[0] <= P < "end"
442static char *new_screen(int, int); // malloc virtual screen memory 515static char *new_screen(int, int); // malloc virtual screen memory
443static 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
519static 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[]!
446static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' 522static 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
448static int st_test(char *, int, int, char *); // helper for skip_thing() 524static int st_test(char *, int, int, char *); // helper for skip_thing()
449static char *skip_thing(char *, int, int, int); // skip some object 525static char *skip_thing(char *, int, int, int); // skip some object
450static char *find_pair(char *, char); // find matching pair () [] {} 526static char *find_pair(char *, char); // find matching pair () [] {}
451static 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
530static 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[]!
454static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole 533static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
455static 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
537static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
456static void show_help(void); // display some help info 538static void show_help(void); // display some help info
457static void rawmode(void); // set "raw" mode on tty 539static void rawmode(void); // set "raw" mode on tty
458static void cookmode(void); // return to "cooked" mode on tty 540static void cookmode(void); // return to "cooked" mode on tty
@@ -460,7 +542,6 @@ static void cookmode(void); // return to "cooked" mode on tty
460static int mysleep(int); 542static int mysleep(int);
461static int readit(void); // read (maybe cursor) key from stdin 543static int readit(void); // read (maybe cursor) key from stdin
462static int get_one_char(void); // read 1 char from stdin 544static int get_one_char(void); // read 1 char from stdin
463static 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
496static char *get_one_address(char *, int *); // get colon addr, if present 577static char *get_one_address(char *, int *); // get colon addr, if present
497static char *get_address(char *, int *, int *); // get two colon addrs, if present 578static char *get_address(char *, int *, int *); // get two colon addrs, if present
498static void colon(char *); // execute the "colon" mode cmds
499#endif 579#endif
580static void colon(char *); // execute the "colon" mode cmds
500#if ENABLE_FEATURE_VI_USE_SIGNALS 581#if ENABLE_FEATURE_VI_USE_SIGNALS
501static void winch_sig(int); // catch window size changes 582static void winch_sig(int); // catch window size changes
502static void suspend_sig(int); // catch ctrl-Z 583static 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[]!
517static 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
601static 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
520static char *text_yank(char *, char *, int); // save copy of "p" into a register 604static char *text_yank(char *, char *, int); // save copy of "p" into a register
521static char what_reg(void); // what is letter of current YDreg 605static char what_reg(void); // what is letter of current YDreg
522static void check_context(char); // remember context for '' command 606static void check_context(char); // remember context for '' command
523#endif 607#endif
608#if ENABLE_FEATURE_VI_UNDO
609static void flush_undo_data(void);
610static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
611static void undo_pop(void); // Undo the last operation
612# if ENABLE_FEATURE_VI_UNDO_QUEUE
613static 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
525static void crash_dummy(); 623static void crash_dummy();
526static void crash_test(); 624static void crash_test();
527static int crashme = 0; 625static int crashme = 0;
528#endif 626#endif
529 627
530
531static void write1(const char *out) 628static 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)
618static int init_text_buffer(char *fn) 723static 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!
913static void colon(char *buf) 1019static 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
1366static void Hit_Return(void) 1542static 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 ----------------------------------
1573static void dot_left(void) 1749static 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
1579static void dot_right(void) 1756static 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
1585static void dot_begin(void) 1763static 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
1590static void dot_end(void) 1769static 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
1615static void dot_next(void) 1795static void dot_next(void)
1616{ 1796{
1797 undo_queue_commit();
1617 dot = next_line(dot); 1798 dot = next_line(dot);
1618} 1799}
1619 1800
1620static void dot_prev(void) 1801static 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
1656static void dot_delete(void) // delete the char at 'dot'
1657{
1658 text_hole_delete(dot, dot);
1659}
1660
1661static char *bound_dot(char *p) // make sure text[0] <= P < "end" 1839static 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
1807static char *char_insert(char *p, char c) // insert the char c at 'p' 1985static 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
2273static 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)
2285static 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
2384static 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
2448static 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[]
2090static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive 2500// "undo" value indicates if this operation should be undo-able
2501static 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//
2126static char *yank_delete(char *start, char *stop, int dist, int yf) 2555static 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[]!
2221static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p' 2650static 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
2484static 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[]!
2496static int file_insert(const char *fn, char *p, int update_ro_status) 2924static 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