Subject: bin/17546: vi bug fixes and extensions
To: None <gnats-bugs@gnats.netbsd.org>
From: None <dsl@l8s.co.uk>
List: netbsd-bugs
Date: 07/10/2002 16:12:15
>Number:         17546
>Category:       bin
>Synopsis:       vi bug fixes and extensions
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Wed Jul 10 08:17:00 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator:     David Laight
>Release:        NetBSD 1.6B
>Organization:
	none
>Environment:
System: NetBSD snowdrop 1.6B NetBSD 1.6B (GENERIC) #17: Wed Jul 10 12:43:27 BST 2002
dsl@snowdrop:/oldroot/usr/bsd-current/src/sys/arch/i386/compile/GENERIC i386
Architecture: i386
Machine: i386
>Description:
	I've got rather annoyed with vi % treating <> as a bracket pair.
	- Added an option 'matchchars' to set the pairs of characters
	  that % will search for.

	I was also trying to edit a file with tab separated fields where
	some of the column widths varied by more than a single tab.

	Setting a very wide screen hits an arbitrary width limit of
	500 screen columns (real screen are too close to that one).
	- increased the limit to 4000.

	It seemed better to allow explicit positioning of tabs.
	- added options:
	    tabadd=col		adds tab at column 'col'
	    taddel=col		removes tab from column col
	    tablist=col_list	adds at +ve numbers, removes from -ve.
	  changes made by tabadd and tabdel affect the 'tablist' string.

	The errorbells and escapetime options were missorted.
	- fixed list.

>How-To-Repeat:
>Fix:
	Diffs below, changed files can be downloaded from:
	http://www.btinternet.com/~david.laight/netbsd/vi
	I think the catalog changes are ok - done by symmetry!

Index: catalog/dutch.base
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/catalog/dutch.base,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 dutch.base
--- dutch.base	2001/03/31 11:29:50	1.1.1.2
+++ dutch.base	2002/07/05 21:14:12
@@ -43,8 +43,7 @@
 044 "De lisp optie is niet ondersteund"
 045 "messages niet uitgeschakeld: %s"
 046 "messages niet geactiveerd: %s"
-048 "De paragraph optie moet karakter paren bevatten"
-049 "De section optie moet karakter paren bevatten"
+047 "set: de %s optie moet karakter paren bevatten"
 053 "De standaard buffer is leeg"
 054 "Buffer %s is leeg"
 055 "Bestanden met newlines in de naam kunnen niet hersteld worden"
Index: catalog/french.base
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/catalog/french.base,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 french.base
--- french.base	2001/03/31 11:29:50	1.1.1.1
+++ french.base	2002/07/05 21:14:18
@@ -43,8 +43,7 @@
 044 "L'option lisp n'est pas impl‚ment‚e"
 045 "Les messages ne sont pas d‚sactiv‚s : %s"
 046 "Les messages ne sont pas activ‚s : %s"
-048 "L'option de paragraphe doit ˆtre en groupe de deux caractŠres"
-049 "L'option de section doit ˆtre en groupe de deux caractŠres"
+047 "D‚finition : l'option de %s doit ˆtre en groupe de deux caractŠres"
 053 "Le tampon par d‚faut est vide"
 054 "Le tampon %s est vide"
 055 "Les fichiers dont le nom contient des caractŠres de saut de ligne sont irr‚cup‚rables"
Index: catalog/german.base
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/catalog/german.base,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 german.base
--- german.base	2001/03/31 11:29:50	1.1.1.2
+++ german.base	2002/07/05 21:14:24
@@ -43,8 +43,7 @@
 044 "Die lisp Option ist nicht implementiert"
 045 "Messages nicht abgeschalten: %s"
 046 "Messages nicht eingeschalten: %s"
-048 "Die paragraph Option muss Gruppen zu zwei Zeichen enthalten"
-049 "Die section Option muss Gruppen zu zwei Zeichen enthalten"
+047 "set: Die %s Option muss Gruppen zu zwei Zeichen enthalten"
 053 "Der Standardpuffer ist leer"
 054 "Puffer %s ist leer"
 055 "Dateien mit newlines im Namen sind nicht wiederherstellbar"
Index: catalog/ru_RU.KOI8-R.base
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/catalog/ru_RU.KOI8-R.base,v
retrieving revision 1.2
diff -u -r1.2 ru_RU.KOI8-R.base
--- ru_RU.KOI8-R.base	2001/03/31 11:37:44	1.2
+++ ru_RU.KOI8-R.base	2002/07/05 21:14:30
@@ -31,9 +31,7 @@
 044 "oPCIQ lisp OTSUTSTWUET"
 045 "sOOB]ENIQ NE WYKL@^ENY: %s"
 046 "sOOB]ENIQ NE WKL@^ENY: %s"
