xref: /freebsd/contrib/tcsh/tc.prompt.c (revision 59c8e88e72633afbc47a4ace0d2170d00d51f7dc)
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 
47 static const char   *month_list[12];
48 static const char   *day_list[7];
49 
50 void
51 dateinit(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 
106 void
107 printprompt(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 
166 static void
167 tprintf_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 
177 Char *
178 tprintf(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 
616 int
617 expdollar(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