xref: /freebsd/contrib/tcsh/tc.prompt.c (revision f4b37ed0f8b307b1f3f0f630ca725d68f1dff30d)
1 /* $Header: /p/tcsh/cvsroot/tcsh/tc.prompt.c,v 3.70 2011/10/27 22:41:06 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 
35 RCSID("$tcsh: tc.prompt.c,v 3.70 2011/10/27 22:41:06 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 
51 static const char   *month_list[12];
52 static const char   *day_list[7];
53 
54 void
55 dateinit(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 
110 void
111 printprompt(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 
170 static void
171 tprintf_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 
181 Char *
182 tprintf(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 		Scp = (uid == 0 || euid == 0) ? PRCHROOT : PRCH;
217 		if (Scp != '\0')
218 		    Strbuf_append1(&buf, attributes | Scp);
219 		break;
220 	    case '!':
221 	    case 'h':
222 		switch (what) {
223 		case FMT_HISTORY:
224 		    cz = fmthist('h', info);
225 		    break;
226 		case FMT_SCHED:
227 		    cz = xasprintf("%d", *(int *)info);
228 		    break;
229 		default:
230 		    cz = xasprintf("%d", eventno + 1);
231 		    break;
232 		}
233 		tprintf_append_mbs(&buf, cz, attributes);
234 		xfree(cz);
235 		break;
236 	    case 'T':		/* 24 hour format	 */
237 	    case '@':
238 	    case 't':		/* 12 hour am/pm format */
239 	    case 'p':		/* With seconds	*/
240 	    case 'P':
241 		{
242 		    char    ampm = 'a';
243 		    int     hr = t->tm_hour;
244 
245 		    /* addition by Hans J. Albertsson */
246 		    /* and another adapted from Justin Bur */
247 		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
248 			if (hr >= 12) {
249 			    if (hr > 12)
250 				hr -= 12;
251 			    ampm = 'p';
252 			}
253 			else if (hr == 0)
254 			    hr = 12;
255 		    }		/* else do a 24 hour clock */
256 
257 		    /* "DING!" stuff by Hans also */
258 		    if (t->tm_min || print_prompt_did_ding ||
259 			what != FMT_PROMPT || adrof(STRnoding)) {
260 			if (t->tm_min)
261 			    print_prompt_did_ding = 0;
262 			/*
263 			 * Pad hour to 2 characters if padhour is set,
264 			 * by ADAM David Alan Martin
265 			 */
266 			p = Itoa(hr, adrof(STRpadhour) ? 2 : 0, attributes);
267 			Strbuf_append(&buf, p);
268 			xfree(p);
269 			Strbuf_append1(&buf, attributes | ':');
270 			p = Itoa(t->tm_min, 2, attributes);
271 			Strbuf_append(&buf, p);
272 			xfree(p);
273 			if (*cp == 'p' || *cp == 'P') {
274 			    Strbuf_append1(&buf, attributes | ':');
275 			    p = Itoa(t->tm_sec, 2, attributes);
276 			    Strbuf_append(&buf, p);
277 			    xfree(p);
278 			}
279 			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
280 			    Strbuf_append1(&buf, attributes | ampm);
281 			    Strbuf_append1(&buf, attributes | 'm');
282 			}
283 		    }
284 		    else {	/* we need to ding */
285 			size_t i;
286 
287 			for (i = 0; STRDING[i] != 0; i++)
288 			    Strbuf_append1(&buf, attributes | STRDING[i]);
289 			print_prompt_did_ding = 1;
290 		    }
291 		}
292 		break;
293 
294 	    case 'M':
295 #ifndef HAVENOUTMP
296 		if (what == FMT_WHO)
297 		    cz = who_info(info, 'M');
298 		else
299 #endif /* HAVENOUTMP */
300 		    cz = getenv("HOST");
301 		/*
302 		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
303 		 * derefrence that NULL (if HOST is not set)...
304 		 */
305 		if (cz != NULL)
306 		    tprintf_append_mbs(&buf, cz, attributes);
307 		if (what == FMT_WHO)
308 		    xfree(cz);
309 		break;
310 
311 	    case 'm': {
312 		char *scz = NULL;
313 #ifndef HAVENOUTMP
314 		if (what == FMT_WHO)
315 		    scz = cz = who_info(info, 'm');
316 		else
317 #endif /* HAVENOUTMP */
318 		    cz = getenv("HOST");
319 
320 		if (cz != NULL)
321 		    while (*cz != 0 && (what == FMT_WHO || *cz != '.')) {
322 			Char wc;
323 
324 			cz += one_mbtowc(&wc, cz, MB_LEN_MAX);
325 			Strbuf_append1(&buf, wc | attributes);
326 		    }
327 		if (scz)
328 		    xfree(scz);
329 		break;
330 	    }
331 
332 			/* lukem: new directory prompt code */
333 	    case '~':
334 	    case '/':
335 	    case '.':
336 	    case 'c':
337 	    case 'C':
338 		Scp = *cp;
339 		if (Scp == 'c')		/* store format type (c == .) */
340 		    Scp = '.';
341 		if ((z = varval(STRcwd)) == STRNULL)
342 		    break;		/* no cwd, so don't do anything */
343 
344 			/* show ~ whenever possible - a la dirs */
345 		if (Scp == '~' || Scp == '.' ) {
346 		    static Char *olddir = NULL;
347 
348 		    if (tlength == 0 || olddir != z) {
349 			olddir = z;		/* have we changed dir? */
350 			olduser = getusername(&olddir);
351 		    }
352 		    if (olduser)
353 			z = olddir;
354 		}
355 		updirs = pdirs = 0;
356 
357 			/* option to determine fixed # of dirs from path */
358 		if (Scp == '.' || Scp == 'C') {
359 		    int skip;
360 #ifdef WINNT_NATIVE
361 		    Char *oldz = z;
362 		    if (z[1] == ':') {
363 			Strbuf_append1(&buf, attributes | *z++);
364 			Strbuf_append1(&buf, attributes | *z++);
365 		    }
366 		    if (*z == '/' && z[1] == '/') {
367 			Strbuf_append1(&buf, attributes | *z++);
368 			Strbuf_append1(&buf, attributes | *z++);
369 			do {
370 			    Strbuf_append1(&buf, attributes | *z++);
371 			} while(*z != '/');
372 		    }
373 #endif /* WINNT_NATIVE */
374 		    q = z;
375 		    while (*z)				/* calc # of /'s */
376 			if (*z++ == '/')
377 			    updirs++;
378 
379 #ifdef WINNT_NATIVE
380 		    /*
381 		     * for format type c, prompt will be following...
382 		     * c:/path                => c:/path
383 		     * c:/path/to             => c:to
384 		     * //machine/share        => //machine/share
385 		     * //machine/share/folder => //machine:folder
386 		     */
387 		    if (oldz[0] == '/' && oldz[1] == '/' && updirs > 1)
388 			Strbuf_append1(&buf, attributes | ':');
389 #endif /* WINNT_NATIVE */
390 		    if ((Scp == 'C' && *q != '/'))
391 			updirs++;
392 
393 		    if (cp[1] == '0') {			/* print <x> or ...  */
394 			pdirs = 1;
395 			cp++;
396 		    }
397 		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
398 			skip = cp[1] - '0';
399 			cp++;
400 		    }
401 		    else
402 			skip = 1;
403 
404 		    updirs -= skip;
405 		    while (skip-- > 0) {
406 			while ((z > q) && (*z != '/'))
407 			    z--;			/* back up */
408 			if (skip && z > q)
409 			    z--;
410 		    }
411 		    if (*z == '/' && z != q)
412 			z++;
413 		} /* . || C */
414 
415 							/* print ~[user] */
416 		if ((olduser) && ((Scp == '~') ||
417 		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
418 		    Strbuf_append1(&buf, attributes | '~');
419 		    for (q = olduser; *q; q++)
420 			Strbuf_append1(&buf, attributes | *q);
421 		}
422 
423 			/* RWM - tell you how many dirs we've ignored */
424 			/*       and add '/' at front of this         */
425 		if (updirs > 0 && pdirs) {
426 		    if (adrof(STRellipsis)) {
427 			Strbuf_append1(&buf, attributes | '.');
428 			Strbuf_append1(&buf, attributes | '.');
429 			Strbuf_append1(&buf, attributes | '.');
430 		    } else {
431 			Strbuf_append1(&buf, attributes | '/');
432 			Strbuf_append1(&buf, attributes | '<');
433 			if (updirs > 9) {
434 			    Strbuf_append1(&buf, attributes | '9');
435 			    Strbuf_append1(&buf, attributes | '+');
436 			} else
437 			    Strbuf_append1(&buf, attributes | ('0' + updirs));
438 			Strbuf_append1(&buf, attributes | '>');
439 		    }
440 		}
441 
442 		while (*z)
443 		    Strbuf_append1(&buf, attributes | *z++);
444 		break;
445 			/* lukem: end of new directory prompt code */
446 
447 	    case 'n':
448 #ifndef HAVENOUTMP
449 		if (what == FMT_WHO) {
450 		    cz = who_info(info, 'n');
451 		    tprintf_append_mbs(&buf, cz, attributes);
452 		    xfree(cz);
453 		}
454 		else
455 #endif /* HAVENOUTMP */
456 		{
457 		    if ((z = varval(STRuser)) != STRNULL)
458 			while (*z)
459 			    Strbuf_append1(&buf, attributes | *z++);
460 		}
461 		break;
462 	    case 'N':
463 		if ((z = varval(STReuser)) != STRNULL)
464 		    while (*z)
465 			Strbuf_append1(&buf, attributes | *z++);
466 		break;
467 	    case 'l':
468 #ifndef HAVENOUTMP
469 		if (what == FMT_WHO) {
470 		    cz = who_info(info, 'l');
471 		    tprintf_append_mbs(&buf, cz, attributes);
472 		    xfree(cz);
473 		}
474 		else
475 #endif /* HAVENOUTMP */
476 		{
477 		    if ((z = varval(STRtty)) != STRNULL)
478 			while (*z)
479 			    Strbuf_append1(&buf, attributes | *z++);
480 		}
481 		break;
482 	    case 'd':
483 		tprintf_append_mbs(&buf, day_list[t->tm_wday], attributes);
484 		break;
485 	    case 'D':
486 		p = Itoa(t->tm_mday, 2, attributes);
487 		Strbuf_append(&buf, p);
488 		xfree(p);
489 		break;
490 	    case 'w':
491 		tprintf_append_mbs(&buf, month_list[t->tm_mon], attributes);
492 		break;
493 	    case 'W':
494 		p = Itoa(t->tm_mon + 1, 2, attributes);
495 		Strbuf_append(&buf, p);
496 		xfree(p);
497 		break;
498 	    case 'y':
499 		p = Itoa(t->tm_year % 100, 2, attributes);
500 		Strbuf_append(&buf, p);
501 		xfree(p);
502 		break;
503 	    case 'Y':
504 		p = Itoa(t->tm_year + 1900, 4, attributes);
505 		Strbuf_append(&buf, p);
506 		xfree(p);
507 		break;
508 	    case 'S':		/* start standout */
509 		attributes |= STANDOUT;
510 		break;
511 	    case 'B':		/* start bold */
512 		attributes |= BOLD;
513 		break;
514 	    case 'U':		/* start underline */
515 		attributes |= UNDER;
516 		break;
517 	    case 's':		/* end standout */
518 		attributes &= ~STANDOUT;
519 		break;
520 	    case 'b':		/* end bold */
521 		attributes &= ~BOLD;
522 		break;
523 	    case 'u':		/* end underline */
524 		attributes &= ~UNDER;
525 		break;
526 	    case 'L':
527 		ClearToBottom();
528 		break;
529 
530 	    case 'j':
531 		{
532 		    int njobs = -1;
533 		    struct process *pp;
534 
535 		    for (pp = proclist.p_next; pp; pp = pp->p_next)
536 			njobs++;
537 		    if (njobs == -1)
538 			njobs++;
539 		    p = Itoa(njobs, 1, attributes);
540 		    Strbuf_append(&buf, p);
541 		    xfree(p);
542 		    break;
543 		}
544 	    case '?':
545 		if ((z = varval(STRstatus)) != STRNULL)
546 		    while (*z)
547 			Strbuf_append1(&buf, attributes | *z++);
548 		break;
549 	    case '$':
550 		expdollar(&buf, &cp, attributes);
551 		/* cp should point the last char of current % sequence */
552 		cp--;
553 		break;
554 	    case '%':
555 		Strbuf_append1(&buf, attributes | '%');
556 		break;
557 	    case '{':		/* literal characters start */
558 #if LITERAL == 0
559 		/*
560 		 * No literal capability, so skip all chars in the literal
561 		 * string
562 		 */
563 		while (*cp != '\0' && (cp[-1] != '%' || *cp != '}'))
564 		    cp++;
565 #endif				/* LITERAL == 0 */
566 		attributes |= LITERAL;
567 		break;
568 	    case '}':		/* literal characters end */
569 		attributes &= ~LITERAL;
570 		break;
571 	    default:
572 #ifndef HAVENOUTMP
573 		if (*cp == 'a' && what == FMT_WHO) {
574 		    cz = who_info(info, 'a');
575 		    tprintf_append_mbs(&buf, cz, attributes);
576 		    xfree(cz);
577 		}
578 		else
579 #endif /* HAVENOUTMP */
580 		{
581 		    Strbuf_append1(&buf, attributes | '%');
582 		    Strbuf_append1(&buf, attributes | *cp);
583 		}
584 		break;
585 	    }
586 	}
587 	else if (*cp == '\\' || *cp == '^')
588 	    Strbuf_append1(&buf, attributes | parseescape(&cp));
589 	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
590 	    if (what == FMT_HISTORY)
591 		cz = fmthist('h', info);
592 	    else
593 		cz = xasprintf("%d", eventno + 1);
594 	    tprintf_append_mbs(&buf, cz, attributes);
595 	    xfree(cz);
596 	}
597 	else
598 	    Strbuf_append1(&buf, attributes | *cp); /* normal character */
599     }
600     cleanup_ignore(&buf);
601     cleanup_until(&buf);
602     return Strbuf_finish(&buf);
603 }
604 
605 int
606 expdollar(struct Strbuf *buf, const Char **srcp, Char attr)
607 {
608     struct varent *vp;
609     const Char *src = *srcp;
610     Char *var, *val;
611     size_t i;
612     int curly = 0;
613 
614     /* found a variable, expand it */
615     var = xmalloc((Strlen(src) + 1) * sizeof (*var));
616     for (i = 0; ; i++) {
617 	var[i] = *++src & TRIM;
618 	if (i == 0 && var[i] == '{') {
619 	    curly = 1;
620 	    var[i] = *++src & TRIM;
621 	}
622 	if (!alnum(var[i]) && var[i] != '_') {
623 
624 	    var[i] = '\0';
625 	    break;
626 	}
627     }
628     if (curly && (*src & TRIM) == '}')
629 	src++;
630 
631     vp = adrof(var);
632     if (vp && vp->vec) {
633 	for (i = 0; vp->vec[i] != NULL; i++) {
634 	    for (val = vp->vec[i]; *val; val++)
635 		if (*val != '\n' && *val != '\r')
636 		    Strbuf_append1(buf, *val | attr);
637 	    if (vp->vec[i+1])
638 		Strbuf_append1(buf, ' ' | attr);
639 	}
640     }
641     else {
642 	val = (!vp) ? tgetenv(var) : NULL;
643 	if (val) {
644 	    for (; *val; val++)
645 		if (*val != '\n' && *val != '\r')
646 		    Strbuf_append1(buf, *val | attr);
647 	} else {
648 	    *srcp = src;
649 	    xfree(var);
650 	    return 0;
651 	}
652     }
653 
654     *srcp = src;
655     xfree(var);
656     return 1;
657 }
658