On 3/18/2014 6:15 PM, Jody Bruchon wrote:
This code is in a "works for me" state but needs testing and refinement.
D'oh, I didn't test building without the undo feature enabled and missed an #if/#endif. Apologies. Revised patch attached.
Also, there is an "unused parameter" warning if undo is disabled; I'm not sure if there is an elegant way to get rid of this since it'll require every invocation of text_hole_delete to have an #if/#else/#endif around it. Suggestions welcome.
Signed-off-by: Jody Bruchon <j...@jodybruchon.com>
diff -Naurw a/editors/vi.c b/editors/vi.c --- a/editors/vi.c 2014-01-09 13:15:44.000000000 -0500 +++ b/editors/vi.c 2014-03-18 18:23:37.863250314 -0400 @@ -17,7 +17,6 @@ * it would be easier to change the mark when add/delete lines * More intelligence in refresh() * ":r !cmd" and "!cmd" to filter text through an external command - * A true "undo" facility * An "ex" line oriented mode- maybe using "cmdedit" */ @@ -136,6 +135,13 @@ //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. //config: //config: This is not clean but helps a lot on serial lines and such. +//config:config FEATURE_VI_UNDO +//config: bool "Support undo command 'u'" +//config: default y +//config: depends on VI +//config: help +//config: Support the 'u' command to undo insertion, deletion, and replacement +//config: of text. //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP)) @@ -347,6 +353,16 @@ char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; +#if ENABLE_FEATURE_VI_UNDO + struct undo_object { + struct undo_object *prev; // Linking back avoids list traversal (LIFO) + int type; // 0=deleted, 1=inserted, 2=swapped + int start; // Offset where the data should be restored/deleted + int length; // total data size + char *undo_text; // ptr to text that will be inserted + } *undo_stack_tail; +#endif + }; #define G (*ptr_to_globals) #define text (G.text ) @@ -408,6 +424,10 @@ #define last_modifying_cmd (G.last_modifying_cmd ) #define get_input_line__buf (G.get_input_line__buf) +#if ENABLE_FEATURE_VI_UNDO +#define undo_stack_tail (G.undo_stack_tail) +#endif + #define INIT_G() do { \ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ last_file_modified = -1; \ @@ -448,7 +468,7 @@ static int st_test(char *, int, int, char *); // helper for skip_thing() static char *skip_thing(char *, int, int, int); // skip some object static char *find_pair(char *, char); // find matching pair () [] {} -static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole +static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole // might reallocate text[]! use p += text_hole_make(p, ...), // and be careful to not use pointers into potentially freed text[]! static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole @@ -526,7 +546,10 @@ static void crash_test(); static int crashme = 0; #endif - +#if ENABLE_FEATURE_VI_UNDO +static char undo_push(char *, int, int); // Push an operation on the undo stack +static char undo_pop(void); // Undo the last operation +#endif static void write1(const char *out) { @@ -540,6 +563,9 @@ INIT_G(); +#if ENABLE_FEATURE_VI_UNDO + undo_stack_tail = NULL; +#endif #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME my_pid = getpid(); #endif @@ -1269,7 +1295,7 @@ if (found) { uintptr_t bias; // we found the "find" pattern - delete it - text_hole_delete(found, found + len_F - 1); + text_hole_delete(found, found + len_F - 1, 1); // inset the "replace" patern bias = string_insert(found, R); // insert the string found += bias; @@ -1655,7 +1681,7 @@ static void dot_delete(void) // delete the char at 'dot' { - text_hole_delete(dot, dot); + text_hole_delete(dot, dot, 0); } static char *bound_dot(char *p) // make sure text[0] <= P < "end" @@ -1811,6 +1837,9 @@ refresh(FALSE); // show the ^ c = get_one_char(); *p = c; +#if ENABLE_FEATURE_VI_UNDO + undo_push(p, 1, 1); +#endif p++; file_modified++; } else if (c == 27) { // Is this an ESC? @@ -1825,7 +1854,7 @@ // 123456789 if ((p[-1] != '\n') && (dot>text)) { p--; - p = text_hole_delete(p, p); // shrink buffer 1 char + p = text_hole_delete(p, p, 0); // shrink buffer 1 char } } else { #if ENABLE_FEATURE_VI_SETOPTS @@ -1838,6 +1867,9 @@ #if ENABLE_FEATURE_VI_SETOPTS sp = p; // remember addr of insert #endif +#if ENABLE_FEATURE_VI_UNDO + undo_push(p, 1, 1); +#endif p += 1 + stupid_insert(p, c); // insert the char #if ENABLE_FEATURE_VI_SETOPTS if (showmatch && strchr(")]}", *sp) != NULL) { @@ -1853,6 +1885,9 @@ bias = text_hole_make(p, len); p += bias; q += bias; +#if ENABLE_FEATURE_VI_UNDO + undo_push(p, len, 1); +#endif memcpy(p, q, len); p += len; } @@ -2051,6 +2086,76 @@ } #endif /* FEATURE_VI_SETOPTS */ +#if ENABLE_FEATURE_VI_UNDO +// Undo functions added by Jody Bruchon (j...@jodybruchon.com) +static char undo_push(char *src, int length, int type) // Add to the undo stack +{ + struct undo_object *undo_temp; + // "type" values + // 0: deleted text, undo will restore to buffer + // 1: insertion, undo will remove from buffer + // 2: swap undo will perform the remove and insert operations in one shot + // Swap undo pushing is a two-operation process (push with 0, then with 2.) + + if ((type > 2) || (type < 0)) return 1; // only types 0,1,2 are valid + // Allocate a new undo object and use it as the stack tail + if (type == 2) { + undo_stack_tail->type = 2; // Previous insert operation is part of a replace + } else { + undo_temp = undo_stack_tail; + undo_stack_tail = (struct undo_object *) malloc(sizeof(struct undo_object)); + undo_stack_tail->prev = undo_temp; + undo_stack_tail->start = src - text; // use offset from start of text buffer + undo_stack_tail->length = length; + undo_stack_tail->type = type; + if (type == 0) { + undo_stack_tail->undo_text = (char *) malloc(length); + memcpy(undo_stack_tail->undo_text, src, length); + } + } + return 0; +} + +static char undo_pop(void) // Undo the last operation +{ + int repeat = 0; + char *u_start, *u_end; + struct undo_object *undo_temp; + + // Check for an empty undo stack first + if (undo_stack_tail != NULL) { + switch (undo_stack_tail->type) { + case 0: + // make hole and put in text that was deleted; deallocate text + u_start = text + undo_stack_tail->start; + text_hole_make(u_start, undo_stack_tail->length); + memcpy(u_start, undo_stack_tail->undo_text, undo_stack_tail->length); + free(undo_stack_tail->undo_text); + break; + case 1: + case 2: + // delete what was inserted + u_start = undo_stack_tail->start + text; + u_end = u_start - 1 + undo_stack_tail->length; + text_hole_delete(u_start, u_end, 1); + break; + } + if (undo_stack_tail->type == 2) repeat = 1; // handle swap undo + // Lower modification count and deallocate undo object + file_modified--; + undo_temp = undo_stack_tail->prev; + free(undo_stack_tail); + undo_stack_tail = undo_temp; + if (repeat == 1) undo_pop(); // swap undo requires two runs + } else { + status_line("Already at oldest change"); + return 1; + } + refresh(FALSE); + return 0; +} +#endif + // open a hole in text[] // might reallocate text[]! use p += text_hole_make(p, ...), // and be careful to not use pointers into potentially freed text[]! @@ -2087,7 +2192,8 @@ } // close a hole in text[] -static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive +// "undo" value indicates if this operation should be undo-able (0 = yes, 1 = no) +static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive { char *src, *dest; int cnt, hole_size; @@ -2102,6 +2208,9 @@ } hole_size = q - p + 1; cnt = end - src; +#if ENABLE_FEATURE_VI_UNDO + if (undo == 0) undo_push(p, hole_size, 0); // UNDO support +#endif if (src < text || src > end) goto thd0; if (dest < text || dest >= end) @@ -2152,7 +2261,7 @@ text_yank(start, stop, YDreg); #endif if (yf == YANKDEL) { - p = text_hole_delete(start, stop); + p = text_hole_delete(start, stop, 0); } // delete lines return p; } @@ -2224,6 +2333,9 @@ int i; i = strlen(s); +#if ENABLE_FEATURE_VI_UNDO + undo_push(p, i, 1); +#endif bias = text_hole_make(p, i); p += bias; memcpy(p, s, i); @@ -2516,10 +2628,10 @@ cnt = safe_read(fd, p, size); if (cnt < 0) { status_line_bold_errno(fn); - p = text_hole_delete(p, p + size - 1); // un-do buffer insert + p = text_hole_delete(p, p + size - 1, 1); // un-do buffer insert } else if (cnt < size) { // There was a partial read, shrink unused space text[] - p = text_hole_delete(p + cnt, p + size - 1); // un-do buffer insert + p = text_hole_delete(p + cnt, p + size - 1, 1); // un-do buffer insert status_line_bold("can't read '%s'", fn); } if (cnt >= size) @@ -3053,6 +3165,9 @@ if (c != 27) dot = yank_delete(dot, dot, 0, YANKDEL); // delete char dot = char_insert(dot, c); // insert new char +#if ENABLE_FEATURE_VI_UNDO + undo_push(dot, 1, 2); +#endif } goto dc1; } @@ -3108,7 +3223,6 @@ //case ']': // ]- //case '_': // _- //case '`': // `- - //case 'u': // u- FIXME- there is no undo //case 'v': // v- default: // unrecognized command buf[0] = c; @@ -3254,11 +3368,16 @@ string_insert(dot, p); // insert the string end_cmd_q(); // stop adding to q break; +#if ENABLE_FEATURE_VI_UNDO + case 'u': // u- undo last operation + undo_pop(); + break; +#endif case 'U': // U- Undo; replace current line with original version if (reg[Ureg] != NULL) { p = begin_line(dot); q = end_line(dot); - p = text_hole_delete(p, q); // delete cur line + p = text_hole_delete(p, q, 1); // delete cur line p += string_insert(p, reg[Ureg]); // insert orig line dot = p; dot_skip_over_ws(); @@ -3494,11 +3613,11 @@ // shift left- remove tab or 8 spaces if (*p == '\t') { // shrink buffer 1 char - text_hole_delete(p, p); + text_hole_delete(p, p, 1); } else if (*p == ' ') { // we should be calculating columns, not just SPACE for (j = 0; *p == ' ' && j < tabstop; j++) { - text_hole_delete(p, p); + text_hole_delete(p, p, 1); } } } else if (c == '>') {
_______________________________________________ busybox mailing list busybox@busybox.net http://lists.busybox.net/mailman/listinfo/busybox