/*
 * $Id: zle_misc.c,v 1.24 1995/05/30 06:21:41 coleman Exp coleman $
 *
 * zle_misc.c - miscellaneous editor routines
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 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 its documentation 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.
 *
 */

#define ZLE
#include "zsh.h"

/**/
void
selfinsert(void)
{
    int ncs = cs + mult;

    if (complexpect && isset(AUTOPARAMKEYS)) {
	if (complexpect == 2 && c == '}') {
	    if (!menucmp || line[cs-1] == '/' || addedsuffix) {
		int i = 0;
		spaceinline(1);
		do {
		    line[cs-i] = line[cs-i-1];
		} while (++i < addedsuffix);
		line[cs-i] = c;
		cs++;
		return;
	    }
	} else if (c == ':' || c == '[' || (complexpect == 2 &&
		    (c == '#' || c == '%' || c == '-' ||
		     c == '?' || c == '+' || c == '='))) {
	    if (addedsuffix > 1)
		backdel(addedsuffix - 1);

	    if (!menucmp || line[cs-1] == '/' || addedsuffix) {
		line[cs - 1] = c;
		addedsuffix = 0;
		return;
	    }
	}
    }
    else
	addedsuffix = 0;
    if (mult < 0) {
	mult = -mult;
	ncs = cs;
    }
    if (insmode || ll == cs)
	spaceinline(mult);
    else if (mult + cs > ll)
	spaceinline(ll - (mult + cs));
    while (mult--)
	line[cs++] = c;
    cs = ncs;
    menucmp = 0;
}

/**/
void
selfinsertunmeta(void)
{
    c &= 0x7f;
    if (c == '\r')
	c = '\n';
    selfinsert();
}

/**/
void
deletechar(void)
{
    if (mult < 0) {
	mult = -mult;
	backwarddeletechar();
	return;
    }
    if (!(cs + mult > ll || line[cs] == '\n')) {
	cs += mult;
	backdel(mult);
    } else
	feep();
}

/**/
void
backwarddeletechar(void)
{
    if (mult < 0) {
	mult = -mult;
	deletechar();
	return;
    }
    if (mult > cs)
	mult = cs;
    backdel(mult);
}

/**/
void
killwholeline(void)
{
    int i, fg;

    if (mult < 0)
	return;
    while (mult--) {
	if ((fg = (cs && cs == ll)))
	    cs--;
	while (cs && line[cs - 1] != '\n')
	    cs--;
	for (i = cs; i != ll && line[i] != '\n'; i++);
	forekill(i - cs + (i != ll), fg);
    }
}

/**/
void
killbuffer(void)
{
    cs = 0;
    forekill(ll, 0);
}

/**/
void
backwardkillline(void)
{
    int i = 0;

    if (mult < 0) {
	mult = -mult;
	killline();
	return;
    }
    while (mult--) {
	if (cs && line[cs - 1] == '\n')
	    cs--, i++;
	else
	    while (cs && line[cs - 1] != '\n')
		cs--, i++;
    }
    forekill(i, 1);
}

/**/
void
gosmacstransposechars(void)
{
    int cc;

    if (cs < 2 || line[cs - 1] == '\n' || line[cs - 2] == '\n') {
	if (line[cs] == '\n' || line[cs + 1] == '\n') {
	    feep();
	    return;
	}
	cs += (cs == 0 || line[cs - 1] == '\n') ? 2 : 1;
    }
    cc = line[cs - 2];
    line[cs - 2] = line[cs - 1];
    line[cs - 1] = cc;
}

/**/
void
transposechars(void)
{
    int cc, ct;
    int neg = mult < 0;

    if (neg)
	mult = -mult;
    while (mult--) {
	if (!(ct = cs) || line[cs - 1] == '\n') {
	    if (ll == cs || line[cs] == '\n') {
		feep();
		return;
	    }
	    if (!neg)
		cs++;
	    ct++;
	}
	if (neg) {
	    if (cs && line[cs - 1] != '\n') {
		cs--;
		if (ct > 1 && line[ct - 2] != '\n')
		    ct--;
	    }
	} else {
	    if (cs != ll && line[cs] != '\n')
		cs++;
	}
	if (ct == ll || line[ct] == '\n')
	    ct--;
	if (ct < 1 || line[ct - 1] == '\n') {
	    feep();
	    return;
	}
	cc = line[ct - 1];
	line[ct - 1] = line[ct];
	line[ct] = cc;
    }
}

