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