1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-09-04 22:51:42 +02:00
zsh/Src/prompt.c
Daniel Shahaf dd6e702ee4 46068 (tweaked) (was: github #57): region_highlight: Add memo= support.
This is useful when multiple plugins add region_highlight entries and
subsequently want to remove only their own entries.  Without this
functionality, recognizing one's region_highlight entries is not trivial
because the 'start' and 'end' offsets are modified by editing of $BUFFER
and the highlight specification may not be unique or distinctive.

The tweaks are as follows:

- Change zfree() to zsfree() per workers/46070.

- Remove the mem.c hunk, as it changed the signature of only one out of
  two alternative definitions of zsfree().  (The definition that hunk
  touched is the one that's not used by default.)
2020-06-25 11:50:33 +00:00

2153 lines
52 KiB
C

/*
* prompt.c - construct zsh prompts
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1997 Paul Falstad
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Paul Falstad or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Paul Falstad and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Paul Falstad and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Paul Falstad and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "zsh.mdh"
#include "prompt.pro"
/* text attribute mask */
/**/
mod_export zattr txtattrmask;
/* the command stack for use with %_ in prompts */
/**/
unsigned char *cmdstack;
/**/
int cmdsp;
/* parser states, for %_ */
static char *cmdnames[CS_COUNT] = {
"for", "while", "repeat", "select",
"until", "if", "then", "else",
"elif", "math", "cond", "cmdor",
"cmdand", "pipe", "errpipe", "foreach",
"case", "function", "subsh", "cursh",
"array", "quote", "dquote", "bquote",
"cmdsubst", "mathsubst", "elif-then", "heredoc",
"heredocd", "brace", "braceparam", "always",
};
struct buf_vars;
struct buf_vars {
/* Previous set of prompt variables on the stack. */
struct buf_vars *last;
/* The buffer into which an expanded and metafied prompt is being written, *
* and its size. */
char *buf;
int bufspc;
/* bp is the pointer to the current position in the buffer, where the next *
* character will be added. */
char *bp;
/* Position of the start of the current line in the buffer */
char *bufline;
/* bp1 is an auxiliary pointer into the buffer, which when non-NULL is *
* moved whenever the buffer is reallocated. It is used when data is *
* being temporarily held in the buffer. */
char *bp1;
/* The format string, for %-expansion. */
char *fm;
/* Non-zero if truncating the current segment of the buffer. */
int truncwidth;
/* Current level of nesting of %{ / %} sequences. */
int dontcount;
/* Level of %{ / %} surrounding a truncation segment. */
int trunccount;
/* Strings to use for %r and %R (for the spelling prompt). */
char *rstring, *Rstring;
};
typedef struct buf_vars *Buf_vars;
/* The currently active prompt output variables */
static Buf_vars bv;
/*
* Expand path p; maximum is npath segments where 0 means the whole path.
* If tilde is 1, try and find a named directory to use.
*/
static void
promptpath(char *p, int npath, int tilde)
{
char *modp = p;
Nameddir nd;
if (tilde && ((nd = finddir(p))))
modp = tricat("~", nd->node.nam, p + strlen(nd->dir));
if (npath) {
char *sptr;
if (npath > 0) {
for (sptr = modp + strlen(modp); sptr > modp; sptr--) {
if (*sptr == '/' && !--npath) {
sptr++;
break;
}
}
if (*sptr == '/' && sptr[1] && sptr != modp)
sptr++;
stradd(sptr);
} else {
char cbu;
for (sptr = modp+1; *sptr; sptr++)
if (*sptr == '/' && !++npath)
break;
cbu = *sptr;
*sptr = 0;
stradd(modp);
*sptr = cbu;
}
} else
stradd(modp);
if (p != modp)
zsfree(modp);
}
/*
* Perform prompt expansion on a string, putting the result in a
* permanently-allocated string. If ns is non-zero, this string
* may have embedded Inpar and Outpar, which indicate a toggling
* between spacing and non-spacing parts of the prompt, and
* Nularg, which (in a non-spacing sequence) indicates a
* `glitch' space.
*
* txtchangep gives an integer controlling the attributes of
* the prompt. This is for use in zle to maintain the attributes
* consistently. Other parts of the shell should not need to use it.
*/
/**/
mod_export char *
promptexpand(char *s, int ns, char *rs, char *Rs, zattr *txtchangep)
{
struct buf_vars new_vars;
if(!s)
return ztrdup("");
if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
init_term();
if (isset(PROMPTSUBST)) {
int olderr = errflag;
int oldval = lastval;
s = dupstring(s);
if (!parsestr(&s))
singsub(&s);
/*
* We don't need the special Nularg hack here and we're
* going to be using Nularg for other things.
*/
if (*s == Nularg && s[1] == '\0')
*s = '\0';
/*
* Ignore errors and status change in prompt substitution.
* However, keep any user interrupt error that occurred.
*/
errflag = olderr | (errflag & ERRFLAG_INT);
lastval = oldval;
}
memset(&new_vars, 0, sizeof(new_vars));
new_vars.last = bv;
bv = &new_vars;
new_vars.rstring = rs;
new_vars.Rstring = Rs;
new_vars.fm = s;
new_vars.bufspc = 256;
new_vars.bp = new_vars.bufline = new_vars.buf = zshcalloc(new_vars.bufspc);
new_vars.bp1 = NULL;
new_vars.truncwidth = 0;
putpromptchar(1, '\0', txtchangep);
addbufspc(2);
if (new_vars.dontcount)
*new_vars.bp++ = Outpar;
*new_vars.bp = '\0';
if (!ns) {
/* If zero, Inpar, Outpar and Nularg should be removed. */
for (new_vars.bp = new_vars.buf; *new_vars.bp; ) {
if (*new_vars.bp == Meta)
new_vars.bp += 2;
else if (*new_vars.bp == Inpar || *new_vars.bp == Outpar ||
*new_vars.bp == Nularg)
chuck(new_vars.bp);
else
new_vars.bp++;
}
}
bv = new_vars.last;
return new_vars.buf;
}
/* Parse the argument for %F and %K */
static zattr
parsecolorchar(zattr arg, int is_fg)
{
if (bv->fm[1] == '{') {
char *ep;
bv->fm += 2; /* skip over F{ */
if ((ep = strchr(bv->fm, '}'))) {
char oc = *ep, *col, *coll;
*ep = '\0';
/* expand the contents of the argument so you can use
* %v for example */
coll = col = promptexpand(bv->fm, 0, NULL, NULL, NULL);
*ep = oc;
arg = match_colour((const char **)&coll, is_fg, 0);
free(col);
bv->fm = ep;
} else {
arg = match_colour((const char **)&bv->fm, is_fg, 0);
if (*bv->fm != '}')
bv->fm--;
}
} else
arg = match_colour(NULL, 1, arg);
return arg;
}
/* Perform %- and !-expansion as required on a section of the prompt. The *
* section is ended by an instance of endchar. If doprint is 0, the valid *
* % sequences are merely skipped over, and nothing is stored. */
/**/
static int
putpromptchar(int doprint, int endchar, zattr *txtchangep)
{
char *ss, *hostnam;
int t0, arg, test, sep, j, numjobs, len;
zattr atr;
struct tm *tm;
struct timespec ts;
time_t timet;
Nameddir nd;
for (; *bv->fm && *bv->fm != endchar; bv->fm++) {
arg = 0;
if (*bv->fm == '%' && isset(PROMPTPERCENT)) {
int minus = 0;
bv->fm++;
if (*bv->fm == '-') {
minus = 1;
bv->fm++;
}
if (idigit(*bv->fm)) {
arg = zstrtol(bv->fm, &bv->fm, 10);
if (minus)
arg *= -1;
} else if (minus)
arg = -1;
if (*bv->fm == '(') {
int tc, otruncwidth;
if (idigit(*++bv->fm)) {
arg = zstrtol(bv->fm, &bv->fm, 10);
} else if (arg < 0) {
/* negative numbers don't make sense here */
arg *= -1;
}
test = 0;
ss = pwd;
switch (tc = *bv->fm) {
case 'c':
case '.':
case '~':
if ((nd = finddir(ss))) {
arg--;
ss += strlen(nd->dir);
} /*FALLTHROUGH*/
case '/':
case 'C':
/* `/' gives 0, `/any' gives 1, etc. */
if (*ss && *ss++ == '/' && *ss)
arg--;
for (; *ss; ss++)
if (*ss == '/')
arg--;
if (arg <= 0)
test = 1;
break;
case 't':
case 'T':
case 'd':
case 'D':
case 'w':
timet = time(NULL);
tm = localtime(&timet);
switch (tc) {
case 't':
test = (arg == tm->tm_min);
break;
case 'T':
test = (arg == tm->tm_hour);
break;
case 'd':
test = (arg == tm->tm_mday);
break;
case 'D':
test = (arg == tm->tm_mon);
break;
case 'w':
test = (arg == tm->tm_wday);
break;
}
break;
case '?':
if (lastval == arg)
test = 1;
break;
case '#':
if (geteuid() == (uid_t)arg)
test = 1;
break;
case 'g':
if (getegid() == (gid_t)arg)
test = 1;
break;
case 'j':
for (numjobs = 0, j = 1; j <= maxjob; j++)
if (jobtab[j].stat && jobtab[j].procs &&
!(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
if (numjobs >= arg)
test = 1;
break;
case 'l':
*bv->bp = '\0';
countprompt(bv->bufline, &t0, 0, 0);
if (minus)
t0 = zterm_columns - t0;
if (t0 >= arg)
test = 1;
break;
case 'e':
{
Funcstack fsptr = funcstack;
test = arg;
while (fsptr && test > 0) {
test--;
fsptr = fsptr->prev;
}
test = !test;
}
break;
case 'L':
if (shlvl >= arg)
test = 1;
break;
case 'S':
if (time(NULL) - shtimer.tv_sec >= arg)
test = 1;
break;
case 'v':
if (arrlen_ge(psvar, arg))
test = 1;
break;
case 'V':
if (psvar && *psvar && arrlen_ge(psvar, arg)) {
if (*psvar[(arg ? arg : 1) - 1])
test = 1;
}
break;
case '_':
test = (cmdsp >= arg);
break;
case '!':
test = privasserted();
break;
default:
test = -1;
break;
}
if (!*bv->fm || !(sep = *++bv->fm))
return 0;
bv->fm++;
/* Don't do the current truncation until we get back */
otruncwidth = bv->truncwidth;
bv->truncwidth = 0;
if (!putpromptchar(test == 1 && doprint, sep,
txtchangep) || !*++bv->fm ||
!putpromptchar(test == 0 && doprint, ')',
txtchangep)) {
bv->truncwidth = otruncwidth;
return 0;
}
bv->truncwidth = otruncwidth;
continue;
}
if (!doprint)
switch(*bv->fm) {
case '[':
while(idigit(*++bv->fm));
while(*++bv->fm != ']');
continue;
case '<':
while(*++bv->fm != '<');
continue;
case '>':
while(*++bv->fm != '>');
continue;
case 'D':
if(bv->fm[1]=='{')
while(*++bv->fm != '}');
continue;
default:
continue;
}
switch (*bv->fm) {
case '~':
promptpath(pwd, arg, 1);
break;
case 'd':
case '/':
promptpath(pwd, arg, 0);
break;
case 'c':
case '.':
promptpath(pwd, arg ? arg : 1, 1);
break;
case 'C':
promptpath(pwd, arg ? arg : 1, 0);
break;
case 'N':
promptpath(scriptname ? scriptname : argzero, arg, 0);
break;
case 'h':
case '!':
addbufspc(DIGBUFSIZE);
convbase(bv->bp, curhist, 10);
bv->bp += strlen(bv->bp);
break;
case 'j':
for (numjobs = 0, j = 1; j <= maxjob; j++)
if (jobtab[j].stat && jobtab[j].procs &&
!(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
addbufspc(DIGBUFSIZE);
sprintf(bv->bp, "%d", numjobs);
bv->bp += strlen(bv->bp);
break;
case 'M':
queue_signals();
if ((hostnam = getsparam("HOST")))
stradd(hostnam);
unqueue_signals();
break;
case 'm':
if (!arg)
arg++;
queue_signals();
if (!(hostnam = getsparam("HOST"))) {
unqueue_signals();
break;
}
if (arg < 0) {
for (ss = hostnam + strlen(hostnam); ss > hostnam; ss--)
if (ss[-1] == '.' && !++arg)
break;
stradd(ss);
} else {
for (ss = hostnam; *ss; ss++)
if (*ss == '.' && !--arg)
break;
stradd(*ss ? dupstrpfx(hostnam, ss - hostnam) : hostnam);
}
unqueue_signals();
break;
case 'S':
txtchangeset(txtchangep, TXTSTANDOUT, TXTNOSTANDOUT);
txtset(TXTSTANDOUT);
tsetcap(TCSTANDOUTBEG, TSC_PROMPT);
break;
case 's':
txtchangeset(txtchangep, TXTNOSTANDOUT, TXTSTANDOUT);
txtunset(TXTSTANDOUT);
tsetcap(TCSTANDOUTEND, TSC_PROMPT|TSC_DIRTY);
break;
case 'B':
txtchangeset(txtchangep, TXTBOLDFACE, TXTNOBOLDFACE);
txtset(TXTBOLDFACE);
tsetcap(TCBOLDFACEBEG, TSC_PROMPT|TSC_DIRTY);
break;
case 'b':
txtchangeset(txtchangep, TXTNOBOLDFACE, TXTBOLDFACE);
txtunset(TXTBOLDFACE);
tsetcap(TCALLATTRSOFF, TSC_PROMPT|TSC_DIRTY);
break;
case 'U':
txtchangeset(txtchangep, TXTUNDERLINE, TXTNOUNDERLINE);
txtset(TXTUNDERLINE);
tsetcap(TCUNDERLINEBEG, TSC_PROMPT);
break;
case 'u':
txtchangeset(txtchangep, TXTNOUNDERLINE, TXTUNDERLINE);
txtunset(TXTUNDERLINE);
tsetcap(TCUNDERLINEEND, TSC_PROMPT|TSC_DIRTY);
break;
case 'F':
atr = parsecolorchar(arg, 1);
if (!(atr & (TXT_ERROR | TXTNOFGCOLOUR))) {
txtchangeset(txtchangep, atr & TXT_ATTR_FG_ON_MASK,
TXTNOFGCOLOUR | TXT_ATTR_FG_COL_MASK);
txtunset(TXT_ATTR_FG_COL_MASK);
txtset(atr & TXT_ATTR_FG_ON_MASK);
set_colour_attribute(atr, COL_SEQ_FG, TSC_PROMPT);
break;
}
/* else FALLTHROUGH */
case 'f':
txtchangeset(txtchangep, TXTNOFGCOLOUR, TXT_ATTR_FG_ON_MASK);
txtunset(TXT_ATTR_FG_ON_MASK);
set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, TSC_PROMPT);
break;
case 'K':
atr = parsecolorchar(arg, 0);
if (!(atr & (TXT_ERROR | TXTNOBGCOLOUR))) {
txtchangeset(txtchangep, atr & TXT_ATTR_BG_ON_MASK,
TXTNOBGCOLOUR | TXT_ATTR_BG_COL_MASK);
txtunset(TXT_ATTR_BG_COL_MASK);
txtset(atr & TXT_ATTR_BG_ON_MASK);
set_colour_attribute(atr, COL_SEQ_BG, TSC_PROMPT);
break;
}
/* else FALLTHROUGH */
case 'k':
txtchangeset(txtchangep, TXTNOBGCOLOUR, TXT_ATTR_BG_ON_MASK);
txtunset(TXT_ATTR_BG_ON_MASK);
set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, TSC_PROMPT);
break;
case '[':
if (idigit(*++bv->fm))
arg = zstrtol(bv->fm, &bv->fm, 10);
if (!prompttrunc(arg, ']', doprint, endchar, txtchangep))
return *bv->fm;
break;
case '<':
case '>':
/* Test (minus) here so -0 means "at the right margin" */
if (minus) {
*bv->bp = '\0';
countprompt(bv->bufline, &t0, 0, 0);
arg = zterm_columns - t0 + arg;
if (arg <= 0)
arg = 1;
}
if (!prompttrunc(arg, *bv->fm, doprint, endchar, txtchangep))
return *bv->fm;
break;
case '{': /*}*/
if (!bv->dontcount++) {
addbufspc(1);
*bv->bp++ = Inpar;
}
if (arg <= 0)
break;
/* else */
/* FALLTHROUGH */
case 'G':
if (arg > 0) {
addbufspc(arg);
while (arg--)
*bv->bp++ = Nularg;
} else {
addbufspc(1);
*bv->bp++ = Nularg;
}
break;
case /*{*/ '}':
if (bv->trunccount && bv->trunccount >= bv->dontcount)
return *bv->fm;
if (bv->dontcount && !--bv->dontcount) {
addbufspc(1);
*bv->bp++ = Outpar;
}
break;
case 't':
case '@':
case 'T':
case '*':
case 'w':
case 'W':
case 'D':
{
char *tmfmt, *dd, *tmbuf = NULL;
switch (*bv->fm) {
case 'T':
tmfmt = "%K:%M";
break;
case '*':
tmfmt = "%K:%M:%S";
break;
case 'w':
tmfmt = "%a %f";
break;
case 'W':
tmfmt = "%m/%d/%y";
break;
case 'D':
if (bv->fm[1] == '{' /*}*/) {
for (ss = bv->fm + 2; *ss && *ss != /*{*/ '}'; ss++)
if(*ss == '\\' && ss[1])
ss++;
dd = tmfmt = tmbuf = zalloc(ss - bv->fm);
for (ss = bv->fm + 2; *ss && *ss != /*{*/ '}';
ss++) {
if(*ss == '\\' && ss[1])
ss++;
*dd++ = *ss;
}
*dd = 0;
bv->fm = ss - !*ss;
if (!*tmfmt) {
free(tmbuf);
continue;
}
} else
tmfmt = "%y-%m-%d";
break;
default:
tmfmt = "%l:%M%p";
break;
}
zgettime(&ts);
tm = localtime(&ts.tv_sec);
/*
* Hack because strftime won't say how
* much space it actually needs. Try to add it
* a few times until it works. Some formats don't
* actually have a length, so we could go on for
* ever.
*/
for(j = 0, t0 = strlen(tmfmt)*8; j < 3; j++, t0*=2) {
addbufspc(t0);
if ((len = ztrftime(bv->bp, t0, tmfmt, tm, ts.tv_nsec))
>= 0)
break;
}
/* There is enough room for this because addbufspc(t0)
* allocates room for t0 * 2 bytes. */
if (len >= 0)
metafy(bv->bp, len, META_NOALLOC);
bv->bp += strlen(bv->bp);
zsfree(tmbuf);
break;
}
case 'n':
stradd(get_username());
break;
case 'l':
if (*ttystrname) {
ss = (strncmp(ttystrname, "/dev/tty", 8) ?
ttystrname + 5 : ttystrname + 8);
stradd(ss);
} else
stradd("()");
break;
case 'y':
if (*ttystrname) {
ss = (strncmp(ttystrname, "/dev/", 5) ?
ttystrname : ttystrname + 5);
stradd(ss);
} else
stradd("()");
break;
case 'L':
addbufspc(DIGBUFSIZE);
#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
sprintf(bv->bp, "%lld", shlvl);
#else
sprintf(bv->bp, "%ld", (long)shlvl);
#endif
bv->bp += strlen(bv->bp);
break;
case '?':
addbufspc(DIGBUFSIZE);
#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
sprintf(bv->bp, "%lld", lastval);
#else
sprintf(bv->bp, "%ld", (long)lastval);
#endif
bv->bp += strlen(bv->bp);
break;
case '%':
case ')':
addbufspc(1);
*bv->bp++ = *bv->fm;
break;
case '#':
addbufspc(1);
*bv->bp++ = privasserted() ? '#' : '%';
break;
case 'v':
if (!arg)
arg = 1;
else if (arg < 0)
arg += arrlen(psvar) + 1;
if (arg > 0 && arrlen_ge(psvar, arg))
stradd(psvar[arg - 1]);
break;
case 'E':
tsetcap(TCCLEAREOL, TSC_PROMPT);
break;
case '^':
if (cmdsp) {
if (arg >= 0) {
if (arg > cmdsp || arg == 0)
arg = cmdsp;
for (t0 = cmdsp - 1; arg--; t0--) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bv->bp++=' ';
}
}
} else {
arg = -arg;
if (arg > cmdsp)
arg = cmdsp;
for (t0 = arg - 1; arg--; t0--) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bv->bp++=' ';
}
}
}
}
break;
case '_':
if (cmdsp) {
if (arg >= 0) {
if (arg > cmdsp || arg == 0)
arg = cmdsp;
for (t0 = cmdsp - arg; arg--; t0++) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bv->bp++=' ';
}
}
} else {
arg = -arg;
if (arg > cmdsp)
arg = cmdsp;
for (t0 = 0; arg--; t0++) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bv->bp++=' ';
}
}
}
}
break;
case 'r':
if(bv->rstring)
stradd(bv->rstring);
break;
case 'R':
if(bv->Rstring)
stradd(bv->Rstring);
break;
case 'e':
{
int depth = 0;
Funcstack fsptr = funcstack;
while (fsptr) {
depth++;
fsptr = fsptr->prev;
}
addbufspc(DIGBUFSIZE);
sprintf(bv->bp, "%d", depth);
bv->bp += strlen(bv->bp);
break;
}
case 'I':
if (funcstack && funcstack->tp != FS_SOURCE &&
!IN_EVAL_TRAP()) {
/*
* We're in a function or an eval with
* EVALLINENO. Calculate the line number in
* the file.
*/
zlong flineno = lineno + funcstack->flineno;
/* take account of eval line nos. starting at 1 */
if (funcstack->tp == FS_EVAL)
lineno--;
addbufspc(DIGBUFSIZE);
#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
sprintf(bv->bp, "%lld", flineno);
#else
sprintf(bv->bp, "%ld", (long)flineno);
#endif
bv->bp += strlen(bv->bp);
break;
}
/* else we're in a file and lineno is already correct */
/* FALLTHROUGH */
case 'i':
addbufspc(DIGBUFSIZE);
#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
sprintf(bv->bp, "%lld", lineno);
#else
sprintf(bv->bp, "%ld", (long)lineno);
#endif
bv->bp += strlen(bv->bp);
break;
case 'x':
if (funcstack && funcstack->tp != FS_SOURCE &&
!IN_EVAL_TRAP())
promptpath(funcstack->filename ? funcstack->filename : "",
arg, 0);
else
promptpath(scriptfilename ? scriptfilename : argzero,
arg, 0);
break;
case '\0':
return 0;
case Meta:
bv->fm++;
break;
}
} else if(*bv->fm == '!' && isset(PROMPTBANG)) {
if(doprint) {
if(bv->fm[1] == '!') {
bv->fm++;
addbufspc(1);
pputc('!');
} else {
addbufspc(DIGBUFSIZE);
convbase(bv->bp, curhist, 10);
bv->bp += strlen(bv->bp);
}
}
} else {
char c = *bv->fm == Meta ? *++bv->fm ^ 32 : *bv->fm;
if (doprint) {
addbufspc(1);
pputc(c);
}
}
}
return *bv->fm;
}
/* pputc adds a character to the buffer, metafying. There must *
* already be space. */
/**/
static void
pputc(char c)
{
if (imeta(c)) {
*bv->bp++ = Meta;
c ^= 32;
}
*bv->bp++ = c;
if (c == '\n' && !bv->dontcount)
bv->bufline = bv->bp;
}
/* Make sure there is room for `need' more characters in the buffer. */
/**/
static void
addbufspc(int need)
{
need *= 2; /* for metafication */
if((bv->bp - bv->buf) + need > bv->bufspc) {
int bo = bv->bp - bv->buf;
int bo1 = bv->bp1 ? bv->bp1 - bv->buf : -1;
ptrdiff_t bufline_off = bv->bufline ? bv->bufline - bv->buf : -1;
if(need & 255)
need = (need | 255) + 1;
bv->buf = realloc(bv->buf, bv->bufspc += need);
memset(bv->buf + bv->bufspc - need, 0, need);
bv->bp = bv->buf + bo;
if(bo1 != -1)
bv->bp1 = bv->buf + bo1;
if (bufline_off != -1)
bv->bufline = bv->buf + bufline_off;
}
}
/* stradd() adds a metafied string to the prompt, *
* in a visible representation. */
/**/
void
stradd(char *d)
{
#ifdef MULTIBYTE_SUPPORT
char *ums, *ups;
int upslen, eol = 0;
mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
ums = ztrdup(d);
ups = unmetafy(ums, &upslen);
/*
* We now have a raw string of possibly multibyte characters.
* Read each character one by one.
*/
while (upslen > 0) {
wchar_t cc;
char *pc;
size_t cnt = eol ? MB_INVALID : mbrtowc(&cc, ups, upslen, &mbs);
switch (cnt) {
case MB_INCOMPLETE:
eol = 1;
/* FALL THROUGH */
case MB_INVALID:
/* Bad character. Take the next byte on its own. */
pc = nicechar(*ups);
cnt = 1;
memset(&mbs, 0, sizeof mbs);
break;
case 0:
cnt = 1;
/* FALL THROUGH */
default:
/* Take full wide character in one go */
mb_charinit();
pc = wcs_nicechar(cc, NULL, NULL);
break;
}
/* Keep output as metafied string. */
addbufspc(strlen(pc));
upslen -= cnt;
ups += cnt;
/* Put printed representation into the buffer */
while (*pc)
*bv->bp++ = *pc++;
}
free(ums);
#else
char *ps, *pc;
addbufspc(niceztrlen(d));
/* This loop puts the nice representation of the string into the
* prompt buffer. */
for (ps = d; *ps; ps++) {
for (pc = nicechar(*ps == Meta ? *++ps^32 : *ps); *pc; pc++)
*bv->bp++ = *pc;
}
#endif
}
/* tsetcap(), among other things, can write a termcap string into the buffer. */
/**/
mod_export void
tsetcap(int cap, int flags)
{
if (tccan(cap) && !isset(SINGLELINEZLE) &&
!(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
switch (flags & TSC_OUTPUT_MASK) {
case TSC_RAW:
tputs(tcstr[cap], 1, putraw);
break;
case 0:
default:
tputs(tcstr[cap], 1, putshout);
break;
case TSC_PROMPT:
if (!bv->dontcount) {
addbufspc(1);
*bv->bp++ = Inpar;
}
tputs(tcstr[cap], 1, putstr);
if (!bv->dontcount) {
int glitch = 0;
if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
glitch = tgetnum("sg");
else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
glitch = tgetnum("ug");
if(glitch < 0)
glitch = 0;
addbufspc(glitch + 1);
while(glitch--)
*bv->bp++ = Nularg;
*bv->bp++ = Outpar;
}
break;
}
if (flags & TSC_DIRTY) {
flags &= ~TSC_DIRTY;
if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
tsetcap(TCBOLDFACEBEG, flags);
if (txtisset(TXTSTANDOUT))
tsetcap(TCSTANDOUTBEG, flags);
if (txtisset(TXTUNDERLINE))
tsetcap(TCUNDERLINEBEG, flags);
if (txtisset(TXTFGCOLOUR))
set_colour_attribute(txtattrmask, COL_SEQ_FG, TSC_PROMPT);
if (txtisset(TXTBGCOLOUR))
set_colour_attribute(txtattrmask, COL_SEQ_BG, TSC_PROMPT);
}
}
}
/**/
int
putstr(int d)
{
addbufspc(1);
pputc(d);
return 0;
}
/*
* Count height etc. of a prompt string returned by promptexpand().
* This depends on the current terminal width, and tabs and
* newlines require nontrivial processing.
* Passing `overf' as -1 means to ignore columns (absolute width).
*
* If multibyte is enabled, take account of multibyte characters
* by locating them and finding out their screen width.
*/
/**/
mod_export void
countprompt(char *str, int *wp, int *hp, int overf)
{
int w = 0, h = 1, multi = 0, wcw = 0;
int s = 1;
#ifdef MULTIBYTE_SUPPORT
char inchar;
mbstate_t mbs;
wchar_t wc;
memset(&mbs, 0, sizeof(mbs));
#endif
for (; *str; str++) {
/*
* Avoid double-incrementing the height when there's a newline in the
* prompt and the line it terminates takes up exactly the width of the
* terminal
*/
while (w > zterm_columns && overf >= 0 && !multi) {
h++;
if (wcw) {
/*
* Wide characters don't get split off. They move to the
* next line if there is not enough space.
*/
w = wcw;
break;
} else {
/*
* Tabs overflow to the next line as if they were made of spaces.
*/
w -= zterm_columns;
}
}
wcw = 0;
/*
* Input string should be metafied, so tokens in it should
* be real tokens, even if there are multibyte characters.
*/
if (*str == Inpar)
s = 0;
else if (*str == Outpar)
s = 1;
else if (*str == Nularg)
w++;
else if (s) {
if (*str == Meta) {
#ifdef MULTIBYTE_SUPPORT
inchar = *++str ^ 32;
#else
str++;
#endif
} else {
#ifdef MULTIBYTE_SUPPORT
/*
* Don't look for tab or newline in the middle
* of a multibyte character. Otherwise, we are
* relying on the character set being an extension
* of ASCII so it's safe to test a single byte.
*/
if (!multi) {
#endif
if (*str == '\t') {
w = (w | 7) + 1;
continue;
} else if (*str == '\n') {
w = 0;
h++;
continue;
}
#ifdef MULTIBYTE_SUPPORT
}
inchar = *str;
#endif
}
#ifdef MULTIBYTE_SUPPORT
switch (mbrtowc(&wc, &inchar, 1, &mbs)) {
case MB_INCOMPLETE:
/* Character is incomplete -- keep looking. */
multi = 1;
break;
case MB_INVALID:
memset(&mbs, 0, sizeof mbs);
/* Invalid character: assume single width. */
multi = 0;
w++;
break;
case 0:
multi = 0;
break;
default:
/*
* If the character isn't printable, WCWIDTH() returns
* -1. We assume width 1.
*/
wcw = WCWIDTH(wc);
if (wcw >= 0)
w += wcw;
else
w++;
multi = 0;
break;
}
#else
w++;
#endif
}
}
/*
* multi may still be set if we were in the middle of the character.
* This isn't easy to handle generally; just assume there's no
* output.
*/
while (w > zterm_columns && overf >= 0) {
h++;
if (wcw) {
w = wcw;
break;
} else {
w -= zterm_columns;
}
}
if (w == zterm_columns && overf == 0) {
w = 0;
h++;
}
if(wp)
*wp = w;
if(hp)
*hp = h;
}
/**/
static int
prompttrunc(int arg, int truncchar, int doprint, int endchar,
zattr *txtchangep)
{
if (arg > 0) {
char ch = *bv->fm, *ptr, *truncstr;
int truncatleft = ch == '<';
int w = bv->bp - bv->buf;
/*
* If there is already a truncation active, return so that
* can be finished, backing up so that the new truncation
* can be started afterwards.
*/
if (bv->truncwidth) {
while (*--bv->fm != '%')
;
bv->fm--;
return 0;
}
bv->truncwidth = arg;
if (*bv->fm != ']')
bv->fm++;
while (*bv->fm && *bv->fm != truncchar) {
if (*bv->fm == '\\' && bv->fm[1])
++bv->fm;
addbufspc(1);
*bv->bp++ = *bv->fm++;
}
if (!*bv->fm)
return 0;
if (bv->bp - bv->buf == w && truncchar == ']') {
addbufspc(1);
*bv->bp++ = '<';
}
ptr = bv->buf + w; /* addbufspc() may have realloc()'d bv->buf */
/*
* Now:
* bv->buf is the start of the output prompt buffer
* ptr is the start of the truncation string
* bv->bp is the end of the truncation string
*/
truncstr = ztrduppfx(ptr, bv->bp - ptr);
bv->bp = ptr;
w = bv->bp - bv->buf;
bv->fm++;
bv->trunccount = bv->dontcount;
putpromptchar(doprint, endchar, txtchangep);
bv->trunccount = 0;
ptr = bv->buf + w; /* putpromptchar() may have realloc()'d */
*bv->bp = '\0';
/*
* Now:
* ptr is the start of the truncation string and also
* where we need to start putting any truncated output
* bv->bp is the end of the string we have just added, which
* may need truncating.
*/
/*
* w below is screen width if multibyte support is enabled
* (note that above it was a raw string pointer difference).
* It's the full width of the string we may need to truncate.
*
* bv->truncwidth has come from the user, so we interpret this
* as a screen width, too.
*/
countprompt(ptr, &w, 0, -1);
if (w > bv->truncwidth) {
/*
* We need to truncate. t points to the truncation string
* -- which is inserted literally, without nice
* representation. twidth is its printing width, and maxwidth
* is the amount of the main string that we want to keep.
* Note that if the truncation string is longer than the
* truncation length (twidth > bv->truncwidth), the truncation
* string is used in full.
*/
char *t = truncstr;
int fullen = bv->bp - ptr;
int twidth, maxwidth;
int ntrunc = strlen(t);
twidth = MB_METASTRWIDTH(t);
if (twidth < bv->truncwidth) {
maxwidth = bv->truncwidth - twidth;
/*
* It's not safe to assume there are no invisible substrings
* just because the width is less than the full string
* length since there may be multibyte characters.
*/
addbufspc(ntrunc+1);
/* may have realloc'd */
ptr = bv->bp - fullen;
if (truncatleft) {
/*
* To truncate at the left, selectively copy
* maxwidth bytes from the main prompt, preceded
* by the truncation string in full.
*
* We're overwriting the string containing the
* text to be truncated, so copy it. We've
* just ensured there's sufficient space at the
* end of the prompt string.
*
* Pointer into text to be truncated.
*/
char *fulltextptr, *fulltext;
int remw;
#ifdef MULTIBYTE_SUPPORT
mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
#endif
fulltextptr = fulltext = ptr + ntrunc;
memmove(fulltext, ptr, fullen);
fulltext[fullen] = '\0';
/* Copy the truncstr into place. */
while (*t)
*ptr++ = *t++;
/*
* Find the point in the text at which we should
* start copying, i.e. when the remaining width
* is less than or equal to the maximum width.
*/
remw = w;
while (remw > maxwidth && *fulltextptr) {
if (*fulltextptr == Inpar) {
/*
* Text marked as invisible: copy
* regardless, since we don't know what
* this does. It only affects the width
* if there are Nularg's present.
* However, even in that case we
* can't break the sequence down, so
* we still loop over the entire group.
*/
for (;;) {
*ptr++ = *fulltextptr;
if (*fulltextptr == '\0' ||
*fulltextptr++ == Outpar)
break;
if (fulltextptr[-1] == Nularg)
remw--;
}
} else {
#ifdef MULTIBYTE_SUPPORT
/*
* Normal text: build up a multibyte character.
*/
char inchar;
wchar_t cc;
int wcw;
/*
* careful: string is still metafied (we
* need that because we don't know a
* priori when to stop and the resulting
* string must be metafied).
*/
if (*fulltextptr == Meta)
inchar = *++fulltextptr ^ 32;
else
inchar = *fulltextptr;
fulltextptr++;
switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
case MB_INCOMPLETE:
/* Incomplete multibyte character. */
break;
case MB_INVALID:
/* Reset invalid state. */
memset(&mbs, 0, sizeof mbs);
/* FALL THROUGH */
case 0:
/* Assume a single-byte character. */
remw--;
break;
default:
wcw = WCWIDTH(cc);
if (wcw >= 0)
remw -= wcw;
else
remw--;
break;
}
#else
/* Single byte character */
if (*fulltextptr == Meta)
fulltextptr++;
fulltextptr++;
remw--;
#endif
}
}
/*
* Now simply copy the rest of the text. Still
* metafied, so this is easy.
*/
while (*fulltextptr)
*ptr++ = *fulltextptr++;
/* Mark the end of copying */
bv->bp = ptr;
} else {
/*
* Truncating at the right is easier: just leave
* enough characters until we have reached the
* maximum width.
*/
char *skiptext = ptr;
#ifdef MULTIBYTE_SUPPORT
mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
#endif
while (maxwidth > 0 && *skiptext) {
if (*skiptext == Inpar) {
/* see comment on left truncation above */
for (;;) {
if (*skiptext == '\0' ||
*skiptext++ == Outpar)
break;
if (skiptext[-1] == Nularg)
maxwidth--;
}
} else {
#ifdef MULTIBYTE_SUPPORT
char inchar;
wchar_t cc;
int wcw;
if (*skiptext == Meta)
inchar = *++skiptext ^ 32;
else
inchar = *skiptext;
skiptext++;
switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
case MB_INCOMPLETE:
/* Incomplete character. */
break;
case MB_INVALID:
/* Reset invalid state. */
memset(&mbs, 0, sizeof mbs);
/* FALL THROUGH */
case 0:
/* Assume a single-byte character. */
maxwidth--;
break;
default:
wcw = WCWIDTH(cc);
if (wcw >= 0)
maxwidth -= wcw;
else
maxwidth--;
break;
}
#else
if (*skiptext == Meta)
skiptext++;
skiptext++;
maxwidth--;
#endif
}
}
/*
* We don't need the visible text from now on,
* but we'd better copy any invisible bits.
* History dictates that these go after the
* truncation string. This is sensible since
* they may, for example, turn off an effect which
* should apply to all text at this point.
*
* Copy the truncstr.
*/
ptr = skiptext;
while (*t)
*ptr++ = *t++;
bv->bp = ptr;
if (*skiptext) {
/* Move remaining text so we don't overwrite it */
memmove(bv->bp, skiptext, strlen(skiptext)+1);
skiptext = bv->bp;
/*
* Copy anything we want, updating bv->bp
*/
while (*skiptext) {
if (*skiptext == Inpar) {
for (;;) {
*bv->bp++ = *skiptext;
if (*skiptext == Outpar ||
*skiptext == '\0')
break;
skiptext++;
}
}
else
skiptext++;
}
}
}
} else {
/* Just copy truncstr; no other text appears. */
while (*t)
*ptr++ = *t++;
bv->bp = ptr;
}
*bv->bp = '\0';
}
zsfree(truncstr);
bv->truncwidth = 0;
/*
* We may have returned early from the previous putpromptchar *
* because we found another truncation following this one. *
* In that case we need to do the rest now. *
*/
if (!*bv->fm)
return 0;
if (*bv->fm != endchar) {
bv->fm++;
/*
* With bv->truncwidth set to zero, we always reach endchar *
* (or the terminating NULL) this time round. *
*/
if (!putpromptchar(doprint, endchar, txtchangep))
return 0;
}
/* Now we have to trick it into matching endchar again */
bv->fm--;
} else {
if (*bv->fm != endchar)
bv->fm++;
while(*bv->fm && *bv->fm != truncchar) {
if (*bv->fm == '\\' && bv->fm[1])
bv->fm++;
bv->fm++;
}
if (bv->truncwidth || !*bv->fm)
return 0;
}
return 1;
}
/**/
void
cmdpush(int cmdtok)
{
if (cmdsp >= 0 && cmdsp < CMDSTACKSZ)
cmdstack[cmdsp++] = (unsigned char)cmdtok;
}
/**/
void
cmdpop(void)
{
if (cmdsp <= 0) {
DPUTS(1, "BUG: cmdstack empty");
fflush(stderr);
} else
cmdsp--;
}
/*****************************************************************************
* Utilities dealing with colour and other forms of highlighting.
*
* These are shared by prompts and by zle, so it's easiest to have them
* in the main shell.
*****************************************************************************/
/* Defines standard ANSI colour names in index order */
static const char *ansi_colours[] = {
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
"default", NULL
};
/* Defines the available types of highlighting */
struct highlight {
const char *name;
zattr mask_on;
zattr mask_off;
};
static const struct highlight highlights[] = {
{ "none", 0, TXT_ATTR_ON_MASK },
{ "bold", TXTBOLDFACE, 0 },
{ "standout", TXTSTANDOUT, 0 },
{ "underline", TXTUNDERLINE, 0 },
{ NULL, 0, 0 }
};
/*
* Return index of ANSI colour for which *teststrp is an abbreviation.
* Any non-alphabetic character ends the abbreviation.
* 8 is the special value for default (note this is *not* the
* right sequence for default which is typically 9).
* -1 is failure.
*/
static int
match_named_colour(const char **teststrp)
{
const char *teststr = *teststrp, *end, **cptr;
int len;
for (end = teststr; ialpha(*end); end++)
;
len = end - teststr;
*teststrp = end;
for (cptr = ansi_colours; *cptr; cptr++) {
if (!strncmp(teststr, *cptr, len))
return cptr - ansi_colours;
}
return -1;
}
/*
* Match just the colour part of a highlight specification.
* If teststrp is NULL, use the already parsed numeric colour.
* Return the attributes to set in the attribute variable.
* Return -1 for out of range. Does not check the character
* following the colour specification.
*/
/**/
mod_export zattr
match_colour(const char **teststrp, int is_fg, int colour)
{
int shft, named = 0, tc;
zattr on;
if (is_fg) {
shft = TXT_ATTR_FG_COL_SHIFT;
on = TXTFGCOLOUR;
tc = TCFGCOLOUR;
} else {
shft = TXT_ATTR_BG_COL_SHIFT;
on = TXTBGCOLOUR;
tc = TCBGCOLOUR;
}
if (teststrp) {
if (**teststrp == '#' && isxdigit((*teststrp)[1])) {
struct color_rgb color;
char *end;
zlong col = zstrtol(*teststrp+1, &end, 16);
if (end - *teststrp == 4) {
color.red = col >> 8 | ((col >> 8) << 4);
color.green = (col & 0xf0) >> 4;
color.green |= color.green << 4;
color.blue = col & 0xf;
color.blue |= color.blue << 4;
} else if (end - *teststrp == 7) {
color.red = col >> 16;
color.green = (col & 0xff00) >> 8;
color.blue = col & 0xff;
} else
return TXT_ERROR;
*teststrp = end;
colour = runhookdef(GETCOLORATTR, &color) - 1;
if (colour == -1) { /* no hook function added, try true color (24-bit) */
colour = (((color.red << 8) + color.green) << 8) + color.blue;
return on | (is_fg ? TXT_ATTR_FG_24BIT : TXT_ATTR_BG_24BIT) |
(zattr)colour << shft;
} else if (colour <= -2) {
return TXT_ERROR;
}
} else if ((named = ialpha(**teststrp))) {
colour = match_named_colour(teststrp);
if (colour == 8) {
/* default */
return is_fg ? TXTNOFGCOLOUR : TXTNOBGCOLOUR;
}
if (colour < 0)
return TXT_ERROR;
}
else {
colour = (int)zstrtol(*teststrp, (char **)teststrp, 10);
if (colour < 0 || colour >= 256)
return TXT_ERROR;
}
}
/*
* Try termcap for numbered characters if possible.
* Don't for named characters, since our best bet
* of getting the names right is with ANSI sequences.
*/
if (!named && tccan(tc)) {
if (tccolours >= 0 && colour >= tccolours) {
/*
* Out of range of termcap colours.
* Can we assume ANSI colours work?
*/
if (colour > 7)
return TXT_ERROR; /* No. */
} else {
/*
* We can handle termcap colours and the number
* is in range, so use termcap.
*/
on |= is_fg ? TXT_ATTR_FG_TERMCAP :
TXT_ATTR_BG_TERMCAP;
}
}
return on | (zattr)colour << shft;
}
/*
* Match a set of highlights in the given teststr.
* Set *on_var to reflect the values found.
* Return a pointer to the first character not consumed.
*/
/**/
mod_export const char *
match_highlight(const char *teststr, zattr *on_var)
{
int found = 1;
*on_var = 0;
while (found && *teststr) {
const struct highlight *hl;
found = 0;
if (strpfx("fg=", teststr) || strpfx("bg=", teststr)) {
int is_fg = (teststr[0] == 'f');
zattr atr;
teststr += 3;
atr = match_colour(&teststr, is_fg, 0);
if (*teststr == ',')
teststr++;
else if (*teststr && *teststr != ' ')
break;
found = 1;
/* skip out of range colours but keep scanning attributes */
if (atr != TXT_ERROR)
*on_var |= atr;
} else {
for (hl = highlights; hl->name; hl++) {
if (strpfx(hl->name, teststr)) {
const char *val = teststr + strlen(hl->name);
if (*val == ',')
val++;
else if (*val && *val != ' ')
break;
*on_var |= hl->mask_on;
*on_var &= ~hl->mask_off;
teststr = val;
found = 1;
}
}
}
}
return teststr;
}
/*
* Count or output a string for colour information: used
* by output_highlight(). count when buf is NULL.
* returned count excludes the terminating null byte.
*/
static int
output_colour(int colour, int fg_bg, int use_tc, int truecol, char *buf)
{
int atrlen = 3, len;
char *ptr = buf;
if (buf) {
strcpy(ptr, fg_bg == COL_SEQ_FG ? "fg=" : "bg=");
ptr += 3;
}
if (truecol) {
/* length of hex triplet always 7, don't need sprintf to count */
atrlen += buf ? sprintf(ptr, "#%02x%02x%02x", colour >> 16,
(colour >> 8) & 0xff, colour & 0xff) : 7;
/* colour should only be > 7 if using termcap but let's be safe. Update:
* currently other places in code don't always imply that colour > 7 =>
* using-termcap - if zle_highlight will be non-default, then it will be
* used instead of termcap even for colour > 7. Here this just emits the
* color number, so it works fine for both zle_highlight and tercap cases
*/
} else if (use_tc || colour > 7) {
char digbuf[DIGBUFSIZE];
sprintf(digbuf, "%d", colour);
len = strlen(digbuf);
atrlen += len;
if (buf)
strcpy(ptr, digbuf);
} else {
len = strlen(ansi_colours[colour]);
atrlen += len;
if (buf)
strcpy(ptr, ansi_colours[colour]);
}
return atrlen;
}
/*
* Count the length needed for outputting highlighting information
* as a string based on the bits for the attributes.
*
* If buf is not NULL, output the strings into the buffer, too.
* As conventional with strings, the allocated length should be
* at least the returned value plus 1 for the NUL byte.
*/
/**/
mod_export int
output_highlight(zattr atr, char *buf)
{
const struct highlight *hp;
int atrlen = 0, len;
char *ptr = buf;
if (atr & TXTFGCOLOUR) {
len = output_colour(txtchangeget(atr, TXT_ATTR_FG_COL),
COL_SEQ_FG,
(atr & TXT_ATTR_FG_TERMCAP),
(atr & TXT_ATTR_FG_24BIT),
ptr);
atrlen += len;
if (buf)
ptr += len;
}
if (atr & TXTBGCOLOUR) {
if (atrlen) {
atrlen++;
if (buf) {
strcpy(ptr, ",");
ptr++;
}
}
len = output_colour(txtchangeget(atr, TXT_ATTR_BG_COL),
COL_SEQ_BG,
(atr & TXT_ATTR_BG_TERMCAP),
(atr & TXT_ATTR_BG_24BIT),
ptr);
atrlen += len;
if (buf)
ptr += len;
}
for (hp = highlights; hp->name; hp++) {
if (hp->mask_on & atr) {
if (atrlen) {
atrlen++;
if (buf) {
strcpy(ptr, ",");
ptr++;
}
}
len = strlen(hp->name);
atrlen += len;
if (buf) {
strcpy(ptr, hp->name);
ptr += len;
}
}
}
if (atrlen == 0) {
if (buf)
strcpy(ptr, "none");
return 4;
}
return atrlen;
}
/* Structure and array for holding special colour terminal sequences */
/* Start of escape sequence for foreground colour */
#define TC_COL_FG_START "\033[3"
/* End of escape sequence for foreground colour */
#define TC_COL_FG_END "m"
/* Code to reset foreground colour */
#define TC_COL_FG_DEFAULT "9"
/* Start of escape sequence for background colour */
#define TC_COL_BG_START "\033[4"
/* End of escape sequence for background colour */
#define TC_COL_BG_END "m"
/* Code to reset background colour */
#define TC_COL_BG_DEFAULT "9"
struct colour_sequences {
char *start; /* Escape sequence start */
char *end; /* Escape sequence terminator */
char *def; /* Code to reset default colour */
};
static struct colour_sequences fg_bg_sequences[2];
/*
* We need a buffer for colour sequence composition. It may
* vary depending on the sequences set. However, it's inefficient
* allocating it separately every time we send a colour sequence,
* so do it once per refresh.
*/
static char *colseq_buf;
/*
* Count how often this has been allocated, for recursive usage.
*/
static int colseq_buf_allocs;
/**/
void
set_default_colour_sequences(void)
{
fg_bg_sequences[COL_SEQ_FG].start = ztrdup(TC_COL_FG_START);
fg_bg_sequences[COL_SEQ_FG].end = ztrdup(TC_COL_FG_END);
fg_bg_sequences[COL_SEQ_FG].def = ztrdup(TC_COL_FG_DEFAULT);
fg_bg_sequences[COL_SEQ_BG].start = ztrdup(TC_COL_BG_START);
fg_bg_sequences[COL_SEQ_BG].end = ztrdup(TC_COL_BG_END);
fg_bg_sequences[COL_SEQ_BG].def = ztrdup(TC_COL_BG_DEFAULT);
}
static void
set_colour_code(char *str, char **var)
{
char *keyseq;
int len;
zsfree(*var);
keyseq = getkeystring(str, &len, GETKEYS_BINDKEY, NULL);
*var = metafy(keyseq, len, META_DUP);
}
/* Allocate buffer for colour code composition */
/**/
mod_export void
allocate_colour_buffer(void)
{
char **atrs;
int lenfg, lenbg, len;
if (colseq_buf_allocs++)
return;
atrs = getaparam("zle_highlight");
if (atrs) {
for (; *atrs; atrs++) {
if (strpfx("fg_start_code:", *atrs)) {
set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_FG].start);
} else if (strpfx("fg_default_code:", *atrs)) {
set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_FG].def);
} else if (strpfx("fg_end_code:", *atrs)) {
set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_FG].end);
} else if (strpfx("bg_start_code:", *atrs)) {
set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_BG].start);
} else if (strpfx("bg_default_code:", *atrs)) {
set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_BG].def);
} else if (strpfx("bg_end_code:", *atrs)) {
set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_BG].end);
}
}
}
lenfg = strlen(fg_bg_sequences[COL_SEQ_FG].def);
/* always need 1 character for non-default code */
if (lenfg < 1)
lenfg = 1;
lenfg += strlen(fg_bg_sequences[COL_SEQ_FG].start) +
strlen(fg_bg_sequences[COL_SEQ_FG].end);
lenbg = strlen(fg_bg_sequences[COL_SEQ_BG].def);
/* always need 1 character for non-default code */
if (lenbg < 1)
lenbg = 1;
lenbg += strlen(fg_bg_sequences[COL_SEQ_BG].start) +
strlen(fg_bg_sequences[COL_SEQ_BG].end);
len = lenfg > lenbg ? lenfg : lenbg;
/* add 1 for the null and 14 for truecolor */
colseq_buf = (char *)zalloc(len+15);
}
/* Free the colour buffer previously allocated. */
/**/
mod_export void
free_colour_buffer(void)
{
if (--colseq_buf_allocs)
return;
DPUTS(!colseq_buf, "Freeing colour sequence buffer without alloc");
/* Free buffer for colour code composition */
free(colseq_buf);
colseq_buf = NULL;
}
/*
* Handle outputting of a colour for prompts or zle.
* colour is the numeric colour, 0 to 255 (or less if termcap
* says fewer are supported).
* fg_bg indicates if we're changing the foreground or background.
* tc indicates the termcap code to use, if appropriate.
* def indicates if we're resetting the default colour.
* use_termcap indicates if we should use termcap to output colours.
* flags is either 0 or TSC_PROMPT.
*/
/**/
mod_export void
set_colour_attribute(zattr atr, int fg_bg, int flags)
{
char *ptr;
int do_free, is_prompt = (flags & TSC_PROMPT) ? 1 : 0;
int colour, tc, def, use_termcap, use_truecolor;
int is_default_zle_highlight = 1;
if (fg_bg == COL_SEQ_FG) {
colour = txtchangeget(atr, TXT_ATTR_FG_COL);
tc = TCFGCOLOUR;
def = txtchangeisset(atr, TXTNOFGCOLOUR);
use_truecolor = txtchangeisset(atr, TXT_ATTR_FG_24BIT);
use_termcap = txtchangeisset(atr, TXT_ATTR_FG_TERMCAP);
} else {
colour = txtchangeget(atr, TXT_ATTR_BG_COL);
tc = TCBGCOLOUR;
def = txtchangeisset(atr, TXTNOBGCOLOUR);
use_truecolor = txtchangeisset(atr, TXT_ATTR_BG_24BIT);
use_termcap = txtchangeisset(atr, TXT_ATTR_BG_TERMCAP);
}
/* Test if current zle_highlight settings are customized, or
* the typical "standard" codes */
if (0 != strcmp(fg_bg_sequences[fg_bg].start, fg_bg == COL_SEQ_FG ? TC_COL_FG_START : TC_COL_BG_START) ||
/* the same in-fix for both FG and BG */
0 != strcmp(fg_bg_sequences[fg_bg].def, TC_COL_FG_DEFAULT) ||
/* the same suffix for both FG and BG */
0 != strcmp(fg_bg_sequences[fg_bg].end, TC_COL_FG_END))
{
is_default_zle_highlight = 0;
}
/*
* If we're not restoring the default, and either have a
* colour value that is too large for ANSI, or have been told
* to use the termcap sequence, try to use the termcap sequence.
* True color is not covered by termcap.
*
* We have already sanitised the values we allow from the
* highlighting variables, so much of this shouldn't be
* necessary at this point, but we might as well be safe.
*/
if (!def && !use_truecolor &&
(is_default_zle_highlight && (colour > 7 || use_termcap)))
{
/*
* We can if it's available, and either we couldn't get
* the maximum number of colours, or the colour is in range.
*/
if (tccan(tc) && (tccolours < 0 || colour < tccolours))
{
if (is_prompt)
{
if (!bv->dontcount) {
addbufspc(1);
*bv->bp++ = Inpar;
}
tputs(tgoto(tcstr[tc], colour, colour), 1, putstr);
if (!bv->dontcount) {
addbufspc(1);
*bv->bp++ = Outpar;
}
} else {
tputs(tgoto(tcstr[tc], colour, colour), 1, putshout);
}
/* That worked. */
return;
}
/*
* Nope, that didn't work.
* If 0 to 7, assume standard ANSI works, if 8 to 255, assume
* typical 256-color escapes works, otherwise it won't.
*/
if (colour > 255)
return;
}
if ((do_free = (colseq_buf == NULL))) {
/* This can happen when moving the cursor in trashzle() */
allocate_colour_buffer();
}
/* Build the reset-code: .start + .def + . end
* or the typical true-color code: .start + 8;2;%d;%d;%d + .end
* or the typical 256-color code: .start + 8;5;%d + .end
*/
if (use_truecolor)
strcpy(colseq_buf, fg_bg == COL_SEQ_FG ? TC_COL_FG_START : TC_COL_BG_START);
else
strcpy(colseq_buf, fg_bg_sequences[fg_bg].start);
ptr = colseq_buf + strlen(colseq_buf);
if (def) {
if (use_truecolor)
strcpy(ptr, fg_bg == COL_SEQ_FG ? TC_COL_FG_DEFAULT : TC_COL_BG_DEFAULT);
else
strcpy(ptr, fg_bg_sequences[fg_bg].def);
while (*ptr)
ptr++;
} else if (use_truecolor) {
ptr += sprintf(ptr, "8;2;%d;%d;%d", colour >> 16,
(colour >> 8) & 0xff, colour & 0xff);
} else if (colour > 7 && colour <= 255) {
ptr += sprintf(ptr, "%d", colour);
} else
*ptr++ = colour + '0';
if (use_truecolor)
strcpy(ptr, fg_bg == COL_SEQ_FG ? TC_COL_FG_END : TC_COL_BG_END);
else
strcpy(ptr, fg_bg_sequences[fg_bg].end);
if (is_prompt) {
if (!bv->dontcount) {
addbufspc(1);
*bv->bp++ = Inpar;
}
tputs(colseq_buf, 1, putstr);
if (!bv->dontcount) {
addbufspc(1);
*bv->bp++ = Outpar;
}
} else
tputs(colseq_buf, 1, putshout);
if (do_free)
free_colour_buffer();
}