[git commit] vi: make buffer handling more vi-like

Denys Vlasenko vda.linux at googlemail.com
Mon Mar 29 10:05:53 UTC 2021


commit: https://git.busybox.net/busybox/commit/?id=25d259264019e4171eddde570271a82b6dd0f79a
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

Vi places text affected by change/delete/yank operations into a
buffer.  The contents of such buffers can be restored with the put
commands, 'p' or 'P'.  These behave differently depending on whether
the buffer contains whole lines or partial lines.  For whole lines
the text is copied into the file on the line before (P) or after
(p) the current line.  For partial lines the text is copied before
or after the current cursor position.

Whether an operation results in whole or partial lines depends on
the command used.

BusyBox vi treats any buffer with a newline as though it contained
whole lines.  This is incorrect.  Deleting multiple words across
a line boundary results in a buffer with a newline but not having
whole lines.

Rework how buffers are handled to behave more like vi.

function                                             old     new   delta
static.text_yank                                      79      99     +20
colon                                               3092    3097      +5
edit_file                                            885     887      +2
yank_delete                                          127     112     -15
.rodata                                           105139  105101     -38
find_range                                           514     467     -47
do_cmd                                              5088    4842    -246
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/4 up/down: 27/-346)          Total: -319 bytes

Signed-off-by: Ron Yorston <rmy at pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 editors/vi.c | 162 +++++++++++++++++++++++++++--------------------------------
 1 file changed, 73 insertions(+), 89 deletions(-)

diff --git a/editors/vi.c b/editors/vi.c
index 7a7247c10..ccf2870ab 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -254,6 +254,9 @@ enum {
 	BACK = -1,	// code depends on "-1" for array index
 	LIMITED = 0,	// char_search() only current line
 	FULL = 1,	// char_search() to the end/beginning of entire text
+	PARTIAL = 0,	// buffer contains partial line
+	WHOLE = 1,	// buffer contains whole lines
+	MULTI = 2,	// buffer may include newlines
 
 	S_BEFORE_WS = 1,	// used in skip_thing() for moving "dot"
 	S_TO_WS = 2,		// used in skip_thing() for moving "dot"
@@ -343,6 +346,7 @@ struct globals {
 	smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
 #define Ureg 27
 	char *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
+	char regtype[28];       // buffer type: WHOLE, MULTI or PARTIAL
 	char *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
 	char *context_start, *context_end;
 #endif
@@ -452,6 +456,7 @@ struct globals {
 
 #define YDreg          (G.YDreg         )
 //#define Ureg           (G.Ureg          )
+#define regtype        (G.regtype       )
 #define mark           (G.mark          )
 #define context_start  (G.context_start )
 #define context_end    (G.context_end   )
@@ -1314,7 +1319,8 @@ static void not_implemented(const char *s)
 
 //----- Block insert/delete, undo ops --------------------------
 #if ENABLE_FEATURE_VI_YANKMARK
-static char *text_yank(char *p, char *q, int dest)	// copy text into a register
+// copy text into a register
+static char *text_yank(char *p, char *q, int dest, int buftype)
 {
 	int cnt = q - p;
 	if (cnt < 0) {		// they are backwards- reverse them
@@ -1323,6 +1329,7 @@ static char *text_yank(char *p, char *q, int dest)	// copy text into a register
 	}
 	free(reg[dest]);	//  if already a yank register, free it
 	reg[dest] = xstrndup(p, cnt + 1);
+	regtype[dest] = buftype;
 	return p;
 }
 
@@ -1819,12 +1826,11 @@ static void end_cmd_q(void)
 #endif /* FEATURE_VI_DOT_CMD */
 
 // copy text into register, then delete text.
-// if dist <= 0, do not include, or go past, a NewLine
 //
 #if !ENABLE_FEATURE_VI_UNDO
 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
 #endif
-static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
+static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo)
 {
 	char *p;
 
@@ -1835,22 +1841,11 @@ static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
 		start = stop;
 		stop = p;
 	}
-	if (dist <= 0) {
-		// we cannot cross NL boundaries
-		p = start;
-		if (*p == '\n')
-			return p;
-		// dont go past a NewLine
-		for (; p + 1 <= stop; p++) {
-			if (p[1] == '\n') {
-				stop = p;	// "stop" just before NewLine
-				break;
-			}
-		}
-	}
+	if (buftype == PARTIAL && *start == '\n')
+		return start;
 	p = start;
 #if ENABLE_FEATURE_VI_YANKMARK
-	text_yank(start, stop, YDreg);
+	text_yank(start, stop, YDreg, buftype);
 #endif
 	if (yf == YANKDEL) {
 		p = text_hole_delete(start, stop, undo);
@@ -2521,7 +2516,7 @@ static void colon(char *buf)
 			q = begin_line(dot);	// assume .,. for the range
 			r = end_line(dot);
 		}
-		dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO);	// save, then delete lines
+		dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO);	// save, then delete lines
 		dot_skip_over_ws();
 	} else if (strncmp(cmd, "edit", i) == 0) {	// Edit a file
 		int size;
@@ -2874,7 +2869,7 @@ static void colon(char *buf)
 			q = begin_line(dot);	// assume .,. for the range
 			r = end_line(dot);
 		}
