[PATCH 11/11 v2] vi: changes to option handling

Ron Yorston rmy at pobox.com
Tue Apr 6 21:11:21 UTC 2021


Since commit 70ee23399 (vi: code shrink) the ':set' command is
unable to process multiple options on a line.  Fix this by
temporarily null-terminating each option.

Change the default setting for all options to off to match vim.
Actually, 'flash' isn't an option in vim, only traditional vi,
where it's on by default.  In vim the corresponding option is
'visualbell' which defaults to off.  POSIX doesn't have either
of these.

Allow the abbreviation 'ts' for the 'tabstop' option.

Issue an error message if:

- an option is not implemented
- an option that takes a value has no '=' or has a 'no' prefix
- a boolean option has a '='

function                                             old     new   delta
colon                                               3292    3341     +49
.rodata                                           105147  105167     +20
vi_main                                              305     301      -4
setops                                                85       -     -85
------------------------------------------------------------------------------
(add/remove: 0/1 grow/shrink: 2/1 up/down: 69/-89)            Total: -20 bytes

v2: Try harder to detect invalid options.  Thanks to Peter D for pointing
    this out.

Signed-off-by: Ron Yorston <rmy at pobox.com>
---
 editors/vi.c | 94 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 64 insertions(+), 30 deletions(-)

diff --git a/editors/vi.c b/editors/vi.c
index 3e09900a8..143c76d11 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -287,16 +287,21 @@ struct globals {
 
 	// the rest
 	smallint vi_setops;
-#define VI_AUTOINDENT 1
-#define VI_SHOWMATCH  2
-#define VI_IGNORECASE 4
-#define VI_ERR_METHOD 8
+// the order of these values must match that in setops()
+#define VI_AUTOINDENT (1 << 0)
+#define VI_ERR_METHOD (1 << 1)
+#define VI_IGNORECASE (1 << 2)
+#define VI_SHOWMATCH  (1 << 3)
+#define VI_TABSTOP    (1 << 4)
 #define autoindent (vi_setops & VI_AUTOINDENT)
 #define showmatch  (vi_setops & VI_SHOWMATCH )
 #define ignorecase (vi_setops & VI_IGNORECASE)
 // indicate error with beep or flash
 #define err_method (vi_setops & VI_ERR_METHOD)
 
+// bitmask to indicate which options take a value
+#define VI_TAKE_VALUE VI_TABSTOP
+
 #if ENABLE_FEATURE_VI_READONLY
 	smallint readonly_mode;
 #define SET_READONLY_FILE(flags)        ((flags) |= 0x01)
@@ -2401,17 +2406,51 @@ static char *get_address(char *p, int *b, int *e)	// get two colon addrs, if pre
 }
 
 # if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
-static void setops(const char *args, const char *nm_longname, int flg_no, int opt)
+// order must match option flags
+#define OPTS \
+		"ai\0""autoindent\0" \
+		"fl\0""flash\0" \
+		"ic\0""ignorecase\0" \
+		"sm\0""showmatch\0" \
+		"ts\0""tabstop\0"
+
+static void setops(const char *args, int flg_no)
 {
-	const char *a = args + flg_no;
+	char *eq;
+	int index, opt = 0, error = FALSE;
 
-	if (strcmp(a, nm_longname) == 0
-	 || strcmp(a, nm_longname + 3) == 0
-	) {
-		if (flg_no)
-			vi_setops &= ~opt;
-		else
-			vi_setops |= opt;
+	eq = strchr(args, '=');
+	if (eq)
+		*eq = '\0';
+
+	index = index_in_strings(OPTS, args + flg_no);
+	if (index == -1) {
+		error = TRUE;
+	} else {
+		opt = 1 << (index/2);
+
+		// It's an error if:
+		//   an option that takes a value has no "=" or has a prefix
+		//   a boolean option has "="
+		if ((opt & VI_TAKE_VALUE && (!eq || flg_no)) ||
+				(opt & ~VI_TAKE_VALUE && eq))
+			error = TRUE;
+	}
+
+	if (error) {
+		status_line_bold("option error: %s", args + flg_no);
+		return;
+	}
+
+	if (opt == VI_TABSTOP) {
+		int t = bb_strtou(eq + 1, NULL, 10);
+		if (t > 0 && t <= MAX_TABSTOP)
+			tabstop = t;
+	}
+	else if (flg_no) {
+		vi_setops &= ~opt;
+	} else {
+		vi_setops |= opt;
 	}
 }
 # endif
@@ -2771,10 +2810,10 @@ static void colon(char *buf)
 # if ENABLE_FEATURE_VI_SET
 	} else if (strncmp(cmd, "set", i) == 0) {	// set or clear features
 #  if ENABLE_FEATURE_VI_SETOPTS
-		char *argp;
+		char *argp, *argn, oldch;
 #  endif
 		// only blank is regarded as args delimiter. What about tab '\t'?
-		if (!args[0] || strcasecmp(args, "all") == 0) {
+		if (!args[0] || strcmp(args, "all") == 0) {
 			// print out values of all options
 #  if ENABLE_FEATURE_VI_SETOPTS
 			status_line_bold(
@@ -2798,17 +2837,12 @@ static void colon(char *buf)
 			i = 0;
 			if (argp[0] == 'n' && argp[1] == 'o') // "noXXX"
 				i = 2;
-			setops(argp, "ai""\0""autoindent", i, VI_AUTOINDENT);
-			setops(argp, "fl""\0""flash"     , i, VI_ERR_METHOD);
-			setops(argp, "ic""\0""ignorecase", i, VI_IGNORECASE);
-			setops(argp, "sm""\0""showmatch" , i, VI_SHOWMATCH );
-			if (strncmp(argp, "tabstop=", 8) == 0) {
-				int t = bb_strtou(argp + 8, NULL, 10);
-				if (t > 0 && t <= MAX_TABSTOP)
-					tabstop = t;
-			}
-			argp = skip_non_whitespace(argp);
-			argp = skip_whitespace(argp);
+			argn = skip_non_whitespace(argp);
+			oldch = *argn;
+			*argn = '\0';
+			setops(argp, i);
+			*argn = oldch;
+			argp = skip_whitespace(argn);
 		}
 #  endif /* FEATURE_VI_SETOPTS */
 # endif /* FEATURE_VI_SET */
@@ -4435,10 +4469,10 @@ int vi_main(int argc, char **argv)
 	}
 #endif
 
-	// autoindent is not default in vim 7.3
-	vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
-	//  1-  process $HOME/.exrc file (not inplemented yet)
-	//  2-  process EXINIT variable from environment
+	// all of our options are disabled by default in vim
+	//vi_setops = 0;
+	//  1-  process EXINIT variable from environment
+	//  2-  if EXINIT is unset process $HOME/.exrc file (not inplemented yet)
 	//  3-  process command line args
 #if ENABLE_FEATURE_VI_COLON
 	{
-- 
2.30.2



More information about the busybox mailing list