-047 "oPCIQ modeline(s) NE MOVET BYTX PEREUSTANOWLENA"
-048 "oPCIQ paragraph DOLVNA SOSTOQTX IZ GRUPP S DWUMQ SIMWOLAMI"
-049 "oPCIQ section DOLVNA SOSTOQTX IZ GRUPP S DWUMQ SIMWOLAMI"
+047 "set: oPCIQ %s DOLVNA SOSTOQTX IZ GRUPP S DWUMQ SIMWOLAMI"
 050 "oPCIQ shiftwidth NE MOVET BYTX USTANOWLENA NA 0"
 051 "oPCIQ sourceany NE MOVET BYTX USTANOWLENA"
 052 "tABULQCIQ NE MOVET BYTX USTANOWLENA NA 0"
Index: catalog/spanish.base
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/catalog/spanish.base,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 spanish.base
--- spanish.base	2001/03/31 11:29:49	1.1.1.1
+++ spanish.base	2002/07/05 21:14:36
@@ -43,8 +43,7 @@
 044 "La opci¢n lisp no est  implementada"
 045 "mensajes no desconectados: %s"
 046 "mensajes no conectados: %s"
-048 "La opci¢n de p rrafo debe estar en dos grupos de caracteres"
-049 "La opci¢n de secci¢n debe estar en dos grupos de caracteres"
+047 "determinar: La opci¢n de %s debe estar en dos grupos de caracteres"
 053 "El buffer por omisi¢n est  vac¡o"
 054 "El buffer %s est  vac¡o"
 055 "Los archivos con nuevas l¡neas en el nombre son irrecuperables"
Index: catalog/swedish.base
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/catalog/swedish.base,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 swedish.base
--- swedish.base	2001/03/31 11:29:50	1.1.1.2
+++ swedish.base	2002/07/05 21:14:42
@@ -43,8 +43,7 @@
 044 "Lisp flaggan är inte implementerad"
 045 "meddelanden är inte avslagna: %s"
 046 "meddelanden är inte påslagna: %s"
-048 "Paragraph flaggan måste ges i teckengrupper om två"
-049 "Section flaggan måste ges i teckengrupper om två"
+047 "set: %s flaggan måste ges i teckengrupper om två"
 053 "Standardbufferten är tom"
 054 "Buffer %s är tom"
 055 "Filer med radmatning i namnet kan inte återskapas"
Index: options.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/common/options.c,v
retrieving revision 1.6
diff -u -r1.6 options.c
--- options.c	2002/04/09 01:47:31	1.6
+++ options.c	2002/07/06 09:09:54
@@ -49,6 +49,8 @@
  *
  * HPUX noted options and abbreviations are from "The Ultimate Guide to the
  * VI and EX Text Editors", 1990.