-		text_yank(q, r, YDreg);
+		text_yank(q, r, YDreg, WHOLE);
 		li = count_lines(q, r);
 		status_line("Yank %d lines (%d chars) into [%c]",
 				li, strlen(reg[YDreg]), what_reg());
@@ -3000,74 +2995,65 @@ static void do_cmd(int c);
 static int find_range(char **start, char **stop, char c)
 {
 	char *save_dot, *p, *q, *t;
-	int cnt, multiline = 0, forward;
+	int buftype = -1;
 
 	save_dot = dot;
 	p = q = dot;
 
-	// will a 'G' command move forwards or backwards?
-	forward = cmdcnt == 0 || cmdcnt > count_lines(text, dot);
-
 	if (strchr("cdy><", c)) {
 		// these cmds operate on whole lines
-		p = q = begin_line(p);
-		for (cnt = 1; cnt < cmdcnt; cnt++) {
-			q = next_line(q);
-		}
-		q = end_line(q);
+		buftype = WHOLE;
+		if (--cmdcnt > 0)
+			do_cmd('j');
 	} else if (strchr("^%$0bBeEfth\b\177", c)) {
 		// These cmds operate on char positions
+		buftype = PARTIAL;
 		do_cmd(c);		// execute movement cmd
-		q = dot;
+		if (p == dot)	// no movement is an error
+			buftype = -1;
 	} else if (strchr("wW", c)) {
+		buftype = MULTI;
 		do_cmd(c);		// execute movement cmd
 		// step back one char, but not if we're at end of file
 		if (dot > p && !((dot == end - 2 && end[-1] == '\n') || dot == end - 1))
 			dot--;
-		q = dot;
-	} else if (strchr("H-k{", c) || (c == 'G' && !forward)) {
-		// these operate on multi-lines backwards
-		q = end_line(dot);	// find NL
-		do_cmd(c);		// execute movement cmd
-		dot_begin();
-		p = dot;
-	} else if (strchr("L+j}\r\n", c) || (c == 'G' && forward)) {
-		// these operate on multi-lines forwards
-		p = begin_line(dot);
+	} else if (strchr("GHL+-jk{}\r\n", c)) {
+		// these operate on whole lines
+		buftype = WHOLE;
 		do_cmd(c);		// execute movement cmd
-		dot_end();		// find NL
-		q = dot;
-	} else /* if (c == ' ' || c == 'l') */ {
+	} else if (c == ' ' || c == 'l') {
 		// forward motion by character
 		int tmpcnt = (cmdcnt ?: 1);
+		buftype = PARTIAL;
 		do_cmd(c);		// execute movement cmd
 		// exclude last char unless range isn't what we expected
 		// this indicates we've hit EOL
 		if (tmpcnt == dot - p)
 			dot--;
-		q = dot;
 	}
+
+	if (buftype == -1)
+		return buftype;
+
+	q = dot;
 	if (q < p) {
 		t = q;
 		q = p;
 		p = t;
 	}
 
+	if (buftype == WHOLE) {
+		p = begin_line(p);
+		q = end_line(q);
+	}
+
 	// backward char movements don't include start position
 	if (q > p && strchr("^0bBh\b\177", c)) q--;
 
-	multiline = 0;
-	for (t = p; t <= q; t++) {
-		if (*t == '\n') {
-			multiline = 1;
-			break;
-		}
-	}
-
 	*start = p;
 	*stop = q;
 	dot = save_dot;
-	return multiline;
+	return buftype;
 }
 
 //---------------------------------------------------------------------
@@ -3132,7 +3118,7 @@ static void do_cmd(int c)
 		} else {
 			if (1 <= c || Isprint(c)) {
 				if (c != 27)
-					dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);	// delete char
+					dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);	// delete char
 				dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);	// insert new char
 			}
 			goto dc1;
@@ -3312,7 +3298,7 @@ static void do_cmd(int c)
 			break;
 		}
 		// are we putting whole lines or strings
-		if (strchr(p, '\n') != NULL) {
+		if (regtype[YDreg] == WHOLE) {
 			if (c == 'P') {
 				dot_begin();	// putting lines- Put above
 			}
@@ -3523,7 +3509,7 @@ static void do_cmd(int c)
 		cnt = count_lines(text, dot);	// remember what line we are on
 		c1 = get_one_char();	// get the type of thing to delete
 		find_range(&p, &q, c1);
-		yank_delete(p, q, 1, YANKONLY, NO_UNDO);	// save copy before change
+		yank_delete(p, q, WHOLE, YANKONLY, NO_UNDO);	// save copy before change
 		p = begin_line(p);
 		q = end_line(q);
 		i = count_lines(p, q);	// # of lines we are shifting
@@ -3576,7 +3562,7 @@ static void do_cmd(int c)
 		save_dot = dot;
 		dot = dollar_line(dot);	// move to before NL
 		// copy text into a register and delete
-		dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO);	// delete to e-o-l
+		dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);	// delete to e-o-l
 		if (c == 'C')
 			goto dc_i;	// start inserting
 #if ENABLE_FEATURE_VI_DOT_CMD
