[PATCH v2] 'u' (Undo) command support for vi
Jody Bruchon
jody at jodybruchon.com
Tue Mar 18 22:25:54 UTC 2014
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 <jody at jodybruchon.com>
-------------- next part --------------
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 (jody at 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 == '>') {
More information about the busybox
mailing list