+ *
+ * This list must be sorted...
  */
 OPTLIST const optlist[] = {
 /* O_ALTWERASE	  4.4BSD */
@@ -75,10 +77,10 @@
 	{"directory",	NULL,		OPT_STR,	0},
 /* O_EDCOMPATIBLE   4BSD */
 	{"edcompatible",NULL,		OPT_0BOOL,	0},
-/* O_ESCAPETIME	  4.4BSD */
-	{"escapetime",	NULL,		OPT_NUM,	0},
 /* O_ERRORBELLS	    4BSD */
 	{"errorbells",	NULL,		OPT_0BOOL,	0},
+/* O_ESCAPETIME	  4.4BSD */
+	{"escapetime",	NULL,		OPT_NUM,	0},
 /* O_EXRC	System V (undocumented) */
 	{"exrc",	NULL,		OPT_0BOOL,	0},
 /* O_EXTENDED	  4.4BSD */
@@ -119,6 +121,8 @@
 	{"lock",	NULL,		OPT_1BOOL,	0},
 /* O_MAGIC	    4BSD */
 	{"magic",	NULL,		OPT_1BOOL,	0},
+/* O_MATCHCHARS	  netbsd 1.6 */
+	{"matchchars",	NULL,		OPT_STR,	OPT_PAIRS},
 /* O_MATCHTIME	  4.4BSD */
 	{"matchtime",	NULL,		OPT_NUM,	0},
 /* O_MESG	    4BSD */
@@ -145,7 +149,7 @@
 /* O_OPTIMIZE	    4BSD */
 	{"optimize",	NULL,		OPT_1BOOL,	0},
 /* O_PARAGRAPHS	    4BSD */
-	{"paragraphs",	f_paragraph,	OPT_STR,	0},
+	{"paragraphs",	NULL,		OPT_STR,	OPT_PAIRS},
 /* O_PATH	  4.4BSD */
 	{"path",	NULL,		OPT_STR,	0},
 /* O_PRINT	  4.4BSD */
@@ -169,7 +173,7 @@
 /* O_SEARCHINCR	  4.4BSD */
 	{"searchincr",	NULL,		OPT_0BOOL,	0},
 /* O_SECTIONS	    4BSD */
-	{"sections",	f_section,	OPT_STR,	0},
+	{"sections",	NULL,		OPT_STR,	OPT_PAIRS},
 /* O_SECURE	  4.4BSD */
 	{"secure",	NULL,		OPT_0BOOL,	OPT_NOUNSET},
 /* O_SHELL	    4BSD */
@@ -195,8 +199,14 @@
  *	and we ignore the option.
  */
 	{"sourceany",	NULL,		OPT_0BOOL,	OPT_NOSET},
+/* O_TABADD	    netbsd 1.6 */
+	{"tabadd",	f_tabadd,	OPT_NUM,	OPT_NOZERO|OPT_NOSAVE},
+/* O_TABDEL	    netbsd 1.6 */
+	{"tabdel",	f_tabdel,	OPT_NUM,	OPT_NOZERO|OPT_NOSAVE},
+/* O_TABLIST	    netbsd 1.6 */
+	{"tablist",	f_tablist,	OPT_STR,	0},
 /* O_TABSTOP	    4BSD */
-	{"tabstop",	f_reformat,	OPT_NUM,	OPT_NOZERO},
+	{"tabstop",	f_tabstop,	OPT_NUM,	OPT_NOZERO},
 /* O_TAGLENGTH	    4BSD */
 	{"taglength",	NULL,		OPT_NUM,	0},
 /* O_TAGS	    4BSD */
@@ -376,6 +386,7 @@
 	OI(O_TABSTOP, "tabstop=8");
 	(void)snprintf(b1, sizeof(b1), "tags=%s", _PATH_TAGS);
 	OI(O_TAGS, b1);
+	OI(O_MATCHCHARS, "matchchars=()[]{}<>");
 
 	/*
 	 * XXX
@@ -705,6 +716,14 @@
 				if (!disp)
 					disp = SELECT_DISPLAY;
 				F_SET(spo, OPT_SELECTED);
+				break;
+			}
+
+			/* Check for strings that must have even length */
+			if (F_ISSET(op, OPT_PAIRS) && strlen(sep) & 1) {
+				msgq_str(sp, M_ERR, name,
+				    "047|set: the %s option must be in two character groups");
+				rval = 1;
 				break;
 			}
 
Index: common/options.h
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/common/options.h,v
retrieving revision 1.4
diff -u -r1.4 options.h
--- options.h	2001/03/31 11:37:46	1.4
+++ options.h	2002/07/05 21:14:55
@@ -30,6 +30,8 @@
 	(F_ISSET(&(sp)->opts[(o)], OPT_GLOBAL) ?			\
 	    (sp)->gp->opts[(sp)->opts[(o)].o_cur.val].fld :		\
 	    (sp)->opts[(o)].fld)
+/* if known to be local */
+#define	OL_V(sp, o, fld)	((sp)->opts[(o)].fld)
 
 /* Global option macros. */
 #define	OG_CLR(gp, o)		((gp)->opts[(o)].o_cur.val) = 0
@@ -59,6 +61,7 @@
 #define	O_SET(sp, o)		o_set(sp, o, 0, NULL, 1)
 #define	O_STR(sp, o)		O_V(sp, o, o_cur.str)
 #define	O_VAL(sp, o)		O_V(sp, o, o_cur.val)
+#define	OL_VAL(sp, o)		OL_V(sp, o, o_cur.val)
 #define	O_ISSET(sp, o)		O_VAL(sp, o)
 
 	union {
@@ -92,7 +95,8 @@
 #define	OPT_NOUNSET	0x020		/* Option may not be unset. */
 #define	OPT_NOZERO	0x040		/* Option may not be set to 0. */
 #define OPT_EARLYSET	0x080		/* Subsys called after value is set. */
-	u_int8_t flags;
+#define OPT_PAIRS	0x100		/* String with even length */
+	int flags;
 };
 
 /* Option argument to opts_dump(). */
Index: common/options_f.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/common/options_f.c,v
retrieving revision 1.4
diff -u -r1.4 options_f.c
--- options_f.c	2002/04/09 01:47:31	1.4
+++ options_f.c	2002/07/05 21:15:00
@@ -74,7 +74,7 @@
 	 * number of lines/columns for the screen, but at least we don't drop
 	 * core.
 	 */
-#define	MAXIMUM_SCREEN_COLS	500
+#define	MAXIMUM_SCREEN_COLS	4000
 	if (*valp > MAXIMUM_SCREEN_COLS) {
 		msgq(sp, M_ERR, "041|Screen columns too large, greater than %d",
 		    MAXIMUM_SCREEN_COLS);
@@ -108,7 +108,7 @@
 	 * number of lines/columns for the screen, but at least we don't drop
 	 * core.
 	 */
-#define	MAXIMUM_SCREEN_ROWS	500
+#define	MAXIMUM_SCREEN_ROWS	4000
 	if (*valp > MAXIMUM_SCREEN_ROWS) {
 		msgq(sp, M_ERR, "043|Screen lines too large, greater than %d",
 		    MAXIMUM_SCREEN_ROWS);
@@ -169,24 +169,6 @@
 }
 
 /*
- * PUBLIC: int f_paragraph __P((SCR *, OPTION *, char *, u_long *));
- */
-int
-f_paragraph(sp, op, str, valp)
-	SCR *sp;
-	OPTION *op;
-	char *str;
-	u_long *valp;
-{
-	if (strlen(str) & 1) {
-		msgq(sp, M_ERR,
-		    "048|The paragraph option must be in two character groups");
-		return (1);
-	}
-	return (0);
-}
-
-/*
  * PUBLIC: int f_print __P((SCR *, OPTION *, char *, u_long *));
  */
 int
@@ -261,24 +243,6 @@
 }
 
 /*
- * PUBLIC: int f_section __P((SCR *, OPTION *, char *, u_long *));
- */
-int
-f_section(sp, op, str, valp)
-	SCR *sp;
-	OPTION *op;
-	char *str;
-	u_long *valp;
-{
-	if (strlen(str) & 1) {
-		msgq(sp, M_ERR,
-		    "049|The section option must be in two character groups");
-		return (1);
-	}
-	return (0);
-}
-
-/*
  * PUBLIC: int f_ttywerase __P((SCR *, OPTION *, char *, u_long *));
  */
 int
@@ -370,4 +334,156 @@
 	    (*valp = O_VAL(sp, O_LINES) - 1) == 0)
 		*valp = 1;
 	return (0);
