[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

ksh extension: bash-style prompt expansion



A number of people have asked for our ksh to act more like bash over
the last few years.  I think this is smart.  ksh is smaller, faster,
simpler -- and except for a few teeny glaring bash-isms that some
people addicted to -- completely better.

So here comes the first thing that people are missing:

bash-style expansions of the prompt variables.


Index: edit.c
===================================================================
RCS file: /cvs/src/bin/ksh/edit.c,v
retrieving revision 1.18
diff -u -r1.18 edit.c
--- edit.c	22 Aug 2003 18:17:10 -0000	1.18
+++ edit.c	5 Sep 2004 02:50:03 -0000
@@ -19,6 +19,7 @@
 #endif /* OS_SCO */
 #include <sys/ioctl.h>
 #include <ctype.h>
+#include <libgen.h>
 #include "ksh_stat.h"
 
 
@@ -316,59 +317,6 @@
 	}
 
 	return prev;
-}
-
-/* NAME:
- *      promptlen - calculate the length of PS1 etc.
- *
- * DESCRIPTION:
- *      This function is based on a fix from guy_(_at_)_demon_(_dot_)_co_(_dot_)_uk
- *      It fixes a bug in that if PS1 contains '!', the length
- *      given by strlen() is probably wrong.
- *
- * RETURN VALUE:
- *      length
- */
-int
-promptlen(cp, spp)
-    const char  *cp;
-    const char **spp;
-{
-    int count = 0;
-    const char *sp = cp;
-    char delimiter = 0;
-    int indelimit = 0;
-
-    /* Undocumented AT&T ksh feature:
-     * If the second char in the prompt string is \r then the first char
-     * is taken to be a non-printing delimiter and any chars between two
-     * instances of the delimiter are not considered to be part of the
-     * prompt length
-     */
-    if (*cp && cp[1] == '\r') {
-	delimiter = *cp;
-	cp += 2;
-    }
-
-    for (; *cp; cp++) {
-	if (indelimit && *cp != delimiter)
-	    ;
-	else if (*cp == '\n' || *cp == '\r') {
-	    count = 0;
-	    sp = cp + 1;
-	} else if (*cp == '\t') {
-	    count = (count | 7) + 1;
-	} else if (*cp == '\b') {
-	    if (count > 0)
-		count--;
-	} else if (*cp == delimiter)
-	    indelimit = !indelimit;
-	else
-	    count++;
-    }
-    if (spp)
-	*spp = sp;
-    return count;
 }
 
 void
Index: jobs.c
===================================================================
RCS file: /cvs/src/bin/ksh/jobs.c,v
retrieving revision 1.21
diff -u -r1.21 jobs.c
--- jobs.c	10 Nov 2003 21:26:39 -0000	1.21
+++ jobs.c	28 Aug 2004 01:57:39 -0000
@@ -177,7 +177,7 @@
 static pid_t		async_pid;
 
 static int		nzombie;	/* # of zombies owned by this process */
-static INT32		njobs;		/* # of jobs started */
+INT32			njobs;		/* # of jobs started */
 static int		child_max;	/* CHILD_MAX */
 
 
Index: lex.c
===================================================================
RCS file: /cvs/src/bin/ksh/lex.c,v
retrieving revision 1.18
diff -u -r1.18 lex.c
--- lex.c	6 Aug 2003 21:08:05 -0000	1.18
+++ lex.c	3 Nov 2004 19:36:17 -0000
@@ -5,6 +5,7 @@
  */
 
 #include "sh.h"
+#include <libgen.h>
 #include <ctype.h>
 
 
@@ -1164,27 +1165,229 @@
 	}
 }
 
-/* See also related routine, promptlen() in edit.c */
-void
-pprompt(cp, ntruncate)
-	const char *cp;
+static int
+dopprompt(sp, ntruncate, spp, doprint)
+	const char *sp;
 	int ntruncate;