/**/
void
poundinsert(void)
{
    cs = 0;
    if (*line != '#') {
	spaceinline(1);
	*line = '#';
    } else {
	foredel(1);
    }
    done = 1;
}

/**/
void
acceptline(void)
{
    done = 1;
}

/**/
void
acceptandhold(void)
{
    pushnode(bufstack, ztrdup((char *)line));
    stackcs = cs;
    done = 1;
}

/**/
void
killline(void)
{
    int i = 0;

    if (mult < 0) {
	mult = -mult;
	backwardkillline();
	return;
    }
    while (mult--) {
	if (line[cs] == '\n')
	    cs++, i++;
	else
	    while (cs != ll && line[cs] != '\n')
		cs++, i++;
    }
    backkill(i, 0);
}

/**/
void
killregion(void)
{
    if (mark > ll)
	mark = ll;
    if (mark > cs)
	forekill(mark - cs, 0);
    else
	backkill(cs - mark, 1);
}

/**/
void
copyregionaskill(void)
{
    if (mark > ll)
	mark = ll;
    if (mark > cs)
	cut(cs, mark - cs, 0);
    else
	cut(mark, cs - mark, 1);
}

static int kct, yankb, yanke;

/**/
void
yank(void)
{
    int cc;
    char *buf = cutbuf;

    if (!cutbuf) {
	feep();
	return;
    }
    if (mult < 0)
	return;
    if (vibufspec) {
	if (!(buf = vibuf[vibufspec])) {
	    feep();
	    vibufspec = 0;
	    return;
	}
	vibufspec = 0;
    }
    yankb = cs;
    while (mult--) {
	kct = kringnum;
	cc = strlen(buf);
	spaceinline(cc);
	strncpy((char *)line + cs, buf, cc);
	cs += cc;
	yanke = cs;
    }
}

/**/
void
yankpop(void)
{
    int cc;

    if (!(lastcmd & ZLE_YANK) || !kring[kct]) {
	feep();
	return;
    }
    cs = yankb;
    foredel(yanke - yankb);
    cc = strlen(kring[kct]);
    spaceinline(cc);
    strncpy((char *)line + cs, kring[kct], cc);
    cs += cc;
    yanke = cs;
    kct = (kct - 1) & (KRINGCT - 1);
}

/**/
void
overwritemode(void)
{
    insmode ^= 1;
}

/**/
void
undefinedkey(void)
{
    feep();
}

/**/
void
quotedinsert(void)
{
#ifndef HAS_TIO
    struct sgttyb sob;

    sob = shttyinfo.sgttyb;
    sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO;
    ioctl(SHTTY, TIOCSETN, &sob);
#endif
    c = getkey(0);
#ifndef HAS_TIO
    setterm();
#endif
    if (c > 0)
	selfinsert();
    else
	feep();
}

/**/
void
digitargument(void)
{
    int sign = (mult < 0) ? -1 : 1;

    if (!(lastcmd & ZLE_DIGIT))
	mult = 0;
    mult = mult * 10 + sign * (c & 0xf);
    gotmult = 1;
}

/**/
void
negargument(void)
{
    if (lastcmd & (ZLE_NEGARG | ZLE_DIGIT))
	feep();
    mult = -1;
    gotmult = 1;
}

/**/
void
universalargument(void)
{
    if (!(lastcmd & ZLE_ARG))
	mult = 4;
    else
	mult *= 4;
    gotmult = 1;
}

/**/
void
copyprevword(void)
{
    int len, t0;

    for (t0 = cs - 1; t0 >= 0; t0--)
	if (iword(line[t0]))
	    break;
    for (; t0 >= 0; t0--)
	if (!iword(line[t0]))
	    break;
    if (t0)
	t0++;
    len = cs - t0;
    spaceinline(len);
    strncpy((char *)line + cs, (char *)line + t0, len);
    cs += len;
}