@@ -3682,7 +3668,7 @@ static void do_cmd(int c)
 		break;
 	case KEYCODE_DELETE:
 		if (dot < end - 1)
-			dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
+			dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
 		break;
 	case 'X':			// X- delete char before dot
 	case 'x':			// x- delete the current char
@@ -3694,7 +3680,7 @@ static void do_cmd(int c)
 			if (dot[dir] != '\n') {
 				if (c == 'X')
 					dot--;	// delete prev char
-				dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);	// delete char
+				dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);	// delete char
 			}
 		} while (--cmdcnt > 0);
 		end_cmd_q();	// stop adding to q
@@ -3754,21 +3740,29 @@ static void do_cmd(int c)
 	case 'Y':			// Y- Yank a line
 #endif
 	{
-		int yf, ml, whole = 0;
+#if ENABLE_FEATURE_VI_YANKMARK
+		char *savereg = reg[YDreg];
+#endif
+		int yf, buftype = 0;
 		yf = YANKDEL;	// assume either "c" or "d"
 #if ENABLE_FEATURE_VI_YANKMARK
 		if (c == 'y' || c == 'Y')
 			yf = YANKONLY;
 #endif
 		c1 = 'y';
-		if (c != 'Y')
+		if (c != 'Y') {
 			c1 = get_one_char();	// get the type of thing to delete
+			if (c1 == 27)	// ESC- user changed mind and wants out
+				goto dc6;
+		}
 		// determine range, and whether it spans lines
-		ml = find_range(&p, &q, c1);
+		buftype = find_range(&p, &q, c1);
 		place_cursor(0, 0);
-		if (c1 == 27) {	// ESC- user changed mind and wants out
-			c = c1 = 27;	// Escape- do nothing
-		} else if (c1 == 'w' || c1 == 'W') {
+		if (buftype == -1) { // invalid range
+			indicate_error();
+			goto dc6;
+		}
+		if (c1 == 'w' || c1 == 'W') {
 			char *q0 = q;
 			// don't include trailing WS as part of word
 			while (q > p && isspace(*q)) {
@@ -3778,25 +3772,13 @@ static void do_cmd(int c)
 			// for non-change operations WS after NL is not part of word
 			if (c != 'c' && q != p && *q != '\n')
 				q = q0;
-			dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);	// delete word
-		} else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
-			// partial line copy text into a register and delete
-			dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);	// delete word
-		} else if (strchr("cdykjGHL+-{}\r\n", c1)) {
-			// whole line copy text into a register and delete
-			dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);	// delete lines
-			whole = 1;
-		} else {
-			// could not recognize object
-			c = c1 = 27;	// error-
-			ml = 0;
-			indicate_error();
 		}
-		if (ml && whole) {
+		dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO);	// delete word
+		if (buftype == WHOLE) {
 			if (c == 'c') {
 				dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
 				// on the last line of file don't move to prev line
-				if (whole && dot != (end-1)) {
+				if (dot != (end-1)) {
 					dot_prev();
 				}
 			} else if (c == 'd') {
@@ -3804,16 +3786,17 @@ static void do_cmd(int c)
 				dot_skip_over_ws();
 			}
 		}
-		if (c1 != 27) {
-			// if CHANGING, not deleting, start inserting after the delete
-			if (c == 'c') {
-				strcpy(buf, "Change");
-				goto dc_i;	// start inserting
-			}
+		// if CHANGING, not deleting, start inserting after the delete
+		if (c == 'c') {
+			//strcpy(buf, "Change");
+			goto dc_i;	// start inserting
+		}
+#if ENABLE_FEATURE_VI_YANKMARK
+		// only update status if a yank has actually happened
+		if (reg[YDreg] != savereg) {
 			if (c == 'd') {
 				strcpy(buf, "Delete");
 			}
-#if ENABLE_FEATURE_VI_YANKMARK
 			if (c == 'y' || c == 'Y') {
 				strcpy(buf, "Yank");
 			}
@@ -3825,9 +3808,10 @@ static void do_cmd(int c)
 			}
 			status_line("%s %u lines (%u chars) using [%c]",
 				buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
-#endif
-			end_cmd_q();	// stop adding to q
 		}
+#endif
+ dc6:
+		end_cmd_q();	// stop adding to q
 		break;
 	}
 	case 'k':			// k- goto prev line, same col
@@ -4271,7 +4255,7 @@ static void edit_file(char *fn)
 		// save a copy of the current line- for the 'U" command
 		if (begin_line(dot) != cur_line) {
 			cur_line = begin_line(dot);
-			text_yank(begin_line(dot), end_line(dot), Ureg);
+			text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL);
 		}
 #endif
 #if ENABLE_FEATURE_VI_DOT_CMD


More information about the busybox-cvs mailing list