+	const char **spp;
+	int doprint;
 {
-#if 0
-	char nbuf[32];
-	int c;
+	char strbuf[1024], tmpbuf[1024], *p, *str, nbuf[32], delimiter = '\0';
+	int len, c, n, totlen = 0, indelimit = 0;
+	const char *cp = sp, *ccp;
+	extern INT32 njobs;
+	struct tm *tm;
+	time_t t;
+
+	if (*cp && cp[1] == '\r') {
+		delimiter = *cp;
+		cp += 2;
+	}
 
 	while (*cp != 0) {
-		if (*cp != '!')
+		if (indelimit && *cp != delimiter)
+			;
+		else if (*cp == '\n' || *cp == '\r') {
+			totlen = 0;
+			sp = cp + 1;
+		} else if (*cp == '\t')
+			totlen = (totlen | 7) + 1;
+		else if (*cp == delimiter)
+			indelimit = !indelimit;
+
+		if (*cp == '\\') {
+			cp++;
+			if (!*cp)
+				break;
+			if (Flag(FSH))
+				snprintf(strbuf, sizeof strbuf, "\\%c", *cp);
+			else switch (*cp) {
+			case 'a':	/* '\' 'a' bell */
+				strbuf[0] = '\007';
+				strbuf[1] = '\0';
+				break;
+			case 'd':	/* '\' 'd' Dow Mon DD */ 
+				time(&t);
+				tm = localtime(&t);
+				strftime(strbuf, sizeof strbuf, "%a %b %d", tm);
+				p = strchr(cp + 2, '}') + 1;
+				break;
+			case 'D': /* '\' 'D' '{' strftime format '}' */
+				p = strchr(cp + 2, '}');
+				if (cp[1] != '{' || p == NULL) {
+					snprintf(strbuf, sizeof strbuf,
+					    "\\%c", *cp);
+					break;
+				}
+				strlcpy(tmpbuf, cp + 2, sizeof tmpbuf);
+				p = strchr(tmpbuf, '}');
+				if (p)
+					*p = '\0';
+				time(&t);
+				tm = localtime(&t);
+				strftime(strbuf, sizeof strbuf, tmpbuf, tm);
+				p = strchr(cp + 2, '}');
+				break;
+			case 'e':	/* '\' 'e' escape */
+				strbuf[0] = '\033';
+				strbuf[1] = '\0';
+				break;
+			case 'h':	/* '\' 'h' shortened hostname */
+				gethostname(strbuf, sizeof strbuf);
+				p = strchr(strbuf, '.');
+				if (p)
+					*p = '\0';
+				break;
+			case 'H':	/* '\' 'H' full hostname */
+				gethostname(strbuf, sizeof strbuf);
+				break;
+			case 'j':	/* '\' 'j' number of jobs */
+				snprintf(strbuf, sizeof strbuf, "%d",
+				    njobs);
+				break;
+			case 'l':	/* '\' 'l' basename of tty */
+				p = ttyname(0);
+				if (p)
+					p = basename(p);
+				if (p)
+					strlcpy(strbuf, p, sizeof strbuf);
+				break;
+			case 'n':	/* '\' 'n' newline */
+				strbuf[0] = '\n';
+				strbuf[1] = '\0';
+				break;
+			case 'r':	/* '\' 'r' return */
+				strbuf[0] = '\r';
+				strbuf[1] = '\0';
+				break;
+			case 's':	/* '\' 's' basename $0 */
+				strlcpy(strbuf, kshname, sizeof strbuf);
+				break;
+			case 't':	/* '\' 't' 24 hour HH:MM:SS */
+				time(&t);
+				tm = localtime(&t);
+				strftime(strbuf, sizeof strbuf, "%T", tm);
+				p = strchr(cp + 2, '}') + 1;
+				break;
+			case 'T':	/* '\' 'T' 12 hour HH:MM:SS */
+				time(&t);
+				tm = localtime(&t);
+				strftime(strbuf, sizeof strbuf, "%l:%M:%S", tm);
+				p = strchr(cp + 2, '}') + 1;
+				break;
+			case '@':	/* '\' '@' 12 hour am/pm format */
+				time(&t);
+				tm = localtime(&t);
+				strftime(strbuf, sizeof strbuf, "%r", tm);
+				p = strchr(cp + 2, '}') + 1;
+				break;
+			case 'A':	/* '\' 'A' 24 hour HH:MM */
+				time(&t);
+				tm = localtime(&t);
+				strftime(strbuf, sizeof strbuf, "%R", tm);
+				p = strchr(cp + 2, '}') + 1;
+				break;
+			case 'u':	/* '\' 'u' username */
+				p = getlogin();
+				if (p)
+					strlcpy(strbuf, p, sizeof strbuf);
+				else
+					strbuf[0] = '\0';
+				break;
+			case 'v':	/* '\' 'v' version (short) */
+			case 'V':	/* '\' 'V' version (long) */
+				strlcpy(strbuf, ksh_version, sizeof strbuf);
+				break;
+			case 'w':	/* '\' 'w' cwd */
+				p = str_val(global("PWD"));
+				strlcpy(strbuf, p, sizeof strbuf);
+				break;
+			case 'W':	/* '\' 'W' cwd, but with ~ de-expansion */
+				p = str_val(global("PWD"));
+				strlcpy(strbuf, p, sizeof strbuf);
+				/* XXX ~ expansion needed */
+				break;
+			case '!':	/* '\' '!' history line number XXX below */
+				snprintf(strbuf, sizeof strbuf, "%d",
+				    source->line + 1);
+				break;
+			case '#':	/* '\' '#' command line number XXX above */
+				snprintf(strbuf, sizeof strbuf, "%d",
+				    source->line + 1);
+				break;
+			case '$':	/* '\' '$' $ or # */
+				strbuf[0] = ksheuid ? '$' : '#';
+				strbuf[1] = '\0';
+				break;
+			case '0':	/* '\' '#' '#' ' #' octal numeric handling */
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+				if ((cp[1] > '7' || cp[1] < '0') ||
+				    (cp[2] > '7' || cp[2] < '0')) {
+					snprintf(strbuf, sizeof strbuf,
+					    "\\%c", *cp);
+					break;
+				}
+				n = cp[0] * 8 * 8 + cp[1] * 8 + cp[2];
+				snprintf(strbuf, sizeof strbuf, "%c", n);
+				cp += 2;
+				break;
+			case '\\':	/* '\' '\' */
+				strbuf[0] = '\\';
+				strbuf[1] = '\0';
+				break;
+			case '[': /* '\' '[' text '\' ']' */
+				ccp = cp + 1;
+				do {
+					ccp = strchr(ccp, '\\');
+				} while (ccp && ccp[1] == ']');
+				n = ccp - cp + 1;
+				if (ccp == NULL) {
+					snprintf(strbuf, sizeof strbuf,
+					    "\\%c", *cp);
+					break;
+				}
+				strlcpy(tmpbuf, cp + 1, sizeof tmpbuf);
+				if (n < sizeof tmpbuf)
+					tmpbuf[n] = '\0';
+				cp = ccp;
+				break;
+
+			default:
+				snprintf(strbuf, sizeof strbuf, "\\%c", *cp);
+				break;
+			}
+			cp++;
+
+			str = strbuf;
+			len = strlen(str);
+			if (ntruncate) {
+				if (ntruncate >= len) {
+					ntruncate -= len;
+					continue;
+				}
+				str += ntruncate;
+				len -= ntruncate;
+				ntruncate = 0;
+			}
+			if (doprint)
+				shf_write(str, len, shl_out);
+			totlen += len;
+			continue;
+		} else if (*cp != '!')
 			c = *cp++;
 		else if (*++cp == '!')
 			c = *cp++;
 		else {
-			int len;
 			char *p;
 
 			shf_snprintf(p = nbuf, sizeof(nbuf), "%d",
-				source->line + 1);
+			    source->line + 1);
 			len = strlen(nbuf);
 			if (ntruncate) {
 				if (ntruncate >= len) {
@@ -1195,17 +1398,39 @@
 				len -= ntruncate;
 				ntruncate = 0;
 			}
-			shf_write(p, len, shl_out);
+			if (doprint)
+				shf_write(p, len, shl_out);
+			totlen += len;
 			continue;
 		}
 		if (ntruncate)
 			--ntruncate;
-		else
+		else if (doprint) {
 			shf_putc(c, shl_out);
+		}
+		totlen++;
 	}
-#endif /* 0 */
-	shf_puts(cp + ntruncate, shl_out);
-	shf_flush(shl_out);
+	if (doprint)
+		shf_flush(shl_out);
+	if (spp)
+		*spp = sp;
+	return (totlen);
+}
+
+void
+pprompt(cp, ntruncate)
+	const char *cp;
+	int ntruncate;
+{
+	dopprompt(cp, ntruncate, NULL, 1);
+}
+
+int
+promptlen(cp, spp)
+    const char  *cp;
+    const char **spp;
+{
+	return dopprompt(cp, 0, spp, 0);
 }
 
 /* Read the variable part of a ${...} expression (ie, up to but not including