+}
+
+/*
+ * PUBLIC: int f_tabstop __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_tabstop(SCR *sp, OPTION *op, char *str, ulong *valp)
+{
+	char *tl;
+
+	if (*valp > 2048)
+		/* sanity */
+		return 1;
+
+	/* Hack tabstop value in for tabadd code... */
+	O_VAL(sp, O_TABSTOP) = *valp;
+
+	/* get modified tab positions */
+	tl = O_STR(sp, O_TABLIST);
+	if (tl && (tl = strdup(tl))) {
+		/* and reapply */
+		f_tablist(sp, op, tl, 0);
+		free(tl);
+	}
+	 
+	return f_reformat(sp, op, str, valp);
+}
+
+/*
+ * PUBLIC: int f_tabadd __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_tabadd(SCR *sp, OPTION *op, char *str, ulong *valp)
+{
+	int col = *valp;
+	size_t tc, ts = O_VAL(sp, O_TABSTOP);
+	size_t *tt;
+	char *as, *ns;
+	int i;
+
+	if (!col)
+		return 0;
+	/* force redraw */
+	f_reformat(sp, op, str, valp);
+
+	if (col < 0)
+		col = -col;
+
+	/* Make table large enough to include this tabstop */
+	tt = sp->tab_table;
+	if (col >= sp->tab_table_len) {
+		REALLOC(sp, tt, size_t *, (col + 1) * sizeof *sp->tab_table);
+		/* fill in default tabs */
+		for (i = sp->tab_table_len; i <= col; i++)
+			tt[i] = COL_OFF(i, ts);
+		sp->tab_table_len = col + 1;
+		sp->tab_table = tt;
+	}
+	/* overwrite offsets going back to previous tab position */
+	i = *(long *)valp < 0 ? tt[col] : 0;
+	do {
+		tt[--col] = ++i;
+	} while (col && tt[col-1] != 1);
+
+	/* finally generate equivalent 'string' tablist value */
+	as = 0;
+	for (tc = 0, col = 0; col < sp->tab_table_len; col++, tc--) {
+		if (!tc)
+			tc = ts;
+		if (tt[col] != 1) {
+			/* there isn't a tab here... */
+			if (tc != 1)
+				/* and we dont expect one */
+				continue;
+			i = -(col + 1);
+		} else {
+			/* there is a tab here */
+			if (tc == 1)
+				/* we expected it */
+				continue;
+			i = col + 1;
+		}
+		if (as) {
+			asprintf( &ns, "%s,%d", as, i );
+			free(as);
+		} else
+			asprintf( &ns, "%d", i );
+		as = ns;
+	}
+
+	if (!as) {
+		free(tt);
+		sp->tab_table_len = 0;
+		sp->tab_table = 0;
+	}
+
+	o_set(sp, O_TABLIST, OS_STRDUP, as ? as : "", 0);
+	free(as);
+
+	return 0;
+}
+
+/*
+ * PUBLIC: int f_tabdel __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_tabdel(SCR *sp, OPTION *op, char *str, ulong *valp)
+{
+	ulong col = -*valp;
+
+	return f_tabadd(sp, op, str, &col);
+}
+
+/*
+ * PUBLIC: int f_tablist __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_tablist(SCR *sp, OPTION *op, char *str, ulong *valp)
+{
+	char *cp, *ncp;
+	ulong col;
+
+	/* validate that string is comma separated numbers */
+	if (*str) {
+		for (cp = str; ; cp = ncp + 1) {
+			strtol( cp, &ncp, 0 );
+			if (!*ncp)
+				break;
+			if (*ncp != ',')
+				return 1;
+		}
+	}
+
+	/* Remove all old info */
+	if (sp->tab_table) {
+		free( sp->tab_table );
+		sp->tab_table = 0;
+		sp->tab_table_len = 0;
+	}
+
+	if (!*str)
+		return 0;
+
+	/* now add in one by one */
+	for (cp = str; ; cp = ncp + 1) {
+		col = strtol( cp, &ncp, 0 );
+		f_tabadd(sp, op, 0, &col);
+		if (!*ncp)
+			break;
+	}
+
+	return 0;
 }
