// 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;
}
