[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