/**/
void
sendbreak(void)
{
    errflag = 1;
}

/**/
void
undo(void)
{
    char *s;
    struct undoent *ue;

    ue = undos + undoct;
    if (!ue->change) {
	feep();
	return;
    }
    line[ll] = '\0';
    s = ztrdup((char *)line + ll - ue->suff);
    sizeline((ll = ue->pref + ue->suff + ue->len) + 1);
    strncpy((char *)line + ue->pref, ue->change, ue->len);
    strcpy((char *)line + ue->pref + ue->len, s);
    zsfree(s);
    ue->change = NULL;
    undoct = (undoct - 1) & (UNDOCT - 1);
    cs = ue->cs;
    ll = strlen((char *)line);
    if (cs > ll)
	cs = ll;
}

/**/
void
quoteregion(void)
{
    char *s, *t;
    int x, y;

    if (mark > ll)
	mark = ll;
    if (mark < cs) {
	x = mark;
	mark = cs;
	cs = x;
    }
    s = (char *)hcalloc((y = mark - cs) + 1);
    strncpy(s, (char *)line + cs, y);
    s[y] = '\0';
    foredel(mark - cs);
    t = makequote(s);
    spaceinline(x = strlen(t));
    strncpy((char *)line + cs, t, x);
    mark = cs;
    cs += x;
}

/**/
void
quoteline(void)
{
    char *s;

    line[ll] = '\0';
    s = makequote((char *)line);
    setline(s);
}

/**/
char *
makequote(char *s)
{
    int qtct = 0;
    char *l, *ol;

    for (l = s; *l; l++)
	if (*l == '\'')
	    qtct++;
    l = ol = (char *)halloc((qtct * 3) + 3 + strlen(s));
    *l++ = '\'';
    for (; *s; s++)
	if (*s == '\'') {
	    *l++ = '\'';
	    *l++ = '\\';
	    *l++ = '\'';
	    *l++ = '\'';
	} else
	    *l++ = *s;
    *l++ = '\'';
    *l = '\0';
    return ol;
}

#define NAMLEN 70

/**/
int
executenamedcommand(char *prmt)
{
    char buf[NAMLEN+1], *ptr;
    int len, cmd, t0, l = strlen(prmt);

    strcpy(buf, prmt);
    ptr = buf + l;
    len = 0;
    statusline = buf;
    for (;;) {
	ptr[0] = '_';
	ptr[1] = '\0';
	refresh();
	*ptr = '\0';
	if ((cmd = getkeycmd()) < 0 || cmd == z_sendbreak) {
	    statusline = NULL;
	    return z_undefinedkey;
	}
	switch (cmd) {
	case z_backwarddeletechar:
	case z_vibackwarddeletechar:
	    if (len)
		len--, ptr--;
	    break;
	case z_killregion:
	case z_backwardkillword:
	case z_vibackwardkillword:
	    while (len && (len--, *--ptr != '-'));
	    break;
	case z_killwholeline:
	case z_vikillline:
	case z_backwardkillline:
	    len = 0;
	    ptr = buf + l;
	    break;
	case z_acceptline:
	    for (t0 = 0; t0 != ZLECMDCOUNT; t0++)
		if (!strcmp(buf + l, zlecmds[t0].name))
		    break;
	    if (t0 != ZLECMDCOUNT) {
		lastnamed = t0;
		statusline = NULL;
		return t0;
	    }
	    /* fall through */
	default:
	    if (cmd == z_listchoices || cmd == z_deletecharorlist ||
		cmd == z_acceptline || c == ' ' || c == '\t') {
		Lklist cmdll;
		int ambig = 100;

		heapalloc();
		cmdll = newlist();
		for (t0 = 0; t0 != ZLECMDCOUNT; t0++)
		    if (strpfx(buf + l, zlecmds[t0].name)) {
			int xx;

			addnode(cmdll, zlecmds[t0].name);
			xx = pfxlen(peekfirst(cmdll), zlecmds[t0].name);
			if (xx < ambig)
			    ambig = xx;
		    }
		*ptr = '_';
		permalloc();
		if (empty(cmdll))
		    feep();
		else if (cmd == z_listchoices ||
			 cmd == z_deletecharorlist)
		    listlist(cmdll);
		else if (!nextnode(firstnode(cmdll))) {
		    strcpy(ptr = buf + l, peekfirst(cmdll));
		    ptr += (len = strlen(ptr));
		} else {
		    strcpy(buf + l, peekfirst(cmdll));
		    ptr = buf + l + ambig;
		    ptr[0] = '_';
		    ptr[1] = '\0';
		    if (isset(AUTOLIST) &&
			!(isset(LISTAMBIGUOUS) && ambig > len)) {
			if (unset(NOLISTBEEP))
			    feep();
			listlist(cmdll);
		    }
		    len = ambig;
		}
	    } else {
		if (len == NAMLEN - l - 1 || icntrl(c) || cmd != z_selfinsert)
		    feep();
		else
		    *ptr++ = c, len++;
	    }
	}
    }
}

