Subject: fix for pd-ksh globbing
To: None <tech-userlevel@netbsd.org>
From: Simon J. Gerraty <sjg@crufty.net>
List: tech-userlevel
Date: 01/25/2002 01:36:09
Ok, this bug has anoyed me for years.  I finally made time to take a
look at it.

In ksh's source directory if you do

echo $PWD/em<ESC><ESC> 

it will expand to say:

echo /u3/NetBSD/current/src/bin/ksh/em<SPACE>

instead of 

echo /u3/NetBSD/current/src/bin/ksh/emacs

with no trailing space.  

With real ksh 

echo ~<ESC><ESC> 
echo $HOME<ESC><ESC>

both expand with a trailing '/' and no space.  In pd-ksh we get a
space instead.  This is due to interactions b/w add_glob(), expand()
and do_complete().  

The patch below fixes the above - at the cost of an extra stat() call.
While find_match might be overkill but I've had it for years and it works.

One difference b/w pd-ksh and real ksh which I've not addressed -
since its a bug in real ksh, is:

FOO=bar
echo $FOO<ESC><ESC>

real ksh apparently expands this to 

echo bar*<SPACE>

whereas pd-ksh expands it

echo bar<SPACE>

which is more correct me thinks.

Thanks
--sjg

Index: edit.c
===================================================================
RCS file: /cvsroot/basesrc/bin/ksh/edit.c,v
retrieving revision 1.6
diff -u -p -r1.6 edit.c
--- edit.c	1999/11/02 22:06:45	1.6
+++ edit.c	2002/01/25 09:26:24
@@ -840,6 +840,40 @@ x_cf_glob(flags, buf, buflen, pos, start
 	return nwords;
 }
 
+
+static char *
+find_match(const char *s, int have)
+{
+	int     want = 0;
+	int     nest = 1;
+	int     c;
+	
+	switch (have) {
+	case '(':	want = ')'; break;
+	case '{':	want = '}'; break;
+	case '[':	want = ']'; break;
+	case '\'':	
+	case '"':	want = have; break;
+	}
+	if (want == 0 || s == NULL)
+		return NULL;
+
+	while (nest > 0 && (c = *s)) {
+		if (c == '\\') {
+			s++;
+			s++;
+			continue;
+		}
+		if (c == want)
+			nest--;
+		else if (c == have)
+			nest++;
+		if (nest > 0)
+			s++;
+	}
+	return (nest == 0) ? (char *) s : NULL;
+}
+
 /* Given a string, copy it and possibly add a '*' to the end.  The
  * new string is returned.
  */
@@ -859,19 +893,33 @@ add_glob(str, slen)
 	toglob[slen] = '\0';
 
 	/*
-	 * If the pathname contains a wildcard (an unquoted '*',
-	 * '?', or '[') or parameter expansion ('$'), or a ~username
-	 * with no trailing slash, then it is globbed based on that
-	 * value (i.e., without the appended '*').
+	 * If the pathname contains a wildcard (an unquoted '*', '?',
+	 * or '[') or parameter expansion ('$') with nothing following
+	 * it, or a ~username with no trailing slash, then it is
+	 * globbed based on that value (i.e., without the appended
+	 * '*').
 	 */
 	for (s = toglob; *s; s++) {
 		if (*s == '\\' && s[1])
 			s++;
-		else if (*s == '*' || *s == '[' || *s == '?' || *s == '$'
+		else if (*s == '*' || *s == '[' || *s == '?'
 			 || (s[1] == '(' /*)*/ && strchr("*+?@!", *s)))
 			break;
 		else if (ISDIRSEP(*s))
 			saw_slash = TRUE;
+		else if (*s++ == '$') {
+			if (*s == '{') {
+				char *cp;
+				
+				if ((cp = find_match(&s[1], '{')))
+					s = ++cp;
+			}
+			if (*s)
+				s += strcspn(s,
+					".,/?-<>[]{}()'\";:\\|=+*&^%$#@!`~");
+			if (!*s)
+				return toglob;
+		}
 	}
 	if (!*s && (*toglob != '~' || saw_slash)) {
 		toglob[slen] = '*';
Index: emacs.c
===================================================================
RCS file: /cvsroot/basesrc/bin/ksh/emacs.c,v
retrieving revision 1.10
diff -u -p -r1.10 emacs.c
--- emacs.c	1999/11/09 00:01:49	1.10
+++ emacs.c	2002/01/25 09:26:25
@@ -1768,6 +1768,17 @@ x_expand(c)
 	return KSTD;
 }
 
+
+static int
+is_dir(const char *path)
+{
+	struct stat st;
+
+	if (stat(path, &st) == 0)
+		return S_ISDIR(st.st_mode);
+	return 0;
+}
+
 /* type == 0 for list, 1 for complete and 2 for complete-list */
 static void
 do_complete(flags, type)
@@ -1854,8 +1865,15 @@ do_complete(flags, type)
 				 * space to the end...
 				 */
 				if (nwords == 1
-				    && !ISDIRSEP(words[0][nlen - 1]))
-					x_ins(space);
+				    && !ISDIRSEP(words[0][nlen - 1])) {
+					/*
+					 * we may be here because we
+					 * just expanded $HOME in
+					 * which case adding '/' is
+					 * correct.
+					 */
+					x_ins(is_dir(words[0]) ? slash : space);
+				}
 			} else
 				x_e_putc(BEL);
 		}