tc.prompt.c revision 167466
165483Simp/* $Header: /p/tcsh/cvsroot/tcsh/tc.prompt.c,v 3.67 2006/11/17 16:26:58 christos Exp $ */
252506Simp/*
3119418Sobrien * tc.prompt.c: Prompt printing stuff
4119418Sobrien */
5119418Sobrien/*-
652506Simp * Copyright (c) 1980, 1991 The Regents of the University of California.
752506Simp * All rights reserved.
8139749Simp *
952506Simp * Redistribution and use in source and binary forms, with or without
1052506Simp * modification, are permitted provided that the following conditions
1152506Simp * are met:
1252506Simp * 1. Redistributions of source code must retain the above copyright
1352506Simp *    notice, this list of conditions and the following disclaimer.
1452506Simp * 2. Redistributions in binary form must reproduce the above copyright
1552506Simp *    notice, this list of conditions and the following disclaimer in the
1652506Simp *    documentation and/or other materials provided with the distribution.
1752506Simp * 3. Neither the name of the University nor the names of its contributors
1852506Simp *    may be used to endorse or promote products derived from this software
1952506Simp *    without specific prior written permission.
2052506Simp *
2152506Simp * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2252506Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2352506Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2452506Simp * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2552506Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2652506Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2752506Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2852506Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2952506Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3052506Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3152506Simp * SUCH DAMAGE.
3252506Simp */
3352506Simp#include "sh.h"
3452506Simp
3552506SimpRCSID("$tcsh: tc.prompt.c,v 3.67 2006/11/17 16:26:58 christos Exp $")
3652506Simp
3752506Simp#include "ed.h"
38129762Simp#include "tw.h"
39129781Simp
4052506Simp/*
4152506Simp * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
4252506Simp * PWP 4/27/87 -- rearange for tcsh.
4352506Simp * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
44129762Simp *                 instead of if/elseif
45129762Simp * Luke Mewburn, <lukem@cs.rmit.edu.au>
46150371Sglebius *	6-Sep-91	changed date format
47129781Simp *	16-Feb-94	rewrote directory prompt code, added $ellipsis
48129740Simp *	29-Dec-96	added rprompt support
4952506Simp */
5052506Simp
5152506Simpstatic const char   *month_list[12];
5252506Simpstatic const char   *day_list[7];
5352506Simp
5452506Simpvoid
5552506Simpdateinit(void)
5652506Simp{
5752506Simp#ifdef notyet
5852506Simp  int i;
5952506Simp
6052506Simp  setlocale(LC_TIME, "");
6152506Simp
6252506Simp  for (i = 0; i < 12; i++)
6352506Simp      xfree((ptr_t) month_list[i]);
6452506Simp  month_list[0] = strsave(_time_info->abbrev_month[0]);
6552506Simp  month_list[1] = strsave(_time_info->abbrev_month[1]);
6652506Simp  month_list[2] = strsave(_time_info->abbrev_month[2]);
6752506Simp  month_list[3] = strsave(_time_info->abbrev_month[3]);
6852506Simp  month_list[4] = strsave(_time_info->abbrev_month[4]);
6952506Simp  month_list[5] = strsave(_time_info->abbrev_month[5]);
7052506Simp  month_list[6] = strsave(_time_info->abbrev_month[6]);
7152506Simp  month_list[7] = strsave(_time_info->abbrev_month[7]);
7252506Simp  month_list[8] = strsave(_time_info->abbrev_month[8]);
7352506Simp  month_list[9] = strsave(_time_info->abbrev_month[9]);
7452506Simp  month_list[10] = strsave(_time_info->abbrev_month[10]);
7552506Simp  month_list[11] = strsave(_time_info->abbrev_month[11]);
7652506Simp
7752506Simp  for (i = 0; i < 7; i++)
7852506Simp      xfree((ptr_t) day_list[i]);
7952506Simp  day_list[0] = strsave(_time_info->abbrev_wkday[0]);
8052506Simp  day_list[1] = strsave(_time_info->abbrev_wkday[1]);
8152506Simp  day_list[2] = strsave(_time_info->abbrev_wkday[2]);
8252506Simp  day_list[3] = strsave(_time_info->abbrev_wkday[3]);
8352506Simp  day_list[4] = strsave(_time_info->abbrev_wkday[4]);
8452506Simp  day_list[5] = strsave(_time_info->abbrev_wkday[5]);
8552506Simp  day_list[6] = strsave(_time_info->abbrev_wkday[6]);
8652506Simp#else
8752506Simp  month_list[0] = "Jan";
8852506Simp  month_list[1] = "Feb";
8952506Simp  month_list[2] = "Mar";
9052506Simp  month_list[3] = "Apr";
9152506Simp  month_list[4] = "May";
9252506Simp  month_list[5] = "Jun";
9352506Simp  month_list[6] = "Jul";
9452506Simp  month_list[7] = "Aug";
9552506Simp  month_list[8] = "Sep";
9652506Simp  month_list[9] = "Oct";
9752506Simp  month_list[10] = "Nov";
9853813Simp  month_list[11] = "Dec";
9953813Simp
10053813Simp  day_list[0] = "Sun";
10153813Simp  day_list[1] = "Mon";
10253813Simp  day_list[2] = "Tue";
10353813Simp  day_list[3] = "Wed";
10453813Simp  day_list[4] = "Thu";
10553813Simp  day_list[5] = "Fri";
10653813Simp  day_list[6] = "Sat";
10753813Simp#endif
10853813Simp}
10953813Simp
11053813Simpvoid
11153813Simpprintprompt(int promptno, const char *str)
11253813Simp{
11353813Simp    static  const Char *ocp = NULL;
11453813Simp    static  const char *ostr = NULL;
11553813Simp    time_t  lclock = time(NULL);
11653813Simp    const Char *cp;
11753813Simp
11853813Simp    switch (promptno) {
11953813Simp    default:
12053813Simp    case 0:
12153813Simp	cp = varval(STRprompt);
12253813Simp	break;
12353813Simp    case 1:
12453813Simp	cp = varval(STRprompt2);
12553813Simp	break;
12653813Simp    case 2:
12753813Simp	cp = varval(STRprompt3);
12853813Simp	break;
12953813Simp    case 3:
13053813Simp	if (ocp != NULL) {
13153813Simp	    cp = ocp;
13253813Simp	    str = ostr;
13353813Simp	}
13453813Simp	else
13553813Simp	    cp = varval(STRprompt);
13653813Simp	break;
13753813Simp    }
13853813Simp
13953813Simp    if (promptno < 2) {
140182142Simp	ocp = cp;
141182142Simp	ostr = str;
142182142Simp    }
143182142Simp
144182142Simp    xfree(Prompt);
145182142Simp    Prompt = NULL;
146182142Simp    Prompt = tprintf(FMT_PROMPT, cp, str, lclock, NULL);
147182142Simp    if (!editing) {
148182142Simp	for (cp = Prompt; *cp ; )
149182142Simp	    (void) putwraw(*cp++);
150182142Simp	SetAttributes(0);
151182142Simp	flush();
152182142Simp    }
153182142Simp
154182142Simp    xfree(RPrompt);
155182142Simp    RPrompt = NULL;
156182142Simp    if (promptno == 0) {	/* determine rprompt if using main prompt */
157182142Simp	cp = varval(STRrprompt);
158182142Simp	RPrompt = tprintf(FMT_PROMPT, cp, NULL, lclock, NULL);
159182142Simp				/* if not editing, put rprompt after prompt */
160182142Simp	if (!editing && RPrompt[0] != '\0') {
16152506Simp	    for (cp = RPrompt; *cp ; )
16252506Simp		(void) putwraw(*cp++);
16352506Simp	    SetAttributes(0);
16452506Simp	    putraw(' ');
16552506Simp	    flush();
16652506Simp	}
16752506Simp    }
16852506Simp}
16952506Simp
17052506Simpstatic void
17152506Simptprintf_append_mbs(struct Strbuf *buf, const char *mbs, Char attributes)
17252506Simp{
17352506Simp    while (*mbs != 0) {
17452506Simp	Char wc;
17552506Simp
17652506Simp	mbs += one_mbtowc(&wc, mbs, MB_LEN_MAX);
17752506Simp	Strbuf_append1(buf, wc | attributes);
17852506Simp    }
17952506Simp}
18052506Simp
18152506SimpChar *
18252506Simptprintf(int what, const Char *fmt, const char *str, time_t tim, ptr_t info)
18365483Simp{
18465483Simp    struct Strbuf buf = Strbuf_INIT;
18565483Simp    Char   *z, *q;
18665483Simp    Char    attributes = 0;
18765483Simp    static int print_prompt_did_ding = 0;
18865483Simp    char *cz;
18965483Simp
19065483Simp    Char *p;
19165483Simp    const Char *cp = fmt;
19265483Simp    Char Scp;
19365483Simp    struct tm *t = localtime(&tim);
19465483Simp
19565483Simp			/* prompt stuff */
19665483Simp    static Char *olduser = NULL;
19765483Simp    int updirs;
19865483Simp    size_t pdirs;
19965483Simp
20065483Simp    cleanup_push(&buf, Strbuf_cleanup);
20165483Simp    for (; *cp; cp++) {
20265483Simp	if ((*cp == '%') && ! (cp[1] == '\0')) {
20365483Simp	    cp++;
204172572Sremko	    switch (*cp) {
205172572Sremko	    case 'R':
206172572Sremko		if (what == FMT_HISTORY) {
207172572Sremko		    cz = fmthist('R', info);
208172572Sremko		    tprintf_append_mbs(&buf, cz, attributes);
209172572Sremko		    xfree(cz);
210172572Sremko		} else {
211172572Sremko		    if (str != NULL)
212172572Sremko			tprintf_append_mbs(&buf, str, attributes);
213172572Sremko		}
214172572Sremko		break;
215172572Sremko	    case '#':
216172572Sremko		Strbuf_append1(&buf,
217172572Sremko			       attributes | ((uid == 0) ? PRCHROOT : PRCH));
218172572Sremko		break;
219172572Sremko	    case '!':
220172572Sremko	    case 'h':
221172572Sremko		switch (what) {
222172572Sremko		case FMT_HISTORY:
223172572Sremko		    cz = fmthist('h', info);
224172572Sremko		    break;
22552506Simp		case FMT_SCHED:
22686272Simp		    cz = xasprintf("%d", *(int *)info);
22752506Simp		    break;
22886272Simp		default:
22952506Simp		    cz = xasprintf("%d", eventno + 1);
23086272Simp		    break;
23153813Simp		}
23286272Simp		tprintf_append_mbs(&buf, cz, attributes);
23353813Simp		xfree(cz);
23486272Simp		break;
23586272Simp	    case 'T':		/* 24 hour format	 */
23653813Simp	    case '@':
23786272Simp	    case 't':		/* 12 hour am/pm format */
23886272Simp	    case 'p':		/* With seconds	*/
23953813Simp	    case 'P':
240172572Sremko		{
241172572Sremko		    char    ampm = 'a';
242172572Sremko		    int     hr = t->tm_hour;
243172572Sremko
244172572Sremko		    /* addition by Hans J. Albertsson */
245172572Sremko		    /* and another adapted from Justin Bur */
246172572Sremko		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
247172572Sremko			if (hr >= 12) {
248172572Sremko			    if (hr > 12)
249172572Sremko				hr -= 12;
250172572Sremko			    ampm = 'p';
251172572Sremko			}
252182142Simp			else if (hr == 0)
253182142Simp			    hr = 12;
25486272Simp		    }		/* else do a 24 hour clock */
25552506Simp
25686272Simp		    /* "DING!" stuff by Hans also */
25765483Simp		    if (t->tm_min || print_prompt_did_ding ||
25852506Simp			what != FMT_PROMPT || adrof(STRnoding)) {
25952506Simp			if (t->tm_min)
26052506Simp			    print_prompt_did_ding = 0;
26152506Simp			/*
26252506Simp			 * Pad hour to 2 characters if padhour is set,
263120849Simp			 * by ADAM David Alan Martin
264120849Simp			 */
265120849Simp			p = Itoa(hr, adrof(STRpadhour) ? 2 : 0, attributes);
266120849Simp			Strbuf_append(&buf, p);
267120849Simp			xfree(p);
268120849Simp			Strbuf_append1(&buf, attributes | ':');
269120849Simp			p = Itoa(t->tm_min, 2, attributes);
270120849Simp			Strbuf_append(&buf, p);
271120849Simp			xfree(p);
272120849Simp			if (*cp == 'p' || *cp == 'P') {
273120849Simp			    Strbuf_append1(&buf, attributes | ':');
274120849Simp			    p = Itoa(t->tm_sec, 2, attributes);
275120849Simp			    Strbuf_append(&buf, p);
276120849Simp			    xfree(p);
277120849Simp			}
278120849Simp			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
279120849Simp			    Strbuf_append1(&buf, attributes | ampm);
28052506Simp			    Strbuf_append1(&buf, attributes | 'm');
28152506Simp			}
28264850Simp		    }
28352506Simp		    else {	/* we need to ding */
28452506Simp			size_t i;
28552506Simp
28652506Simp			for (i = 0; STRDING[i] != 0; i++)
287120849Simp			    Strbuf_append1(&buf, attributes | STRDING[i]);
28852506Simp			print_prompt_did_ding = 1;
28952506Simp		    }
29052506Simp		}
29152506Simp		break;
29252506Simp
293120849Simp	    case 'M':
294120849Simp#ifndef HAVENOUTMP
295120849Simp		if (what == FMT_WHO)
296120849Simp		    cz = who_info(info, 'M');
297120849Simp		else
298120849Simp#endif /* HAVENOUTMP */
299120849Simp		    cz = getenv("HOST");
300120849Simp		/*
301120849Simp		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
302120849Simp		 * derefrence that NULL (if HOST is not set)...
303120849Simp		 */
304120849Simp		if (cz != NULL)
30552506Simp		    tprintf_append_mbs(&buf, cz, attributes);
306120849Simp		if (what == FMT_WHO)
307120849Simp		    xfree(cz);
30852506Simp		break;
309120849Simp
310120849Simp	    case 'm': {
311120849Simp		char *scz = NULL;
312120849Simp#ifndef HAVENOUTMP
313120849Simp		if (what == FMT_WHO)
314120849Simp		    scz = cz = who_info(info, 'm');
31552506Simp		else
316120849Simp#endif /* HAVENOUTMP */
317120849Simp		    cz = getenv("HOST");
31852506Simp
31952506Simp		if (cz != NULL)
320120849Simp		    while (*cz != 0 && (what == FMT_WHO || *cz != '.')) {
321120849Simp			Char wc;
322120849Simp
32352506Simp			cz += one_mbtowc(&wc, cz, MB_LEN_MAX);
324120849Simp			Strbuf_append1(&buf, wc | attributes);
325120849Simp		    }
326144158Ssam		if (scz)
327144158Ssam		    xfree(scz);
328144158Ssam		break;
329144158Ssam	    }
330120849Simp
331120849Simp			/* lukem: new directory prompt code */
332120849Simp	    case '~':
333120849Simp	    case '/':
334144158Ssam	    case '.':
335144158Ssam	    case 'c':
336144158Ssam	    case 'C':
337144158Ssam		Scp = *cp;
338144158Ssam		if (Scp == 'c')		/* store format type (c == .) */
339120849Simp		    Scp = '.';
340120849Simp		if ((z = varval(STRcwd)) == STRNULL)
341120849Simp		    break;		/* no cwd, so don't do anything */
342144158Ssam
343144158Ssam			/* show ~ whenever possible - a la dirs */
344144158Ssam		if (Scp == '~' || Scp == '.' ) {
345144158Ssam		    static Char *olddir = NULL;
346144158Ssam
347120849Simp		    if (tlength == 0 || olddir != z) {
348120849Simp			olddir = z;		/* have we changed dir? */
349120849Simp			olduser = getusername(&olddir);
350120849Simp		    }
35152506Simp		    if (olduser)
35252506Simp			z = olddir;
35352506Simp		}
354		updirs = pdirs = 0;
355
356			/* option to determine fixed # of dirs from path */
357		if (Scp == '.' || Scp == 'C') {
358		    int skip;
359#ifdef WINNT_NATIVE
360		    Char *oldz = z;
361		    if (z[1] == ':') {
362			Strbuf_append1(&buf, attributes | *z++);
363			Strbuf_append1(&buf, attributes | *z++);
364		    }
365		    if (*z == '/' && z[1] == '/') {
366			Strbuf_append1(&buf, attributes | *z++);
367			Strbuf_append1(&buf, attributes | *z++);
368			do {
369			    Strbuf_append1(&buf, attributes | *z++);
370			} while(*z != '/');
371		    }
372#endif /* WINNT_NATIVE */
373		    q = z;
374		    while (*z)				/* calc # of /'s */
375			if (*z++ == '/')
376			    updirs++;
377
378#ifdef WINNT_NATIVE
379		    /*
380		     * for format type c, prompt will be following...
381		     * c:/path                => c:/path
382		     * c:/path/to             => c:to
383		     * //machine/share        => //machine/share
384		     * //machine/share/folder => //machine:folder
385		     */
386		    if (oldz[0] == '/' && oldz[1] == '/' && updirs > 1)
387			Strbuf_append1(&buf, attributes | ':');
388#endif /* WINNT_NATIVE */
389		    if ((Scp == 'C' && *q != '/'))
390			updirs++;
391
392		    if (cp[1] == '0') {			/* print <x> or ...  */
393			pdirs = 1;
394			cp++;
395		    }
396		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
397			skip = cp[1] - '0';
398			cp++;
399		    }
400		    else
401			skip = 1;
402
403		    updirs -= skip;
404		    while (skip-- > 0) {
405			while ((z > q) && (*z != '/'))
406			    z--;			/* back up */
407			if (skip && z > q)
408			    z--;
409		    }
410		    if (*z == '/' && z != q)
411			z++;
412		} /* . || C */
413
414							/* print ~[user] */
415		if ((olduser) && ((Scp == '~') ||
416		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
417		    Strbuf_append1(&buf, attributes | '~');
418		    for (q = olduser; *q; q++)
419			Strbuf_append1(&buf, attributes | *q);
420		}
421
422			/* RWM - tell you how many dirs we've ignored */
423			/*       and add '/' at front of this         */
424		if (updirs > 0 && pdirs) {
425		    if (adrof(STRellipsis)) {
426			Strbuf_append1(&buf, attributes | '.');
427			Strbuf_append1(&buf, attributes | '.');
428			Strbuf_append1(&buf, attributes | '.');
429		    } else {
430			Strbuf_append1(&buf, attributes | '/');
431			Strbuf_append1(&buf, attributes | '<');
432			if (updirs > 9) {
433			    Strbuf_append1(&buf, attributes | '9');
434			    Strbuf_append1(&buf, attributes | '+');
435			} else
436			    Strbuf_append1(&buf, attributes | ('0' + updirs));
437			Strbuf_append1(&buf, attributes | '>');
438		    }
439		}
440
441		while (*z)
442		    Strbuf_append1(&buf, attributes | *z++);
443		break;
444			/* lukem: end of new directory prompt code */
445
446	    case 'n':
447#ifndef HAVENOUTMP
448		if (what == FMT_WHO) {
449		    cz = who_info(info, 'n');
450		    tprintf_append_mbs(&buf, cz, attributes);
451		    xfree(cz);
452		}
453		else
454#endif /* HAVENOUTMP */
455		{
456		    if ((z = varval(STRuser)) != STRNULL)
457			while (*z)
458			    Strbuf_append1(&buf, attributes | *z++);
459		}
460		break;
461	    case 'l':
462#ifndef HAVENOUTMP
463		if (what == FMT_WHO) {
464		    cz = who_info(info, 'l');
465		    tprintf_append_mbs(&buf, cz, attributes);
466		    xfree(cz);
467		}
468		else
469#endif /* HAVENOUTMP */
470		{
471		    if ((z = varval(STRtty)) != STRNULL)
472			while (*z)
473			    Strbuf_append1(&buf, attributes | *z++);
474		}
475		break;
476	    case 'd':
477		tprintf_append_mbs(&buf, day_list[t->tm_wday], attributes);
478		break;
479	    case 'D':
480		p = Itoa(t->tm_mday, 2, attributes);
481		Strbuf_append(&buf, p);
482		xfree(p);
483		break;
484	    case 'w':
485		tprintf_append_mbs(&buf, month_list[t->tm_mon], attributes);
486		break;
487	    case 'W':
488		p = Itoa(t->tm_mon + 1, 2, attributes);
489		Strbuf_append(&buf, p);
490		xfree(p);
491		break;
492	    case 'y':
493		p = Itoa(t->tm_year % 100, 2, attributes);
494		Strbuf_append(&buf, p);
495		xfree(p);
496		break;
497	    case 'Y':
498		p = Itoa(t->tm_year + 1900, 4, attributes);
499		Strbuf_append(&buf, p);
500		xfree(p);
501		break;
502	    case 'S':		/* start standout */
503		attributes |= STANDOUT;
504		break;
505	    case 'B':		/* start bold */
506		attributes |= BOLD;
507		break;
508	    case 'U':		/* start underline */
509		attributes |= UNDER;
510		break;
511	    case 's':		/* end standout */
512		attributes &= ~STANDOUT;
513		break;
514	    case 'b':		/* end bold */
515		attributes &= ~BOLD;
516		break;
517	    case 'u':		/* end underline */
518		attributes &= ~UNDER;
519		break;
520	    case 'L':
521		ClearToBottom();
522		break;
523
524	    case 'j':
525		{
526		    int njobs = -1;
527		    struct process *pp;
528
529		    for (pp = proclist.p_next; pp; pp = pp->p_next)
530			njobs++;
531		    p = Itoa(njobs, 1, attributes);
532		    Strbuf_append(&buf, p);
533		    xfree(p);
534		    break;
535		}
536	    case '?':
537		if ((z = varval(STRstatus)) != STRNULL)
538		    while (*z)
539			Strbuf_append1(&buf, attributes | *z++);
540		break;
541	    case '$':
542		expdollar(&buf, &cp, attributes);
543		/* cp should point the last char of current % sequence */
544		cp--;
545		break;
546	    case '%':
547		Strbuf_append1(&buf, attributes | '%');
548		break;
549	    case '{':		/* literal characters start */
550#if LITERAL == 0
551		/*
552		 * No literal capability, so skip all chars in the literal
553		 * string
554		 */
555		while (*cp != '\0' && (cp[-1] != '%' || *cp != '}'))
556		    cp++;
557#endif				/* LITERAL == 0 */
558		attributes |= LITERAL;
559		break;
560	    case '}':		/* literal characters end */
561		attributes &= ~LITERAL;
562		break;
563	    default:
564#ifndef HAVENOUTMP
565		if (*cp == 'a' && what == FMT_WHO) {
566		    cz = who_info(info, 'a');
567		    tprintf_append_mbs(&buf, cz, attributes);
568		    xfree(cz);
569		}
570		else
571#endif /* HAVENOUTMP */
572		{
573		    Strbuf_append1(&buf, attributes | '%');
574		    Strbuf_append1(&buf, attributes | *cp);
575		}
576		break;
577	    }
578	}
579	else if (*cp == '\\' || *cp == '^')
580	    Strbuf_append1(&buf, attributes | parseescape(&cp));
581	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
582	    if (what == FMT_HISTORY)
583		cz = fmthist('h', info);
584	    else
585		cz = xasprintf("%d", eventno + 1);
586	    tprintf_append_mbs(&buf, cz, attributes);
587	    xfree(cz);
588	}
589	else
590	    Strbuf_append1(&buf, attributes | *cp); /* normal character */
591    }
592    cleanup_ignore(&buf);
593    cleanup_until(&buf);
594    return Strbuf_finish(&buf);
595}
596
597int
598expdollar(struct Strbuf *buf, const Char **srcp, Char attr)
599{
600    struct varent *vp;
601    const Char *src = *srcp;
602    Char *var, *val;
603    size_t i;
604    int curly = 0;
605
606    /* found a variable, expand it */
607    var = xmalloc((Strlen(src) + 1) * sizeof (*var));
608    for (i = 0; ; i++) {
609	var[i] = *++src & TRIM;
610	if (i == 0 && var[i] == '{') {
611	    curly = 1;
612	    var[i] = *++src & TRIM;
613	}
614	if (!alnum(var[i]) && var[i] != '_') {
615
616	    var[i] = '\0';
617	    break;
618	}
619    }
620    if (curly && (*src & TRIM) == '}')
621	src++;
622
623    vp = adrof(var);
624    if (vp && vp->vec) {
625	for (i = 0; vp->vec[i] != NULL; i++) {
626	    for (val = vp->vec[i]; *val; val++)
627		if (*val != '\n' && *val != '\r')
628		    Strbuf_append1(buf, *val | attr);
629	    if (vp->vec[i+1])
630		Strbuf_append1(buf, ' ' | attr);
631	}
632    }
633    else {
634	val = (!vp) ? tgetenv(var) : NULL;
635	if (val) {
636	    for (; *val; val++)
637		if (*val != '\n' && *val != '\r')
638		    Strbuf_append1(buf, *val | attr);
639	} else {
640	    *srcp = src;
641	    xfree(var);
642	    return 0;
643	}
644    }
645
646    *srcp = src;
647    xfree(var);
648    return 1;
649}
650