Index: common/screen.h
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/common/screen.h,v
retrieving revision 1.3
diff -u -r1.3 screen.h
--- screen.h	2001/03/31 11:37:47	1.3
+++ screen.h	2002/07/05 21:15:05
@@ -136,6 +136,9 @@
 
 	OPTION	 opts[O_OPTIONCOUNT];	/* Ex/vi: Options. */
 
+	size_t	tab_table_len;		/* size of tab offset table */
+	size_t	*tab_table;		/* offsets of tabs */
+
 /*
  * Screen flags.
  *
Index: common/util.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/common/util.c,v
retrieving revision 1.4
diff -u -r1.4 util.c
--- util.c	2002/04/09 01:47:32	1.4
+++ util.c	2002/07/05 21:15:10
@@ -196,6 +196,22 @@
 	return (NUM_ERR);
 }
 
+
+/*
+ * PUBLIC: size_t tab_off( SCR *, size_t );
+ *
+ * Return number of characters to next tab stop.
+ */
+size_t
+tab_off( SCR *sp, size_t off )
+{
+	/* Has user been fiddling with the tabs? */
+	if (off < sp->tab_table_len)
+		return sp->tab_table[off];
+
+	return COL_OFF( off, OL_VAL( sp, O_TABSTOP ) );
+}
+
 #ifdef DEBUG
 #ifdef __STDC__
 #include <stdarg.h>
Index: ex/ex_txt.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/ex/ex_txt.c,v
retrieving revision 1.6
diff -u -r1.6 ex_txt.c
--- ex_txt.c	2002/04/09 01:47:34	1.6
+++ ex_txt.c	2002/07/05 21:15:16
@@ -393,16 +393,16 @@
 	SCR *sp;
 	TEXT *tp;
 {
-	u_long sw, ts;
+	u_long sw;
 	size_t cno, off, scno, spaces, tabs;
+	size_t nco;
 
-	ts = O_VAL(sp, O_TABSTOP);
 	sw = O_VAL(sp, O_SHIFTWIDTH);
 
 	/* Get the current screen column. */
 	for (off = scno = 0; off < tp->len; ++off)
 		if (tp->lb[off] == '\t')
-			scno += COL_OFF(scno, ts);
+			scno += tab_off(sp, scno);
 		else
 			++scno;
 
@@ -421,8 +421,8 @@
 	 *
 	 * Count up spaces/tabs needed to get to the target.
 	 */
-	for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs)
-		cno += COL_OFF(cno, ts);
+	for (cno = 0, tabs = 0; (nco = cno + tab_off(sp, cno)) <= scno; ++tabs)
+		cno = nco;
 	spaces = scno - cno;
 
 	/* Make sure there's enough room. */
