1 /*
2  * prompt.c - construct zsh prompts
3  *
4  * This file is part of zsh, the Z shell.
5  *
6  * Copyright (c) 1992-1997 Paul Falstad
7  * All rights reserved.
8  *
9  * Permission is hereby granted, without written agreement and without
10  * license or royalty fees, to use, copy, modify, and distribute this
11  * software and to distribute modified versions of this software for any
12  * purpose, provided that the above copyright notice and the following
13  * two paragraphs appear in all copies of this software.
14  *
15  * In no event shall Paul Falstad or the Zsh Development Group be liable
16  * to any party for direct, indirect, special, incidental, or consequential
17  * damages arising out of the use of this software and its documentation,
18  * even if Paul Falstad and the Zsh Development Group have been advised of
19  * the possibility of such damage.
20  *
21  * Paul Falstad and the Zsh Development Group specifically disclaim any
22  * warranties, including, but not limited to, the implied warranties of
23  * merchantability and fitness for a particular purpose.  The software
24  * provided hereunder is on an "as is" basis, and Paul Falstad and the
25  * Zsh Development Group have no obligation to provide maintenance,
26  * support, updates, enhancements, or modifications.
27  *
28  */
29 
30 #include "zsh.mdh"
31 #include "prompt.pro"
32 
33 /* text attribute mask */
34 
35 /**/
36 mod_export zattr txtattrmask;
37 
38 /* the command stack for use with %_ in prompts */
39 
40 /**/
41 unsigned char *cmdstack;
42 /**/
43 int cmdsp;
44 
45 /* parser states, for %_ */
46 
47 static char *cmdnames[CS_COUNT] = {
48     "for",      "while",     "repeat",    "select",
49     "until",    "if",        "then",      "else",
50     "elif",     "math",      "cond",      "cmdor",
51     "cmdand",   "pipe",      "errpipe",   "foreach",
52     "case",     "function",  "subsh",     "cursh",
53     "array",    "quote",     "dquote",    "bquote",
54     "cmdsubst", "mathsubst", "elif-then", "heredoc",
55     "heredocd", "brace",     "braceparam", "always",
56 };
57 
58 
59 struct buf_vars;
60 
61 struct buf_vars {
62 /* Previous set of prompt variables on the stack. */
63 
64     struct buf_vars *last;
65 
66 /* The buffer into which an expanded and metafied prompt is being written, *
67  * and its size.                                                           */
68 
69     char *buf;
70     int bufspc;
71 
72 /* bp is the pointer to the current position in the buffer, where the next *
73  * character will be added.                                                */
74 
75     char *bp;
76 
77 /* Position of the start of the current line in the buffer */
78 
79     char *bufline;
80 
81 /* bp1 is an auxiliary pointer into the buffer, which when non-NULL is *
82  * moved whenever the buffer is reallocated.  It is used when data is   *
83  * being temporarily held in the buffer.                                */
84 
85     char *bp1;
86 
87 /* The format string, for %-expansion. */
88 
89     char *fm;
90 
91 /* Non-zero if truncating the current segment of the buffer. */
92 
93     int truncwidth;
94 
95 /* Current level of nesting of %{ / %} sequences. */
96 
97     int dontcount;
98 
99 /* Level of %{ / %} surrounding a truncation segment. */
100 
101     int trunccount;
102 
103 /* Strings to use for %r and %R (for the spelling prompt). */
104 
105     char *rstring, *Rstring;
106 };
107 
108 typedef struct buf_vars *Buf_vars;
109 
110 /* The currently active prompt output variables */
111 static Buf_vars bv;
112 
113 /*
114  * Expand path p; maximum is npath segments where 0 means the whole path.
115  * If tilde is 1, try and find a named directory to use.
116  */
117 
118 static void
promptpath(char * p,int npath,int tilde)119 promptpath(char *p, int npath, int tilde)
120 {
121     char *modp = p;
122     Nameddir nd;
123 
124     if (tilde && ((nd = finddir(p))))
125 	modp = tricat("~", nd->node.nam, p + strlen(nd->dir));
126 
127     if (npath) {
128 	char *sptr;
129 	if (npath > 0) {
130 	    for (sptr = modp + strlen(modp); sptr > modp; sptr--) {
131 		if (*sptr == '/' && !--npath) {
132 		    sptr++;
133 		    break;
134 		}
135 	    }
136 	    if (*sptr == '/' && sptr[1] && sptr != modp)
137 		sptr++;
138 	    stradd(sptr);
139 	} else {
140 	    char cbu;
141 	    for (sptr = modp+1; *sptr; sptr++)
142 		if (*sptr == '/' && !++npath)
143 		    break;
144 	    cbu = *sptr;
145 	    *sptr = 0;
146 	    stradd(modp);
147 	    *sptr = cbu;
148 	}
149     } else
150 	stradd(modp);
151 
152     if (p != modp)
153 	zsfree(modp);
154 }
155 
156 /*
157  * Perform prompt expansion on a string, putting the result in a
158  * permanently-allocated string.  If ns is non-zero, this string
159  * may have embedded Inpar and Outpar, which indicate a toggling
160  * between spacing and non-spacing parts of the prompt, and
161  * Nularg, which (in a non-spacing sequence) indicates a
162  * `glitch' space.
163  *
164  * txtchangep gives an integer controlling the attributes of
165  * the prompt.  This is for use in zle to maintain the attributes
166  * consistently.  Other parts of the shell should not need to use it.
167  */
168 
169 /**/
170 mod_export char *
promptexpand(char * s,int ns,char * rs,char * Rs,zattr * txtchangep)171 promptexpand(char *s, int ns, char *rs, char *Rs, zattr *txtchangep)
172 {
173     struct buf_vars new_vars;
174 
175     if(!s)
176 	return ztrdup("");
177 
178     if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
179         init_term();
180 
181     if (isset(PROMPTSUBST)) {
182 	int olderr = errflag;
183 	int oldval = lastval;
184 
185 	s = dupstring(s);
186 	if (!parsestr(&s))
187 	    singsub(&s);
188 	/*
189 	 * We don't need the special Nularg hack here and we're
190 	 * going to be using Nularg for other things.
191 	 */
192 	if (*s == Nularg && s[1] == '\0')
193 	    *s = '\0';
194 
195 	/*
196 	 * Ignore errors and status change in prompt substitution.
197 	 * However, keep any user interrupt error that occurred.
198 	 */
199 	errflag = olderr | (errflag & ERRFLAG_INT);
200 	lastval = oldval;
201     }
202 
203     memset(&new_vars, 0, sizeof(new_vars));
204     new_vars.last = bv;
205     bv = &new_vars;
206 
207     new_vars.rstring = rs;
208     new_vars.Rstring = Rs;
209     new_vars.fm = s;
210     new_vars.bufspc = 256;
211     new_vars.bp = new_vars.bufline = new_vars.buf = zshcalloc(new_vars.bufspc);
212     new_vars.bp1 = NULL;
213     new_vars.truncwidth = 0;
214 
215     putpromptchar(1, '\0', txtchangep);
216     addbufspc(2);
217     if (new_vars.dontcount)
218 	*new_vars.bp++ = Outpar;
219     *new_vars.bp = '\0';
220     if (!ns) {
221 	/* If zero, Inpar, Outpar and Nularg should be removed. */
222 	for (new_vars.bp = new_vars.buf; *new_vars.bp; ) {
223 	    if (*new_vars.bp == Meta)
224 		new_vars.bp += 2;
225 	    else if (*new_vars.bp == Inpar || *new_vars.bp == Outpar ||
226 		     *new_vars.bp == Nularg)
227 		chuck(new_vars.bp);
228 	    else
229 		new_vars.bp++;
230 	}
231     }
232 
233     bv = new_vars.last;
234 
235     return new_vars.buf;
236 }
237 
238 /* Parse the argument for %F and %K */
239 static zattr
parsecolorchar(zattr arg,int is_fg)240 parsecolorchar(zattr arg, int is_fg)
241 {
242     if (bv->fm[1] == '{') {
243 	char *ep;
244 	bv->fm += 2; /* skip over F{ */
245 	if ((ep = strchr(bv->fm, '}'))) {
246 	    char oc = *ep, *col, *coll;
247 	    int ops = opts[PROMPTSUBST], opb = opts[PROMPTBANG];
248 	    int opp = opts[PROMPTPERCENT];
249 
250 	    opts[PROMPTPERCENT] = 1;
251 	    opts[PROMPTSUBST] = opts[PROMPTBANG] = 0;
252 
253 	    *ep = '\0';
254 	    /* expand the contents of the argument so you can use
255 	     * %v for example */
256 	    coll = col = promptexpand(bv->fm, 0, NULL, NULL, NULL);
257 	    *ep = oc;
258 	    arg = match_colour((const char **)&coll, is_fg, 0);
259 	    free(col);
260 	    bv->fm = ep;
261 
262 	    opts[PROMPTSUBST] = ops;
263 	    opts[PROMPTBANG] = opb;
264 	    opts[PROMPTPERCENT] = opp;
265 	} else {
266 	    arg = match_colour((const char **)&bv->fm, is_fg, 0);
267 	    if (*bv->fm != '}')
268 		bv->fm--;
269 	}
270     } else
271 	arg = match_colour(NULL, 1, arg);
272     return arg;
273 }
274 
275 /* Perform %- and !-expansion as required on a section of the prompt.  The *
276  * section is ended by an instance of endchar.  If doprint is 0, the valid *
277  * % sequences are merely skipped over, and nothing is stored.             */
278 
279 /**/
280 static int
putpromptchar(int doprint,int endchar,zattr * txtchangep)281 putpromptchar(int doprint, int endchar, zattr *txtchangep)
282 {
283     char *ss, *hostnam;
284     int t0, arg, test, sep, j, numjobs, len;
285     zattr atr;
286     struct tm *tm;
287     struct timespec ts;
288     time_t timet;
289     Nameddir nd;
290 
291     for (; *bv->fm && *bv->fm != endchar; bv->fm++) {
292 	arg = 0;
293 	if (*bv->fm == '%' && isset(PROMPTPERCENT)) {
294 	    int minus = 0;
295 	    bv->fm++;
296 	    if (*bv->fm == '-') {
297 		minus = 1;
298 		bv->fm++;
299 	    }
300 	    if (idigit(*bv->fm)) {
301 		arg = zstrtol(bv->fm, &bv->fm, 10);
302 		if (minus)
303 		    arg *= -1;
304 	    } else if (minus)
305 		arg = -1;
306 	    if (*bv->fm == '(') {
307 		int tc, otruncwidth;
308 
309 		if (idigit(*++bv->fm)) {
310 		    arg = zstrtol(bv->fm, &bv->fm, 10);
311 		} else if (arg < 0) {
312 		    /* negative numbers don't make sense here */
313 		    arg *= -1;
314 		}
315 		test = 0;
316 		ss = pwd;
317 		switch (tc = *bv->fm) {
318 		case 'c':
319 		case '.':
320 		case '~':
321 		    if ((nd = finddir(ss))) {
322 			arg--;
323 			ss += strlen(nd->dir);
324 		    } /*FALLTHROUGH*/
325 		case '/':
326 		case 'C':
327 		    /* `/' gives 0, `/any' gives 1, etc. */
328 		    if (*ss && *ss++ == '/' && *ss)
329 			arg--;
330 		    for (; *ss; ss++)
331 			if (*ss == '/')
332 			    arg--;
333 		    if (arg <= 0)
334 			test = 1;
335 		    break;
336 		case 't':
337 		case 'T':
338 		case 'd':
339 		case 'D':
340 		case 'w':
341 		    timet = time(NULL);
342 		    tm = localtime(&timet);
343 		    switch (tc) {
344 		    case 't':
345 			test = (arg == tm->tm_min);
346 			break;
347 		    case 'T':
348 			test = (arg == tm->tm_hour);
349 			break;
350 		    case 'd':
351 			test = (arg == tm->tm_mday);
352 			break;
353 		    case 'D':
354 			test = (arg == tm->tm_mon);
355 			break;
356 		    case 'w':
357 			test = (arg == tm->tm_wday);
358 			break;
359 		    }
360 		    break;
361 		case '?':
362 		    if (lastval == arg)
363 			test = 1;
364 		    break;
365 		case '#':
366 		    if (geteuid() == (uid_t)arg)
367 			test = 1;
368 		    break;
369 		case 'g':
370 		    if (getegid() == (gid_t)arg)
371 			test = 1;
372 		    break;
373 		case 'j':
374 		    for (numjobs = 0, j = 1; j <= maxjob; j++)
375 			if (jobtab[j].stat && jobtab[j].procs &&
376 		    	    !(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
377 		    if (numjobs >= arg)
378 		    	test = 1;
379 		    break;
380 		case 'l':
381 		    *bv->bp = '\0';
382 		    countprompt(bv->bufline, &t0, 0, 0);
383 		    if (minus)
384 			t0 = zterm_columns - t0;
385 		    if (t0 >= arg)
386 			test = 1;
387 		    break;
388 		case 'e':
389 		    {
390 			Funcstack fsptr = funcstack;
391 			test = arg;
392 			while (fsptr && test > 0) {
393 			    test--;
394 			    fsptr = fsptr->prev;
395 			}
396 			test = !test;
397 		    }
398 		    break;
399 		case 'L':
400 		    if (shlvl >= arg)
401 			test = 1;
402 		    break;
403 		case 'S':
404 		    if (time(NULL) - shtimer.tv_sec >= arg)
405 			test = 1;
406 		    break;
407 		case 'v':
408 		    if (arrlen_ge(psvar, arg))
409 			test = 1;
410 		    break;
411 		case 'V':
412 		    if (psvar && *psvar && arrlen_ge(psvar, arg)) {
413 			if (*psvar[(arg ? arg : 1) - 1])
414 			    test = 1;
415 		    }
416 		    break;
417 		case '_':
418 		    test = (cmdsp >= arg);
419 		    break;
420 		case '!':
421 		    test = privasserted();
422 		    break;
423 		default:
424 		    test = -1;
425 		    break;
426 		}
427 		if (!*bv->fm || !(sep = *++bv->fm))
428 		    return 0;
429 		bv->fm++;
430 		/* Don't do the current truncation until we get back */
431 		otruncwidth = bv->truncwidth;
432 		bv->truncwidth = 0;
433 		if (!putpromptchar(test == 1 && doprint, sep,
434 				   txtchangep) || !*++bv->fm ||
435 		    !putpromptchar(test == 0 && doprint, ')',
436 				   txtchangep)) {
437 		    bv->truncwidth = otruncwidth;
438 		    return 0;
439 		}
440 		bv->truncwidth = otruncwidth;
441 		continue;
442 	    }
443 	    if (!doprint)
444 		switch(*bv->fm) {
445 		  case '[':
446 		    while(idigit(*++bv->fm));
447 		    while(*++bv->fm != ']');
448 		    continue;
449 		  case '<':
450 		    while(*++bv->fm != '<');
451 		    continue;
452 		  case '>':
453 		    while(*++bv->fm != '>');
454 		    continue;
455 		  case 'D':
456 		    if(bv->fm[1]=='{')
457 			while(*++bv->fm != '}');
458 		    continue;
459 		  default:
460 		    continue;
461 		}
462 	    switch (*bv->fm) {
463 	    case '~':
464 		promptpath(pwd, arg, 1);
465 		break;
466 	    case 'd':
467 	    case '/':
468 		promptpath(pwd, arg, 0);
469 		break;
470 	    case 'c':
471 	    case '.':
472 		promptpath(pwd, arg ? arg : 1, 1);
473 		break;
474 	    case 'C':
475 		promptpath(pwd, arg ? arg : 1, 0);
476 		break;
477 	    case 'N':
478 		promptpath(scriptname ? scriptname : argzero, arg, 0);
479 		break;
480 	    case 'h':
481 	    case '!':
482 		addbufspc(DIGBUFSIZE);
483 		convbase(bv->bp, curhist, 10);
484 		bv->bp += strlen(bv->bp);
485 		break;
486 	    case 'j':
487 		for (numjobs = 0, j = 1; j <= maxjob; j++)
488 		    if (jobtab[j].stat && jobtab[j].procs &&
489 		    	!(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
490 		addbufspc(DIGBUFSIZE);
491 		sprintf(bv->bp, "%d", numjobs);
492 		bv->bp += strlen(bv->bp);
493 		break;
494 	    case 'M':
495 		queue_signals();
496 		if ((hostnam = getsparam("HOST")))
497 		    stradd(hostnam);
498 		unqueue_signals();
499 		break;
500 	    case 'm':
501 		if (!arg)
502 		    arg++;
503 		queue_signals();
504 		if (!(hostnam = getsparam("HOST"))) {
505 		    unqueue_signals();
506 		    break;
507 		}
508 		if (arg < 0) {
509 		    for (ss = hostnam + strlen(hostnam); ss > hostnam; ss--)
510 			if (ss[-1] == '.' && !++arg)
511 			    break;
512 		    stradd(ss);
513 		} else {
514 		    for (ss = hostnam; *ss; ss++)
515 			if (*ss == '.' && !--arg)
516 			    break;
517 		    stradd(*ss ? dupstrpfx(hostnam, ss - hostnam) : hostnam);
518 		}
519 		unqueue_signals();
520 		break;
521 	    case 'S':
522 		txtchangeset(txtchangep, TXTSTANDOUT, TXTNOSTANDOUT);
523 		txtset(TXTSTANDOUT);
524 		tsetcap(TCSTANDOUTBEG, TSC_PROMPT);
525 		break;
526 	    case 's':
527 		txtchangeset(txtchangep, TXTNOSTANDOUT, TXTSTANDOUT);
528 		txtunset(TXTSTANDOUT);
529 		tsetcap(TCSTANDOUTEND, TSC_PROMPT|TSC_DIRTY);
530 		break;
531 	    case 'B':
532 		txtchangeset(txtchangep, TXTBOLDFACE, TXTNOBOLDFACE);
533 		txtset(TXTBOLDFACE);
534 		tsetcap(TCBOLDFACEBEG, TSC_PROMPT|TSC_DIRTY);
535 		break;
536 	    case 'b':
537 		txtchangeset(txtchangep, TXTNOBOLDFACE, TXTBOLDFACE);
538 		txtunset(TXTBOLDFACE);
539 		tsetcap(TCALLATTRSOFF, TSC_PROMPT|TSC_DIRTY);
540 		break;
541 	    case 'U':
542 		txtchangeset(txtchangep, TXTUNDERLINE, TXTNOUNDERLINE);
543 		txtset(TXTUNDERLINE);
544 		tsetcap(TCUNDERLINEBEG, TSC_PROMPT);
545 		break;
546 	    case 'u':
547 		txtchangeset(txtchangep, TXTNOUNDERLINE, TXTUNDERLINE);
548 		txtunset(TXTUNDERLINE);
549 		tsetcap(TCUNDERLINEEND, TSC_PROMPT|TSC_DIRTY);
550 		break;
551 	    case 'F':
552 		atr = parsecolorchar(arg, 1);
553 		if (!(atr & (TXT_ERROR | TXTNOFGCOLOUR))) {
554 		    txtchangeset(txtchangep, atr & TXT_ATTR_FG_ON_MASK,
555 				 TXTNOFGCOLOUR | TXT_ATTR_FG_COL_MASK);
556 		    txtunset(TXT_ATTR_FG_COL_MASK);
557 		    txtset(atr & TXT_ATTR_FG_ON_MASK);
558 		    set_colour_attribute(atr, COL_SEQ_FG, TSC_PROMPT);
559 		    break;
560 		}
561 		/* else FALLTHROUGH */
562 	    case 'f':
563 		txtchangeset(txtchangep, TXTNOFGCOLOUR, TXT_ATTR_FG_ON_MASK);
564 		txtunset(TXT_ATTR_FG_ON_MASK);
565 		set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, TSC_PROMPT);
566 		break;
567 	    case 'K':
568 		atr = parsecolorchar(arg, 0);
569 		if (!(atr & (TXT_ERROR | TXTNOBGCOLOUR))) {
570 		    txtchangeset(txtchangep, atr & TXT_ATTR_BG_ON_MASK,
571 				 TXTNOBGCOLOUR | TXT_ATTR_BG_COL_MASK);
572 		    txtunset(TXT_ATTR_BG_COL_MASK);
573 		    txtset(atr & TXT_ATTR_BG_ON_MASK);
574 		    set_colour_attribute(atr, COL_SEQ_BG, TSC_PROMPT);
575 		    break;
576 		}
577 		/* else FALLTHROUGH */
578 	    case 'k':
579 		txtchangeset(txtchangep, TXTNOBGCOLOUR, TXT_ATTR_BG_ON_MASK);
580 		txtunset(TXT_ATTR_BG_ON_MASK);
581 		set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, TSC_PROMPT);
582 		break;
583 	    case '[':
584 		if (idigit(*++bv->fm))
585 		    arg = zstrtol(bv->fm, &bv->fm, 10);
586 		if (!prompttrunc(arg, ']', doprint, endchar, txtchangep))
587 		    return *bv->fm;
588 		break;
589 	    case '<':
590 	    case '>':
591 		/* Test (minus) here so -0 means "at the right margin" */
592 		if (minus) {
593 		    *bv->bp = '\0';
594 		    countprompt(bv->bufline, &t0, 0, 0);
595 		    arg = zterm_columns - t0 + arg;
596 		    if (arg <= 0)
597 			arg = 1;
598 		}
599 		if (!prompttrunc(arg, *bv->fm, doprint, endchar, txtchangep))
600 		    return *bv->fm;
601 		break;
602 	    case '{': /*}*/
603 		if (!bv->dontcount++) {
604 		    addbufspc(1);
605 		    *bv->bp++ = Inpar;
606 		}
607 		if (arg <= 0)
608 		    break;
609 		/* else */
610 		/* FALLTHROUGH */
611 	    case 'G':
612 		if (arg > 0) {
613 		    addbufspc(arg);
614 		    while (arg--)
615 			*bv->bp++ = Nularg;
616 		} else {
617 		    addbufspc(1);
618 		    *bv->bp++ = Nularg;
619 		}
620 		break;
621 	    case /*{*/ '}':
622 		if (bv->trunccount && bv->trunccount >= bv->dontcount)
623 		    return *bv->fm;
624 		if (bv->dontcount && !--bv->dontcount) {
625 		    addbufspc(1);
626 		    *bv->bp++ = Outpar;
627 		}
628 		break;
629 	    case 't':
630 	    case '@':
631 	    case 'T':
632 	    case '*':
633 	    case 'w':
634 	    case 'W':
635 	    case 'D':
636 		{
637 		    char *tmfmt, *dd, *tmbuf = NULL;
638 
639 		    switch (*bv->fm) {
640 		    case 'T':
641 			tmfmt = "%K:%M";
642 			break;
643 		    case '*':
644 			tmfmt = "%K:%M:%S";
645 			break;
646 		    case 'w':
647 			tmfmt = "%a %f";
648 			break;
649 		    case 'W':
650 			tmfmt = "%m/%d/%y";
651 			break;
652 		    case 'D':
653 			if (bv->fm[1] == '{' /*}*/) {
654 			    for (ss = bv->fm + 2; *ss && *ss != /*{*/ '}'; ss++)
655 				if(*ss == '\\' && ss[1])
656 				    ss++;
657 			    dd = tmfmt = tmbuf = zalloc(ss - bv->fm);
658 			    for (ss = bv->fm + 2; *ss && *ss != /*{*/ '}';
659 				 ss++) {
660 				if(*ss == '\\' && ss[1])
661 				    ss++;
662 				*dd++ = *ss;
663 			    }
664 			    *dd = 0;
665 			    bv->fm = ss - !*ss;
666 			    if (!*tmfmt) {
667 				free(tmbuf);
668 				continue;
669 			    }
670 			} else
671 			    tmfmt = "%y-%m-%d";
672 			break;
673 		    default:
674 			tmfmt = "%l:%M%p";
675 			break;
676 		    }
677 		    zgettime(&ts);
678 		    tm = localtime(&ts.tv_sec);
679 		    /*
680 		     * Hack because strftime won't say how
681 		     * much space it actually needs.  Try to add it
682 		     * a few times until it works.  Some formats don't
683 		     * actually have a length, so we could go on for
684 		     * ever.
685 		     */
686 		    for(j = 0, t0 = strlen(tmfmt)*8; j < 3; j++, t0*=2) {
687 			addbufspc(t0);
688 			if ((len = ztrftime(bv->bp, t0, tmfmt, tm, ts.tv_nsec))
689 			    >= 0)
690 			    break;
691 		    }
692 		    /* There is enough room for this because addbufspc(t0)
693 		     * allocates room for t0 * 2 bytes. */
694 		    if (len >= 0)
695 			metafy(bv->bp, len, META_NOALLOC);
696 		    bv->bp += strlen(bv->bp);
697 		    zsfree(tmbuf);
698 		    break;
699 		}
700 	    case 'n':
701 		stradd(get_username());
702 		break;
703 	    case 'l':
704 		if (*ttystrname) {
705                    ss = (strncmp(ttystrname, "/dev/tty", 8) ?
706                            ttystrname + 5 : ttystrname + 8);
707 		    stradd(ss);
708 		} else
709 		    stradd("()");
710 		break;
711 	    case 'y':
712 		if (*ttystrname) {
713 		    ss = (strncmp(ttystrname, "/dev/", 5) ?
714 			    ttystrname : ttystrname + 5);
715 		    stradd(ss);
716 		} else
717 		    stradd("()");
718 		break;
719 	    case 'L':
720 		addbufspc(DIGBUFSIZE);
721 #if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
722 		sprintf(bv->bp, "%lld", shlvl);
723 #else
724 		sprintf(bv->bp, "%ld", (long)shlvl);
725 #endif
726 		bv->bp += strlen(bv->bp);
727 		break;
728 	    case '?':
729 		addbufspc(DIGBUFSIZE);
730 #if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
731 		sprintf(bv->bp, "%lld", lastval);
732 #else
733 		sprintf(bv->bp, "%ld", (long)lastval);
734 #endif
735 		bv->bp += strlen(bv->bp);
736 		break;
737 	    case '%':
738 	    case ')':
739 		addbufspc(1);
740 		*bv->bp++ = *bv->fm;
741 		break;
742 	    case '#':
743 		addbufspc(1);
744 		*bv->bp++ = privasserted() ? '#' : '%';
745 		break;
746 	    case 'v':
747 		if (!arg)
748 		    arg = 1;
749 		else if (arg < 0)
750 		    arg += arrlen(psvar) + 1;
751 		if (arg > 0 && arrlen_ge(psvar, arg))
752 		    stradd(psvar[arg - 1]);
753 		break;
754 	    case 'E':
755                 tsetcap(TCCLEAREOL, TSC_PROMPT);
756 		break;
757 	    case '^':
758 		if (cmdsp) {
759 		    if (arg >= 0) {
760 			if (arg > cmdsp || arg == 0)
761 			    arg = cmdsp;
762 			for (t0 = cmdsp - 1; arg--; t0--) {
763 			    stradd(cmdnames[cmdstack[t0]]);
764 			    if (arg) {
765 				addbufspc(1);
766 				*bv->bp++=' ';
767 			    }
768 			}
769 		    } else {
770 			arg = -arg;
771 			if (arg > cmdsp)
772 			    arg = cmdsp;
773 			for (t0 = arg - 1; arg--; t0--) {
774 			    stradd(cmdnames[cmdstack[t0]]);
775 			    if (arg) {
776 				addbufspc(1);
777 				*bv->bp++=' ';
778 			    }
779 			}
780 		    }
781 		}
782 		break;
783 	    case '_':
784 		if (cmdsp) {
785 		    if (arg >= 0) {
786 			if (arg > cmdsp || arg == 0)
787 			    arg = cmdsp;
788 			for (t0 = cmdsp - arg; arg--; t0++) {
789 			    stradd(cmdnames[cmdstack[t0]]);
790 			    if (arg) {
791 				addbufspc(1);
792 				*bv->bp++=' ';
793 			    }
794 			}
795 		    } else {
796 			arg = -arg;
797 			if (arg > cmdsp)
798 			    arg = cmdsp;
799 			for (t0 = 0; arg--; t0++) {
800 			    stradd(cmdnames[cmdstack[t0]]);
801 			    if (arg) {
802 				addbufspc(1);
803 				*bv->bp++=' ';
804 			    }
805 			}
806 		    }
807 		}
808 		break;
809 	    case 'r':
810 		if(bv->rstring)
811 		    stradd(bv->rstring);
812 		break;
813 	    case 'R':
814 		if(bv->Rstring)
815 		    stradd(bv->Rstring);
816 		break;
817 	    case 'e':
818 	    {
819 		int depth = 0;
820 		Funcstack fsptr = funcstack;
821 		while (fsptr) {
822 		    depth++;
823 		    fsptr = fsptr->prev;
824 		}
825 		addbufspc(DIGBUFSIZE);
826 		sprintf(bv->bp, "%d", depth);
827 		bv->bp += strlen(bv->bp);
828 		break;
829 	    }
830 	    case 'I':
831 		if (funcstack && funcstack->tp != FS_SOURCE &&
832 		    !IN_EVAL_TRAP()) {
833 		    /*
834 		     * We're in a function or an eval with
835 		     * EVALLINENO.  Calculate the line number in
836 		     * the file.
837 		     */
838 		    zlong flineno = lineno + funcstack->flineno;
839 		    /* take account of eval line nos. starting at 1 */
840 		    if (funcstack->tp == FS_EVAL)
841 			lineno--;
842 		    addbufspc(DIGBUFSIZE);
843 #if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
844 		    sprintf(bv->bp, "%lld", flineno);
845 #else
846 		    sprintf(bv->bp, "%ld", (long)flineno);
847 #endif
848 		    bv->bp += strlen(bv->bp);
849 		    break;
850 		}
851 		/* else we're in a file and lineno is already correct */
852 		/* FALLTHROUGH */
853 	    case 'i':
854 		addbufspc(DIGBUFSIZE);
855 #if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
856 		sprintf(bv->bp, "%lld", lineno);
857 #else
858 		sprintf(bv->bp, "%ld", (long)lineno);
859 #endif
860 		bv->bp += strlen(bv->bp);
861 		break;
862 	    case 'x':
863 		if (funcstack && funcstack->tp != FS_SOURCE &&
864 		    !IN_EVAL_TRAP())
865 		    promptpath(funcstack->filename ? funcstack->filename : "",
866 			       arg, 0);
867 		else
868 		    promptpath(scriptfilename ? scriptfilename : argzero,
869 			       arg, 0);
870 		break;
871 	    case '\0':
872 		return 0;
873 	    case Meta:
874 		bv->fm++;
875 		break;
876 	    }
877 	} else if(*bv->fm == '!' && isset(PROMPTBANG)) {
878 	    if(doprint) {
879 		if(bv->fm[1] == '!') {
880 		    bv->fm++;
881 		    addbufspc(1);
882 		    pputc('!');
883 		} else {
884 		    addbufspc(DIGBUFSIZE);
885 		    convbase(bv->bp, curhist, 10);
886 		    bv->bp += strlen(bv->bp);
887 		}
888 	    }
889 	} else {
890 	    char c = *bv->fm == Meta ? *++bv->fm ^ 32 : *bv->fm;
891 
892 	    if (doprint) {
893 		addbufspc(1);
894 		pputc(c);
895 	    }
896 	}
897     }
898 
899     return *bv->fm;
900 }
901 
902 /* pputc adds a character to the buffer, metafying.  There must *
903  * already be space.                                            */
904 
905 /**/
906 static void
pputc(char c)907 pputc(char c)
908 {
909     if (imeta(c)) {
910 	*bv->bp++ = Meta;
911 	c ^= 32;
912     }
913     *bv->bp++ = c;
914     if (c == '\n' && !bv->dontcount)
915 	bv->bufline = bv->bp;
916 }
917 
918 /* Make sure there is room for `need' more characters in the buffer. */
919 
920 /**/
921 static void
addbufspc(int need)922 addbufspc(int need)
923 {
924     need *= 2;   /* for metafication */
925     if((bv->bp - bv->buf) + need > bv->bufspc) {
926 	int bo = bv->bp - bv->buf;
927 	int bo1 = bv->bp1 ? bv->bp1 - bv->buf : -1;
928 	ptrdiff_t bufline_off = bv->bufline ? bv->bufline - bv->buf : -1;
929 
930 	if(need & 255)
931 	    need = (need | 255) + 1;
932 	bv->buf = realloc(bv->buf, bv->bufspc += need);
933 	memset(bv->buf + bv->bufspc - need, 0, need);
934 	bv->bp = bv->buf + bo;
935 	if(bo1 != -1)
936 	    bv->bp1 = bv->buf + bo1;
937 	if (bufline_off != -1)
938 	    bv->bufline = bv->buf + bufline_off;
939     }
940 }
941 
942 /* stradd() adds a metafied string to the prompt, *
943  * in a visible representation.                   */
944 
945 /**/
946 void
stradd(char * d)947 stradd(char *d)
948 {
949 #ifdef MULTIBYTE_SUPPORT
950     char *ums, *ups;
951     int upslen, eol = 0;
952     mbstate_t mbs;
953 
954     memset(&mbs, 0, sizeof mbs);
955     ums = ztrdup(d);
956     ups = unmetafy(ums, &upslen);
957 
958     /*
959      * We now have a raw string of possibly multibyte characters.
960      * Read each character one by one.
961      */
962     while (upslen > 0) {
963 	wchar_t cc;
964 	char *pc;
965 	size_t cnt = eol ? MB_INVALID : mbrtowc(&cc, ups, upslen, &mbs);
966 
967 	switch (cnt) {
968 	case MB_INCOMPLETE:
969 	    eol = 1;
970 	    /* FALL THROUGH */
971 	case MB_INVALID:
972 	    /* Bad character.  Take the next byte on its own. */
973 	    pc = nicechar(*ups);
974 	    cnt = 1;
975 	    memset(&mbs, 0, sizeof mbs);
976 	    break;
977 	case 0:
978 	    cnt = 1;
979 	    /* FALL THROUGH */
980 	default:
981 	    /* Take full wide character in one go */
982 	    mb_charinit();
983 	    pc = wcs_nicechar(cc, NULL, NULL);
984 	    break;
985 	}
986 	/* Keep output as metafied string. */
987 	addbufspc(strlen(pc));
988 
989 	upslen -= cnt;
990 	ups += cnt;
991 
992 	/* Put printed representation into the buffer */
993 	while (*pc)
994 	    *bv->bp++ = *pc++;
995     }
996 
997     free(ums);
998 #else
999     char *ps, *pc;
1000     addbufspc(niceztrlen(d));
1001     /* This loop puts the nice representation of the string into the
1002      * prompt buffer. */
1003     for (ps = d; *ps; ps++) {
1004 	for (pc = nicechar(*ps == Meta ? *++ps^32 : *ps); *pc; pc++)
1005 	    *bv->bp++ = *pc;
1006     }
1007 #endif
1008 }
1009 
1010 /* tsetcap(), among other things, can write a termcap string into the buffer. */
1011 
1012 /**/
1013 mod_export void
tsetcap(int cap,int flags)1014 tsetcap(int cap, int flags)
1015 {
1016     if (tccan(cap) && !isset(SINGLELINEZLE) &&
1017         !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
1018 	switch (flags & TSC_OUTPUT_MASK) {
1019 	case TSC_RAW:
1020 	    tputs(tcstr[cap], 1, putraw);
1021 	    break;
1022 	case 0:
1023 	default:
1024 	    tputs(tcstr[cap], 1, putshout);
1025 	    break;
1026 	case TSC_PROMPT:
1027 	    if (!bv->dontcount) {
1028 		addbufspc(1);
1029 		*bv->bp++ = Inpar;
1030 	    }
1031 	    tputs(tcstr[cap], 1, putstr);
1032 	    if (!bv->dontcount) {
1033 		int glitch = 0;
1034 
1035 		if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
1036 		    glitch = tgetnum("sg");
1037 		else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
1038 		    glitch = tgetnum("ug");
1039 		if(glitch < 0)
1040 		    glitch = 0;
1041 		addbufspc(glitch + 1);
1042 		while(glitch--)
1043 		    *bv->bp++ = Nularg;
1044 		*bv->bp++ = Outpar;
1045 	    }
1046 	    break;
1047 	}
1048 
1049 	if (flags & TSC_DIRTY) {
1050 	    flags &= ~TSC_DIRTY;
1051 	    if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
1052 		tsetcap(TCBOLDFACEBEG, flags);
1053 	    if (txtisset(TXTSTANDOUT))
1054 		tsetcap(TCSTANDOUTBEG, flags);
1055 	    if (txtisset(TXTUNDERLINE))
1056 		tsetcap(TCUNDERLINEBEG, flags);
1057 	    if (txtisset(TXTFGCOLOUR))
1058 		set_colour_attribute(txtattrmask, COL_SEQ_FG, TSC_PROMPT);
1059 	    if (txtisset(TXTBGCOLOUR))
1060 		set_colour_attribute(txtattrmask, COL_SEQ_BG, TSC_PROMPT);
1061 	}
1062     }
1063 }
1064 
1065 /**/
1066 int
putstr(int d)1067 putstr(int d)
1068 {
1069     addbufspc(1);
1070     pputc(d);
1071     return 0;
1072 }
1073 
1074 /*
1075  * Count height etc. of a prompt string returned by promptexpand().
1076  * This depends on the current terminal width, and tabs and
1077  * newlines require nontrivial processing.
1078  * Passing `overf' as -1 means to ignore columns (absolute width).
1079  *
1080  * If multibyte is enabled, take account of multibyte characters
1081  * by locating them and finding out their screen width.
1082  */
1083 
1084 /**/
1085 mod_export void
countprompt(char * str,int * wp,int * hp,int overf)1086 countprompt(char *str, int *wp, int *hp, int overf)
1087 {
1088     int w = 0, h = 1, multi = 0, wcw = 0;
1089     int s = 1;
1090 #ifdef MULTIBYTE_SUPPORT
1091     char inchar;
1092     mbstate_t mbs;
1093     wchar_t wc;
1094 
1095     memset(&mbs, 0, sizeof(mbs));
1096 #endif
1097 
1098     for (; *str; str++) {
1099 	/*
1100 	 * Avoid double-incrementing the height when there's a newline in the
1101 	 * prompt and the line it terminates takes up exactly the width of the
1102 	 * terminal
1103 	 */
1104 	while (w > zterm_columns && overf >= 0 && !multi) {
1105 	    h++;
1106 	    if (wcw) {
1107 		/*
1108 		 * Wide characters don't get split off. They move to the
1109 		 * next line if there is not enough space.
1110 		 */
1111 		w = wcw;
1112 		break;
1113 	    } else {
1114 		/*
1115 		 * Tabs overflow to the next line as if they were made of spaces.
1116 		 */
1117 		w -= zterm_columns;
1118 	    }
1119 	}
1120 	wcw = 0;
1121 	/*
1122 	 * Input string should be metafied, so tokens in it should
1123 	 * be real tokens, even if there are multibyte characters.
1124 	 */
1125 	if (*str == Inpar)
1126 	    s = 0;
1127 	else if (*str == Outpar)
1128 	    s = 1;
1129 	else if (*str == Nularg)
1130 	    w++;
1131 	else if (s) {
1132 	    if (*str == Meta) {
1133 #ifdef MULTIBYTE_SUPPORT
1134 		inchar = *++str ^ 32;
1135 #else
1136 		str++;
1137 #endif
1138 	    } else {
1139 #ifdef MULTIBYTE_SUPPORT
1140 		/*
1141 		 * Don't look for tab or newline in the middle
1142 		 * of a multibyte character.  Otherwise, we are
1143 		 * relying on the character set being an extension
1144 		 * of ASCII so it's safe to test a single byte.
1145 		 */
1146 		if (!multi) {
1147 #endif
1148 		    if (*str == '\t') {
1149 			w = (w | 7) + 1;
1150 			continue;
1151 		    } else if (*str == '\n') {
1152 			w = 0;
1153 			h++;
1154 			continue;
1155 		    }
1156 #ifdef MULTIBYTE_SUPPORT
1157 		}
1158 
1159 		inchar = *str;
1160 #endif
1161 	    }
1162 
1163 #ifdef MULTIBYTE_SUPPORT
1164 	    switch (mbrtowc(&wc, &inchar, 1, &mbs)) {
1165 	    case MB_INCOMPLETE:
1166 		/* Character is incomplete -- keep looking. */
1167 		multi = 1;
1168 		break;
1169 	    case MB_INVALID:
1170 		memset(&mbs, 0, sizeof mbs);
1171 		/* Invalid character: assume single width. */
1172 		multi = 0;
1173 		w++;
1174 		break;
1175 	    case 0:
1176 		multi = 0;
1177 		break;
1178 	    default:
1179 		/*
1180 		 * If the character isn't printable, WCWIDTH() returns
1181 		 * -1.  We assume width 1.
1182 		 */
1183 		wcw = WCWIDTH(wc);
1184 		if (wcw >= 0)
1185 		    w += wcw;
1186 		else
1187 		    w++;
1188 		multi = 0;
1189 		break;
1190 	    }
1191 #else
1192 	    w++;
1193 #endif
1194 	}
1195     }
1196     /*
1197      * multi may still be set if we were in the middle of the character.
1198      * This isn't easy to handle generally; just assume there's no
1199      * output.
1200      */
1201     while (w > zterm_columns && overf >= 0) {
1202 	h++;
1203 	if (wcw) {
1204 	    w = wcw;
1205 	    break;
1206 	} else {
1207 	    w -= zterm_columns;
1208 	}
1209     }
1210     if (w == zterm_columns && overf == 0) {
1211 	w = 0;
1212 	h++;
1213     }
1214     if(wp)
1215 	*wp = w;
1216     if(hp)
1217 	*hp = h;
1218 }
1219 
1220 /**/
1221 static int
prompttrunc(int arg,int truncchar,int doprint,int endchar,zattr * txtchangep)1222 prompttrunc(int arg, int truncchar, int doprint, int endchar,
1223 	    zattr *txtchangep)
1224 {
1225     if (arg > 0) {
1226 	char ch = *bv->fm, *ptr, *truncstr;
1227 	int truncatleft = ch == '<';
1228 	int w = bv->bp - bv->buf;
1229 
1230 	/*
1231 	 * If there is already a truncation active, return so that
1232 	 * can be finished, backing up so that the new truncation
1233 	 * can be started afterwards.
1234 	 */
1235 	if (bv->truncwidth) {
1236 	    while (*--bv->fm != '%')
1237 		;
1238 	    bv->fm--;
1239 	    return 0;
1240 	}
1241 
1242 	bv->truncwidth = arg;
1243 	if (*bv->fm != ']')
1244 	    bv->fm++;
1245 	while (*bv->fm && *bv->fm != truncchar) {
1246 	    if (*bv->fm == '\\' && bv->fm[1])
1247 		++bv->fm;
1248 	    addbufspc(1);
1249 	    *bv->bp++ = *bv->fm++;
1250 	}
1251 	if (!*bv->fm)
1252 	    return 0;
1253 	if (bv->bp - bv->buf == w && truncchar == ']') {
1254 	    addbufspc(1);
1255 	    *bv->bp++ = '<';
1256 	}
1257 	ptr = bv->buf + w;	/* addbufspc() may have realloc()'d bv->buf */
1258 	/*
1259 	 * Now:
1260 	 *   bv->buf is the start of the output prompt buffer
1261 	 *   ptr is the start of the truncation string
1262 	 *   bv->bp is the end of the truncation string
1263 	 */
1264 	truncstr = ztrduppfx(ptr, bv->bp - ptr);
1265 
1266 	bv->bp = ptr;
1267 	w = bv->bp - bv->buf;
1268 	bv->fm++;
1269 	bv->trunccount = bv->dontcount;
1270 	putpromptchar(doprint, endchar, txtchangep);
1271 	bv->trunccount = 0;
1272 	ptr = bv->buf + w;	/* putpromptchar() may have realloc()'d */
1273 	*bv->bp = '\0';
1274 	/*
1275 	 * Now:
1276 	 *   ptr is the start of the truncation string and also
1277 	 *     where we need to start putting any truncated output
1278 	 *   bv->bp is the end of the string we have just added, which
1279 	 *     may need truncating.
1280 	 */
1281 
1282 	/*
1283 	 * w below is screen width if multibyte support is enabled
1284 	 * (note that above it was a raw string pointer difference).
1285 	 * It's the full width of the string we may need to truncate.
1286 	 *
1287 	 * bv->truncwidth has come from the user, so we interpret this
1288 	 * as a screen width, too.
1289 	 */
1290 	countprompt(ptr, &w, 0, -1);
1291 	if (w > bv->truncwidth) {
1292 	    /*
1293 	     * We need to truncate.  t points to the truncation string
1294 	     * -- which is inserted literally, without nice
1295 	     * representation.  twidth is its printing width, and maxwidth
1296 	     * is the amount of the main string that we want to keep.
1297 	     * Note that if the truncation string is longer than the
1298 	     * truncation length (twidth > bv->truncwidth), the truncation
1299 	     * string is used in full.
1300 	     */
1301 	    char *t = truncstr;
1302 	    int fullen = bv->bp - ptr;
1303 	    int twidth, maxwidth;
1304 	    int ntrunc = strlen(t);
1305 
1306 	    twidth = MB_METASTRWIDTH(t);
1307 	    if (twidth < bv->truncwidth) {
1308 		maxwidth = bv->truncwidth - twidth;
1309 		/*
1310 		 * It's not safe to assume there are no invisible substrings
1311 		 * just because the width is less than the full string
1312 		 * length since there may be multibyte characters.
1313 		 */
1314 		addbufspc(ntrunc+1);
1315 		/* may have realloc'd */
1316 		ptr = bv->bp - fullen;
1317 
1318 		if (truncatleft) {
1319 		    /*
1320 		     * To truncate at the left, selectively copy
1321 		     * maxwidth bytes from the main prompt, preceded
1322 		     * by the truncation string in full.
1323 		     *
1324 		     * We're overwriting the string containing the
1325 		     * text to be truncated, so copy it.  We've
1326 		     * just ensured there's sufficient space at the
1327 		     * end of the prompt string.
1328 		     *
1329 		     * Pointer into text to be truncated.
1330 		     */
1331 		    char *fulltextptr, *fulltext;
1332 		    int remw;
1333 #ifdef MULTIBYTE_SUPPORT
1334 		    mbstate_t mbs;
1335 		    memset(&mbs, 0, sizeof mbs);
1336 #endif
1337 
1338 		    fulltextptr = fulltext = ptr + ntrunc;
1339 		    memmove(fulltext, ptr, fullen);
1340 		    fulltext[fullen] = '\0';
1341 
1342 		    /* Copy the truncstr into place. */
1343 		    while (*t)
1344 			*ptr++ = *t++;
1345 
1346 		    /*
1347 		     * Find the point in the text at which we should
1348 		     * start copying, i.e. when the remaining width
1349 		     * is less than or equal to the maximum width.
1350 		     */
1351 		    remw = w;
1352 		    while (remw > maxwidth && *fulltextptr) {
1353 			if (*fulltextptr == Inpar) {
1354 			    /*
1355 			     * Text marked as invisible: copy
1356 			     * regardless, since we don't know what
1357 			     * this does.  It only affects the width
1358 			     * if there are Nularg's present.
1359 			     * However, even in that case we
1360 			     * can't break the sequence down, so
1361 			     * we still loop over the entire group.
1362 			     */
1363 			    for (;;) {
1364 				*ptr++ = *fulltextptr;
1365 				if (*fulltextptr == '\0' ||
1366 				    *fulltextptr++ == Outpar)
1367 				    break;
1368 				if (fulltextptr[-1] == Nularg)
1369 				    remw--;
1370 			    }
1371 			} else {
1372 #ifdef MULTIBYTE_SUPPORT
1373 			    /*
1374 			     * Normal text: build up a multibyte character.
1375 			     */
1376 			    char inchar;
1377 			    wchar_t cc;
1378 			    int wcw;
1379 
1380 			    /*
1381 			     * careful: string is still metafied (we
1382 			     * need that because we don't know a
1383 			     * priori when to stop and the resulting
1384 			     * string must be metafied).
1385 			     */
1386 			    if (*fulltextptr == Meta)
1387 				inchar = *++fulltextptr ^ 32;
1388 			    else
1389 				inchar = *fulltextptr;
1390 			    fulltextptr++;
1391 			    switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
1392 			    case MB_INCOMPLETE:
1393 				/* Incomplete multibyte character. */
1394 				break;
1395 			    case MB_INVALID:
1396 				/* Reset invalid state. */
1397 				memset(&mbs, 0, sizeof mbs);
1398 				/* FALL THROUGH */
1399 			    case 0:
1400 				/* Assume a single-byte character. */
1401 				remw--;
1402 				break;
1403 			    default:
1404 				wcw = WCWIDTH(cc);
1405 				if (wcw >= 0)
1406 				    remw -= wcw;
1407 				else
1408 				    remw--;
1409 				break;
1410 			    }
1411 #else
1412 			    /* Single byte character */
1413 			    if (*fulltextptr == Meta)
1414 				fulltextptr++;
1415 			    fulltextptr++;
1416 			    remw--;
1417 #endif
1418 			}
1419 		    }
1420 
1421 		    /*
1422 		     * Now simply copy the rest of the text.  Still
1423 		     * metafied, so this is easy.
1424 		     */
1425 		    while (*fulltextptr)
1426 			*ptr++ = *fulltextptr++;
1427 		    /* Mark the end of copying */
1428 		    bv->bp = ptr;
1429 		} else {
1430 		    /*
1431 		     * Truncating at the right is easier: just leave
1432 		     * enough characters until we have reached the
1433 		     * maximum width.
1434 		     */
1435 		    char *skiptext = ptr;
1436 #ifdef MULTIBYTE_SUPPORT
1437 		    mbstate_t mbs;
1438 		    memset(&mbs, 0, sizeof mbs);
1439 #endif
1440 
1441 		    while (maxwidth > 0 && *skiptext) {
1442 			if (*skiptext == Inpar) {
1443 			    /* see comment on left truncation above */
1444 			    for (;;) {
1445 				if (*skiptext == '\0' ||
1446 				    *skiptext++ == Outpar)
1447 				    break;
1448 				if (skiptext[-1] == Nularg)
1449 				    maxwidth--;
1450 			    }
1451 			} else {
1452 #ifdef MULTIBYTE_SUPPORT
1453 			    char inchar;
1454 			    wchar_t cc;
1455 			    int wcw;
1456 
1457 			    if (*skiptext == Meta)
1458 				inchar = *++skiptext ^ 32;
1459 			    else
1460 				inchar = *skiptext;
1461 			    skiptext++;
1462 			    switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
1463 			    case MB_INCOMPLETE:
1464 				/* Incomplete character. */
1465 				break;
1466 			    case MB_INVALID:
1467 				/* Reset invalid state. */
1468 				memset(&mbs, 0, sizeof mbs);
1469 				/* FALL THROUGH */
1470 			    case 0:
1471 				/* Assume a single-byte character. */
1472 				maxwidth--;
1473 				break;
1474 			    default:
1475 				wcw = WCWIDTH(cc);
1476 				if (wcw >= 0)
1477 				    maxwidth -= wcw;
1478 				else
1479 				    maxwidth--;
1480 				break;
1481 			    }
1482 #else
1483 			    if (*skiptext == Meta)
1484 				skiptext++;
1485 			    skiptext++;
1486 			    maxwidth--;
1487 #endif
1488 			}
1489 		    }
1490 		    /*
1491 		     * We don't need the visible text from now on,
1492 		     * but we'd better copy any invisible bits.
1493 		     * History dictates that these go after the
1494 		     * truncation string.  This is sensible since
1495 		     * they may, for example, turn off an effect which
1496 		     * should apply to all text at this point.
1497 		     *
1498 		     * Copy the truncstr.
1499 		     */
1500 		    ptr = skiptext;
1501 		    while (*t)
1502 			*ptr++ = *t++;
1503 		    bv->bp = ptr;
1504 		    if (*skiptext) {
1505 			/* Move remaining text so we don't overwrite it */
1506 			memmove(bv->bp, skiptext, strlen(skiptext)+1);
1507 			skiptext = bv->bp;
1508 
1509 			/*
1510 			 * Copy anything we want, updating bv->bp
1511 			 */
1512 			while (*skiptext) {
1513 			    if (*skiptext == Inpar) {
1514 				for (;;) {
1515 				    *bv->bp++ = *skiptext;
1516 				    if (*skiptext == Outpar ||
1517 					*skiptext == '\0')
1518 					break;
1519 				    skiptext++;
1520 				}
1521 			    }
1522 			    else
1523 				skiptext++;
1524 			}
1525 		    }
1526 		}
1527 	    } else {
1528 		/* Just copy truncstr; no other text appears. */
1529 		while (*t)
1530 		    *ptr++ = *t++;
1531 		bv->bp = ptr;
1532 	    }
1533 	    *bv->bp = '\0';
1534 	}
1535 	zsfree(truncstr);
1536 	bv->truncwidth = 0;
1537 	/*
1538 	 * We may have returned early from the previous putpromptchar *
1539 	 * because we found another truncation following this one.    *
1540 	 * In that case we need to do the rest now.                   *
1541 	 */
1542 	if (!*bv->fm)
1543 	    return 0;
1544 	if (*bv->fm != endchar) {
1545 	    bv->fm++;
1546 	    /*
1547 	     * With bv->truncwidth set to zero, we always reach endchar *
1548 	     * (or the terminating NULL) this time round.         *
1549 	     */
1550 	    if (!putpromptchar(doprint, endchar, txtchangep))
1551 		return 0;
1552 	}
1553 	/* Now we have to trick it into matching endchar again */
1554 	bv->fm--;
1555     } else {
1556 	if (*bv->fm != endchar)
1557 	    bv->fm++;
1558 	while(*bv->fm && *bv->fm != truncchar) {
1559 	    if (*bv->fm == '\\' && bv->fm[1])
1560 		bv->fm++;
1561 	    bv->fm++;
1562 	}
1563 	if (bv->truncwidth || !*bv->fm)
1564 	    return 0;
1565     }
1566     return 1;
1567 }
1568 
1569 /**/
1570 void
cmdpush(int cmdtok)1571 cmdpush(int cmdtok)
1572 {
1573     if (cmdsp >= 0 && cmdsp < CMDSTACKSZ)
1574 	cmdstack[cmdsp++] = (unsigned char)cmdtok;
1575 }
1576 
1577 /**/
1578 void
cmdpop(void)1579 cmdpop(void)
1580 {
1581     if (cmdsp <= 0) {
1582 	DPUTS(1, "BUG: cmdstack empty");
1583 	fflush(stderr);
1584     } else
1585 	cmdsp--;
1586 }
1587 
1588 
1589 /*****************************************************************************
1590  * Utilities dealing with colour and other forms of highlighting.
1591  *
1592  * These are shared by prompts and by zle, so it's easiest to have them
1593  * in the main shell.
1594  *****************************************************************************/
1595 
1596 /* Defines standard ANSI colour names in index order */
1597 static const char *ansi_colours[] = {
1598     "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
1599     "default", NULL
1600 };
1601 
1602 /* Defines the available types of highlighting */
1603 struct highlight {
1604     const char *name;
1605     zattr mask_on;
1606     zattr mask_off;
1607 };
1608 
1609 static const struct highlight highlights[] = {
1610     { "none", 0, TXT_ATTR_ON_MASK },
1611     { "bold", TXTBOLDFACE, 0 },
1612     { "standout", TXTSTANDOUT, 0 },
1613     { "underline", TXTUNDERLINE, 0 },
1614     { NULL, 0, 0 }
1615 };
1616 
1617 /*
1618  * Return index of ANSI colour for which *teststrp is an abbreviation.
1619  * Any non-alphabetic character ends the abbreviation.
1620  * 8 is the special value for default (note this is *not* the
1621  * right sequence for default which is typically 9).
1622  * -1 is failure.
1623  */
1624 
1625 static int
match_named_colour(const char ** teststrp)1626 match_named_colour(const char **teststrp)
1627 {
1628     const char *teststr = *teststrp, *end, **cptr;
1629     int len;
1630 
1631     for (end = teststr; ialpha(*end); end++)
1632 	;
1633     len = end - teststr;
1634     *teststrp = end;
1635 
1636     for (cptr = ansi_colours; *cptr; cptr++) {
1637 	if (!strncmp(teststr, *cptr, len))
1638 	    return cptr - ansi_colours;
1639     }
1640 
1641     return -1;
1642 }
1643 
1644 /*
1645  * Match just the colour part of a highlight specification.
1646  * If teststrp is NULL, use the already parsed numeric colour.
1647  * Return the attributes to set in the attribute variable.
1648  * Return -1 for out of range.  Does not check the character
1649  * following the colour specification.
1650  */
1651 
1652 /**/
1653 mod_export zattr
match_colour(const char ** teststrp,int is_fg,int colour)1654 match_colour(const char **teststrp, int is_fg, int colour)
1655 {
1656     int shft, named = 0, tc;
1657     zattr on;
1658 
1659     if (is_fg) {
1660 	shft = TXT_ATTR_FG_COL_SHIFT;
1661 	on = TXTFGCOLOUR;
1662 	tc = TCFGCOLOUR;
1663     } else {
1664 	shft = TXT_ATTR_BG_COL_SHIFT;
1665 	on = TXTBGCOLOUR;
1666 	tc = TCBGCOLOUR;
1667     }
1668     if (teststrp) {
1669 	if (**teststrp == '#' && isxdigit((*teststrp)[1])) {
1670 	    struct color_rgb color;
1671 	    char *end;
1672 	    zlong col = zstrtol(*teststrp+1, &end, 16);
1673             if (end - *teststrp == 4) {
1674 		color.red = col >> 8 | ((col >> 8) << 4);
1675 		color.green = (col & 0xf0) >> 4;
1676 		color.green |= color.green << 4;
1677 		color.blue = col & 0xf;
1678 		color.blue |= color.blue << 4;
1679 	    } else if (end - *teststrp == 7) {
1680 		color.red = col >> 16;
1681 		color.green = (col & 0xff00) >> 8;
1682 		color.blue = col & 0xff;
1683 	    } else
1684 		return TXT_ERROR;
1685 	    *teststrp = end;
1686 	    colour = runhookdef(GETCOLORATTR, &color) - 1;
1687 	    if (colour == -1) { /* no hook function added, try true color (24-bit) */
1688 		colour = (((color.red << 8) + color.green) << 8) + color.blue;
1689 		return on | (is_fg ? TXT_ATTR_FG_24BIT : TXT_ATTR_BG_24BIT) |
1690 			(zattr)colour << shft;
1691 	    } else if (colour <= -2) {
1692 		return TXT_ERROR;
1693 	    }
1694 	} else if ((named = ialpha(**teststrp))) {
1695 	    colour = match_named_colour(teststrp);
1696 	    if (colour == 8) {
1697 		/* default */
1698 		return is_fg ? TXTNOFGCOLOUR : TXTNOBGCOLOUR;
1699 	    }
1700 	    if (colour < 0)
1701 		return TXT_ERROR;
1702 	}
1703 	else {
1704 	    colour = (int)zstrtol(*teststrp, (char **)teststrp, 10);
1705 	    if (colour < 0 || colour >= 256)
1706 		return TXT_ERROR;
1707 	}
1708     }
1709     /*
1710      * Try termcap for numbered characters if possible.
1711      * Don't for named characters, since our best bet
1712      * of getting the names right is with ANSI sequences.
1713      */
1714     if (!named && tccan(tc)) {
1715 	if (tccolours >= 0 && colour >= tccolours) {
1716 	    /*
1717 	     * Out of range of termcap colours.
1718 	     * Can we assume ANSI colours work?
1719 	     */
1720 	    if (colour > 7)
1721 		return TXT_ERROR; /* No. */
1722 	} else {
1723 	    /*
1724 	     * We can handle termcap colours and the number
1725 	     * is in range, so use termcap.
1726 	     */
1727 	    on |= is_fg ? TXT_ATTR_FG_TERMCAP :
1728 		TXT_ATTR_BG_TERMCAP;
1729 	}
1730     }
1731     return on | (zattr)colour << shft;
1732 }
1733 
1734 /*
1735  * Match a set of highlights in the given teststr.
1736  * Set *on_var to reflect the values found.
1737  */
1738 
1739 /**/
1740 mod_export void
match_highlight(const char * teststr,zattr * on_var)1741 match_highlight(const char *teststr, zattr *on_var)
1742 {
1743     int found = 1;
1744 
1745     *on_var = 0;
1746     while (found && *teststr) {
1747 	const struct highlight *hl;
1748 
1749 	found = 0;
1750 	if (strpfx("fg=", teststr) || strpfx("bg=", teststr)) {
1751 	    int is_fg = (teststr[0] == 'f');
1752 	    zattr atr;
1753 
1754 	    teststr += 3;
1755 	    atr = match_colour(&teststr, is_fg, 0);
1756 	    if (*teststr == ',')
1757 		teststr++;
1758 	    else if (*teststr)
1759 		break;
1760 	    found = 1;
1761 	    /* skip out of range colours but keep scanning attributes */
1762 	    if (atr != TXT_ERROR)
1763 		*on_var |= atr;
1764 	} else {
1765 	    for (hl = highlights; hl->name; hl++) {
1766 		if (strpfx(hl->name, teststr)) {
1767 		    const char *val = teststr + strlen(hl->name);
1768 
1769 		    if (*val == ',')
1770 			val++;
1771 		    else if (*val)
1772 			break;
1773 
1774 		    *on_var |= hl->mask_on;
1775 		    *on_var &= ~hl->mask_off;
1776 		    teststr = val;
1777 		    found = 1;
1778 		}
1779 	    }
1780 	}
1781     }
1782 }
1783 
1784 /*
1785  * Count or output a string for colour information: used
1786  * by output_highlight(). count when buf is NULL.
1787  * returned count excludes the terminating null byte.
1788  */
1789 
1790 static int
output_colour(int colour,int fg_bg,int use_tc,int truecol,char * buf)1791 output_colour(int colour, int fg_bg, int use_tc, int truecol, char *buf)
1792 {
1793     int atrlen = 3, len;
1794     char *ptr = buf;
1795     if (buf) {
1796 	strcpy(ptr, fg_bg == COL_SEQ_FG ? "fg=" : "bg=");
1797 	ptr += 3;
1798     }
1799     if (truecol) {
1800 	/* length of hex triplet always 7, don't need sprintf to count */
1801 	atrlen += buf ? sprintf(ptr, "#%02x%02x%02x", colour >> 16,
1802 		(colour >> 8) & 0xff, colour & 0xff) : 7;
1803     /* colour should only be > 7 if using termcap but let's be safe. Update:
1804      * currently other places in code don't always imply that colour > 7 =>
1805      * using-termcap - if zle_highlight will be non-default, then it will be
1806      * used instead of termcap even for colour > 7. Here this just emits the
1807      * color number, so it works fine for both zle_highlight and tercap cases
1808      */
1809     } else if (use_tc || colour > 7) {
1810 	char digbuf[DIGBUFSIZE];
1811 	sprintf(digbuf, "%d", colour);
1812 	len = strlen(digbuf);
1813 	atrlen += len;
1814 	if (buf)
1815 	    strcpy(ptr, digbuf);
1816     } else {
1817 	len = strlen(ansi_colours[colour]);
1818 	atrlen += len;
1819 	if (buf)
1820 	    strcpy(ptr, ansi_colours[colour]);
1821     }
1822 
1823     return atrlen;
1824 }
1825 
1826 /*
1827  * Count the length needed for outputting highlighting information
1828  * as a string based on the bits for the attributes.
1829  *
1830  * If buf is not NULL, output the strings into the buffer, too.
1831  * As conventional with strings, the allocated length should be
1832  * at least the returned value plus 1 for the NUL byte.
1833  */
1834 
1835 /**/
1836 mod_export int
output_highlight(zattr atr,char * buf)1837 output_highlight(zattr atr, char *buf)
1838 {
1839     const struct highlight *hp;
1840     int atrlen = 0, len;
1841     char *ptr = buf;
1842 
1843     if (atr & TXTFGCOLOUR) {
1844 	len = output_colour(txtchangeget(atr, TXT_ATTR_FG_COL),
1845 			    COL_SEQ_FG,
1846 			    (atr & TXT_ATTR_FG_TERMCAP),
1847 			    (atr & TXT_ATTR_FG_24BIT),
1848 			    ptr);
1849 	atrlen += len;
1850 	if (buf)
1851 	    ptr += len;
1852     }
1853     if (atr & TXTBGCOLOUR) {
1854 	if (atrlen) {
1855 	    atrlen++;
1856 	    if (buf) {
1857 		strcpy(ptr, ",");
1858 		ptr++;
1859 	    }
1860 	}
1861 	len = output_colour(txtchangeget(atr, TXT_ATTR_BG_COL),
1862 			    COL_SEQ_BG,
1863 			    (atr & TXT_ATTR_BG_TERMCAP),
1864 			    (atr & TXT_ATTR_BG_24BIT),
1865 			    ptr);
1866 	atrlen += len;
1867 	if (buf)
1868 	    ptr += len;
1869     }
1870     for (hp = highlights; hp->name; hp++) {
1871 	if (hp->mask_on & atr) {
1872 	    if (atrlen) {
1873 		atrlen++;
1874 		if (buf) {
1875 		    strcpy(ptr, ",");
1876 		    ptr++;
1877 		}
1878 	    }
1879 	    len = strlen(hp->name);
1880 	    atrlen += len;
1881 	    if (buf) {
1882 		strcpy(ptr, hp->name);
1883 		ptr += len;
1884 	    }
1885 	}
1886     }
1887 
1888     if (atrlen == 0) {
1889 	if (buf)
1890 	    strcpy(ptr, "none");
1891 	return 4;
1892     }
1893     return atrlen;
1894 }
1895 
1896 /* Structure and array for holding special colour terminal sequences */
1897 
1898 /* Start of escape sequence for foreground colour */
1899 #define TC_COL_FG_START	"\033[3"
1900 /* End of escape sequence for foreground colour */
1901 #define TC_COL_FG_END	"m"
1902 /* Code to reset foreground colour */
1903 #define TC_COL_FG_DEFAULT	"9"
1904 
1905 /* Start of escape sequence for background colour */
1906 #define TC_COL_BG_START	"\033[4"
1907 /* End of escape sequence for background colour */
1908 #define TC_COL_BG_END	"m"
1909 /* Code to reset background colour */
1910 #define TC_COL_BG_DEFAULT	"9"
1911 
1912 struct colour_sequences {
1913     char *start;		/* Escape sequence start */
1914     char *end;			/* Escape sequence terminator */
1915     char *def;			/* Code to reset default colour */
1916 };
1917 static struct colour_sequences fg_bg_sequences[2];
1918 
1919 /*
1920  * We need a buffer for colour sequence composition.  It may
1921  * vary depending on the sequences set.  However, it's inefficient
1922  * allocating it separately every time we send a colour sequence,
1923  * so do it once per refresh.
1924  */
1925 static char *colseq_buf;
1926 
1927 /*
1928  * Count how often this has been allocated, for recursive usage.
1929  */
1930 static int colseq_buf_allocs;
1931 
1932 /**/
1933 void
set_default_colour_sequences(void)1934 set_default_colour_sequences(void)
1935 {
1936     fg_bg_sequences[COL_SEQ_FG].start = ztrdup(TC_COL_FG_START);
1937     fg_bg_sequences[COL_SEQ_FG].end = ztrdup(TC_COL_FG_END);
1938     fg_bg_sequences[COL_SEQ_FG].def = ztrdup(TC_COL_FG_DEFAULT);
1939 
1940     fg_bg_sequences[COL_SEQ_BG].start = ztrdup(TC_COL_BG_START);
1941     fg_bg_sequences[COL_SEQ_BG].end = ztrdup(TC_COL_BG_END);
1942     fg_bg_sequences[COL_SEQ_BG].def = ztrdup(TC_COL_BG_DEFAULT);
1943 }
1944 
1945 static void
set_colour_code(char * str,char ** var)1946 set_colour_code(char *str, char **var)
1947 {
1948     char *keyseq;
1949     int len;
1950 
1951     zsfree(*var);
1952     keyseq = getkeystring(str, &len, GETKEYS_BINDKEY, NULL);
1953     *var = metafy(keyseq, len, META_DUP);
1954 }
1955 
1956 /* Allocate buffer for colour code composition */
1957 
1958 /**/
1959 mod_export void
allocate_colour_buffer(void)1960 allocate_colour_buffer(void)
1961 {
1962     char **atrs;
1963     int lenfg, lenbg, len;
1964 
1965     if (colseq_buf_allocs++)
1966 	return;
1967 
1968     atrs = getaparam("zle_highlight");
1969     if (atrs) {
1970 	for (; *atrs; atrs++) {
1971 	    if (strpfx("fg_start_code:", *atrs)) {
1972 		set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_FG].start);
1973 	    } else if (strpfx("fg_default_code:", *atrs)) {
1974 		set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_FG].def);
1975 	    } else if (strpfx("fg_end_code:", *atrs)) {
1976 		set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_FG].end);
1977 	    } else if (strpfx("bg_start_code:", *atrs)) {
1978 		set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_BG].start);
1979 	    } else if (strpfx("bg_default_code:", *atrs)) {
1980 		set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_BG].def);
1981 	    } else if (strpfx("bg_end_code:", *atrs)) {
1982 		set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_BG].end);
1983 	    }
1984 	}
1985     }
1986 
1987     lenfg = strlen(fg_bg_sequences[COL_SEQ_FG].def);
1988     /* always need 1 character for non-default code */
1989     if (lenfg < 1)
1990 	lenfg = 1;
1991     lenfg += strlen(fg_bg_sequences[COL_SEQ_FG].start) +
1992 	strlen(fg_bg_sequences[COL_SEQ_FG].end);
1993 
1994     lenbg = strlen(fg_bg_sequences[COL_SEQ_BG].def);
1995     /* always need 1 character for non-default code */
1996     if (lenbg < 1)
1997 	lenbg = 1;
1998     lenbg += strlen(fg_bg_sequences[COL_SEQ_BG].start) +
1999 	strlen(fg_bg_sequences[COL_SEQ_BG].end);
2000 
2001     len = lenfg > lenbg ? lenfg : lenbg;
2002     /* add 1 for the null and 14 for truecolor */
2003     colseq_buf = (char *)zalloc(len+15);
2004 }
2005 
2006 /* Free the colour buffer previously allocated. */
2007 
2008 /**/
2009 mod_export void
free_colour_buffer(void)2010 free_colour_buffer(void)
2011 {
2012     if (--colseq_buf_allocs)
2013 	return;
2014 
2015     DPUTS(!colseq_buf, "Freeing colour sequence buffer without alloc");
2016     /* Free buffer for colour code composition */
2017     free(colseq_buf);
2018     colseq_buf = NULL;
2019 }
2020 
2021 /*
2022  * Handle outputting of a colour for prompts or zle.
2023  * colour is the numeric colour, 0 to 255 (or less if termcap
2024  * says fewer are supported).
2025  * fg_bg indicates if we're changing the foreground or background.
2026  * tc indicates the termcap code to use, if appropriate.
2027  * def indicates if we're resetting the default colour.
2028  * use_termcap indicates if we should use termcap to output colours.
2029  * flags is either 0 or TSC_PROMPT.
2030  */
2031 
2032 /**/
2033 mod_export void
set_colour_attribute(zattr atr,int fg_bg,int flags)2034 set_colour_attribute(zattr atr, int fg_bg, int flags)
2035 {
2036     char *ptr;
2037     int do_free, is_prompt = (flags & TSC_PROMPT) ? 1 : 0;
2038     int colour, tc, def, use_termcap, use_truecolor;
2039     int is_default_zle_highlight = 1;
2040 
2041     if (fg_bg == COL_SEQ_FG) {
2042 	colour = txtchangeget(atr, TXT_ATTR_FG_COL);
2043 	tc = TCFGCOLOUR;
2044 	def = txtchangeisset(atr, TXTNOFGCOLOUR);
2045 	use_truecolor = txtchangeisset(atr, TXT_ATTR_FG_24BIT);
2046 	use_termcap = txtchangeisset(atr, TXT_ATTR_FG_TERMCAP);
2047     } else {
2048 	colour = txtchangeget(atr, TXT_ATTR_BG_COL);
2049 	tc = TCBGCOLOUR;
2050 	def = txtchangeisset(atr, TXTNOBGCOLOUR);
2051 	use_truecolor = txtchangeisset(atr, TXT_ATTR_BG_24BIT);
2052 	use_termcap = txtchangeisset(atr, TXT_ATTR_BG_TERMCAP);
2053     }
2054 
2055     /* Test if current zle_highlight settings are customized, or
2056      * the typical "standard" codes */
2057     if (0 != strcmp(fg_bg_sequences[fg_bg].start, fg_bg == COL_SEQ_FG ? TC_COL_FG_START : TC_COL_BG_START) ||
2058 	/* the same in-fix for both FG and BG */
2059 	0 != strcmp(fg_bg_sequences[fg_bg].def, TC_COL_FG_DEFAULT) ||
2060 	/* the same suffix for both FG and BG */
2061 	0 != strcmp(fg_bg_sequences[fg_bg].end, TC_COL_FG_END))
2062     {
2063 	is_default_zle_highlight = 0;
2064     }
2065 
2066     /*
2067      * If we're not restoring the default, and either have a
2068      * colour value that is too large for ANSI, or have been told
2069      * to use the termcap sequence, try to use the termcap sequence.
2070      * True color is not covered by termcap.
2071      *
2072      * We have already sanitised the values we allow from the
2073      * highlighting variables, so much of this shouldn't be
2074      * necessary at this point, but we might as well be safe.
2075      */
2076     if (!def && !use_truecolor &&
2077 	(is_default_zle_highlight && (colour > 7 || use_termcap)))
2078     {
2079 	/*
2080 	 * We can if it's available, and either we couldn't get
2081 	 * the maximum number of colours, or the colour is in range.
2082 	 */
2083 	if (tccan(tc) && (tccolours < 0 || colour < tccolours))
2084 	{
2085 	    if (is_prompt)
2086 	    {
2087 		if (!bv->dontcount) {
2088 		    addbufspc(1);
2089 		    *bv->bp++ = Inpar;
2090 		}
2091 		tputs(tgoto(tcstr[tc], colour, colour), 1, putstr);
2092 		if (!bv->dontcount) {
2093 		    addbufspc(1);
2094 		    *bv->bp++ = Outpar;
2095 		}
2096 	    } else {
2097 		tputs(tgoto(tcstr[tc], colour, colour), 1, putshout);
2098 	    }
2099 	    /* That worked. */
2100 	    return;
2101 	}
2102 	/*
2103 	 * Nope, that didn't work.
2104 	 * If 0 to 7, assume standard ANSI works, if 8 to 255, assume
2105          * typical 256-color escapes works, otherwise it won't.
2106 	 */
2107 	if (colour > 255)
2108 	    return;
2109     }
2110 
2111     if ((do_free = (colseq_buf == NULL))) {
2112 	/* This can happen when moving the cursor in trashzle() */
2113 	allocate_colour_buffer();
2114     }
2115 
2116     /* Build the reset-code: .start + .def + . end
2117      * or the typical true-color code: .start + 8;2;%d;%d;%d + .end
2118      * or the typical 256-color code: .start + 8;5;%d + .end
2119      */
2120     if (use_truecolor)
2121 	strcpy(colseq_buf, fg_bg == COL_SEQ_FG ? TC_COL_FG_START : TC_COL_BG_START);
2122     else
2123 	strcpy(colseq_buf, fg_bg_sequences[fg_bg].start);
2124 
2125     ptr = colseq_buf + strlen(colseq_buf);
2126     if (def) {
2127 	if (use_truecolor)
2128 	    strcpy(ptr, fg_bg == COL_SEQ_FG ? TC_COL_FG_DEFAULT : TC_COL_BG_DEFAULT);
2129 	else
2130 	    strcpy(ptr, fg_bg_sequences[fg_bg].def);
2131 	while (*ptr)
2132 	    ptr++;
2133     } else if (use_truecolor) {
2134 	ptr += sprintf(ptr, "8;2;%d;%d;%d", colour >> 16,
2135 		(colour >> 8) & 0xff, colour & 0xff);
2136     } else if (colour > 7 && colour <= 255) {
2137 	ptr += sprintf(ptr, "%d", colour);
2138     } else
2139 	*ptr++ = colour + '0';
2140     if (use_truecolor)
2141 	strcpy(ptr, fg_bg == COL_SEQ_FG ? TC_COL_FG_END : TC_COL_BG_END);
2142     else
2143 	strcpy(ptr, fg_bg_sequences[fg_bg].end);
2144 
2145     if (is_prompt) {
2146 	if (!bv->dontcount) {
2147 	    addbufspc(1);
2148 	    *bv->bp++ = Inpar;
2149 	}
2150 	tputs(colseq_buf, 1, putstr);
2151 	if (!bv->dontcount) {
2152 	    addbufspc(1);
2153 	    *bv->bp++ = Outpar;
2154 	}
2155     } else
2156 	tputs(colseq_buf, 1, putshout);
2157 
2158     if (do_free)
2159 	free_colour_buffer();
2160 }
2161