static char *bp;
static char *truncstr;
static int docount, lensb, trunclen;

/**/
void
stradd(char *d)
{
    if (trunclen) {
    	int dlen = strlen(d);
	if (dlen > trunclen) {
	    char *t = truncstr + 1;
	    int len, tlen;
	    tlen = strlen(t);
	    len = tlen < trunclen ? trunclen - tlen : 0;
	    if (*truncstr == '>') {
		while (len--) pputc(*d++);
		while (*t) pputc(*t++);
	    } else {
		while (*t) pputc(*t++);
		d += dlen - len;
		while (len--) pputc(*d++);
	    }
	    return;
	}
    }
    while (*d) pputc(*d++);
}

/**/
int
putstr(int d)
{
    *bp++ = d;
    if (docount)
	lensb++;
    return 0;
}

/**/
void
tsetcap(int cap, int flag)
{
    if (termok && !isset(SINGLELINEZLE) && tcstr[cap]) {
	if (flag == 0) 
	    tputs(tcstr[cap], 1, putshout);
	else {
	    if (docount) {
		int  t0;

		if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
		    t0 = tgetnum("sg");
		else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
		    t0 = tgetnum("ug");
		else
		    t0 = 0;
		if (t0 > 0)
		    lensb -= t0;
	    }
	    tputs(tcstr[cap], 1, putstr);
	}

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

/* get a prompt string */

static char *buf, *bl0, *fm, *pmpt;
static int bracepos;

/**/
char *
putprompt(char *fmin, int *lenp, int *wp, int cnt)
{
    static char buf0[256], buf1[256], buf2[256];

    if (!fmin) {
	*lenp = 0;
	if (wp)
	    *wp = 0;
	return "";
    }

    bracepos = -1;
    fm = fmin;
    lensb = 0;
    pmpt = (docount = cnt) ? fm : NULL;

    /* KLUDGE ALERT!  What we have here are three buffers:
     *  buf1 and buf2 alternate between PS1 and PS2, though which is
     *   which is indeterminate depending on spellchecking, "select",
     *   etc. -- those operations also share these two buffers.
     *  buf0 is used for any prompting that manages to happen while
     *   zleread() is in progress (signal traps, etc.), because
     *   zleread() re-uses the pointers returned to buf1 and buf2
     *   and will be confused if either of those is overwritten.
     */
    buf = zleactive ? buf0 : ((buf == buf1) ? buf2 : buf1);
    bp = bl0 = buf;

    clearerr(stdin);

    putpromptchar(1, '\0');

    if (wp) {
	*wp = bp - bl0 - lensb;
	if (pmpt != rpmpt) {
	    *wp %= columns;
	    if (*wp == columns - 1)
		*wp = 0, *bp++ = ' ';
	    if (!*wp && *lenp)
	    	*bp++ = ' ';
	}
    }
    *lenp = bp - buf;

    return buf;
}

/**/
int
putpromptchar(int doprint, int endchar)
{
    char buf3[PATH_MAX], *ss;
    int t0, arg, test, sep;
    struct tm *tm;
    time_t timet;

    if (isset(PROMPTSUBST)) {
	char *sss;
	int olderr = errflag;

	fm = dupstring(fm);
	for (ss = fm; *ss; ss++)
	    if (*ss == '$' && ss[1]) {
		*ss = String;
		if (ss[1] == '[') {
		    ss[1] = Inbrack;
		    for (t0 = 0, sss = ss + 2; *sss && (t0 || *sss != ']'); sss++) {
			if (*sss == '[')
			    t0++;
			if (*sss == ']')
			    t0--;
			if (*sss == '\\' && sss[1])
			    sss++;
		    }
		    if (*sss == ']')
			*sss = Outbrack, ss = sss;
		} else if (ss[1] == '(') {
		    ss[1] = Inpar;
		    for (t0 = 0, sss = ss + 2; *sss && (t0 || *sss != ')'); sss++) {
			if (*sss == '(')
			    t0++;
			if (*sss == ')')
			    t0--;
			if (*sss == '\\' && sss[1])
			    sss++;
		    }
		    if (*sss == ')')
			*sss = Outpar, ss = sss;
		}
	    }
	lexsave();
	singsub(&fm);
	lexrestore();
	/* Ignore errors in prompt substitution */
	errflag = olderr;
    }
    for (; *fm && *fm != endchar; fm++) {
	if (bp - buf >= 220)
	    break;
	arg = 0;
	if (*fm == '%') {
	    if (idigit(*++fm)) {
		arg = strtol(fm, &fm, 10);
	    }
	    if (*fm == '(') {
		int tc;

		if (idigit(*++fm)) {
		    arg = strtol(fm, &fm, 10);
		}
		test = 0;
		ss = pwd;
		switch (tc = *fm) {
		case 'c':
		case '.':
		case '~':
		    t0 = finddir(ss);
		    if (t0 != -1) {
			arg--;
			ss += namdirs[t0].len;
		    }
		case '/':
		case 'C':
		    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() == arg)
			test = 1;
		    break;
		case 'g':
		    if (getegid() == 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;
		default:
		    test = -1;
		    break;
		}
		if (!*fm || !(sep = *++fm))
		    return 0;
		fm++;
		if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
		    !putpromptchar(test == 0 && doprint, ')')) {
		    return 0;
		}
		continue;
	    }
	    if (!doprint)
		continue;
	    switch (*fm) {
	    case '~':
		t0 = finddir(pwd);
		if (t0 != -1) {
		    *bp++ = '~';
		    stradd(namdirs[t0].name);
		    stradd(pwd + namdirs[t0].len);
		    break;
		}
	    case 'd':
	    case '/':
		stradd(pwd);
		break;
	    case 'c':
	    case '.':
		t0 = finddir(pwd);
		if (t0 != -1) {
		    sprintf(buf3, "~%s%s", namdirs[t0].name,
			    pwd + namdirs[t0].len);
		} else {
		    strcpy(buf3, pwd);
		}
		if (!arg)
		    arg++;
		for (ss = buf3 + strlen(buf3); ss > buf3; ss--)
		    if (*ss == '/' && !--arg) {
			ss++;
			break;
		    }
		if (*ss == '/' && ss[1] && (ss != buf3))
		    ss++;
		stradd(ss);
		break;
	    case 'C':
		strcpy(buf3, pwd);
		if (!arg)
		    arg++;
		for (ss = buf3 + strlen(buf3); ss > buf3; ss--)
		    if (*ss == '/' && !--arg) {
			ss++;
			break;
		    }
		if (*ss == '/' && ss[1] && (ss != buf3))
		    ss++;
		stradd(ss);
		break;
	    case 'h':
	    case '!':
		sprintf(bp, "%d", curhist);
		bp += strlen(bp);
		break;
	    case 'M':
		stradd(hostnam);
		break;
	    case 'm':
		if (!arg)
		    arg++;
		for (ss = hostnam; *ss; ss++)
		    if (*ss == '.' && !--arg)
			break;
		t0 = *ss;
		*ss = '\0';
		stradd(hostnam);
		*ss = t0;
		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))
                    trunclen = strtol(fm, &fm, 10);
                else
                    trunclen = arg;
                ss = bp;
                while (*fm && *fm != ']') {
		    if (*fm == '\\' && *(fm + 1))
			++fm;
		    *ss++ = *fm++;
		}
                if (trunclen) {
                    if (truncstr)
                        zsfree(truncstr);
		    if (ss == bp)
			*ss++ = '<';
                    *ss = '\0';
                    truncstr = ztrdup(bp);
                }
		break;
	    case '{':
		if (docount) {
		    bracepos = bp - buf;
		    docount = 0;
		}
		break;
	    case '}':
		if (bracepos != -1) {
		    lensb += (bp - buf) - bracepos;
		    bracepos = -1;
		    docount = 1;
		}
		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 %e";
			break;
		    case 'W':
			tmfmt = "%m/%d/%y";
			break;
		    case 'D':
			tmfmt = "%y-%m-%d";
			if (fm[1] == '{') {
			    for (ss = fm + 2, dd = buf3; *ss && *ss != '}'; ++ss, ++dd)
				*dd = *((*ss == '\\' && ss[1]) ? ++ss : ss);
			    if (*ss == '}') {
				*dd = '\0';
				fm = ss;
				tmfmt = buf3;
			    }
			}
			break;
		    default:
			tmfmt = "%l:%M%p";
			break;
		    }
		    timet = time(NULL);
		    tm = localtime(&timet);
		    ztrftime(bp, buf + 220 - bp, tmfmt, tm);
		    if (*bp == ' ')
			chuck(bp);
		    bp += strlen(bp);
		    break;
		}
	    case 'n':
		stradd(username);
		break;
	    case 'l':
		if (*ttystrname)
		    stradd((strncmp(ttystrname, "/dev/tty", 8) ?
			    ttystrname + 5 : ttystrname + 8));
		else
		    stradd("()");
		break;
	    case '?':
		sprintf(bp, "%ld", (long)lastval);
		bp += strlen(bp);
		break;
	    case '%':
		*bp++ = '%';
		break;
	    case '#':
		*bp++ = (geteuid())? '%' : '#';
		break;
	    case 'v':
		if (!arg)
		    arg++;
	    /* The number 35 here comes from 256-220-1, where 256 is
	       sizeof(buf), 220 is from the overflow test made at the
	       top of the loop, and 1 is for the \0 byte at the end. */

		if (arrlen(psvar) >= arg && (int)strlen(psvar[arg - 1]) < 35)
		    stradd(psvar[arg - 1]);
		else
		    stradd("");
		break;
	    case 'E':
                tsetcap(TCCLEAREOL, 1);
		break;
	    case '_':
		if (cmdsp) {
		    if (arg <= 0)
			arg = 1;
		    if (arg > cmdsp)
			arg = cmdsp;
		    for (t0 = cmdsp - arg; arg--; t0++) {
			stradd(cmdnames[cmdstack[t0]]);
			if (arg)
			    stradd(" ");
		    }
		}
		break;
	    case 'r':
		if (pmpt == sprompt) {
		    stradd(rstring);
		    break;
		}
	    case 'R':
		if (pmpt == sprompt) {
		    stradd(Rstring);
		    break;
		}
	    default:
		*bp++ = '%';
		pputc(*fm);
		break;
	    }
	} else if (*fm == '!' && doprint) {
	    sprintf(bp, "%d", curhist);
	    bp += strlen(bp);
	} else {
	    if (fm[0] == '\\' && fm[1])
		fm++;
	    if (doprint)
		pputc(*fm);
	}
    }

    return *fm;
}

/**/
void
pputc(char c)
{
    if (docount) {
	if (pmpt == rpmpt) {
	    if (c == '\t' || c == '\n')
		c = ' ';
	} else {
            if (c == '\t') {
	    	int t0 = 7 - (7 & (bp - bl0 - lensb));
	    	lensb -= t0;
	    } else if (c == '\n') {
	    	*bp++ = c;
	    	bl0 = bp;
	    	lensb = 0;
	    	return;
	    }
	}
    }
    *bp++ = c;
}