Index: ex/ex_shift.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/ex/ex_shift.c,v
retrieving revision 1.9
diff -u -r1.9 ex_shift.c
--- ex_shift.c	2002/04/09 01:47:34	1.9
+++ ex_shift.c	2002/07/05 21:15:21
@@ -73,6 +73,7 @@
 {
 	recno_t from, to;
 	size_t blen, len, newcol, newidx, oldcol, oldidx, sw;
+	size_t col, nc;
 	int curset;
 	char *p, *bp, *tbp;
 
@@ -120,8 +121,7 @@
 			if (p[oldidx] == ' ')
 				++oldcol;
 			else if (p[oldidx] == '\t')
-				oldcol += O_VAL(sp, O_TABSTOP) -
-				    oldcol % O_VAL(sp, O_TABSTOP);
+				oldcol += tab_off(sp, oldcol);
 			else
 				break;
 
@@ -144,13 +144,13 @@
 		 * Build a new indent string and count the number of
 		 * characters it uses.
 		 */
-		for (tbp = bp, newidx = 0;
-		    newcol >= O_VAL(sp, O_TABSTOP); ++newidx) {
+		for (col = 0, tbp = bp;
+		    (nc = col + tab_off(sp, col)) <= newcol; col = nc) {
 			*tbp++ = '\t';
-			newcol -= O_VAL(sp, O_TABSTOP);
 		}
-		for (; newcol > 0; --newcol, ++newidx)
+		for (; col < newcol; col++)
 			*tbp++ = ' ';
+		newidx = tbp - bp;
 
 		/* Add the original line. */
 		memcpy(tbp, p + oldidx, len - oldidx);
Index: include/com_extern.h
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/include/com_extern.h,v
retrieving revision 1.2
diff -u -r1.2 com_extern.h
--- com_extern.h	2001/03/31 11:37:51	1.2
+++ com_extern.h	2002/07/05 21:15:25
@@ -152,12 +152,14 @@
 int f_lines __P((SCR *, OPTION *, char *, u_long *));
 int f_lisp __P((SCR *, OPTION *, char *, u_long *));
 int f_msgcat __P((SCR *, OPTION *, char *, u_long *));
-int f_paragraph __P((SCR *, OPTION *, char *, u_long *));
 int f_print __P((SCR *, OPTION *, char *, u_long *));
 int f_readonly __P((SCR *, OPTION *, char *, u_long *));
 int f_recompile __P((SCR *, OPTION *, char *, u_long *));
 int f_reformat __P((SCR *, OPTION *, char *, u_long *));
-int f_section __P((SCR *, OPTION *, char *, u_long *));
+int f_tabadd __P((SCR *, OPTION *, char *, u_long *));
+int f_tabdel __P((SCR *, OPTION *, char *, u_long *));
+int f_tablist __P((SCR *, OPTION *, char *, u_long *));
+int f_tabstop __P((SCR *, OPTION *, char *, u_long *));
 int f_ttywerase __P((SCR *, OPTION *, char *, u_long *));
 int f_w300 __P((SCR *, OPTION *, char *, u_long *));
 int f_w1200 __P((SCR *, OPTION *, char *, u_long *));
@@ -194,3 +196,4 @@
 enum nresult nget_uslong __P((u_long *, const char *, char **, int));
 enum nresult nget_slong __P((long *, const char *, char **, int));
 void TRACE __P((SCR *, const char *, ...));
+size_t tab_off(SCR *, size_t);
Index: include/options_def.h
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/include/options_def.h,v
retrieving revision 1.4
diff -u -r1.4 options_def.h
--- options_def.h	2001/03/31 11:37:51	1.4
+++ options_def.h	2002/07/05 21:15:39
@@ -1,5 +1,3 @@
-/*	$NetBSD: options_def.h,v 1.4 2001/03/31 11:37:51 aymeric Exp $	*/
-
 #define O_ALTWERASE 0
 #define O_AUTOINDENT 1
 #define O_AUTOPRINT 2
@@ -29,54 +27,58 @@
 #define O_LIST 26
 #define O_LOCKFILES 27
 #define O_MAGIC 28
-#define O_MATCHTIME 29
-#define O_MESG 30
-#define O_MODELINE 31
-#define O_MSGCAT 32
-#define O_NOPRINT 33
-#define O_NUMBER 34
-#define O_OCTAL 35
-#define O_OPEN 36
-#define O_OPTIMIZE 37
-#define O_PARAGRAPHS 38
-#define O_PATH 39
-#define O_PRINT 40
-#define O_PROMPT 41
-#define O_READONLY 42
-#define O_RECDIR 43
-#define O_REDRAW 44
-#define O_REMAP 45
-#define O_REPORT 46
-#define O_RULER 47
-#define O_SCROLL 48
-#define O_SEARCHINCR 49
-#define O_SECTIONS 50
-#define O_SECURE 51
-#define O_SHELL 52
-#define O_SHELLMETA 53
-#define O_SHIFTWIDTH 54
-#define O_SHOWMATCH 55
-#define O_SHOWMODE 56
-#define O_SIDESCROLL 57
-#define O_SLOWOPEN 58
-#define O_SOURCEANY 59
-#define O_TABSTOP 60
-#define O_TAGLENGTH 61
-#define O_TAGS 62
-#define O_TERM 63
-#define O_TERSE 64
-#define O_TILDEOP 65
-#define O_TIMEOUT 66
-#define O_TTYWERASE 67
-#define O_VERBOSE 68
-#define O_W1200 69
-#define O_W300 70
-#define O_W9600 71
-#define O_WARN 72
-#define O_WINDOW 73
-#define O_WINDOWNAME 74
-#define O_WRAPLEN 75
-#define O_WRAPMARGIN 76
-#define O_WRAPSCAN 77
-#define O_WRITEANY 78
-#define O_OPTIONCOUNT 79
+#define O_MATCHCHARS 29
+#define O_MATCHTIME 30
+#define O_MESG 31
+#define O_MODELINE 32
+#define O_MSGCAT 33
+#define O_NOPRINT 34
+#define O_NUMBER 35
+#define O_OCTAL 36
+#define O_OPEN 37
+#define O_OPTIMIZE 38
+#define O_PARAGRAPHS 39
+#define O_PATH 40
+#define O_PRINT 41
+#define O_PROMPT 42
+#define O_READONLY 43
+#define O_RECDIR 44
+#define O_REDRAW 45
+#define O_REMAP 46
+#define O_REPORT 47
+#define O_RULER 48
+#define O_SCROLL 49
+#define O_SEARCHINCR 50
+#define O_SECTIONS 51
+#define O_SECURE 52
+#define O_SHELL 53
+#define O_SHELLMETA 54
+#define O_SHIFTWIDTH 55
+#define O_SHOWMATCH 56
+#define O_SHOWMODE 57
+#define O_SIDESCROLL 58
+#define O_SLOWOPEN 59
+#define O_SOURCEANY 60
+#define O_TABADD 61
+#define O_TABDEL 62
+#define O_TABLIST 63
+#define O_TABSTOP 64
+#define O_TAGLENGTH 65
+#define O_TAGS 66
+#define O_TERM 67
+#define O_TERSE 68
+#define O_TILDEOP 69
+#define O_TIMEOUT 70
+#define O_TTYWERASE 71
+#define O_VERBOSE 72
+#define O_W1200 73
+#define O_W300 74
+#define O_W9600 75
+#define O_WARN 76
+#define O_WINDOW 77
+#define O_WINDOWNAME 78
+#define O_WRAPLEN 79
+#define O_WRAPMARGIN 80
+#define O_WRAPSCAN 81
+#define O_WRITEANY 82
+#define O_OPTIONCOUNT 83
Index: vi/v_match.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/vi/v_match.c,v
retrieving revision 1.8
diff -u -r1.8 v_match.c
--- v_match.c	2002/04/09 01:47:35	1.8
+++ v_match.c	2002/07/05 21:15:43
@@ -48,7 +48,17 @@
 	size_t cno, len, off;
 	int cnt, isempty, matchc, startc, (*gc)__P((SCR *, VCS *));
 	char *p;
+	char *match_chars;
 
+	static int match_lno, match_col, match_dir;
+
+	/* Historically vi would match (), {} and [] however
+	   an update included <>.  This is ok for editing HTML
+	   but a pain in the butt for C source.
+	   Making it an option lets the user decide what is 'right'.
+	   Also fixed to do something sensible with "". */
+	match_chars = O_STR( sp, O_MATCHCHARS );
+
 	/*
 	 * !!!
 	 * Historic practice; ignore the count.
@@ -63,48 +73,33 @@
 		return (1);
 	}
 	for (off = vp->m_start.cno;; ++off) {
+		char *cp;
 		if (off >= len) {
 nomatch:		msgq(sp, M_BERR, "184|No match character on this line");
 			return (1);
 		}
-		switch (startc = p[off]) {
-		case '(':
-			matchc = ')';
-			gc = cs_next;
-			break;
-		case ')':
-			matchc = '(';
-			gc = cs_prev;
-			break;
-		case '[':
-			matchc = ']';
-			gc = cs_next;
-			break;
-		case ']':
-			matchc = '[';
-			gc = cs_prev;
-			break;
-		case '{':
-			matchc = '}';
-			gc = cs_next;
-			break;
-		case '}':
-			matchc = '{';
-			gc = cs_prev;
-			break;
-		case '<':
-			matchc = '>';
-			gc = cs_next;
-			break;
-		case '>':
-			matchc = '<';
-			gc = cs_prev;
-			break;
-		default:
-			continue;
-		}
+		startc = p[off];
+		cp = strchr( match_chars, startc );
+		if (!cp)
+		    continue;
+		cnt = cp - match_chars;
 		break;
 	}
+	matchc = match_chars[ cnt ^ 1 ];
+
+	/* Alternate back-forward search if startc and matchc the same */
+	if (startc == matchc) {
+		/* are we continuing from where last match finished? */
+		if (match_lno == vp->m_start.lno && match_col ==vp->m_start.cno)
+			/* yes - continue in sequence */
+			match_dir++;
+		else
+			/* no - go forward, back, back, forward */
+			match_dir = 1;
+		if (match_dir & 2)
+		    cnt++;
+	}
+	gc = cnt & 1 ? cs_prev : cs_next;
 
 	cs.cs_lno = vp->m_start.lno;
 	cs.cs_cno = off;
@@ -118,10 +113,10 @@
 				break;
 			continue;
 		}
+		if (cs.cs_ch == matchc && --cnt == 0)
+			break;
 		if (cs.cs_ch == startc)
 			++cnt;
-		else if (cs.cs_ch == matchc && --cnt == 0)
-			break;
 	}
 	if (cnt) {
 		msgq(sp, M_BERR, "185|Matching character not found");
@@ -147,6 +142,9 @@
 		vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
 	else
 		vp->m_final = vp->m_stop;
+
+	match_lno = vp->m_final.lno;
+	match_col = vp->m_final.cno;
 
 	/*
 	 * !!!
Index: vi/v_txt.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/vi/v_txt.c,v
retrieving revision 1.9
diff -u -r1.9 v_txt.c
--- v_txt.c	2002/04/09 01:47:36	1.9
+++ v_txt.c	2002/07/05 21:16:00
@@ -1698,9 +1698,9 @@
 	TEXT *tp;
 	int *changedp;
 {
-	u_long ts;
 	int del;
 	size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs;
+	size_t nco;
 	char *p;
 
 	*changedp = 0;
@@ -1726,15 +1726,16 @@
 	 * in the line are resolved into the minimum number of characters.
 	 * Historic practice.
 	 */
-	ts = O_VAL(sp, O_TABSTOP);
 
 	/* Figure out the last <blank> screen column. */
 	for (p = tp->lb, scno = 0, len = tp->len,
-	    spaces = tab_after_sp = 0; len-- && isblank(*p); ++p)
+	    nco = spaces = tab_after_sp = 0; len-- && isblank(*p); ++p)
 		if (*p == '\t') {
 			if (spaces)
 				tab_after_sp = 1;
-			scno += COL_OFF(scno, ts);
+			scno += tab_off(sp, scno);
+			/* get spaces to next tab stop... */
+			nco = tab_off(sp, scno);
 		} else {
 			++spaces;
 			++scno;
@@ -1742,14 +1743,14 @@
 
 	/*
 	 * If there are no spaces, or no tabs after spaces and less than
-	 * ts spaces, it's already minimal.
+	 * nco spaces, it's already minimal.
 	 */
-	if (!spaces || !tab_after_sp && spaces < ts)
+	if (!spaces || !tab_after_sp && spaces < nco)
 		return;
 
 	/* Count up spaces/tabs needed to get to the target. */
-	for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs)
-		cno += COL_OFF(cno, ts);
+	for (cno = 0, tabs = 0; (nco = cno + tab_off(sp, cno)) <= scno; ++tabs)
+		cno = nco;
 	spaces = scno - cno;
 
 	/*
@@ -1922,11 +1923,11 @@
 	int isindent;
 {
 	CHAR_T ch;
-	u_long sw, ts;
+	u_long sw;
 	size_t cno, current, spaces, target, tabs;
+	size_t nco;
 	int ai_reset;
 
-	ts = O_VAL(sp, O_TABSTOP);
 	sw = O_VAL(sp, O_SHIFTWIDTH);
 
 	/*
@@ -1947,7 +1948,7 @@
 	 */
 	for (current = cno = 0; cno < tp->cno; ++cno)
 		current += tp->lb[cno] == '\t' ?
