159243Sobrien/*
259243Sobrien * tc.prompt.c: Prompt printing stuff
359243Sobrien */
459243Sobrien/*-
559243Sobrien * Copyright (c) 1980, 1991 The Regents of the University of California.
659243Sobrien * All rights reserved.
759243Sobrien *
859243Sobrien * Redistribution and use in source and binary forms, with or without
959243Sobrien * modification, are permitted provided that the following conditions
1059243Sobrien * are met:
1159243Sobrien * 1. Redistributions of source code must retain the above copyright
1259243Sobrien *    notice, this list of conditions and the following disclaimer.
1359243Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1459243Sobrien *    notice, this list of conditions and the following disclaimer in the
1559243Sobrien *    documentation and/or other materials provided with the distribution.
16100616Smp * 3. Neither the name of the University nor the names of its contributors
1759243Sobrien *    may be used to endorse or promote products derived from this software
1859243Sobrien *    without specific prior written permission.
1959243Sobrien *
2059243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2159243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2259243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2359243Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2459243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2559243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2659243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2759243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2859243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2959243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3059243Sobrien * SUCH DAMAGE.
3159243Sobrien */
3259243Sobrien#include "sh.h"
3359243Sobrien#include "ed.h"
3459243Sobrien#include "tw.h"
3559243Sobrien
3659243Sobrien/*
3759243Sobrien * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
3859243Sobrien * PWP 4/27/87 -- rearange for tcsh.
3959243Sobrien * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
4059243Sobrien *                 instead of if/elseif
4159243Sobrien * Luke Mewburn, <lukem@cs.rmit.edu.au>
4259243Sobrien *	6-Sep-91	changed date format
4359243Sobrien *	16-Feb-94	rewrote directory prompt code, added $ellipsis
4459243Sobrien *	29-Dec-96	added rprompt support
4559243Sobrien */
4659243Sobrien
47145479Smpstatic const char   *month_list[12];
48145479Smpstatic const char   *day_list[7];
4959243Sobrien
5059243Sobrienvoid
51167465Smpdateinit(void)
5259243Sobrien{
5359243Sobrien#ifdef notyet
5459243Sobrien  int i;
5559243Sobrien
5659243Sobrien  setlocale(LC_TIME, "");
5759243Sobrien
5859243Sobrien  for (i = 0; i < 12; i++)
5959243Sobrien      xfree((ptr_t) month_list[i]);
6059243Sobrien  month_list[0] = strsave(_time_info->abbrev_month[0]);
6159243Sobrien  month_list[1] = strsave(_time_info->abbrev_month[1]);
6259243Sobrien  month_list[2] = strsave(_time_info->abbrev_month[2]);
6359243Sobrien  month_list[3] = strsave(_time_info->abbrev_month[3]);
6459243Sobrien  month_list[4] = strsave(_time_info->abbrev_month[4]);
6559243Sobrien  month_list[5] = strsave(_time_info->abbrev_month[5]);
6659243Sobrien  month_list[6] = strsave(_time_info->abbrev_month[6]);
6759243Sobrien  month_list[7] = strsave(_time_info->abbrev_month[7]);
6859243Sobrien  month_list[8] = strsave(_time_info->abbrev_month[8]);
6959243Sobrien  month_list[9] = strsave(_time_info->abbrev_month[9]);
7059243Sobrien  month_list[10] = strsave(_time_info->abbrev_month[10]);
7159243Sobrien  month_list[11] = strsave(_time_info->abbrev_month[11]);
7259243Sobrien
7359243Sobrien  for (i = 0; i < 7; i++)
7459243Sobrien      xfree((ptr_t) day_list[i]);
7559243Sobrien  day_list[0] = strsave(_time_info->abbrev_wkday[0]);
7659243Sobrien  day_list[1] = strsave(_time_info->abbrev_wkday[1]);
7759243Sobrien  day_list[2] = strsave(_time_info->abbrev_wkday[2]);
7859243Sobrien  day_list[3] = strsave(_time_info->abbrev_wkday[3]);
7959243Sobrien  day_list[4] = strsave(_time_info->abbrev_wkday[4]);
8059243Sobrien  day_list[5] = strsave(_time_info->abbrev_wkday[5]);
8159243Sobrien  day_list[6] = strsave(_time_info->abbrev_wkday[6]);
8259243Sobrien#else
8359243Sobrien  month_list[0] = "Jan";
8459243Sobrien  month_list[1] = "Feb";
8559243Sobrien  month_list[2] = "Mar";
8659243Sobrien  month_list[3] = "Apr";
8759243Sobrien  month_list[4] = "May";
8859243Sobrien  month_list[5] = "Jun";
8959243Sobrien  month_list[6] = "Jul";
9059243Sobrien  month_list[7] = "Aug";
9159243Sobrien  month_list[8] = "Sep";
9259243Sobrien  month_list[9] = "Oct";
9359243Sobrien  month_list[10] = "Nov";
9459243Sobrien  month_list[11] = "Dec";
9559243Sobrien
9659243Sobrien  day_list[0] = "Sun";
9759243Sobrien  day_list[1] = "Mon";
9859243Sobrien  day_list[2] = "Tue";
9959243Sobrien  day_list[3] = "Wed";
10059243Sobrien  day_list[4] = "Thu";
10159243Sobrien  day_list[5] = "Fri";
10259243Sobrien  day_list[6] = "Sat";
10359243Sobrien#endif
10459243Sobrien}
10559243Sobrien
10659243Sobrienvoid
107167465Smpprintprompt(int promptno, const char *str)
10859243Sobrien{
109167465Smp    static  const Char *ocp = NULL;
110145479Smp    static  const char *ostr = NULL;
11159243Sobrien    time_t  lclock = time(NULL);
112167465Smp    const Char *cp;
11359243Sobrien
11459243Sobrien    switch (promptno) {
11559243Sobrien    default:
11659243Sobrien    case 0:
11759243Sobrien	cp = varval(STRprompt);
11859243Sobrien	break;
11959243Sobrien    case 1:
12059243Sobrien	cp = varval(STRprompt2);
12159243Sobrien	break;
12259243Sobrien    case 2:
12359243Sobrien	cp = varval(STRprompt3);
12459243Sobrien	break;
12559243Sobrien    case 3:
12659243Sobrien	if (ocp != NULL) {
12759243Sobrien	    cp = ocp;
12859243Sobrien	    str = ostr;
12959243Sobrien	}
13059243Sobrien	else
13159243Sobrien	    cp = varval(STRprompt);
13259243Sobrien	break;
13359243Sobrien    }
13459243Sobrien
13559243Sobrien    if (promptno < 2) {
13659243Sobrien	ocp = cp;
13759243Sobrien	ostr = str;
13859243Sobrien    }
13959243Sobrien
140167465Smp    xfree(Prompt);
141167465Smp    Prompt = NULL;
142167465Smp    Prompt = tprintf(FMT_PROMPT, cp, str, lclock, NULL);
14359243Sobrien    if (!editing) {
144167465Smp	for (cp = Prompt; *cp ; )
145145479Smp	    (void) putwraw(*cp++);
14659243Sobrien	SetAttributes(0);
14759243Sobrien	flush();
14859243Sobrien    }
14959243Sobrien
150167465Smp    xfree(RPrompt);
151167465Smp    RPrompt = NULL;
15259243Sobrien    if (promptno == 0) {	/* determine rprompt if using main prompt */
15359243Sobrien	cp = varval(STRrprompt);
154167465Smp	RPrompt = tprintf(FMT_PROMPT, cp, NULL, lclock, NULL);
15559243Sobrien				/* if not editing, put rprompt after prompt */
156167465Smp	if (!editing && RPrompt[0] != '\0') {
157167465Smp	    for (cp = RPrompt; *cp ; )
158145479Smp		(void) putwraw(*cp++);
15959243Sobrien	    SetAttributes(0);
16059243Sobrien	    putraw(' ');
16159243Sobrien	    flush();
16259243Sobrien	}
16359243Sobrien    }
16459243Sobrien}
16559243Sobrien
166167465Smpstatic void
167167465Smptprintf_append_mbs(struct Strbuf *buf, const char *mbs, Char attributes)
16859243Sobrien{
169167465Smp    while (*mbs != 0) {
170167465Smp	Char wc;
171167465Smp
172167465Smp	mbs += one_mbtowc(&wc, mbs, MB_LEN_MAX);
173167465Smp	Strbuf_append1(buf, wc | attributes);
174167465Smp    }
175167465Smp}
176167465Smp
177167465SmpChar *
178167465Smptprintf(int what, const Char *fmt, const char *str, time_t tim, ptr_t info)
179167465Smp{
180167465Smp    struct Strbuf buf = Strbuf_INIT;
18159243Sobrien    Char   *z, *q;
18259243Sobrien    Char    attributes = 0;
18359243Sobrien    static int print_prompt_did_ding = 0;
184167465Smp    char *cz;
18559243Sobrien
186167465Smp    Char *p;
18759243Sobrien    const Char *cp = fmt;
18859243Sobrien    Char Scp;
18959243Sobrien    struct tm *t = localtime(&tim);
19059243Sobrien
19159243Sobrien			/* prompt stuff */
192167465Smp    static Char *olduser = NULL;
193100616Smp    int updirs;
194167465Smp    size_t pdirs;
19559243Sobrien
196167465Smp    cleanup_push(&buf, Strbuf_cleanup);
19759243Sobrien    for (; *cp; cp++) {
19859243Sobrien	if ((*cp == '%') && ! (cp[1] == '\0')) {
19959243Sobrien	    cp++;
20059243Sobrien	    switch (*cp) {
20159243Sobrien	    case 'R':
202145479Smp		if (what == FMT_HISTORY) {
203167465Smp		    cz = fmthist('R', info);
204167465Smp		    tprintf_append_mbs(&buf, cz, attributes);
205167465Smp		    xfree(cz);
206167465Smp		} else {
207167465Smp		    if (str != NULL)
208167465Smp			tprintf_append_mbs(&buf, str, attributes);
209167465Smp		}
21059243Sobrien		break;
21159243Sobrien	    case '#':
212316957Sdchagin#ifdef __CYGWIN__
213316957Sdchagin		/* Check for being member of the Administrators group */
214316957Sdchagin		{
215316957Sdchagin			gid_t grps[NGROUPS_MAX];
216316957Sdchagin			int grp, gcnt;
217316957Sdchagin
218316957Sdchagin			gcnt = getgroups(NGROUPS_MAX, grps);
219316957Sdchagin# define DOMAIN_GROUP_RID_ADMINS 544
220316957Sdchagin			for (grp = 0; grp < gcnt; ++grp)
221316957Sdchagin				if (grps[grp] == DOMAIN_GROUP_RID_ADMINS)
222316957Sdchagin					break;
223316957Sdchagin			Scp = (grp < gcnt) ? PRCHROOT : PRCH;
224316957Sdchagin		}
225316957Sdchagin#else
226231990Smp		Scp = (uid == 0 || euid == 0) ? PRCHROOT : PRCH;
227316957Sdchagin#endif
228231990Smp		if (Scp != '\0')
229231990Smp		    Strbuf_append1(&buf, attributes | Scp);
23059243Sobrien		break;
23159243Sobrien	    case '!':
23259243Sobrien	    case 'h':
23359243Sobrien		switch (what) {
23459243Sobrien		case FMT_HISTORY:
235167465Smp		    cz = fmthist('h', info);
23659243Sobrien		    break;
23759243Sobrien		case FMT_SCHED:
238167465Smp		    cz = xasprintf("%d", *(int *)info);
23959243Sobrien		    break;
24059243Sobrien		default:
241167465Smp		    cz = xasprintf("%d", eventno + 1);
24259243Sobrien		    break;
24359243Sobrien		}
244167465Smp		tprintf_append_mbs(&buf, cz, attributes);
245167465Smp		xfree(cz);
24659243Sobrien		break;
24759243Sobrien	    case 'T':		/* 24 hour format	 */
24859243Sobrien	    case '@':
24959243Sobrien	    case 't':		/* 12 hour am/pm format */
25059243Sobrien	    case 'p':		/* With seconds	*/
25159243Sobrien	    case 'P':
25259243Sobrien		{
25359243Sobrien		    char    ampm = 'a';
25459243Sobrien		    int     hr = t->tm_hour;
25559243Sobrien
25659243Sobrien		    /* addition by Hans J. Albertsson */
25759243Sobrien		    /* and another adapted from Justin Bur */
25859243Sobrien		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
25959243Sobrien			if (hr >= 12) {
26059243Sobrien			    if (hr > 12)
26159243Sobrien				hr -= 12;
26259243Sobrien			    ampm = 'p';
26359243Sobrien			}
26459243Sobrien			else if (hr == 0)
26559243Sobrien			    hr = 12;
26659243Sobrien		    }		/* else do a 24 hour clock */
26759243Sobrien
26859243Sobrien		    /* "DING!" stuff by Hans also */
26959243Sobrien		    if (t->tm_min || print_prompt_did_ding ||
27059243Sobrien			what != FMT_PROMPT || adrof(STRnoding)) {
27159243Sobrien			if (t->tm_min)
27259243Sobrien			    print_prompt_did_ding = 0;
273167465Smp			/*
274167465Smp			 * Pad hour to 2 characters if padhour is set,
275167465Smp			 * by ADAM David Alan Martin
276167465Smp			 */
277167465Smp			p = Itoa(hr, adrof(STRpadhour) ? 2 : 0, attributes);
278167465Smp			Strbuf_append(&buf, p);
279167465Smp			xfree(p);
280167465Smp			Strbuf_append1(&buf, attributes | ':');
281167465Smp			p = Itoa(t->tm_min, 2, attributes);
282167465Smp			Strbuf_append(&buf, p);
283167465Smp			xfree(p);
28459243Sobrien			if (*cp == 'p' || *cp == 'P') {
285167465Smp			    Strbuf_append1(&buf, attributes | ':');
286167465Smp			    p = Itoa(t->tm_sec, 2, attributes);
287167465Smp			    Strbuf_append(&buf, p);
288167465Smp			    xfree(p);
28959243Sobrien			}
29059243Sobrien			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
291167465Smp			    Strbuf_append1(&buf, attributes | ampm);
292167465Smp			    Strbuf_append1(&buf, attributes | 'm');
29359243Sobrien			}
29459243Sobrien		    }
29559243Sobrien		    else {	/* we need to ding */
296167465Smp			size_t i;
29759243Sobrien
298167465Smp			for (i = 0; STRDING[i] != 0; i++)
299167465Smp			    Strbuf_append1(&buf, attributes | STRDING[i]);
30059243Sobrien			print_prompt_did_ding = 1;
30159243Sobrien		    }
30259243Sobrien		}
30359243Sobrien		break;
30459243Sobrien
30559243Sobrien	    case 'M':
30659243Sobrien#ifndef HAVENOUTMP
30759243Sobrien		if (what == FMT_WHO)
308167465Smp		    cz = who_info(info, 'M');
30959243Sobrien		else
31059243Sobrien#endif /* HAVENOUTMP */
311167465Smp		    cz = getenv("HOST");
31259243Sobrien		/*
31359243Sobrien		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
31459243Sobrien		 * derefrence that NULL (if HOST is not set)...
31559243Sobrien		 */
31659243Sobrien		if (cz != NULL)
317167465Smp		    tprintf_append_mbs(&buf, cz, attributes);
318167465Smp		if (what == FMT_WHO)
319167465Smp		    xfree(cz);
32059243Sobrien		break;
32159243Sobrien
322167465Smp	    case 'm': {
323167465Smp		char *scz = NULL;
32459243Sobrien#ifndef HAVENOUTMP
32559243Sobrien		if (what == FMT_WHO)
326167465Smp		    scz = cz = who_info(info, 'm');
327167465Smp		else
32859243Sobrien#endif /* HAVENOUTMP */
329167465Smp		    cz = getenv("HOST");
33059243Sobrien
33159243Sobrien		if (cz != NULL)
332167465Smp		    while (*cz != 0 && (what == FMT_WHO || *cz != '.')) {
333167465Smp			Char wc;
334167465Smp
335167465Smp			cz += one_mbtowc(&wc, cz, MB_LEN_MAX);
336167465Smp			Strbuf_append1(&buf, wc | attributes);
337145479Smp		    }
338167465Smp		if (scz)
339167465Smp		    xfree(scz);
34059243Sobrien		break;
341167465Smp	    }
34259243Sobrien
34359243Sobrien			/* lukem: new directory prompt code */
34459243Sobrien	    case '~':
34559243Sobrien	    case '/':
34659243Sobrien	    case '.':
34759243Sobrien	    case 'c':
34859243Sobrien	    case 'C':
34959243Sobrien		Scp = *cp;
35059243Sobrien		if (Scp == 'c')		/* store format type (c == .) */
35159243Sobrien		    Scp = '.';
35259243Sobrien		if ((z = varval(STRcwd)) == STRNULL)
35359243Sobrien		    break;		/* no cwd, so don't do anything */
35459243Sobrien
35559243Sobrien			/* show ~ whenever possible - a la dirs */
35659243Sobrien		if (Scp == '~' || Scp == '.' ) {
357167465Smp		    static Char *olddir = NULL;
358167465Smp
35959243Sobrien		    if (tlength == 0 || olddir != z) {
36059243Sobrien			olddir = z;		/* have we changed dir? */
36159243Sobrien			olduser = getusername(&olddir);
36259243Sobrien		    }
36359243Sobrien		    if (olduser)
36459243Sobrien			z = olddir;
36559243Sobrien		}
36659243Sobrien		updirs = pdirs = 0;
36759243Sobrien
36859243Sobrien			/* option to determine fixed # of dirs from path */
36959243Sobrien		if (Scp == '.' || Scp == 'C') {
37059243Sobrien		    int skip;
37169408Sache#ifdef WINNT_NATIVE
372145479Smp		    Char *oldz = z;
37359243Sobrien		    if (z[1] == ':') {
374167465Smp			Strbuf_append1(&buf, attributes | *z++);
375167465Smp			Strbuf_append1(&buf, attributes | *z++);
37659243Sobrien		    }
377167465Smp		    if (*z == '/' && z[1] == '/') {
378167465Smp			Strbuf_append1(&buf, attributes | *z++);
379167465Smp			Strbuf_append1(&buf, attributes | *z++);
380167465Smp			do {
381167465Smp			    Strbuf_append1(&buf, attributes | *z++);
382167465Smp			} while(*z != '/');
383167465Smp		    }
38469408Sache#endif /* WINNT_NATIVE */
38559243Sobrien		    q = z;
38659243Sobrien		    while (*z)				/* calc # of /'s */
38759243Sobrien			if (*z++ == '/')
38859243Sobrien			    updirs++;
389145479Smp
390145479Smp#ifdef WINNT_NATIVE
391145479Smp		    /*
392145479Smp		     * for format type c, prompt will be following...
393145479Smp		     * c:/path                => c:/path
394145479Smp		     * c:/path/to             => c:to
395145479Smp		     * //machine/share        => //machine/share
396145479Smp		     * //machine/share/folder => //machine:folder
397145479Smp		     */
398145479Smp		    if (oldz[0] == '/' && oldz[1] == '/' && updirs > 1)
399167465Smp			Strbuf_append1(&buf, attributes | ':');
400145479Smp#endif /* WINNT_NATIVE */
40159243Sobrien		    if ((Scp == 'C' && *q != '/'))
40259243Sobrien			updirs++;
40359243Sobrien
40459243Sobrien		    if (cp[1] == '0') {			/* print <x> or ...  */
40559243Sobrien			pdirs = 1;
40659243Sobrien			cp++;
40759243Sobrien		    }
40859243Sobrien		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
40959243Sobrien			skip = cp[1] - '0';
41059243Sobrien			cp++;
41159243Sobrien		    }
41259243Sobrien		    else
41359243Sobrien			skip = 1;
41459243Sobrien
41559243Sobrien		    updirs -= skip;
41659243Sobrien		    while (skip-- > 0) {
41759243Sobrien			while ((z > q) && (*z != '/'))
41859243Sobrien			    z--;			/* back up */
41959243Sobrien			if (skip && z > q)
42059243Sobrien			    z--;
42159243Sobrien		    }
42259243Sobrien		    if (*z == '/' && z != q)
42359243Sobrien			z++;
42459243Sobrien		} /* . || C */
42559243Sobrien
42659243Sobrien							/* print ~[user] */
42759243Sobrien		if ((olduser) && ((Scp == '~') ||
42859243Sobrien		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
429167465Smp		    Strbuf_append1(&buf, attributes | '~');
430167465Smp		    for (q = olduser; *q; q++)
431167465Smp			Strbuf_append1(&buf, attributes | *q);
43259243Sobrien		}
43359243Sobrien
43459243Sobrien			/* RWM - tell you how many dirs we've ignored */
43559243Sobrien			/*       and add '/' at front of this         */
43659243Sobrien		if (updirs > 0 && pdirs) {
43759243Sobrien		    if (adrof(STRellipsis)) {
438167465Smp			Strbuf_append1(&buf, attributes | '.');
439167465Smp			Strbuf_append1(&buf, attributes | '.');
440167465Smp			Strbuf_append1(&buf, attributes | '.');
44159243Sobrien		    } else {
442167465Smp			Strbuf_append1(&buf, attributes | '/');
443167465Smp			Strbuf_append1(&buf, attributes | '<');
44459243Sobrien			if (updirs > 9) {
445167465Smp			    Strbuf_append1(&buf, attributes | '9');
446167465Smp			    Strbuf_append1(&buf, attributes | '+');
44759243Sobrien			} else
448167465Smp			    Strbuf_append1(&buf, attributes | ('0' + updirs));
449167465Smp			Strbuf_append1(&buf, attributes | '>');
45059243Sobrien		    }
45159243Sobrien		}
452167465Smp
453167465Smp		while (*z)
454167465Smp		    Strbuf_append1(&buf, attributes | *z++);
45559243Sobrien		break;
45659243Sobrien			/* lukem: end of new directory prompt code */
45759243Sobrien
45859243Sobrien	    case 'n':
45959243Sobrien#ifndef HAVENOUTMP
46059243Sobrien		if (what == FMT_WHO) {
461167465Smp		    cz = who_info(info, 'n');
462167465Smp		    tprintf_append_mbs(&buf, cz, attributes);
463167465Smp		    xfree(cz);
46459243Sobrien		}
46559243Sobrien		else
46659243Sobrien#endif /* HAVENOUTMP */
46759243Sobrien		{
46859243Sobrien		    if ((z = varval(STRuser)) != STRNULL)
469167465Smp			while (*z)
470167465Smp			    Strbuf_append1(&buf, attributes | *z++);
47159243Sobrien		}
47259243Sobrien		break;
473231990Smp	    case 'N':
474231990Smp		if ((z = varval(STReuser)) != STRNULL)
475231990Smp		    while (*z)
476231990Smp			Strbuf_append1(&buf, attributes | *z++);
477231990Smp		break;
47859243Sobrien	    case 'l':
47959243Sobrien#ifndef HAVENOUTMP
48059243Sobrien		if (what == FMT_WHO) {
481167465Smp		    cz = who_info(info, 'l');
482167465Smp		    tprintf_append_mbs(&buf, cz, attributes);
483167465Smp		    xfree(cz);
48459243Sobrien		}
48559243Sobrien		else
48659243Sobrien#endif /* HAVENOUTMP */
48759243Sobrien		{
48859243Sobrien		    if ((z = varval(STRtty)) != STRNULL)
489167465Smp			while (*z)
490167465Smp			    Strbuf_append1(&buf, attributes | *z++);
49159243Sobrien		}
49259243Sobrien		break;
49359243Sobrien	    case 'd':
494167465Smp		tprintf_append_mbs(&buf, day_list[t->tm_wday], attributes);
49559243Sobrien		break;
49659243Sobrien	    case 'D':
497167465Smp		p = Itoa(t->tm_mday, 2, attributes);
498167465Smp		Strbuf_append(&buf, p);
499167465Smp		xfree(p);
50059243Sobrien		break;
50159243Sobrien	    case 'w':
502167465Smp		tprintf_append_mbs(&buf, month_list[t->tm_mon], attributes);
50359243Sobrien		break;
50459243Sobrien	    case 'W':
505167465Smp		p = Itoa(t->tm_mon + 1, 2, attributes);
506167465Smp		Strbuf_append(&buf, p);
507167465Smp		xfree(p);
50859243Sobrien		break;
50959243Sobrien	    case 'y':
510167465Smp		p = Itoa(t->tm_year % 100, 2, attributes);
511167465Smp		Strbuf_append(&buf, p);
512167465Smp		xfree(p);
51359243Sobrien		break;
51459243Sobrien	    case 'Y':
515167465Smp		p = Itoa(t->tm_year + 1900, 4, attributes);
516167465Smp		Strbuf_append(&buf, p);
517167465Smp		xfree(p);
51859243Sobrien		break;
51959243Sobrien	    case 'S':		/* start standout */
52059243Sobrien		attributes |= STANDOUT;
52159243Sobrien		break;
52259243Sobrien	    case 'B':		/* start bold */
52359243Sobrien		attributes |= BOLD;
52459243Sobrien		break;
52559243Sobrien	    case 'U':		/* start underline */
52659243Sobrien		attributes |= UNDER;
52759243Sobrien		break;
52859243Sobrien	    case 's':		/* end standout */
52959243Sobrien		attributes &= ~STANDOUT;
53059243Sobrien		break;
53159243Sobrien	    case 'b':		/* end bold */
53259243Sobrien		attributes &= ~BOLD;
53359243Sobrien		break;
53459243Sobrien	    case 'u':		/* end underline */
53559243Sobrien		attributes &= ~UNDER;
53659243Sobrien		break;
53759243Sobrien	    case 'L':
53859243Sobrien		ClearToBottom();
53959243Sobrien		break;
540100616Smp
541100616Smp	    case 'j':
542100616Smp		{
543100616Smp		    int njobs = -1;
544100616Smp		    struct process *pp;
545167465Smp
546100616Smp		    for (pp = proclist.p_next; pp; pp = pp->p_next)
547100616Smp			njobs++;
548231990Smp		    if (njobs == -1)
549231990Smp			njobs++;
550167465Smp		    p = Itoa(njobs, 1, attributes);
551167465Smp		    Strbuf_append(&buf, p);
552167465Smp		    xfree(p);
553100616Smp		    break;
554100616Smp		}
55559243Sobrien	    case '?':
55659243Sobrien		if ((z = varval(STRstatus)) != STRNULL)
557167465Smp		    while (*z)
558167465Smp			Strbuf_append1(&buf, attributes | *z++);
55959243Sobrien		break;
56059243Sobrien	    case '$':
561167465Smp		expdollar(&buf, &cp, attributes);
562167465Smp		/* cp should point the last char of current % sequence */
563100616Smp		cp--;
56459243Sobrien		break;
56559243Sobrien	    case '%':
566167465Smp		Strbuf_append1(&buf, attributes | '%');
56759243Sobrien		break;
56859243Sobrien	    case '{':		/* literal characters start */
56959243Sobrien#if LITERAL == 0
57059243Sobrien		/*
57159243Sobrien		 * No literal capability, so skip all chars in the literal
57259243Sobrien		 * string
57359243Sobrien		 */
574167465Smp		while (*cp != '\0' && (cp[-1] != '%' || *cp != '}'))
57559243Sobrien		    cp++;
57659243Sobrien#endif				/* LITERAL == 0 */
57759243Sobrien		attributes |= LITERAL;
57859243Sobrien		break;
57959243Sobrien	    case '}':		/* literal characters end */
58059243Sobrien		attributes &= ~LITERAL;
58159243Sobrien		break;
58259243Sobrien	    default:
58359243Sobrien#ifndef HAVENOUTMP
58459243Sobrien		if (*cp == 'a' && what == FMT_WHO) {
585167465Smp		    cz = who_info(info, 'a');
586167465Smp		    tprintf_append_mbs(&buf, cz, attributes);
587167465Smp		    xfree(cz);
58859243Sobrien		}
589167465Smp		else
59059243Sobrien#endif /* HAVENOUTMP */
59159243Sobrien		{
592167465Smp		    Strbuf_append1(&buf, attributes | '%');
593167465Smp		    Strbuf_append1(&buf, attributes | *cp);
59459243Sobrien		}
59559243Sobrien		break;
59659243Sobrien	    }
59759243Sobrien	}
598167465Smp	else if (*cp == '\\' || *cp == '^')
599167465Smp	    Strbuf_append1(&buf, attributes | parseescape(&cp));
60059243Sobrien	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
601167465Smp	    if (what == FMT_HISTORY)
602167465Smp		cz = fmthist('h', info);
60359243Sobrien	    else
604167465Smp		cz = xasprintf("%d", eventno + 1);
605167465Smp	    tprintf_append_mbs(&buf, cz, attributes);
606167465Smp	    xfree(cz);
60759243Sobrien	}
608167465Smp	else
609167465Smp	    Strbuf_append1(&buf, attributes | *cp); /* normal character */
61059243Sobrien    }
611167465Smp    cleanup_ignore(&buf);
612167465Smp    cleanup_until(&buf);
613167465Smp    return Strbuf_finish(&buf);
61459243Sobrien}
61559243Sobrien
616167465Smpint
617167465Smpexpdollar(struct Strbuf *buf, const Char **srcp, Char attr)
61859243Sobrien{
61959243Sobrien    struct varent *vp;
62059243Sobrien    const Char *src = *srcp;
621167465Smp    Char *var, *val;
622167465Smp    size_t i;
623167465Smp    int curly = 0;
62459243Sobrien
62559243Sobrien    /* found a variable, expand it */
626167465Smp    var = xmalloc((Strlen(src) + 1) * sizeof (*var));
627167465Smp    for (i = 0; ; i++) {
62859243Sobrien	var[i] = *++src & TRIM;
62959243Sobrien	if (i == 0 && var[i] == '{') {
63059243Sobrien	    curly = 1;
63159243Sobrien	    var[i] = *++src & TRIM;
63259243Sobrien	}
633167465Smp	if (!alnum(var[i]) && var[i] != '_') {
634167465Smp
63559243Sobrien	    var[i] = '\0';
63659243Sobrien	    break;
63759243Sobrien	}
63859243Sobrien    }
63959243Sobrien    if (curly && (*src & TRIM) == '}')
64059243Sobrien	src++;
64159243Sobrien
64259243Sobrien    vp = adrof(var);
643100616Smp    if (vp && vp->vec) {
64459243Sobrien	for (i = 0; vp->vec[i] != NULL; i++) {
645167465Smp	    for (val = vp->vec[i]; *val; val++)
646167465Smp		if (*val != '\n' && *val != '\r')
647167465Smp		    Strbuf_append1(buf, *val | attr);
648167465Smp	    if (vp->vec[i+1])
649167465Smp		Strbuf_append1(buf, ' ' | attr);
65059243Sobrien	}
65159243Sobrien    }
65259243Sobrien    else {
653167465Smp	val = (!vp) ? tgetenv(var) : NULL;
654167465Smp	if (val) {
655167465Smp	    for (; *val; val++)
656167465Smp		if (*val != '\n' && *val != '\r')
657167465Smp		    Strbuf_append1(buf, *val | attr);
658167465Smp	} else {
659167465Smp	    *srcp = src;
660167465Smp	    xfree(var);
661167465Smp	    return 0;
662167465Smp	}
66359243Sobrien    }
66459243Sobrien
66559243Sobrien    *srcp = src;
666167465Smp    xfree(var);
667167465Smp    return 1;
66859243Sobrien}
669