openenvutils/commandshell/shell/src/prompt.c
changeset 0 2e3d3ce01487
child 1 0fdb7f6b0309
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/openenvutils/commandshell/shell/src/prompt.c	Tue Feb 02 10:12:00 2010 +0200
@@ -0,0 +1,1035 @@
+// prompt.c - construct zsh prompts
+//
+// © Portions Copyright (c) Symbian Software Ltd 2007. All rights reserved.
+//
+/*
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "prompt.pro"
+
+#ifdef __SYMBIAN32__
+#ifdef __WINSCW__
+#pragma warn_possunwant off
+#endif//__WINSCW__
+#endif//__SYMBIAN32__
+
+/* text attribute mask */
+ 
+/**/
+unsigned txtattrmask;
+
+/* text change - attribute change made by prompts */
+
+/**/
+mod_export unsigned txtchange;
+
+/* the command stack for use with %_ in prompts */
+ 
+/**/
+unsigned char *cmdstack;
+/**/
+int cmdsp;
+
+/* parser states, for %_ */
+
+static char *cmdnames[CS_COUNT] = {
+    "for",      "while",     "repeat",    "select",
+    "until",    "if",        "then",      "else",
+    "elif",     "math",      "cond",      "cmdor",
+    "cmdand",   "pipe",      "errpipe",   "foreach",
+    "case",     "function",  "subsh",     "cursh",
+    "array",    "quote",     "dquote",    "bquote",
+    "cmdsubst", "mathsubst", "elif-then", "heredoc",
+    "heredocd", "brace",     "braceparam", "always",
+};
+ 
+/* The buffer into which an expanded and metafied prompt is being written, *
+ * and its size.                                                           */
+
+static char *buf;
+static int bufspc;
+
+/* bp is the pointer to the current position in the buffer, where the next *
+ * character will be added.                                                */
+
+static char *bp;
+
+/* Position of the start of the current line in the buffer */
+
+static char *bufline;
+
+/* bp1 is an auxiliary pointer into the buffer, which when non-NULL is *
+ * moved whenever the buffer is reallocated.  It is used when data is   *
+ * being temporarily held in the buffer.                                */
+
+static char *bp1;
+
+/* The format string, for %-expansion. */
+
+static char *fm;
+
+/* Non-zero if truncating the current segment of the buffer. */
+
+static int trunclen;
+
+/* Current level of nesting of %{ / %} sequences. */
+
+static int dontcount;
+
+/* Level of %{ / %} surrounding a truncation segment. */
+
+static int trunccount;
+
+/* Strings to use for %r and %R (for the spelling prompt). */
+
+static char *rstring, *Rstring;
+
+/*
+ * Expand path p; maximum is npath segments where 0 means the whole path.
+ * If tilde is 1, try and find a named directory to use.
+ */
+
+static void
+promptpath(char *p, int npath, int tilde)
+{
+    char *modp = p;
+    Nameddir nd;
+
+    if (tilde && ((nd = finddir(p))))
+	modp = tricat("~", nd->nam, p + strlen(nd->dir));
+
+    if (npath) {
+	char *sptr;
+	if (npath > 0) {
+	    for (sptr = modp + strlen(modp); sptr > modp; sptr--) {
+		if (*sptr == '/' && !--npath) {
+		    sptr++;
+		    break;
+		}
+	    }
+	    if (*sptr == '/' && sptr[1] && sptr != modp)
+		sptr++;
+	    stradd(sptr);
+	} else {
+	    char cbu;
+	    for (sptr = modp+1; *sptr; sptr++)
+		if (*sptr == '/' && !++npath)
+		    break;
+	    cbu = *sptr;
+	    *sptr = 0;
+	    stradd(modp);
+	    *sptr = cbu;
+	}
+    } else
+	stradd(modp);
+
+    if (p != modp)
+	zsfree(modp);
+}
+
+/* Perform prompt expansion on a string, putting the result in a *
+ * permanently-allocated string.  If ns is non-zero, this string *
+ * may have embedded Inpar and Outpar, which indicate a toggling *
+ * between spacing and non-spacing parts of the prompt, and      *
+ * Nularg, which (in a non-spacing sequence) indicates a         *
+ * `glitch' space.                                               */
+
+/**/
+mod_export char *
+promptexpand(char *s, int ns, char *rs, char *Rs)
+{
+    if(!s)
+	return ztrdup("");
+
+    if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
+        init_term();
+
+    if (isset(PROMPTSUBST)) {
+	int olderr = errflag;
+	int oldval = lastval;
+
+	s = dupstring(s);
+	if (!parsestr(s))
+	    singsub(&s);
+
+	/* Ignore errors and status change in prompt substitution */
+	errflag = olderr;
+	lastval = oldval;
+    }
+
+    rstring = rs;
+    Rstring = Rs;
+    fm = s;
+    bp = bufline = buf = zshcalloc(bufspc = 256);
+    bp1 = NULL;
+    trunclen = 0;
+    putpromptchar(1, '\0');
+    addbufspc(1);
+    if(dontcount)
+	*bp++ = Outpar;
+    *bp = 0;
+    if (!ns) {
+	/* If zero, Inpar, Outpar and Nularg should be removed. */
+	for (bp = buf; *bp; ) {
+	    if (*bp == Meta)
+		bp += 2;
+	    else if (*bp == Inpar || *bp == Outpar || *bp == Nularg)
+		chuck(bp);
+	    else
+		bp++;
+	}
+    }
+    return buf;
+}
+
+/* Perform %- and !-expansion as required on a section of the prompt.  The *
+ * section is ended by an instance of endchar.  If doprint is 0, the valid *
+ * % sequences are merely skipped over, and nothing is stored.             */
+
+/**/
+static int
+putpromptchar(int doprint, int endchar)
+{
+    char *ss, *tmbuf = NULL, *hostnam;
+    int t0, arg, test, sep, j, numjobs;
+    struct tm *tm;
+    time_t timet;
+    Nameddir nd;
+
+    for (; *fm && *fm != endchar; fm++) {
+	arg = 0;
+	if (*fm == '%' && isset(PROMPTPERCENT)) {
+	    int minus = 0;
+	    fm++;
+	    if (*fm == '-') {
+		minus = 1;
+		fm++;
+	    }
+	    if (idigit(*fm)) {
+		arg = zstrtol(fm, &fm, 10);
+		if (minus)
+		    arg *= -1;
+	    } else if (minus)
+		arg = -1;
+	    if (*fm == '(') {
+		int tc, otrunclen;
+
+		if (idigit(*++fm)) {
+		    arg = zstrtol(fm, &fm, 10);
+		} else if (arg < 0) {
+		    /* negative numbers don't make sense here */
+		    arg *= -1;
+		}
+		test = 0;
+		ss = pwd;
+		switch (tc = *fm) {
+		case 'c':
+		case '.':
+		case '~':
+		    if ((nd = finddir(ss))) {
+			arg--;
+			ss += strlen(nd->dir);
+		    } /*FALLTHROUGH*/
+		case '/':
+		case 'C':
+		    /* `/' gives 0, `/any' gives 1, etc. */
+		    if (*ss++ == '/' && *ss)
+			arg--;
+		    for (; *ss; ss++)
+			if (*ss == '/')
+			    arg--;
+		    if (arg <= 0)
+			test = 1;
+		    break;
+		case 't':
+		case 'T':
+		case 'd':
+		case 'D':
+		case 'w':
+		    timet = time(NULL);
+		    tm = localtime(&timet);
+		    switch (tc) {
+		    case 't':
+			test = (arg == tm->tm_min);
+			break;
+		    case 'T':
+			test = (arg == tm->tm_hour);
+			break;
+		    case 'd':
+			test = (arg == tm->tm_mday);
+			break;
+		    case 'D':
+			test = (arg == tm->tm_mon);
+			break;
+		    case 'w':
+			test = (arg == tm->tm_wday);
+			break;
+		    }
+		    break;
+		case '?':
+		    if (lastval == arg)
+			test = 1;
+		    break;
+		case '#':
+		    if (geteuid() == (uid_t)arg)
+			test = 1;
+		    break;
+		case 'g':
+		    if (getegid() == (gid_t)arg)
+			test = 1;
+		    break;
+		case 'j':
+		    for (numjobs = 0, j = 1; j <= maxjob; j++)
+			if (jobtab[j].stat && jobtab[j].procs &&
+		    	    !(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
+		    if (numjobs >= arg)
+		    	test = 1;
+		    break;
+		case 'l':
+		    *bp = '\0';
+		    countprompt(bufline, &t0, 0, 0);
+		    if (t0 >= arg)
+			test = 1;
+		    break;
+		case 'L':
+		    if (shlvl >= arg)
+			test = 1;
+		    break;
+		case 'S':
+		    if (time(NULL) - shtimer.tv_sec >= arg)
+			test = 1;
+		    break;
+		case 'v':
+		    if (arrlen(psvar) >= arg)
+			test = 1;
+		    break;
+		case '_':
+		    test = (cmdsp >= arg);
+		    break;
+		case '!':
+		    test = privasserted();
+		    break;
+		default:
+		    test = -1;
+		    break;
+		}
+		if (!*fm || !(sep = *++fm))
+		    return 0;
+		fm++;
+		/* Don't do the current truncation until we get back */
+		otrunclen = trunclen;
+		trunclen = 0;
+		if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
+		    !putpromptchar(test == 0 && doprint, ')')) {
+		    trunclen = otrunclen;
+		    return 0;
+		}
+		trunclen = otrunclen;
+		continue;
+	    }
+	    if (!doprint)
+		switch(*fm) {
+		  case '[':
+		    while(idigit(*++fm));
+		    while(*++fm != ']');
+		    continue;
+		  case '<':
+		    while(*++fm != '<');
+		    continue;
+		  case '>':
+		    while(*++fm != '>');
+		    continue;
+		  case 'D':
+		    if(fm[1]=='{')
+			while(*++fm != '}');
+		    continue;
+		  default:
+		    continue;
+		}
+	    switch (*fm) {
+	    case '~':
+		promptpath(pwd, arg, 1);
+		break;
+	    case 'd':
+	    case '/':
+		promptpath(pwd, arg, 0);
+		break;
+	    case 'c':
+	    case '.':
+		promptpath(pwd, arg ? arg : 1, 1);
+		break;
+	    case 'C':
+		promptpath(pwd, arg ? arg : 1, 0);
+		break;
+	    case 'N':
+		promptpath(scriptname ? scriptname : argzero, arg, 0);
+		break;
+	    case 'h':
+	    case '!':
+		addbufspc(DIGBUFSIZE);
+		convbase(bp, curhist, 10);
+		bp += strlen(bp);
+		break;
+	    case 'j':
+		for (numjobs = 0, j = 1; j <= maxjob; j++)
+		    if (jobtab[j].stat && jobtab[j].procs &&
+		    	!(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%d", numjobs);
+		bp += strlen(bp);
+		break;
+	    case 'M':
+		queue_signals();
+		if ((hostnam = getsparam("HOST")))
+		    stradd(hostnam);
+		unqueue_signals();
+		break;
+	    case 'm':
+		if (!arg)
+		    arg++;
+		queue_signals();
+		if (!(hostnam = getsparam("HOST")))
+		    break;
+		if (arg < 0) {
+		    for (ss = hostnam + strlen(hostnam); ss > hostnam; ss--)
+			if (ss[-1] == '.' && !++arg)
+			    break;
+		    stradd(ss);
+		} else {
+		    for (ss = hostnam; *ss; ss++)
+			if (*ss == '.' && !--arg)
+			    break;
+		    stradd(*ss ? dupstrpfx(hostnam, ss - hostnam) : hostnam);
+		}
+		unqueue_signals();
+		break;
+	    case 'S':
+		txtchangeset(TXTSTANDOUT, TXTNOSTANDOUT);
+		txtset(TXTSTANDOUT);
+		tsetcap(TCSTANDOUTBEG, 1);
+		break;
+	    case 's':
+		txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
+		txtset(TXTDIRTY);
+		txtunset(TXTSTANDOUT);
+		tsetcap(TCSTANDOUTEND, 1);
+		break;
+	    case 'B':
+		txtchangeset(TXTBOLDFACE, TXTNOBOLDFACE);
+		txtset(TXTDIRTY);
+		txtset(TXTBOLDFACE);
+		tsetcap(TCBOLDFACEBEG, 1);
+		break;
+	    case 'b':
+		txtchangeset(TXTNOBOLDFACE, TXTBOLDFACE);
+		txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
+		txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
+		txtset(TXTDIRTY);
+		txtunset(TXTBOLDFACE);
+		tsetcap(TCALLATTRSOFF, 1);
+		break;
+	    case 'U':
+		txtchangeset(TXTUNDERLINE, TXTNOUNDERLINE);
+		txtset(TXTUNDERLINE);
+		tsetcap(TCUNDERLINEBEG, 1);
+		break;
+	    case 'u':
+		txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
+		txtset(TXTDIRTY);
+		txtunset(TXTUNDERLINE);
+		tsetcap(TCUNDERLINEEND, 1);
+		break;
+	    case '[':
+		if (idigit(*++fm))
+		    arg = zstrtol(fm, &fm, 10);
+		if (!prompttrunc(arg, ']', doprint, endchar))
+		    return *fm;
+		break;
+	    case '<':
+	    case '>':
+		if (!prompttrunc(arg, *fm, doprint, endchar))
+		    return *fm;
+		break;
+	    case '{': /*}*/
+		if (!dontcount++) {
+		    addbufspc(1);
+		    *bp++ = Inpar;
+		}
+		break;
+	    case /*{*/ '}':
+		if (trunccount && trunccount >= dontcount)
+		    return *fm;
+		if (dontcount && !--dontcount) {
+		    addbufspc(1);
+		    *bp++ = Outpar;
+		}
+		break;
+	    case 't':
+	    case '@':
+	    case 'T':
+	    case '*':
+	    case 'w':
+	    case 'W':
+	    case 'D':
+		{
+		    char *tmfmt, *dd;
+
+		    switch (*fm) {
+		    case 'T':
+			tmfmt = "%K:%M";
+			break;
+		    case '*':
+			tmfmt = "%K:%M:%S";
+			break;
+		    case 'w':
+			tmfmt = "%a %f";
+			break;
+		    case 'W':
+			tmfmt = "%m/%d/%y";
+			break;
+		    case 'D':
+			if (fm[1] == '{' /*}*/) {
+			    for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++)
+				if(*ss == '\\' && ss[1])
+				    ss++;
+			    dd = tmfmt = tmbuf = zalloc(ss - fm);
+			    for (ss = fm + 2; *ss && *ss != /*{*/ '}';
+				 ss++) {
+				if(*ss == '\\' && ss[1])
+				    ss++;
+				*dd++ = *ss;
+			    }
+			    *dd = 0;
+			    fm = ss - !*ss;
+			    if (!*tmfmt) {
+				free(tmbuf);
+				continue;
+			    }
+			} else
+			    tmfmt = "%y-%m-%d";
+			break;
+		    default:
+			tmfmt = "%l:%M%p";
+			break;
+		    }
+		    timet = time(NULL);
+		    tm = localtime(&timet);
+		    /*
+		     * Hack because strftime won't say how
+		     * much space it actually needs.  Try to add it
+		     * a few times until it works.  Some formats don't
+		     * actually have a length, so we could go on for
+		     * ever.
+		     */
+		    for(j = 0, t0 = strlen(tmfmt)*8; j < 3; j++, t0*=2) {
+			addbufspc(t0);
+			if (ztrftime(bp, t0, tmfmt, tm) >= 0)
+			    break;
+		    }
+		    bp += strlen(bp);
+		    free(tmbuf);
+		    tmbuf = NULL;
+		    break;
+		}
+	    case 'n':
+		stradd(get_username());
+		break;
+	    case 'l':
+		if (*ttystrname) {
+                   ss = (strncmp(ttystrname, "/dev/tty", 8) ?
+                           ttystrname + 5 : ttystrname + 8);
+		    stradd(ss);
+		} else
+		    stradd("()");
+		break;
+	    case 'y':
+		if (*ttystrname) {
+		    ss = (strncmp(ttystrname, "/dev/", 5) ?
+			    ttystrname : ttystrname + 5);
+		    stradd(ss);
+		} else
+		    stradd("()");
+		break;
+	    case 'L':
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%ld", (long)shlvl);
+		bp += strlen(bp);
+		break;
+	    case '?':
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%ld", (long)lastval);
+		bp += strlen(bp);
+		break;
+	    case '%':
+	    case ')':
+		addbufspc(1);
+		*bp++ = *fm;
+		break;
+	    case '#':
+		addbufspc(1);
+		*bp++ = privasserted() ? '#' : '%';
+		break;
+	    case 'v':
+		if (!arg)
+		    arg = 1;
+		else if (arg < 0)
+		    arg += arrlen(psvar) + 1;
+		if (arg > 0 && arrlen(psvar) >= arg)
+		    stradd(psvar[arg - 1]);
+		break;
+	    case 'E':
+                tsetcap(TCCLEAREOL, 1);
+		break;
+	    case '^':
+		if (cmdsp) {
+		    if (arg >= 0) {
+			if (arg > cmdsp || arg == 0)
+			    arg = cmdsp;
+			for (t0 = cmdsp - 1; arg--; t0--) {
+			    stradd(cmdnames[cmdstack[t0]]);
+			    if (arg) {
+				addbufspc(1);
+				*bp++=' ';
+			    }
+			}
+		    } else {
+			arg = -arg;
+			if (arg > cmdsp)
+			    arg = cmdsp;
+			for (t0 = arg - 1; arg--; t0--) {
+			    stradd(cmdnames[cmdstack[t0]]);
+			    if (arg) {
+				addbufspc(1);
+				*bp++=' ';
+			    }
+			}
+		    }
+		}
+		break;
+	    case '_':
+		if (cmdsp) {
+		    if (arg >= 0) {
+			if (arg > cmdsp || arg == 0)
+			    arg = cmdsp;
+			for (t0 = cmdsp - arg; arg--; t0++) {
+			    stradd(cmdnames[cmdstack[t0]]);
+			    if (arg) {
+				addbufspc(1);
+				*bp++=' ';
+			    }
+			}
+		    } else {
+			arg = -arg;
+			if (arg > cmdsp)
+			    arg = cmdsp;
+			for (t0 = 0; arg--; t0++) {
+			    stradd(cmdnames[cmdstack[t0]]);
+			    if (arg) {
+				addbufspc(1);
+				*bp++=' ';
+			    }
+			}
+		    }
+		}
+		break;
+	    case 'r':
+		if(rstring)
+		    stradd(rstring);
+		break;
+	    case 'R':
+		if(Rstring)
+		    stradd(Rstring);
+		break;
+	    case 'i':
+		addbufspc(DIGBUFSIZE);
+		sprintf(bp, "%ld", (long)lineno);
+		bp += strlen(bp);
+		break;
+	    case '\0':
+		return 0;
+	    case Meta:
+		fm++;
+		break;
+	    }
+	} else if(*fm == '!' && isset(PROMPTBANG)) {
+	    if(doprint) {
+		if(fm[1] == '!') {
+		    fm++;
+		    addbufspc(1);
+		    pputc('!');
+		} else {
+		    addbufspc(DIGBUFSIZE);
+		    convbase(bp, curhist, 10);
+		    bp += strlen(bp);
+		}
+	    }
+	} else {
+	    char c = *fm == Meta ? *++fm ^ 32 : *fm;
+
+	    if (doprint) {
+		addbufspc(1);
+		pputc(c);
+	    }
+	}
+    }
+
+    return *fm;
+}
+
+/* pputc adds a character to the buffer, metafying.  There must *
+ * already be space.                                            */
+
+/**/
+static void
+pputc(char c)
+{
+    if(imeta(STOUC(c))) {
+	*bp++ = Meta;
+	c ^= 32;
+    }
+    *bp++ = c;
+    if (c == '\n' && !dontcount)
+	bufline = bp;
+}
+
+/* Make sure there is room for `need' more characters in the buffer. */
+
+/**/
+static void
+addbufspc(int need)
+{
+    need *= 2;   /* for metafication */
+    if((bp - buf) + need > bufspc) {
+	int bo = bp - buf;
+	int bo1 = bp1 ? bp1 - buf : -1;
+
+	if(need & 255)
+	    need = (need | 255) + 1;
+	buf = realloc(buf, bufspc += need);
+	bp = buf + bo;
+	if(bo1 != -1)
+	    bp1 = buf + bo1;
+    }
+}
+
+/* stradd() adds a metafied string to the prompt, *
+ * in a visible representation.                   */
+
+/**/
+void
+stradd(char *d)
+{
+    char *ps, *pc;
+    addbufspc(niceztrlen(d));
+    /* This loop puts the nice representation of the string into the prompt *
+     * buffer.                                                              */
+    for(ps=d; *ps; ps++)
+	for(pc=nicechar(*ps == Meta ? STOUC(*++ps)^32 : STOUC(*ps)); *pc; pc++)
+	    *bp++ = *pc;
+}
+
+/* tsetcap(), among other things, can write a termcap string into the buffer. */
+
+/**/
+mod_export void
+tsetcap(int cap, int flag)
+{
+    if (tccan(cap) && !isset(SINGLELINEZLE) &&
+        !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
+	switch(flag) {
+	case -1:
+	    tputs(tcstr[cap], 1, putraw);
+	    break;
+	case 0:
+	    tputs(tcstr[cap], 1, putshout);
+	    break;
+	case 1:
+	    if (!dontcount) {
+		addbufspc(1);
+		*bp++ = Inpar;
+	    }
+	    tputs(tcstr[cap], 1, putstr);
+	    if (!dontcount) {
+		int glitch = 0;
+
+		if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
+		    glitch = tgetnum("sg");
+		else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
+		    glitch = tgetnum("ug");
+		if(glitch < 0)
+		    glitch = 0;
+		addbufspc(glitch + 1);
+		while(glitch--)
+		    *bp++ = Nularg;
+		*bp++ = Outpar;
+	    }
+	    break;
+	}
+
+	if (txtisset(TXTDIRTY)) {
+	    txtunset(TXTDIRTY);
+	    if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
+		tsetcap(TCBOLDFACEBEG, flag);
+	    if (txtisset(TXTSTANDOUT))
+		tsetcap(TCSTANDOUTBEG, flag);
+	    if (txtisset(TXTUNDERLINE))
+		tsetcap(TCUNDERLINEBEG, flag);
+	}
+    }
+}
+
+/**/
+int
+putstr(int d)
+{
+    addbufspc(1);
+    pputc(d);
+    return 0;
+}
+
+/* Count height etc. of a prompt string returned by promptexpand(). *
+ * This depends on the current terminal width, and tabs and         *
+ * newlines require nontrivial processing.                          *
+ * Passing `overf' as -1 means to ignore columns (absolute width).  */
+
+/**/
+mod_export void
+countprompt(char *str, int *wp, int *hp, int overf)
+{
+    int w = 0, h = 1;
+    int s = 1;
+    for(; *str; str++) {
+	if(w >= columns && overf >= 0) {
+	    w = 0;
+	    h++;
+	}
+	if(*str == Meta)
+	    str++;
+	if(*str == Inpar)
+	    s = 0;
+	else if(*str == Outpar)
+	    s = 1;
+	else if(*str == Nularg)
+	    w++;
+	else if(s) {
+	    if(*str == '\t')
+		w = (w | 7) + 1;
+	    else if(*str == '\n') {
+		w = 0;
+		h++;
+	    } else
+		w++;
+	}
+    }
+    if(w >= columns && overf >= 0) {
+	if (!overf || w > columns) {
+	    w = 0;
+	    h++;
+	}
+    }
+    if(wp)
+	*wp = w;
+    if(hp)
+	*hp = h;
+}
+
+/**/
+static int
+prompttrunc(int arg, int truncchar, int doprint, int endchar)
+{
+    if (arg > 0) {
+	char ch = *fm, *ptr, *truncstr;
+	int truncatleft = ch == '<';
+	int w = bp - buf;
+
+	/*
+	 * If there is already a truncation active, return so that
+	 * can be finished, backing up so that the new truncation
+	 * can be started afterwards.
+	 */
+	if (trunclen) {
+	    while (*--fm != '%')
+		;
+	    fm--;
+	    return 0;
+	}
+
+	trunclen = arg;
+	if (*fm != ']')
+	    fm++;
+	while (*fm && *fm != truncchar) {
+	    if (*fm == '\\' && fm[1])
+		++fm;
+	    addbufspc(1);
+	    *bp++ = *fm++;
+	}
+	if (!*fm)
+	    return 0;
+	if (bp - buf == w && truncchar == ']') {
+	    addbufspc(1);
+	    *bp++ = '<';
+	}
+	ptr = buf + w;		/* addbufspc() may have realloc()'d buf */
+	truncstr = ztrduppfx(ptr, bp - ptr);
+
+	bp = ptr;
+	w = bp - buf;
+	fm++;
+	trunccount = dontcount;
+	putpromptchar(doprint, endchar);
+	trunccount = 0;
+	ptr = buf + w;		/* putpromptchar() may have realloc()'d */
+	*bp = '\0';
+
+	countprompt(ptr, &w, 0, -1);
+	if (w > trunclen) {
+	    /*
+	     * We need to truncate.  t points to the truncation string -- *
+	     * which is inserted literally, without nice representation.  *
+	     * tlen is its length, and maxlen is the amount of the main	  *
+	     * string that we want to keep.  Note that if the truncation  *
+	     * string is longer than the truncation length (tlen >	  *
+	     * trunclen), the truncation string is used in full.	  *
+	     */
+	    char *t = truncstr;
+	    int fullen = bp - ptr;
+	    int tlen = ztrlen(t), maxlen;
+	    maxlen = tlen < trunclen ? trunclen - tlen : 0;
+	    if (w < fullen) {
+		/* Invisible substrings, lots of shuffling. */
+		int n = strlen(t);
+		char *p = ptr, *q = buf;
+		addbufspc(n);
+		ptr = buf + (p - q); /* addbufspc() may have realloc()'d */
+
+		if (truncatleft) {
+		    p = ptr + n;
+		    q = p;
+
+		    n = fullen - w;
+
+		    /* Shift the whole string right, then *
+		     * selectively copy to the left.      */
+		    memmove(p, ptr, fullen);
+		    while (w > 0 || n > 0) {
+			if (*p == Inpar)
+			    do {
+				*q++ = *p;
+				--n;
+			    } while (*p++ != Outpar && *p && n);
+			else if (w) {
+			    if (--w < maxlen)
+				*q++ = *p;
+			    ++p;
+			}
+		    }
+		    bp = q;
+		} else {
+		    /* Truncate on the right, selectively */
+		    q = ptr + fullen;
+
+		    /* First skip over as much as will "fit". */
+		    while (w > 0 && maxlen > 0) {
+			if (*ptr == Inpar)
+			    while (*ptr++ != Outpar && *ptr) {;}
+			else
+			    ++ptr, --w, --maxlen;
+		    }
+		    if (ptr < q) {
+			/* We didn't reach the end of the string. *
+			 * In case there are more invisible bits, *
+			 * insert the truncstr and keep looking.  */
+			memmove(ptr + n, ptr, q - ptr);
+			q = ptr + n;
+			while (*t)
+			    *ptr++ = *t++;
+			while (*q) {
+			    if (*q == Inpar)
+				do {
+				    *ptr++ = *q;
+				} while (*q++ != Outpar && *q);
+			    else
+				++q;
+			}
+			bp = ptr;
+			*bp = 0;
+		    } else
+			bp = ptr + n;
+		}
+	    } else {
+		/* No invisible substrings. */
+		if (tlen > fullen) {
+		    addbufspc(tlen - fullen);
+		    ptr = bp;	/* addbufspc() may have realloc()'d buf */
+		    bp += tlen - fullen;
+		} else
+		    bp -= fullen - trunclen;
+		if (truncatleft) {
+		    if (maxlen)
+			memmove(ptr + strlen(t), ptr + fullen - maxlen,
+				maxlen);
+		} else
+		    ptr += maxlen;
+	    }
+	    /* Finally, copy the truncstr into place. */
+	    while (*t)
+		*ptr++ = *t++;
+	}
+	zsfree(truncstr);
+	trunclen = 0;
+	/*
+	 * We may have returned early from the previous putpromptchar *
+	 * because we found another truncation following this one.    *
+	 * In that case we need to do the rest now.                   *
+	 */
+	if (!*fm)
+	    return 0;
+	if (*fm != endchar) {
+	    fm++;
+	    /*
+	     * With trunclen set to zero, we always reach endchar *
+	     * (or the terminating NULL) this time round.         *
+	     */
+	    if (!putpromptchar(doprint, endchar))
+		return 0;
+	}
+	/* Now we have to trick it into matching endchar again */
+	fm--;
+    } else {
+	if (*fm != ']')
+	    fm++;
+	while(*fm && *fm != truncchar) {
+	    if (*fm == '\\' && fm[1])
+		fm++;
+	    fm++;
+	}
+	if (trunclen || !*fm)
+	    return 0;
+    }
+    return 1;
+}