tc.prompt.c revision 167465
1/* $Header: /p/tcsh/cvsroot/tcsh/tc.prompt.c,v 3.67 2006/11/17 16:26:58 christos Exp $ */
2/*
3 * tc.prompt.c: Prompt printing stuff
4 */
5/*-
6 * Copyright (c) 1980, 1991 The Regents of the University of California.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33#include "sh.h"
34
35RCSID("$tcsh: tc.prompt.c,v 3.67 2006/11/17 16:26:58 christos Exp $")
36
37#include "ed.h"
38#include "tw.h"
39
40/*
41 * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
42 * PWP 4/27/87 -- rearange for tcsh.
43 * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
44 *                 instead of if/elseif
45 * Luke Mewburn, <lukem@cs.rmit.edu.au>
46 *	6-Sep-91	changed date format
47 *	16-Feb-94	rewrote directory prompt code, added $ellipsis
48 *	29-Dec-96	added rprompt support
49 */
50
51static const char   *month_list[12];
52static const char   *day_list[7];
53
54void
55dateinit(void)
56{
57#ifdef notyet
58  int i;
59
60  setlocale(LC_TIME, "");
61
62  for (i = 0; i < 12; i++)
63      xfree((ptr_t) month_list[i]);
64  month_list[0] = strsave(_time_info->abbrev_month[0]);
65  month_list[1] = strsave(_time_info->abbrev_month[1]);
66  month_list[2] = strsave(_time_info->abbrev_month[2]);
67  month_list[3] = strsave(_time_info->abbrev_month[3]);
68  month_list[4] = strsave(_time_info->abbrev_month[4]);
69  month_list[5] = strsave(_time_info->abbrev_month[5]);
70  month_list[6] = strsave(_time_info->abbrev_month[6]);
71  month_list[7] = strsave(_time_info->abbrev_month[7]);
72  month_list[8] = strsave(_time_info->abbrev_month[8]);
73  month_list[9] = strsave(_time_info->abbrev_month[9]);
74  month_list[10] = strsave(_time_info->abbrev_month[10]);
75  month_list[11] = strsave(_time_info->abbrev_month[11]);
76
77  for (i = 0; i < 7; i++)
78      xfree((ptr_t) day_list[i]);
79  day_list[0] = strsave(_time_info->abbrev_wkday[0]);
80  day_list[1] = strsave(_time_info->abbrev_wkday[1]);
81  day_list[2] = strsave(_time_info->abbrev_wkday[2]);
82  day_list[3] = strsave(_time_info->abbrev_wkday[3]);
83  day_list[4] = strsave(_time_info->abbrev_wkday[4]);
84  day_list[5] = strsave(_time_info->abbrev_wkday[5]);
85  day_list[6] = strsave(_time_info->abbrev_wkday[6]);
86#else
87  month_list[0] = "Jan";
88  month_list[1] = "Feb";
89  month_list[2] = "Mar";
90  month_list[3] = "Apr";
91  month_list[4] = "May";
92  month_list[5] = "Jun";
93  month_list[6] = "Jul";
94  month_list[7] = "Aug";
95  month_list[8] = "Sep";
96  month_list[9] = "Oct";
97  month_list[10] = "Nov";
98  month_list[11] = "Dec";
99
100  day_list[0] = "Sun";
101  day_list[1] = "Mon";
102  day_list[2] = "Tue";
103  day_list[3] = "Wed";
104  day_list[4] = "Thu";
105  day_list[5] = "Fri";
106  day_list[6] = "Sat";
107#endif
108}
109
110void
111printprompt(int promptno, const char *str)
112{
113    static  const Char *ocp = NULL;
114    static  const char *ostr = NULL;
115    time_t  lclock = time(NULL);
116    const Char *cp;
117
118    switch (promptno) {
119    default:
120    case 0:
121	cp = varval(STRprompt);
122	break;
123    case 1:
124	cp = varval(STRprompt2);
125	break;
126    case 2:
127	cp = varval(STRprompt3);
128	break;
129    case 3:
130	if (ocp != NULL) {
131	    cp = ocp;
132	    str = ostr;
133	}
134	else
135	    cp = varval(STRprompt);
136	break;
137    }
138
139    if (promptno < 2) {
140	ocp = cp;
141	ostr = str;
142    }
143
144    xfree(Prompt);
145    Prompt = NULL;
146    Prompt = tprintf(FMT_PROMPT, cp, str, lclock, NULL);
147    if (!editing) {
148	for (cp = Prompt; *cp ; )
149	    (void) putwraw(*cp++);
150	SetAttributes(0);
151	flush();
152    }
153
154    xfree(RPrompt);
155    RPrompt = NULL;
156    if (promptno == 0) {	/* determine rprompt if using main prompt */
157	cp = varval(STRrprompt);
158	RPrompt = tprintf(FMT_PROMPT, cp, NULL, lclock, NULL);
159				/* if not editing, put rprompt after prompt */
160	if (!editing && RPrompt[0] != '\0') {
161	    for (cp = RPrompt; *cp ; )
162		(void) putwraw(*cp++);
163	    SetAttributes(0);
164	    putraw(' ');
165	    flush();
166	}
167    }
168}
169
170static void
171tprintf_append_mbs(struct Strbuf *buf, const char *mbs, Char attributes)
172{
173    while (*mbs != 0) {
174	Char wc;
175
176	mbs += one_mbtowc(&wc, mbs, MB_LEN_MAX);
177	Strbuf_append1(buf, wc | attributes);
178    }
179}
180
181Char *
182tprintf(int what, const Char *fmt, const char *str, time_t tim, ptr_t info)
183{
184    struct Strbuf buf = Strbuf_INIT;
185    Char   *z, *q;
186    Char    attributes = 0;
187    static int print_prompt_did_ding = 0;
188    char *cz;
189
190    Char *p;
191    const Char *cp = fmt;
192    Char Scp;
193    struct tm *t = localtime(&tim);
194
195			/* prompt stuff */
196    static Char *olduser = NULL;
197    int updirs;
198    size_t pdirs;
199
200    cleanup_push(&buf, Strbuf_cleanup);
201    for (; *cp; cp++) {
202	if ((*cp == '%') && ! (cp[1] == '\0')) {
203	    cp++;
204	    switch (*cp) {
205	    case 'R':
206		if (what == FMT_HISTORY) {
207		    cz = fmthist('R', info);
208		    tprintf_append_mbs(&buf, cz, attributes);
209		    xfree(cz);
210		} else {
211		    if (str != NULL)
212			tprintf_append_mbs(&buf, str, attributes);
213		}
214		break;
215	    case '#':
216		Strbuf_append1(&buf,
217			       attributes | ((uid == 0) ? PRCHROOT : PRCH));
218		break;
219	    case '!':
220	    case 'h':
221		switch (what) {
222		case FMT_HISTORY:
223		    cz = fmthist('h', info);
224		    break;
225		case FMT_SCHED:
226		    cz = xasprintf("%d", *(int *)info);
227		    break;
228		default:
229		    cz = xasprintf("%d", eventno + 1);
230		    break;
231		}
232		tprintf_append_mbs(&buf, cz, attributes);
233		xfree(cz);
234		break;
235	    case 'T':		/* 24 hour format	 */
236	    case '@':
237	    case 't':		/* 12 hour am/pm format */
238	    case 'p':		/* With seconds	*/
239	    case 'P':
240		{
241		    char    ampm = 'a';
242		    int     hr = t->tm_hour;
243
244		    /* addition by Hans J. Albertsson */
245		    /* and another adapted from Justin Bur */
246		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
247			if (hr >= 12) {
248			    if (hr > 12)
249				hr -= 12;
250			    ampm = 'p';
251			}
252			else if (hr == 0)
253			    hr = 12;
254		    }		/* else do a 24 hour clock */
255
256		    /* "DING!" stuff by Hans also */
257		    if (t->tm_min || print_prompt_did_ding ||
258			what != FMT_PROMPT || adrof(STRnoding)) {
259			if (t->tm_min)
260			    print_prompt_did_ding = 0;
261			/*
262			 * Pad hour to 2 characters if padhour is set,
263			 * by ADAM David Alan Martin
264			 */
265			p = Itoa(hr, adrof(STRpadhour) ? 2 : 0, attributes);
266			Strbuf_append(&buf, p);
267			xfree(p);
268			Strbuf_append1(&buf, attributes | ':');
269			p = Itoa(t->tm_min, 2, attributes);
270			Strbuf_append(&buf, p);
271			xfree(p);
272			if (*cp == 'p' || *cp == 'P') {
273			    Strbuf_append1(&buf, attributes | ':');
274			    p = Itoa(t->tm_sec, 2, attributes);
275			    Strbuf_append(&buf, p);
276			    xfree(p);
277			}
278			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
279			    Strbuf_append1(&buf, attributes | ampm);
280			    Strbuf_append1(&buf, attributes | 'm');
281			}
282		    }
283		    else {	/* we need to ding */
284			size_t i;
285
286			for (i = 0; STRDING[i] != 0; i++)
287			    Strbuf_append1(&buf, attributes | STRDING[i]);
288			print_prompt_did_ding = 1;
289		    }
290		}
291		break;
292
293	    case 'M':
294#ifndef HAVENOUTMP
295		if (what == FMT_WHO)
296		    cz = who_info(info, 'M');
297		else
298#endif /* HAVENOUTMP */
299		    cz = getenv("HOST");
300		/*
301		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
302		 * derefrence that NULL (if HOST is not set)...
303		 */
304		if (cz != NULL)
305		    tprintf_append_mbs(&buf, cz, attributes);
306		if (what == FMT_WHO)
307		    xfree(cz);
308		break;
309
310	    case 'm': {
311		char *scz = NULL;
312#ifndef HAVENOUTMP
313		if (what == FMT_WHO)
314		    scz = cz = who_info(info, 'm');
315		else
316#endif /* HAVENOUTMP */
317		    cz = getenv("HOST");
318
319		if (cz != NULL)
320		    while (*cz != 0 && (what == FMT_WHO || *cz != '.')) {
321			Char wc;
322
323			cz += one_mbtowc(&wc, cz, MB_LEN_MAX);
324			Strbuf_append1(&buf, wc | attributes);
325		    }
326		if (scz)
327		    xfree(scz);
328		break;
329	    }
330
331			/* lukem: new directory prompt code */
332	    case '~':
333	    case '/':
334	    case '.':
335	    case 'c':
336	    case 'C':
337		Scp = *cp;
338		if (Scp == 'c')		/* store format type (c == .) */
339		    Scp = '.';
340		if ((z = varval(STRcwd)) == STRNULL)
341		    break;		/* no cwd, so don't do anything */
342
343			/* show ~ whenever possible - a la dirs */
344		if (Scp == '~' || Scp == '.' ) {
345		    static Char *olddir = NULL;
346
347		    if (tlength == 0 || olddir != z) {
348			olddir = z;		/* have we changed dir? */
349			olduser = getusername(&olddir);
350		    }
351		    if (olduser)
352			z = olddir;
353		}
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