mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-12-29 16:25:35 +01:00
2353 lines
56 KiB
C
2353 lines
56 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"
|
|
|
|
/* current text attributes */
|
|
|
|
/**/
|
|
mod_export zattr txtcurrentattrs;
|
|
|
|
/* pending changes for attributes */
|
|
|
|
/**/
|
|
mod_export zattr txtpendingattrs;
|
|
|
|
/* mask of attributes with an unknown state */
|
|
|
|
/**/
|
|
mod_export zattr txtunknownattrs;
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
/**/
|
|
mod_export char *
|
|
promptexpand(char *s, int ns, char *rs, char *Rs)
|
|
{
|
|
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');
|
|
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;
|
|
}
|
|
|
|
/* Get the escape sequence for a given attribute. */
|
|
/**/
|
|
mod_export char *
|
|
zattrescape(zattr atr, int *len)
|
|
{
|
|
struct buf_vars new_vars;
|
|
zattr savecurrent = txtcurrentattrs;
|
|
zattr saveunknown = txtunknownattrs;
|
|
|
|
memset(&new_vars, 0, sizeof(new_vars));
|
|
new_vars.last = bv;
|
|
bv = &new_vars;
|
|
new_vars.bufspc = 256;
|
|
new_vars.bp = new_vars.bufline = new_vars.buf = zshcalloc(new_vars.bufspc);
|
|
new_vars.dontcount = 1;
|
|
|
|
txtunknownattrs = 0;
|
|
treplaceattrs(atr);
|
|
applytextattributes(TSC_PROMPT);
|
|
|
|
bv = new_vars.last;
|
|
|
|
txtpendingattrs = txtcurrentattrs = savecurrent;
|
|
txtunknownattrs = saveunknown;
|
|
|
|
return unmetafy(new_vars.buf, len);
|
|
}
|
|
|
|
/* Parse the argument for %H */
|
|
/**/
|
|
mod_export char *
|
|
parsehighlight(char *arg, char endchar, zattr *atr)
|
|
{
|
|
static int entered = 0;
|
|
char *var = ".zle.hlgroups";
|
|
struct value vbuf;
|
|
Value v;
|
|
char *ep, *attrs;
|
|
if ((ep = strchr(arg, endchar)))
|
|
*ep = '\0';
|
|
if (!entered && (v = getvalue(&vbuf, &var, 0)) &&
|
|
PM_TYPE(v->pm->node.flags) == PM_HASHED)
|
|
{
|
|
Param node;
|
|
HashTable ht = v->pm->gsu.h->getfn(v->pm);
|
|
if (ht && (node = (Param) ht->getnode(ht, arg))) {
|
|
attrs = node->gsu.s->getfn(node);
|
|
entered = 1;
|
|
if (match_highlight(attrs, atr, 0) == attrs)
|
|
*atr = TXT_ERROR;
|
|
} else
|
|
*atr = TXT_ERROR;
|
|
} else
|
|
*atr = TXT_ERROR;
|
|
if (ep)
|
|
*ep++ = endchar;
|
|
else
|
|
ep = strchr(arg, '\0');
|
|
entered = 0;
|
|
return ep;
|
|
}
|
|
|
|
/* 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;
|
|
int ops = opts[PROMPTSUBST], opb = opts[PROMPTBANG];
|
|
int opp = opts[PROMPTPERCENT];
|
|
|
|
opts[PROMPTPERCENT] = 1;
|
|
opts[PROMPTSUBST] = opts[PROMPTBANG] = 0;
|
|
|
|
*ep = '\0';
|
|
/* expand the contents of the argument so you can use
|
|
* %v for example */
|
|
coll = col = promptexpand(bv->fm, 0, NULL, NULL);
|
|
*ep = oc;
|
|
arg = match_colour((const char **)&coll, is_fg, 0);
|
|
free(col);
|
|
bv->fm = ep;
|
|
|
|
opts[PROMPTSUBST] = ops;
|
|
opts[PROMPTBANG] = opb;
|
|
opts[PROMPTPERCENT] = opp;
|
|
} else {
|
|
arg = match_colour((const char **)&bv->fm, is_fg, 0);
|
|
if (*bv->fm != '}')
|
|
bv->fm--;
|
|
}
|
|
} else
|
|
arg = match_colour(NULL, is_fg, 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)
|
|
{
|
|
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) ||
|
|
!*++bv->fm ||
|
|
!putpromptchar(test == 0 && doprint, ')')) {
|
|
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':
|
|
tsetattrs(TXTSTANDOUT);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 's':
|
|
tunsetattrs(TXTSTANDOUT);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'B':
|
|
tsetattrs(TXTBOLDFACE);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'b':
|
|
tunsetattrs(TXTBOLDFACE);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'U':
|
|
tsetattrs(TXTUNDERLINE);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'u':
|
|
tunsetattrs(TXTUNDERLINE);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'F':
|
|
atr = parsecolorchar(arg, 1);
|
|
if (atr && atr != TXT_ERROR) {
|
|
tsetattrs(atr);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
}
|
|
/* else FALLTHROUGH */
|
|
case 'f':
|
|
tunsetattrs(TXTFGCOLOUR);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'K':
|
|
atr = parsecolorchar(arg, 0);
|
|
if (atr && atr != TXT_ERROR) {
|
|
tsetattrs(atr);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
}
|
|
/* else FALLTHROUGH */
|
|
case 'k':
|
|
tunsetattrs(TXTBGCOLOUR);
|
|
applytextattributes(TSC_PROMPT);
|
|
break;
|
|
case 'H':
|
|
if (bv->fm[1] == '{') {
|
|
bv->fm = parsehighlight(bv->fm + 2, '}', &atr);
|
|
--bv->fm;
|
|
if (atr != TXT_ERROR) {
|
|
treplaceattrs(atr);
|
|
applytextattributes(TSC_PROMPT);
|
|
}
|
|
}
|
|
break;
|
|
case '[':
|
|
if (idigit(*++bv->fm))
|
|
arg = zstrtol(bv->fm, &bv->fm, 10);
|
|
if (!prompttrunc(arg, ']', doprint, endchar))
|
|
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))
|
|
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) && !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
|
|
switch (flags) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**/
|
|
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)
|
|
{
|
|
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);
|
|
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))
|
|
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--;
|
|
}
|
|
|
|
/* functions for handling attributes */
|
|
|
|
/**/
|
|
mod_export void
|
|
applytextattributes(int flags)
|
|
{
|
|
zattr change = txtcurrentattrs ^ txtpendingattrs;
|
|
zattr keepon = ~change & txtpendingattrs & TXT_ATTR_ALL;
|
|
zattr turnoff = change & ~txtpendingattrs & TXT_ATTR_ALL;
|
|
int keepcount, turncount = 0;
|
|
|
|
/* bail out early if we wouldn't do anything */
|
|
if (!change)
|
|
return;
|
|
|
|
if (txtunknownattrs) {
|
|
txtunknownattrs &= ~change; /* changes cease to be unknown */
|
|
/* can't turn unknown attrs back on so avoid wiping them */
|
|
keepcount = 1;
|
|
} else {
|
|
/* If we want to turn off more attributes than we want to keep on
|
|
* then it takes fewer termcap sequences to just turn off all the
|
|
* attributes. */
|
|
for (keepcount = 0; keepon; keepcount++) /* count bits */
|
|
keepon &= keepon - 1;
|
|
for (; turnoff; turncount++)
|
|
turnoff &= turnoff - 1;
|
|
}
|
|
|
|
/* enabling bold can be relied upon to disable faint
|
|
* (the converse not so as that commonly does nothing at all) */
|
|
if (txtcurrentattrs & TXTFAINT && txtpendingattrs & TXTBOLDFACE) {
|
|
--turncount;
|
|
change &= ~TXTFAINT;
|
|
}
|
|
|
|
if (keepcount < turncount ||
|
|
(change & ~txtpendingattrs & TXT_ATTR_FONT_WEIGHT)) {
|
|
tsetcap(TCALLATTRSOFF, flags);
|
|
/* this cleared all attributes, may need to restore some */
|
|
change = txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs;
|
|
txtunknownattrs = 0;
|
|
} else {
|
|
if (change & ~txtpendingattrs & TXTSTANDOUT) {
|
|
tsetcap(TCSTANDOUTEND, flags);
|
|
/* in some cases, that clears all attributes */
|
|
change = (txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs) |
|
|
(TXTUNDERLINE & change);
|
|
}
|
|
if (change & ~txtpendingattrs & TXTUNDERLINE) {
|
|
tsetcap(TCUNDERLINEEND, flags);
|
|
/* in some cases, that clears all attributes */
|
|
change = txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs;
|
|
}
|
|
if (change & ~txtpendingattrs & TXTITALIC)
|
|
tsetcap(TCITALICSEND, flags);
|
|
}
|
|
if (change & txtpendingattrs & TXTBOLDFACE)
|
|
tsetcap(TCBOLDFACEBEG, flags);
|
|
if (change & txtpendingattrs & TXTFAINT)
|
|
tsetcap(TCFAINTBEG, flags);
|
|
if (change & txtpendingattrs & TXTSTANDOUT)
|
|
tsetcap(TCSTANDOUTBEG, flags);
|
|
if (change & txtpendingattrs & TXTUNDERLINE)
|
|
tsetcap(TCUNDERLINEBEG, flags);
|
|
if (change & txtpendingattrs & TXTITALIC)
|
|
tsetcap(TCITALICSBEG, flags);
|
|
|
|
if (change & TXT_ATTR_FG_MASK)
|
|
set_colour_attribute(txtpendingattrs, COL_SEQ_FG, flags);
|
|
if (change & TXT_ATTR_BG_MASK)
|
|
set_colour_attribute(txtpendingattrs, COL_SEQ_BG, flags);
|
|
|
|
txtcurrentattrs = txtpendingattrs;
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
cleartextattributes(int flags)
|
|
{
|
|
treplaceattrs(0);
|
|
applytextattributes(flags);
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
treplaceattrs(zattr newattrs)
|
|
{
|
|
if (newattrs == TXT_ERROR)
|
|
return;
|
|
|
|
if (txtunknownattrs) {
|
|
/* Set current attributes to the opposite of the new ones
|
|
* for any that are unknown so that applytextattributes()
|
|
* detects them as changed. */
|
|
txtcurrentattrs &= ~txtunknownattrs;
|
|
txtcurrentattrs |= txtunknownattrs & ~newattrs;
|
|
}
|
|
|
|
txtpendingattrs = newattrs;
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
tsetattrs(zattr newattrs)
|
|
{
|
|
/* assume any unknown attributes that we're now setting were unset */
|
|
txtcurrentattrs &= ~(newattrs & txtunknownattrs);
|
|
|
|
txtpendingattrs |= newattrs & TXT_ATTR_ALL;
|
|
if (newattrs & TXTFGCOLOUR) {
|
|
txtpendingattrs &= ~TXT_ATTR_FG_MASK;
|
|
txtpendingattrs |= newattrs & TXT_ATTR_FG_MASK;
|
|
}
|
|
if (newattrs & TXTBGCOLOUR) {
|
|
txtpendingattrs &= ~TXT_ATTR_BG_MASK;
|
|
txtpendingattrs |= newattrs & TXT_ATTR_BG_MASK;
|
|
}
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
tunsetattrs(zattr newattrs)
|
|
{
|
|
/* assume any unknown attributes that we're now unsetting were set */
|
|
txtcurrentattrs |= newattrs & txtunknownattrs;
|
|
|
|
txtpendingattrs &= ~(newattrs & TXT_ATTR_ALL);
|
|
if (newattrs & TXTFGCOLOUR)
|
|
txtpendingattrs &= ~TXT_ATTR_FG_MASK;
|
|
if (newattrs & TXTBGCOLOUR)
|
|
txtpendingattrs &= ~TXT_ATTR_BG_MASK;
|
|
}
|
|
|
|
/* Merge two attribute sets. In an case where attributes might conflict
|
|
* choose those from the first parameter. Foreground and background
|
|
* colours are taken together - less likely to end up with unreadable
|
|
* combinations. */
|
|
|
|
/**/
|
|
mod_export zattr
|
|
mixattrs(zattr primary, zattr secondary)
|
|
{
|
|
zattr result = secondary;
|
|
/* take colours from primary */
|
|
if (primary & (TXTFGCOLOUR|TXTBGCOLOUR))
|
|
result &= ~TXT_ATTR_COLOUR_MASK;
|
|
/* take font weight from primary */
|
|
if (primary & TXT_ATTR_FONT_WEIGHT)
|
|
result &= ~TXT_ATTR_FONT_WEIGHT;
|
|
return result | primary;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* 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_ALL },
|
|
{ "bold", TXTBOLDFACE, TXTFAINT },
|
|
{ "faint", TXTFAINT, TXTBOLDFACE },
|
|
{ "standout", TXTSTANDOUT, 0 },
|
|
{ "underline", TXTUNDERLINE, 0 },
|
|
{ "italic", TXTITALIC, 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 TXT_ERROR 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((unsigned char) (*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 0;
|
|
if (colour < 0)
|
|
return TXT_ERROR;
|
|
}
|
|
else {
|
|
colour = (int)zstrtol(*teststrp, (char **)teststrp, 10);
|
|
if (colour < 0 || colour >= 256)
|
|
return TXT_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Out of range of termcap colours and basic ANSI set. */
|
|
if (tccan(tc) && colour > 7 && colour >= tccolours)
|
|
return TXT_ERROR;
|
|
|
|
return on | (zattr)colour << shft;
|
|
}
|
|
|
|
/*
|
|
* Match a set of highlights in the given teststr.
|
|
* Set *on_var to reflect the values found.
|
|
* Set *layer to the layer
|
|
* Return a pointer to the first character not consumed.
|
|
*/
|
|
|
|
/**/
|
|
mod_export const char *
|
|
match_highlight(const char *teststr, zattr *on_var, int *layer)
|
|
{
|
|
int found = 1;
|
|
|
|
*on_var = 0;
|
|
while (found && *teststr) {
|
|
const struct highlight *hl;
|
|
zattr atr = 0;
|
|
|
|
found = 0;
|
|
if (strpfx("hl=", teststr)) {
|
|
teststr += 3;
|
|
teststr = parsehighlight((char *)teststr, ',', &atr);
|
|
if (atr != TXT_ERROR)
|
|
*on_var = atr;
|
|
found = 1;
|
|
} else if (strpfx("fg=", teststr) || strpfx("bg=", teststr)) {
|
|
int is_fg = (teststr[0] == 'f');
|
|
|
|
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 if (layer && strpfx("layer=", teststr)) {
|
|
teststr += 6;
|
|
*layer = (int) zstrtol(teststr, (char **) &teststr, 10);
|
|
if (*teststr == ',')
|
|
teststr++;
|
|
else if (*teststr && *teststr != ' ')
|
|
break;
|
|
found = 1;
|
|
} 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 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 (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_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_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.
|
|
* 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_truecolor;
|
|
int is_default_zle_highlight = 1;
|
|
|
|
if (fg_bg == COL_SEQ_FG) {
|
|
colour = txtchangeget(atr, TXT_ATTR_FG_COL);
|
|
tc = TCFGCOLOUR;
|
|
def = !(atr & TXTFGCOLOUR);
|
|
use_truecolor = atr & TXT_ATTR_FG_24BIT;
|
|
} else {
|
|
colour = txtchangeget(atr, TXT_ATTR_BG_COL);
|
|
tc = TCBGCOLOUR;
|
|
def = !(atr & TXTBGCOLOUR);
|
|
use_truecolor = atr & TXT_ATTR_BG_24BIT;
|
|
}
|
|
|
|
/* 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 or applying true color,
|
|
* try to use the termcap sequence.
|
|
*
|
|
* 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)
|
|
{
|
|
/*
|
|
* 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,
|
|
(flags & TSC_RAW) ? putraw : 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, (flags & TSC_RAW) ? putraw : putshout);
|
|
|
|
if (do_free)
|
|
free_colour_buffer();
|
|
}
|