-		    COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]);
+		    tab_off(sp, current) : KEY_LEN(sp, tp->lb[cno]);
 
 	target = current;
 	if (isindent)
@@ -1977,7 +1978,7 @@
 	    --tp->cno, ++tp->owrite);
 	for (current = cno = 0; cno < tp->cno; ++cno)
 		current += tp->lb[cno] == '\t' ?
-		    COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]);
+		    tab_off(sp, current) : KEY_LEN(sp, tp->lb[cno]);
 
 	/*
 	 * If we didn't move up to or past the target, it's because there
@@ -1995,8 +1996,8 @@
 		spaces = tabs = 0;
 	else {
 		for (cno = current,
-		    tabs = 0; cno + COL_OFF(cno, ts) <= target; ++tabs)
-			cno += COL_OFF(cno, ts);
+		    tabs = 0; (nco = cno + tab_off(sp, cno)) <= target; ++tabs)
+			cno = nco;
 		spaces = target - cno;
 	}
 
Index: vi/vi.h
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/vi/vi/vi.h,v
retrieving revision 1.3
diff -u -r1.3 vi.h
--- vi.h	2001/03/31 11:37:52	1.3
+++ vi.h	2002/07/05 21:16:07
@@ -351,7 +351,7 @@
  * positions when <esc> is entered.  I believe that nvi does tabs correctly,
  * but there are some historical incompatibilities.
  */
-#define	TAB_OFF(c)	COL_OFF((c), O_VAL(sp, O_TABSTOP))
+#define	TAB_OFF(c)	tab_off(sp, c)
 
 /* If more than one screen being shown. */
 #define	IS_SPLIT(sp)							\
>Release-Note:
>Audit-Trail:
>Unformatted: