As promised earlier, I have written undo support for BusyBox 'vi'.

This code is in a "works for me" state but needs testing and refinement. I have not tested it on any other platforms than my computer. I have not tested it under all possible configurations or scenarios, and there is room for future improvement. Adding an option for a maximum number of undo operations in order to limit memory usage on severely limited systems would be nice. At some point, the code also needs to ensure there is consistency in the "file_modified" counter since an undo_pop() decrements it until no undo operations remain.

Note that unlike Vim's undo, my undo code was written to plug into existing insert/delete/swap functions in BusyBox vi, and therefore does not group together the typing of or backspacing of multiple characters into one undo operation. I plan to implement this later, but it was far more important to me to get this code to the mailing list ASAP.

The following works in my testing:
* Typing characters, then undoing the typing
* Backspacing characters, then undoing
* Deleting (DEL key) characters, then undoing
* Using buffer commands such as 'dd', 'd100d', or 'p', then undoing
* Switching to REPLACE mode, overwriting text, then undoing
* Comparing a file and its counterpart that was modified with these operations, then undone, then saved.

I hope that this is helpful!

-Jody Bruchon
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 17:58:10.243293500 -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 ([email protected])
+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,7 @@
                                if (c != 27)
                                        dot = yank_delete(dot, dot, 0, 
YANKDEL);        // delete char
                                dot = char_insert(dot, c);      // insert new 
char
+                                       undo_push(dot, 1, 2);
                        }
                        goto dc1;
                }
@@ -3108,7 +3221,6 @@
                //case ']':     // ]-
                //case '_':     // _-
                //case '`':     // `-
-               //case 'u':     // u- FIXME- there is no undo
                //case 'v':     // v-
        default:                        // unrecognized command
                buf[0] = c;
@@ -3254,11 +3366,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 +3611,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
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to