tc.prompt.c revision 131962
11553Srgrimes/* $Header: /src/pub/tcsh/tc.prompt.c,v 3.47 2002/07/25 17:14:59 christos Exp $ */
21553Srgrimes/*
31553Srgrimes * tc.prompt.c: Prompt printing stuff
41553Srgrimes */
51553Srgrimes/*-
61553Srgrimes * Copyright (c) 1980, 1991 The Regents of the University of California.
71553Srgrimes * All rights reserved.
81553Srgrimes *
91553Srgrimes * Redistribution and use in source and binary forms, with or without
101553Srgrimes * modification, are permitted provided that the following conditions
111553Srgrimes * are met:
121553Srgrimes * 1. Redistributions of source code must retain the above copyright
131553Srgrimes *    notice, this list of conditions and the following disclaimer.
141553Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
151553Srgrimes *    notice, this list of conditions and the following disclaimer in the
161553Srgrimes *    documentation and/or other materials provided with the distribution.
171553Srgrimes * 3. Neither the name of the University nor the names of its contributors
181553Srgrimes *    may be used to endorse or promote products derived from this software
191553Srgrimes *    without specific prior written permission.
201553Srgrimes *
211553Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221553Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231553Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241553Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251553Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261553Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271553Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281553Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291553Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301553Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3130642Scharnier * SUCH DAMAGE.
321553Srgrimes */
3330642Scharnier#include "sh.h"
3430642Scharnier
3550479SpeterRCSID("$Id: tc.prompt.c,v 3.47 2002/07/25 17:14:59 christos Exp $")
361553Srgrimes
371553Srgrimes#include "ed.h"
381553Srgrimes#include "tw.h"
391553Srgrimes
401553Srgrimes/*
411553Srgrimes * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
421553Srgrimes * PWP 4/27/87 -- rearange for tcsh.
431553Srgrimes * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
441553Srgrimes *                 instead of if/elseif
451553Srgrimes * Luke Mewburn, <lukem@cs.rmit.edu.au>
461553Srgrimes *	6-Sep-91	changed date format
471553Srgrimes *	16-Feb-94	rewrote directory prompt code, added $ellipsis
481553Srgrimes *	29-Dec-96	added rprompt support
491553Srgrimes */
501553Srgrimes
511553Srgrimesstatic char   *month_list[12];
521553Srgrimesstatic char   *day_list[7];
531553Srgrimes
541553Srgrimesvoid
551553Srgrimesdateinit()
561553Srgrimes{
571553Srgrimes#ifdef notyet
581553Srgrimes  int i;
59246209Scharnier
60246209Scharnier  setlocale(LC_TIME, "");
61246209Scharnier
621553Srgrimes  for (i = 0; i < 12; i++)
631553Srgrimes      xfree((ptr_t) month_list[i]);
64246209Scharnier  month_list[0] = strsave(_time_info->abbrev_month[0]);
651553Srgrimes  month_list[1] = strsave(_time_info->abbrev_month[1]);
661553Srgrimes  month_list[2] = strsave(_time_info->abbrev_month[2]);
671553Srgrimes  month_list[3] = strsave(_time_info->abbrev_month[3]);
681553Srgrimes  month_list[4] = strsave(_time_info->abbrev_month[4]);
691553Srgrimes  month_list[5] = strsave(_time_info->abbrev_month[5]);
701553Srgrimes  month_list[6] = strsave(_time_info->abbrev_month[6]);
711553Srgrimes  month_list[7] = strsave(_time_info->abbrev_month[7]);
721553Srgrimes  month_list[8] = strsave(_time_info->abbrev_month[8]);
731553Srgrimes  month_list[9] = strsave(_time_info->abbrev_month[9]);
741553Srgrimes  month_list[10] = strsave(_time_info->abbrev_month[10]);
751553Srgrimes  month_list[11] = strsave(_time_info->abbrev_month[11]);
761553Srgrimes
771553Srgrimes  for (i = 0; i < 7; i++)
781553Srgrimes      xfree((ptr_t) day_list[i]);
791553Srgrimes  day_list[0] = strsave(_time_info->abbrev_wkday[0]);
801553Srgrimes  day_list[1] = strsave(_time_info->abbrev_wkday[1]);
811553Srgrimes  day_list[2] = strsave(_time_info->abbrev_wkday[2]);
821553Srgrimes  day_list[3] = strsave(_time_info->abbrev_wkday[3]);
83209344Sgavin  day_list[4] = strsave(_time_info->abbrev_wkday[4]);
841553Srgrimes  day_list[5] = strsave(_time_info->abbrev_wkday[5]);
851553Srgrimes  day_list[6] = strsave(_time_info->abbrev_wkday[6]);
861553Srgrimes#else
871553Srgrimes  month_list[0] = "Jan";
881553Srgrimes  month_list[1] = "Feb";
891553Srgrimes  month_list[2] = "Mar";
901553Srgrimes  month_list[3] = "Apr";
911553Srgrimes  month_list[4] = "May";
921553Srgrimes  month_list[5] = "Jun";
931553Srgrimes  month_list[6] = "Jul";
941553Srgrimes  month_list[7] = "Aug";
958857Srgrimes  month_list[8] = "Sep";
961553Srgrimes  month_list[9] = "Oct";
971553Srgrimes  month_list[10] = "Nov";
981553Srgrimes  month_list[11] = "Dec";
991553Srgrimes
1001553Srgrimes  day_list[0] = "Sun";
1011553Srgrimes  day_list[1] = "Mon";
1021553Srgrimes  day_list[2] = "Tue";
1031553Srgrimes  day_list[3] = "Wed";
1041553Srgrimes  day_list[4] = "Thu";
1051553Srgrimes  day_list[5] = "Fri";
1061553Srgrimes  day_list[6] = "Sat";
1071553Srgrimes#endif
1081553Srgrimes}
1091553Srgrimes
1101553Srgrimesvoid
1111553Srgrimesprintprompt(promptno, str)
1121553Srgrimes    int     promptno;
1131553Srgrimes    char   *str;
1141553Srgrimes{
1151553Srgrimes    static  Char *ocp = NULL;
1161553Srgrimes    static  char *ostr = NULL;
1171553Srgrimes    time_t  lclock = time(NULL);
1181553Srgrimes    Char   *cp;
1191553Srgrimes
1201553Srgrimes    switch (promptno) {
1211553Srgrimes    default:
1221553Srgrimes    case 0:
1231553Srgrimes	cp = varval(STRprompt);
1241553Srgrimes	break;
1251553Srgrimes    case 1:
1261553Srgrimes	cp = varval(STRprompt2);
1271553Srgrimes	break;
1281553Srgrimes    case 2:
129239991Sed	cp = varval(STRprompt3);
1301553Srgrimes	break;
1311553Srgrimes    case 3:
1321553Srgrimes	if (ocp != NULL) {
1331553Srgrimes	    cp = ocp;
1341553Srgrimes	    str = ostr;
1351553Srgrimes	}
1361553Srgrimes	else
137239991Sed	    cp = varval(STRprompt);
1381553Srgrimes	break;
1391553Srgrimes    }
1401553Srgrimes
1411553Srgrimes    if (promptno < 2) {
1421553Srgrimes	ocp = cp;
1431553Srgrimes	ostr = str;
14430761Scharnier    }
1454840Sbde
1461553Srgrimes    PromptBuf[0] = '\0';
1471553Srgrimes    tprintf(FMT_PROMPT, PromptBuf, cp, 2 * INBUFSIZE - 2, str, lclock, NULL);
1481553Srgrimes
1491553Srgrimes    if (!editing) {
1501553Srgrimes	for (cp = PromptBuf; *cp ; )
1511553Srgrimes	    (void) putraw(*cp++);
1521553Srgrimes	SetAttributes(0);
1531553Srgrimes	flush();
1541553Srgrimes    }
1551553Srgrimes
1561553Srgrimes    RPromptBuf[0] = '\0';
1571553Srgrimes    if (promptno == 0) {	/* determine rprompt if using main prompt */
1581553Srgrimes	cp = varval(STRrprompt);
1591553Srgrimes	tprintf(FMT_PROMPT, RPromptBuf, cp, INBUFSIZE - 2, NULL, lclock, NULL);
1601553Srgrimes
1611553Srgrimes				/* if not editing, put rprompt after prompt */
1621553Srgrimes	if (!editing && RPromptBuf[0] != '\0') {
1631553Srgrimes	    for (cp = RPromptBuf; *cp ; )
1641553Srgrimes		(void) putraw(*cp++);
1651553Srgrimes	    SetAttributes(0);
1661553Srgrimes	    putraw(' ');
1671553Srgrimes	    flush();
1681553Srgrimes	}
1691553Srgrimes    }
1701553Srgrimes}
1711553Srgrimes
1721553Srgrimesvoid
1731553Srgrimestprintf(what, buf, fmt, siz, str, tim, info)
174239991Sed    int what;
1751553Srgrimes    Char *buf;
1761553Srgrimes    const Char *fmt;
1771553Srgrimes    size_t siz;
1781553Srgrimes    char *str;
1791553Srgrimes    time_t tim;
1801553Srgrimes    ptr_t info;
1811553Srgrimes{
1821553Srgrimes    Char   *z, *q;
1831553Srgrimes    Char    attributes = 0;
1848857Srgrimes    static int print_prompt_did_ding = 0;
1851553Srgrimes    Char    buff[BUFSIZE];
1861553Srgrimes    /* Need to be unsigned to avoid sign extension */
1871553Srgrimes    const unsigned char   *cz;
1881553Srgrimes    unsigned char    cbuff[BUFSIZE];
1891553Srgrimes
1901553Srgrimes    Char *p  = buf;
1911553Srgrimes    Char *ep = &p[siz];
1921553Srgrimes    const Char *cp = fmt;
1931553Srgrimes    Char Scp;
1941553Srgrimes    struct tm *t = localtime(&tim);
1951553Srgrimes
1961553Srgrimes			/* prompt stuff */
1971553Srgrimes    static Char *olddir = NULL, *olduser = NULL;
1981553Srgrimes    extern int tlength;	/* cache cleared */
1991553Srgrimes    int updirs;
2001553Srgrimes    size_t pdirs, sz;
2011553Srgrimes
2021553Srgrimes    for (; *cp; cp++) {
2031553Srgrimes	if (p >= ep)
2041553Srgrimes	    break;
2051553Srgrimes#ifdef DSPMBYTE
2061553Srgrimes	if (Ismbyte1(*cp) && ! (cp[1] == '\0'))
2071553Srgrimes	{
2081553Srgrimes	    *p++ = attributes | *cp++;	/* normal character */
2091553Srgrimes	    *p++ = attributes | *cp;	/* normal character */
2101553Srgrimes	}
2111553Srgrimes	else
2121553Srgrimes#endif /* DSPMBYTE */
2131553Srgrimes	if ((*cp == '%') && ! (cp[1] == '\0')) {
2141553Srgrimes	    cp++;
2151553Srgrimes	    switch (*cp) {
2161553Srgrimes	    case 'R':
2171553Srgrimes		if (what == FMT_HISTORY)
2181553Srgrimes		    fmthist('R', info, (char *) (cz = cbuff), sizeof(cbuff));
2191553Srgrimes		else
2201553Srgrimes		    cz = (unsigned char *) str;
2211553Srgrimes		if (cz != NULL)
2221553Srgrimes		    for (; *cz; *p++ = attributes | *cz++)
2231553Srgrimes			if (p >= ep) break;
2241553Srgrimes		break;
2251553Srgrimes	    case '#':
2261553Srgrimes		*p++ = attributes | ((uid == 0) ? PRCHROOT : PRCH);
2271553Srgrimes		break;
2281553Srgrimes	    case '!':
2291553Srgrimes	    case 'h':
2301553Srgrimes		switch (what) {
2311553Srgrimes		case FMT_HISTORY:
2321553Srgrimes		    fmthist('h', info, (char *) cbuff, sizeof(cbuff));
2331553Srgrimes		    break;
2341553Srgrimes		case FMT_SCHED:
2351553Srgrimes		    (void) xsnprintf((char *) cbuff, sizeof(cbuff), "%d",
2361553Srgrimes			*(int *)info);
2371553Srgrimes		    break;
2381553Srgrimes		default:
2391553Srgrimes		    (void) xsnprintf((char *) cbuff, sizeof(cbuff), "%d",
2401553Srgrimes			eventno + 1);
2411553Srgrimes		    break;
2421553Srgrimes		}
2431553Srgrimes		for (cz = cbuff; *cz; *p++ = attributes | *cz++)
2441553Srgrimes		    if (p >= ep) break;
2451553Srgrimes		break;
2461553Srgrimes	    case 'T':		/* 24 hour format	 */
2471553Srgrimes	    case '@':
2481553Srgrimes	    case 't':		/* 12 hour am/pm format */
2491553Srgrimes	    case 'p':		/* With seconds	*/
2501553Srgrimes	    case 'P':
2511553Srgrimes		{
2521553Srgrimes		    char    ampm = 'a';
2531553Srgrimes		    int     hr = t->tm_hour;
2541553Srgrimes
2551553Srgrimes		    if (p >= ep - 10) break;
2561553Srgrimes
2571553Srgrimes		    /* addition by Hans J. Albertsson */
2581553Srgrimes		    /* and another adapted from Justin Bur */
2591553Srgrimes		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
2601553Srgrimes			if (hr >= 12) {
2611553Srgrimes			    if (hr > 12)
2621553Srgrimes				hr -= 12;
2631553Srgrimes			    ampm = 'p';
2641553Srgrimes			}
2651553Srgrimes			else if (hr == 0)
2661553Srgrimes			    hr = 12;
26730642Scharnier		    }		/* else do a 24 hour clock */
2681553Srgrimes
2691553Srgrimes		    /* "DING!" stuff by Hans also */
2701553Srgrimes		    if (t->tm_min || print_prompt_did_ding ||
2711553Srgrimes			what != FMT_PROMPT || adrof(STRnoding)) {
2721553Srgrimes			if (t->tm_min)
2731553Srgrimes			    print_prompt_did_ding = 0;
2741553Srgrimes			p = Itoa(hr, p, 0, attributes);
2751553Srgrimes			*p++ = attributes | ':';
2761553Srgrimes			p = Itoa(t->tm_min, p, 2, attributes);
2771553Srgrimes			if (*cp == 'p' || *cp == 'P') {
2781553Srgrimes			    *p++ = attributes | ':';
2791553Srgrimes			    p = Itoa(t->tm_sec, p, 2, attributes);
2801553Srgrimes			}
2811553Srgrimes			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
2821553Srgrimes			    *p++ = attributes | ampm;
2831553Srgrimes			    *p++ = attributes | 'm';
2841553Srgrimes			}
2851553Srgrimes		    }
2861553Srgrimes		    else {	/* we need to ding */
2871553Srgrimes			int     i = 0;
2881553Srgrimes
2891553Srgrimes			(void) Strcpy(buff, STRDING);
2901553Srgrimes			while (buff[i]) {
2911553Srgrimes			    *p++ = attributes | buff[i++];
2921553Srgrimes			}
293246209Scharnier			print_prompt_did_ding = 1;
2941553Srgrimes		    }
2951553Srgrimes		}
2961553Srgrimes		break;
2971553Srgrimes
2981553Srgrimes	    case 'M':
2991553Srgrimes#ifndef HAVENOUTMP
3001553Srgrimes		if (what == FMT_WHO)
3011553Srgrimes		    cz = (unsigned char *) who_info(info, 'M',
3021553Srgrimes			(char *) cbuff, sizeof(cbuff));
3031553Srgrimes		else
3041553Srgrimes#endif /* HAVENOUTMP */
3051553Srgrimes		    cz = (unsigned char *) getenv("HOST");
3061553Srgrimes		/*
3071553Srgrimes		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
3081553Srgrimes		 * derefrence that NULL (if HOST is not set)...
309246209Scharnier		 */
3101553Srgrimes		if (cz != NULL)
3111553Srgrimes		    for (; *cz ; *p++ = attributes | *cz++)
3121553Srgrimes			if (p >= ep) break;
3131553Srgrimes		break;
3141553Srgrimes
3151553Srgrimes	    case 'm':
3161553Srgrimes#ifndef HAVENOUTMP
3171553Srgrimes		if (what == FMT_WHO)
3181553Srgrimes		    cz = (unsigned char *) who_info(info, 'm', (char *) cbuff,
3191553Srgrimes			sizeof(cbuff));
3201553Srgrimes		else
3211553Srgrimes#endif /* HAVENOUTMP */
3221553Srgrimes		    cz = (unsigned char *) getenv("HOST");
3231553Srgrimes
324246209Scharnier		if (cz != NULL)
3251553Srgrimes		    for ( ; *cz && (what == FMT_WHO || *cz != '.')
3261553Srgrimes			  ; *p++ = attributes | *cz++ )
3271553Srgrimes			if (p >= ep) break;
3281553Srgrimes		break;
3291553Srgrimes
3301553Srgrimes			/* lukem: new directory prompt code */
3311553Srgrimes	    case '~':
3321553Srgrimes	    case '/':
3331553Srgrimes	    case '.':
3341553Srgrimes	    case 'c':
3351553Srgrimes	    case 'C':
3361553Srgrimes		Scp = *cp;
337		if (Scp == 'c')		/* store format type (c == .) */
338		    Scp = '.';
339		if ((z = varval(STRcwd)) == STRNULL)
340		    break;		/* no cwd, so don't do anything */
341
342			/* show ~ whenever possible - a la dirs */
343		if (Scp == '~' || Scp == '.' ) {
344		    if (tlength == 0 || olddir != z) {
345			olddir = z;		/* have we changed dir? */
346			olduser = getusername(&olddir);
347		    }
348		    if (olduser)
349			z = olddir;
350		}
351		updirs = pdirs = 0;
352
353			/* option to determine fixed # of dirs from path */
354		if (Scp == '.' || Scp == 'C') {
355		    int skip;
356#ifdef WINNT_NATIVE
357		    if (z[1] == ':') {
358		    	*p++ = attributes | *z++;
359		    	*p++ = attributes | *z++;
360		    }
361			if (*z == '/' && z[1] == '/') {
362				*p++ = attributes | *z++;
363				*p++ = attributes | *z++;
364				do {
365					*p++ = attributes | *z++;
366				}while(*z != '/');
367			}
368#endif /* WINNT_NATIVE */
369		    q = z;
370		    while (*z)				/* calc # of /'s */
371			if (*z++ == '/')
372			    updirs++;
373		    if ((Scp == 'C' && *q != '/'))
374			updirs++;
375
376		    if (cp[1] == '0') {			/* print <x> or ...  */
377			pdirs = 1;
378			cp++;
379		    }
380		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
381			skip = cp[1] - '0';
382			cp++;
383		    }
384		    else
385			skip = 1;
386
387		    updirs -= skip;
388		    while (skip-- > 0) {
389			while ((z > q) && (*z != '/'))
390			    z--;			/* back up */
391			if (skip && z > q)
392			    z--;
393		    }
394		    if (*z == '/' && z != q)
395			z++;
396		} /* . || C */
397
398							/* print ~[user] */
399		if ((olduser) && ((Scp == '~') ||
400		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
401		    *p++ = attributes | '~';
402		    if (p >= ep) break;
403		    for (q = olduser; *q; *p++ = attributes | *q++)
404			if (p >= ep) break;
405		}
406
407			/* RWM - tell you how many dirs we've ignored */
408			/*       and add '/' at front of this         */
409		if (updirs > 0 && pdirs) {
410		    if (p >= ep - 5) break;
411		    if (adrof(STRellipsis)) {
412			*p++ = attributes | '.';
413			*p++ = attributes | '.';
414			*p++ = attributes | '.';
415		    } else {
416			*p++ = attributes | '/';
417			*p++ = attributes | '<';
418			if (updirs > 9) {
419			    *p++ = attributes | '9';
420			    *p++ = attributes | '+';
421			} else
422			    *p++ = attributes | ('0' + updirs);
423			*p++ = attributes | '>';
424		    }
425		}
426
427		for (; *z ; *p++ = attributes | *z++)
428		    if (p >= ep) break;
429		break;
430			/* lukem: end of new directory prompt code */
431
432	    case 'n':
433#ifndef HAVENOUTMP
434		if (what == FMT_WHO) {
435		    cz = (unsigned char *) who_info(info, 'n',
436			(char *) cbuff, sizeof(cbuff));
437		    for (; cz && *cz ; *p++ = attributes | *cz++)
438			if (p >= ep) break;
439		}
440		else
441#endif /* HAVENOUTMP */
442		{
443		    if ((z = varval(STRuser)) != STRNULL)
444			for (; *z; *p++ = attributes | *z++)
445			    if (p >= ep) break;
446		}
447		break;
448	    case 'l':
449#ifndef HAVENOUTMP
450		if (what == FMT_WHO) {
451		    cz = (unsigned char *) who_info(info, 'l',
452			(char *) cbuff, sizeof(cbuff));
453		    for (; cz && *cz ; *p++ = attributes | *cz++)
454			if (p >= ep) break;
455		}
456		else
457#endif /* HAVENOUTMP */
458		{
459		    if ((z = varval(STRtty)) != STRNULL)
460			for (; *z; *p++ = attributes | *z++)
461			    if (p >= ep) break;
462		}
463		break;
464	    case 'd':
465		for (cz = (unsigned char *) day_list[t->tm_wday]; *cz;
466		    *p++ = attributes | *cz++)
467		    if (p >= ep) break;
468		break;
469	    case 'D':
470		if (p >= ep - 3) break;
471		p = Itoa(t->tm_mday, p, 2, attributes);
472		break;
473	    case 'w':
474		if (p >= ep - 5) break;
475		for (cz = (unsigned char *) month_list[t->tm_mon]; *cz;
476		    *p++ = attributes | *cz++)
477		    if (p >= ep) break;
478		break;
479	    case 'W':
480		if (p >= ep - 3) break;
481		p = Itoa(t->tm_mon + 1, p, 2, attributes);
482		break;
483	    case 'y':
484		if (p >= ep - 3) break;
485		p = Itoa(t->tm_year % 100, p, 2, attributes);
486		break;
487	    case 'Y':
488		if (p >= ep - 5) break;
489		p = Itoa(t->tm_year + 1900, p, 4, attributes);
490		break;
491	    case 'S':		/* start standout */
492		attributes |= STANDOUT;
493		break;
494	    case 'B':		/* start bold */
495		attributes |= BOLD;
496		break;
497	    case 'U':		/* start underline */
498		attributes |= UNDER;
499		break;
500	    case 's':		/* end standout */
501		attributes &= ~STANDOUT;
502		break;
503	    case 'b':		/* end bold */
504		attributes &= ~BOLD;
505		break;
506	    case 'u':		/* end underline */
507		attributes &= ~UNDER;
508		break;
509	    case 'L':
510		ClearToBottom();
511		break;
512
513	    case 'j':
514		{
515		    Char buf[128], *ebuf, *q;
516		    int njobs = -1;
517		    struct process *pp;
518		    for (pp = proclist.p_next; pp; pp = pp->p_next)
519			njobs++;
520		    /* make sure we have space */
521		    ebuf = Itoa(njobs, buf, 1, attributes);
522		    for (q = buf; q < ebuf; *p++ = *q++)
523			if (p >= ep) break;
524		    break;
525		}
526	    case '?':
527		if ((z = varval(STRstatus)) != STRNULL)
528		    for (; *z; *p++ = attributes | *z++)
529			if (p >= ep) break;
530		break;
531	    case '$':
532		sz = ep - p;
533		(void) expdollar(&p, &cp, &sz, attributes);
534		/* cp should point the last char of currnet % sequence */
535		cp--;
536		break;
537	    case '%':
538		*p++ = attributes | '%';
539		break;
540	    case '{':		/* literal characters start */
541#if LITERAL == 0
542		/*
543		 * No literal capability, so skip all chars in the literal
544		 * string
545		 */
546		while (*cp != '\0' && (*cp != '%' || cp[1] != '}'))
547		    cp++;
548#endif				/* LITERAL == 0 */
549		attributes |= LITERAL;
550		break;
551	    case '}':		/* literal characters end */
552		attributes &= ~LITERAL;
553		break;
554	    default:
555#ifndef HAVENOUTMP
556		if (*cp == 'a' && what == FMT_WHO) {
557		    cz = (unsigned char *) who_info(info, 'a', (char *) cbuff,
558			sizeof(cbuff));
559		    for (; cz && *cz; *p++ = attributes | *cz++)
560			if (p >= ep) break;
561		}
562		else
563#endif /* HAVENOUTMP */
564		{
565		    if (p >= ep - 3) break;
566		    *p++ = attributes | '%';
567		    *p++ = attributes | *cp;
568		}
569		break;
570	    }
571	}
572	else if (*cp == '\\' || *cp == '^')
573	    *p++ = attributes | parseescape(&cp);
574	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
575	    if (what == FMT_HISTORY)
576		fmthist('h', info, (char *) cbuff, sizeof(cbuff));
577	    else
578		(void) xsnprintf((char *) cbuff, sizeof(cbuff), "%d", eventno + 1);
579	    for (cz = cbuff; *cz; *p++ = attributes | *cz++)
580		if (p >= ep) break;
581	}
582	else
583	    *p++ = attributes | *cp;	/* normal character */
584    }
585    *p = '\0';
586}
587
588Char *
589expdollar(dstp, srcp, spp, attr)
590    Char **dstp;
591    const Char **srcp;
592    size_t *spp;
593    int	    attr;
594{
595    struct varent *vp;
596    Char var[MAXVARLEN];
597    const Char *src = *srcp;
598    Char *val;
599    Char *dst = *dstp;
600    int i, curly = 0;
601
602    /* found a variable, expand it */
603    for (i = 0; i < MAXVARLEN; i++) {
604	var[i] = *++src & TRIM;
605	if (i == 0 && var[i] == '{') {
606	    curly = 1;
607	    var[i] = *++src & TRIM;
608	}
609	if (!alnum(var[i])) {
610
611	    var[i] = '\0';
612	    break;
613	}
614    }
615    if (curly && (*src & TRIM) == '}')
616	src++;
617
618    vp = adrof(var);
619    val = (!vp) ? tgetenv(var) : NULL;
620    if (vp && vp->vec) {
621	for (i = 0; vp->vec[i] != NULL; i++) {
622	    for (val = vp->vec[i]; *spp > 0 && *val; (*spp)--)
623		*dst++ = *val++ | attr;
624	    if (vp->vec[i+1] && *spp > 0) {
625		*dst++ = ' ' | attr;
626		(*spp)--;
627	    }
628	}
629    }
630    else if (val) {
631	for (; *spp > 0 && *val; (*spp)--)
632	    *dst++ = *val++ | attr;
633    }
634    else {
635	**dstp = '\0';
636	*srcp = src;
637	return NULL;
638    }
639    *dst = '\0';
640
641    val = *dstp;
642    *srcp = src;
643    *dstp = dst;
644
645    return val;
646}
647