1/*
2 * tc.prompt.c: Prompt printing stuff
3 */
4/*-
5 * Copyright (c) 1980, 1991 The Regents of the University of California.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32#include "sh.h"
33#include "ed.h"
34#include "tw.h"
35
36/*
37 * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
38 * PWP 4/27/87 -- rearange for tcsh.
39 * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
40 *                 instead of if/elseif
41 * Luke Mewburn, <lukem@cs.rmit.edu.au>
42 *	6-Sep-91	changed date format
43 *	16-Feb-94	rewrote directory prompt code, added $ellipsis
44 *	29-Dec-96	added rprompt support
45 */
46
47static const char   *month_list[12];
48static const char   *day_list[7];
49
50void
51dateinit(void)
52{
53#ifdef notyet
54  int i;
55
56  setlocale(LC_TIME, "");
57
58  for (i = 0; i < 12; i++)
59      xfree((ptr_t) month_list[i]);
60  month_list[0] = strsave(_time_info->abbrev_month[0]);
61  month_list[1] = strsave(_time_info->abbrev_month[1]);
62  month_list[2] = strsave(_time_info->abbrev_month[2]);
63  month_list[3] = strsave(_time_info->abbrev_month[3]);
64  month_list[4] = strsave(_time_info->abbrev_month[4]);
65  month_list[5] = strsave(_time_info->abbrev_month[5]);
66  month_list[6] = strsave(_time_info->abbrev_month[6]);
67  month_list[7] = strsave(_time_info->abbrev_month[7]);
68  month_list[8] = strsave(_time_info->abbrev_month[8]);
69  month_list[9] = strsave(_time_info->abbrev_month[9]);
70  month_list[10] = strsave(_time_info->abbrev_month[10]);
71  month_list[11] = strsave(_time_info->abbrev_month[11]);
72
73  for (i = 0; i < 7; i++)
74      xfree((ptr_t) day_list[i]);
75  day_list[0] = strsave(_time_info->abbrev_wkday[0]);
76  day_list[1] = strsave(_time_info->abbrev_wkday[1]);
77  day_list[2] = strsave(_time_info->abbrev_wkday[2]);
78  day_list[3] = strsave(_time_info->abbrev_wkday[3]);
79  day_list[4] = strsave(_time_info->abbrev_wkday[4]);
80  day_list[5] = strsave(_time_info->abbrev_wkday[5]);
81  day_list[6] = strsave(_time_info->abbrev_wkday[6]);
82#else
83  month_list[0] = "Jan";
84  month_list[1] = "Feb";
85  month_list[2] = "Mar";
86  month_list[3] = "Apr";
87  month_list[4] = "May";
88  month_list[5] = "Jun";
89  month_list[6] = "Jul";
90  month_list[7] = "Aug";
91  month_list[8] = "Sep";
92  month_list[9] = "Oct";
93  month_list[10] = "Nov";
94  month_list[11] = "Dec";
95
96  day_list[0] = "Sun";
97  day_list[1] = "Mon";
98  day_list[2] = "Tue";
99  day_list[3] = "Wed";
100  day_list[4] = "Thu";
101  day_list[5] = "Fri";
102  day_list[6] = "Sat";
103#endif
104}
105
106void
107printprompt(int promptno, const char *str)
108{
109    static  const Char *ocp = NULL;
110    static  const char *ostr = NULL;
111    time_t  lclock = time(NULL);
112    const Char *cp;
113
114    switch (promptno) {
115    default:
116    case 0:
117	cp = varval(STRprompt);
118	break;
119    case 1:
120	cp = varval(STRprompt2);
121	break;
122    case 2:
123	cp = varval(STRprompt3);
124	break;
125    case 3:
126	if (ocp != NULL) {
127	    cp = ocp;
128	    str = ostr;
129	}
130	else
131	    cp = varval(STRprompt);
132	break;
133    }
134
135    if (promptno < 2) {
136	ocp = cp;
137	ostr = str;
138    }
139
140    xfree(Prompt);
141    Prompt = NULL;
142    Prompt = tprintf(FMT_PROMPT, cp, str, lclock, NULL);
143    if (!editing) {
144	for (cp = Prompt; *cp ; )
145	    (void) putwraw(*cp++);
146	SetAttributes(0);
147	flush();
148    }
149
150    xfree(RPrompt);
151    RPrompt = NULL;
152    if (promptno == 0) {	/* determine rprompt if using main prompt */
153	cp = varval(STRrprompt);
154	RPrompt = tprintf(FMT_PROMPT, cp, NULL, lclock, NULL);
155				/* if not editing, put rprompt after prompt */
156	if (!editing && RPrompt[0] != '\0') {
157	    for (cp = RPrompt; *cp ; )
158		(void) putwraw(*cp++);
159	    SetAttributes(0);
160	    putraw(' ');
161	    flush();
162	}
163    }
164}
165
166static void
167tprintf_append_mbs(struct Strbuf *buf, const char *mbs, Char attributes)
168{
169    while (*mbs != 0) {
170	Char wc;
171
172	mbs += one_mbtowc(&wc, mbs, MB_LEN_MAX);
173	Strbuf_append1(buf, wc | attributes);
174    }
175}
176
177Char *
178tprintf(int what, const Char *fmt, const char *str, time_t tim, ptr_t info)
179{
180    struct Strbuf buf = Strbuf_INIT;
181    Char   *z, *q;
182    Char    attributes = 0;
183    static int print_prompt_did_ding = 0;
184    char *cz;
185
186    Char *p;
187    const Char *cp = fmt;
188    Char Scp;
189    struct tm *t = localtime(&tim);
190
191			/* prompt stuff */
192    static Char *olduser = NULL;
193    int updirs;
194    size_t pdirs;
195
196    cleanup_push(&buf, Strbuf_cleanup);
197    for (; *cp; cp++) {
198	if ((*cp == '%') && ! (cp[1] == '\0')) {
199	    cp++;
200	    switch (*cp) {
201	    case 'R':
202		if (what == FMT_HISTORY) {
203		    cz = fmthist('R', info);
204		    tprintf_append_mbs(&buf, cz, attributes);
205		    xfree(cz);
206		} else {
207		    if (str != NULL)
208			tprintf_append_mbs(&buf, str, attributes);
209		}
210		break;
211	    case '#':
212#ifdef __CYGWIN__
213		/* Check for being member of the Administrators group */
214		{
215			gid_t grps[NGROUPS_MAX];
216			int grp, gcnt;
217
218			gcnt = getgroups(NGROUPS_MAX, grps);
219# define DOMAIN_GROUP_RID_ADMINS 544
220			for (grp = 0; grp < gcnt; ++grp)
221				if (grps[grp] == DOMAIN_GROUP_RID_ADMINS)
222					break;
223			Scp = (grp < gcnt) ? PRCHROOT : PRCH;
224		}
225#else
226		Scp = (uid == 0 || euid == 0) ? PRCHROOT : PRCH;
227#endif
228		if (Scp != '\0')
229		    Strbuf_append1(&buf, attributes | Scp);
230		break;
231	    case '!':
232	    case 'h':
233		switch (what) {
234		case FMT_HISTORY:
235		    cz = fmthist('h', info);
236		    break;
237		case FMT_SCHED:
238		    cz = xasprintf("%d", *(int *)info);
239		    break;
240		default:
241		    cz = xasprintf("%d", eventno + 1);
242		    break;
243		}
244		tprintf_append_mbs(&buf, cz, attributes);
245		xfree(cz);
246		break;
247	    case 'T':		/* 24 hour format	 */
248	    case '@':
249	    case 't':		/* 12 hour am/pm format */
250	    case 'p':		/* With seconds	*/
251	    case 'P':
252		{
253		    char    ampm = 'a';
254		    int     hr = t->tm_hour;
255
256		    /* addition by Hans J. Albertsson */
257		    /* and another adapted from Justin Bur */
258		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
259			if (hr >= 12) {
260			    if (hr > 12)
261				hr -= 12;
262			    ampm = 'p';
263			}
264			else if (hr == 0)
265			    hr = 12;
266		    }		/* else do a 24 hour clock */
267
268		    /* "DING!" stuff by Hans also */
269		    if (t->tm_min || print_prompt_did_ding ||
270			what != FMT_PROMPT || adrof(STRnoding)) {
271			if (t->tm_min)
272			    print_prompt_did_ding = 0;
273			/*
274			 * Pad hour to 2 characters if padhour is set,
275			 * by ADAM David Alan Martin
276			 */
277			p = Itoa(hr, adrof(STRpadhour) ? 2 : 0, attributes);
278			Strbuf_append(&buf, p);
279			xfree(p);
280			Strbuf_append1(&buf, attributes | ':');
281			p = Itoa(t->tm_min, 2, attributes);
282			Strbuf_append(&buf, p);
283			xfree(p);
284			if (*cp == 'p' || *cp == 'P') {
285			    Strbuf_append1(&buf, attributes | ':');
286			    p = Itoa(t->tm_sec, 2, attributes);
287			    Strbuf_append(&buf, p);
288			    xfree(p);
289			}
290			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
291			    Strbuf_append1(&buf, attributes | ampm);
292			    Strbuf_append1(&buf, attributes | 'm');
293			}
294		    }
295		    else {	/* we need to ding */
296			size_t i;
297
298			for (i = 0; STRDING[i] != 0; i++)
299			    Strbuf_append1(&buf, attributes | STRDING[i]);
300			print_prompt_did_ding = 1;
301		    }
302		}
303		break;
304
305	    case 'M':
306#ifndef HAVENOUTMP
307		if (what == FMT_WHO)
308		    cz = who_info(info, 'M');
309		else
310#endif /* HAVENOUTMP */
311		    cz = getenv("HOST");
312		/*
313		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
314		 * derefrence that NULL (if HOST is not set)...
315		 */
316		if (cz != NULL)
317		    tprintf_append_mbs(&buf, cz, attributes);
318		if (what == FMT_WHO)
319		    xfree(cz);
320		break;
321
322	    case 'm': {
323		char *scz = NULL;
324#ifndef HAVENOUTMP
325		if (what == FMT_WHO)
326		    scz = cz = who_info(info, 'm');
327		else
328#endif /* HAVENOUTMP */
329		    cz = getenv("HOST");
330
331		if (cz != NULL)
332		    while (*cz != 0 && (what == FMT_WHO || *cz != '.')) {
333			Char wc;
334
335			cz += one_mbtowc(&wc, cz, MB_LEN_MAX);
336			Strbuf_append1(&buf, wc | attributes);
337		    }
338		if (scz)
339		    xfree(scz);
340		break;
341	    }
342
343			/* lukem: new directory prompt code */
344	    case '~':
345	    case '/':
346	    case '.':
347	    case 'c':
348	    case 'C':
349		Scp = *cp;
350		if (Scp == 'c')		/* store format type (c == .) */
351		    Scp = '.';
352		if ((z = varval(STRcwd)) == STRNULL)
353		    break;		/* no cwd, so don't do anything */
354
355			/* show ~ whenever possible - a la dirs */
356		if (Scp == '~' || Scp == '.' ) {
357		    static Char *olddir = NULL;
358
359		    if (tlength == 0 || olddir != z) {
360			olddir = z;		/* have we changed dir? */
361			olduser = getusername(&olddir);
362		    }
363		    if (olduser)
364			z = olddir;
365		}
366		updirs = pdirs = 0;
367
368			/* option to determine fixed # of dirs from path */
369		if (Scp == '.' || Scp == 'C') {
370		    int skip;
371#ifdef WINNT_NATIVE
372		    Char *oldz = z;
373		    if (z[1] == ':') {
374			Strbuf_append1(&buf, attributes | *z++);
375			Strbuf_append1(&buf, attributes | *z++);
376		    }
377		    if (*z == '/' && z[1] == '/') {
378			Strbuf_append1(&buf, attributes | *z++);
379			Strbuf_append1(&buf, attributes | *z++);
380			do {
381			    Strbuf_append1(&buf, attributes | *z++);
382			} while(*z != '/');
383		    }
384#endif /* WINNT_NATIVE */
385		    q = z;
386		    while (*z)				/* calc # of /'s */
387			if (*z++ == '/')
388			    updirs++;
389
390#ifdef WINNT_NATIVE
391		    /*
392		     * for format type c, prompt will be following...
393		     * c:/path                => c:/path
394		     * c:/path/to             => c:to
395		     * //machine/share        => //machine/share
396		     * //machine/share/folder => //machine:folder
397		     */
398		    if (oldz[0] == '/' && oldz[1] == '/' && updirs > 1)
399			Strbuf_append1(&buf, attributes | ':');
400#endif /* WINNT_NATIVE */
401		    if ((Scp == 'C' && *q != '/'))
402			updirs++;
403
404		    if (cp[1] == '0') {			/* print <x> or ...  */
405			pdirs = 1;
406			cp++;
407		    }
408		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
409			skip = cp[1] - '0';
410			cp++;
411		    }
412		    else
413			skip = 1;
414
415		    updirs -= skip;
416		    while (skip-- > 0) {
417			while ((z > q) && (*z != '/'))
418			    z--;			/* back up */
419			if (skip && z > q)
420			    z--;
421		    }
422		    if (*z == '/' && z != q)
423			z++;
424		} /* . || C */
425
426							/* print ~[user] */
427		if ((olduser) && ((Scp == '~') ||
428		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
429		    Strbuf_append1(&buf, attributes | '~');
430		    for (q = olduser; *q; q++)
431			Strbuf_append1(&buf, attributes | *q);
432		}
433
434			/* RWM - tell you how many dirs we've ignored */
435			/*       and add '/' at front of this         */
436		if (updirs > 0 && pdirs) {
437		    if (adrof(STRellipsis)) {
438			Strbuf_append1(&buf, attributes | '.');
439			Strbuf_append1(&buf, attributes | '.');
440			Strbuf_append1(&buf, attributes | '.');
441		    } else {
442			Strbuf_append1(&buf, attributes | '/');
443			Strbuf_append1(&buf, attributes | '<');
444			if (updirs > 9) {
445			    Strbuf_append1(&buf, attributes | '9');
446			    Strbuf_append1(&buf, attributes | '+');
447			} else
448			    Strbuf_append1(&buf, attributes | ('0' + updirs));
449			Strbuf_append1(&buf, attributes | '>');
450		    }
451		}
452
453		while (*z)
454		    Strbuf_append1(&buf, attributes | *z++);
455		break;
456			/* lukem: end of new directory prompt code */
457
458	    case 'n':
459#ifndef HAVENOUTMP
460		if (what == FMT_WHO) {
461		    cz = who_info(info, 'n');
462		    tprintf_append_mbs(&buf, cz, attributes);
463		    xfree(cz);
464		}
465		else
466#endif /* HAVENOUTMP */
467		{
468		    if ((z = varval(STRuser)) != STRNULL)
469			while (*z)
470			    Strbuf_append1(&buf, attributes | *z++);
471		}
472		break;
473	    case 'N':
474		if ((z = varval(STReuser)) != STRNULL)
475		    while (*z)
476			Strbuf_append1(&buf, attributes | *z++);
477		break;
478	    case 'l':
479#ifndef HAVENOUTMP
480		if (what == FMT_WHO) {
481		    cz = who_info(info, 'l');
482		    tprintf_append_mbs(&buf, cz, attributes);
483		    xfree(cz);
484		}
485		else
486#endif /* HAVENOUTMP */
487		{
488		    if ((z = varval(STRtty)) != STRNULL)
489			while (*z)
490			    Strbuf_append1(&buf, attributes | *z++);
491		}
492		break;
493	    case 'd':
494		tprintf_append_mbs(&buf, day_list[t->tm_wday], attributes);
495		break;
496	    case 'D':
497		p = Itoa(t->tm_mday, 2, attributes);
498		Strbuf_append(&buf, p);
499		xfree(p);
500		break;
501	    case 'w':
502		tprintf_append_mbs(&buf, month_list[t->tm_mon], attributes);
503		break;
504	    case 'W':
505		p = Itoa(t->tm_mon + 1, 2, attributes);
506		Strbuf_append(&buf, p);
507		xfree(p);
508		break;
509	    case 'y':
510		p = Itoa(t->tm_year % 100, 2, attributes);
511		Strbuf_append(&buf, p);
512		xfree(p);
513		break;
514	    case 'Y':
515		p = Itoa(t->tm_year + 1900, 4, attributes);
516		Strbuf_append(&buf, p);
517		xfree(p);
518		break;
519	    case 'S':		/* start standout */
520		attributes |= STANDOUT;
521		break;
522	    case 'B':		/* start bold */
523		attributes |= BOLD;
524		break;
525	    case 'U':		/* start underline */
526		attributes |= UNDER;
527		break;
528	    case 's':		/* end standout */
529		attributes &= ~STANDOUT;
530		break;
531	    case 'b':		/* end bold */
532		attributes &= ~BOLD;
533		break;
534	    case 'u':		/* end underline */
535		attributes &= ~UNDER;
536		break;
537	    case 'L':
538		ClearToBottom();
539		break;
540
541	    case 'j':
542		{
543		    int njobs = -1;
544		    struct process *pp;
545
546		    for (pp = proclist.p_next; pp; pp = pp->p_next)
547			njobs++;
548		    if (njobs == -1)
549			njobs++;
550		    p = Itoa(njobs, 1, attributes);
551		    Strbuf_append(&buf, p);
552		    xfree(p);
553		    break;
554		}
555	    case '?':
556		if ((z = varval(STRstatus)) != STRNULL)
557		    while (*z)
558			Strbuf_append1(&buf, attributes | *z++);
559		break;
560	    case '$':
561		expdollar(&buf, &cp, attributes);
562		/* cp should point the last char of current % sequence */
563		cp--;
564		break;
565	    case '%':
566		Strbuf_append1(&buf, attributes | '%');
567		break;
568	    case '{':		/* literal characters start */
569#if LITERAL == 0
570		/*
571		 * No literal capability, so skip all chars in the literal
572		 * string
573		 */
574		while (*cp != '\0' && (cp[-1] != '%' || *cp != '}'))
575		    cp++;
576#endif				/* LITERAL == 0 */
577		attributes |= LITERAL;
578		break;
579	    case '}':		/* literal characters end */
580		attributes &= ~LITERAL;
581		break;
582	    default:
583#ifndef HAVENOUTMP
584		if (*cp == 'a' && what == FMT_WHO) {
585		    cz = who_info(info, 'a');
586		    tprintf_append_mbs(&buf, cz, attributes);
587		    xfree(cz);
588		}
589		else
590#endif /* HAVENOUTMP */
591		{
592		    Strbuf_append1(&buf, attributes | '%');
593		    Strbuf_append1(&buf, attributes | *cp);
594		}
595		break;
596	    }
597	}
598	else if (*cp == '\\' || *cp == '^')
599	    Strbuf_append1(&buf, attributes | parseescape(&cp));
600	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
601	    if (what == FMT_HISTORY)
602		cz = fmthist('h', info);
603	    else
604		cz = xasprintf("%d", eventno + 1);
605	    tprintf_append_mbs(&buf, cz, attributes);
606	    xfree(cz);
607	}
608	else
609	    Strbuf_append1(&buf, attributes | *cp); /* normal character */
610    }
611    cleanup_ignore(&buf);
612    cleanup_until(&buf);
613    return Strbuf_finish(&buf);
614}
615
616int
617expdollar(struct Strbuf *buf, const Char **srcp, Char attr)
618{
619    struct varent *vp;
620    const Char *src = *srcp;
621    Char *var, *val;
622    size_t i;
623    int curly = 0;
624
625    /* found a variable, expand it */
626    var = xmalloc((Strlen(src) + 1) * sizeof (*var));
627    for (i = 0; ; i++) {
628	var[i] = *++src & TRIM;
629	if (i == 0 && var[i] == '{') {
630	    curly = 1;
631	    var[i] = *++src & TRIM;
632	}
633	if (!alnum(var[i]) && var[i] != '_') {
634
635	    var[i] = '\0';
636	    break;
637	}
638    }
639    if (curly && (*src & TRIM) == '}')
640	src++;
641
642    vp = adrof(var);
643    if (vp && vp->vec) {
644	for (i = 0; vp->vec[i] != NULL; i++) {
645	    for (val = vp->vec[i]; *val; val++)
646		if (*val != '\n' && *val != '\r')
647		    Strbuf_append1(buf, *val | attr);
648	    if (vp->vec[i+1])
649		Strbuf_append1(buf, ' ' | attr);
650	}
651    }
652    else {
653	val = (!vp) ? tgetenv(var) : NULL;
654	if (val) {
655	    for (; *val; val++)
656		if (*val != '\n' && *val != '\r')
657		    Strbuf_append1(buf, *val | attr);
658	} else {
659	    *srcp = src;
660	    xfree(var);
661	    return 0;
662	}
663    }
664
665    *srcp = src;
666    xfree(var);
667    return 1;
668}
669