54043, 54055: allow highlighing attributes to be turned back off

This commit is contained in:
Oliver Kiddle
2025-11-12 07:54:21 +01:00
parent 4c32677ea8
commit d8666da9de
10 changed files with 175 additions and 76 deletions

View File

@@ -1,5 +1,11 @@
2025-11-12 Oliver Kiddle <opk@zsh.org>
* 54043, 54055 (tweaked to use "reset" as suggested by Mikael):
Doc/Zsh/zle.yo, Src/prompt.c, Src/Modules/hlgroup.c,
Src/Modules/watch.c, Src/Zle/complist.c, Src/Zle/zle.h,
Src/Zle/zle_main.c, Src/Zle/zle_refresh.c, Src/Zle/zle_tricky.c:
allow highlighing attributes to be turned back off
* 54037: Src/Zle/zle_refresh.c: fix to highlight layers where
special is assigned a low layer

View File

@@ -990,14 +990,14 @@ The var(token) is preserved verbatim but not parsed in any way.
Plugins may use this to identify array elements they have added: for example,
a plugin might set var(token) to its (the plugin's) name and then use
`tt(region_highlight=+LPAR() ${region_highlight:#*memo=)var(token)tt(} +RPAR())'
in order to remove array elements it have added.
in order to remove array elements it added.
(This example uses the `tt(${)var(name)tt(:#)var(pattern)tt(})' array-grepping
syntax described in
sectref(Parameter Expansion)(zshexpn).))
enditemize()
For example,
For example,
example(region_highlight=("P0 20 bold memo=foobar"))
@@ -1009,12 +1009,16 @@ as soon as the line is accepted.
Note that zsh 5.8 and older do not support the `tt(memo=)var(token)' field
and may misparse the third (highlight specification) field when a memo
is given.
COMMENT(The syntax `tt(0 20 bold, memo=foobar)' (with an auxiliary comma)
happens to work on both zsh <=5.8 and zsh 5.9-to-be, but that seems to be more of
is given. COMMENT(The syntax `tt(0 20 bold, memo=foobar)' (with an auxiliary comma)
happens to work on both zsh <=5.8 and zsh 5.9, but that seems to be more of
an accident of implementation than something we should make a first-class-citizen
API promise. It's mentioned in the "Incompatibilities" section of README.)
Where a particular region is covered by multiple entries in
tt(region_highlight), their effects are merged. In the case of conflicts, later
entries take precedence over earlier ones. This precedence ordering can be
overridden by specifying layers.
The final highlighting on the command line depends on both tt(region_highlight)
and tt(zle_highlight); see
sectref(Character Highlighting)(below) for details.
@@ -2818,9 +2822,16 @@ not all types of highlighting are available on all terminals:
startitem()
item(tt(none))(
No highlighting is applied to the given context. It is not useful for
this to appear with other types of highlighting; it is used to override
a default.
No additional highlighting is applied to the given context. It is not
useful for this to appear with other types of highlighting; it is used to
override a default. Any inherited highlighting is retained so for example,
with tt(region:none), highlighting associated with tt(default) is still used
for the region.
)
item(tt(reset))(
The terminal's default highlighting is applied to the given context.
This can be useful as a way to clear all inherited highlighting, so
may be used before naming other types of highlighting.
)
item(tt(fg=)var(colour))(
The foreground colour should be set to var(colour), a decimal integer,
@@ -2888,12 +2899,20 @@ the associative array tt(.zle.hlgroups) to determine the actual highlighting.
item(tt(layer=)var(layer))(
The layer is used to determine precedence when multiple highlighting regions
overlap. The var(layer) is a decimal integer, with higher numbers taking
precedence over lower numbers. The default layer is 10 with 30 used as the
default for tt(special), 20 for tt(region) and tt(isearch) and 15 for
tt(paste).
)
precedence over lower numbers. This cannot be used with the tt(default) and
tt(ellipsis) fields of tt(zle_highlight) because they treated as base layers.
With the other fields 30 applies by default for tt(special), 20 for tt(region)
and tt(isearch) and 15 for tt(paste). Highlighting defined in
tt(region_highlight) defaults to layer 10 and would take precedence over
highlighting for any fields of tt(zle_highlight) that are assigned to the same
layer.)
enditem()
In addition, the simple highlighting types can be prefixed with tt("no") to
explicitly disable them. This is useful where highlighting is merged with
inherited highlighting from other definitions. Note that tt(nobold) selects a
normal font weight so also has the effect of disabling tt(faint).
The characters described above as `special' are as follows. The
formatting described here is used irrespective of whether the characters
are highlighted:

View File

@@ -43,7 +43,7 @@ convertattr(char *attrstr, int sgr)
char *r, *s;
int len;
match_highlight(attrstr, &atr, NULL);
match_highlight(attrstr, &atr, NULL, NULL);
s = zattrescape(atr, sgr ? NULL : &len);
if (sgr) {

View File

@@ -375,7 +375,7 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini)
break;
case 'H':
if (*fmt == '{') {
fmt = parsehighlight(fmt + 1, '}', &atr);
fmt = parsehighlight(fmt + 1, '}', &atr, NULL);
if (atr && atr != TXT_ERROR)
treplaceattrs(atr);
}

View File

@@ -1183,7 +1183,7 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop)
break;
case ZWC('H'):
if (*p == '{') {
p = parsehighlight(p + 1, '}', &atr);
p = parsehighlight(p + 1, '}', &atr, NULL);
if (atr != TXT_ERROR && dopr)
treplaceattrs(atr);
}

View File

@@ -433,8 +433,10 @@ enum {
* and mark.
*/
struct region_highlight {
/* Attributes turned on in the region */
/* Attributes for the region */
zattr atr;
/* Explicitly set attributes for the region */
zattr atrmask;
/* Priority for this region relative to others that overlap */
int layer;
/* Start of the region */

View File

@@ -1277,7 +1277,7 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish)
raw_rp = rp;
rpromptbuf = promptexpand(rp ? *rp : NULL, 1, markers[2], NULL, NULL);
rpmpt_attr = txtcurrentattrs;
prompt_attr = mixattrs(pmpt_attr, rpmpt_attr);
prompt_attr = mixattrs(pmpt_attr, pmpt_attr, rpmpt_attr);
free_prepostdisplay();
zlereadflags = flags;
@@ -2032,7 +2032,7 @@ reexpandprompt(void)
new_rprompt = promptexpand(raw_rp ? *raw_rp : NULL, 1, markers[2], NULL, NULL);
rpmpt_attr = txtcurrentattrs;
prompt_attr = mixattrs(pmpt_attr, rpmpt_attr);
prompt_attr = mixattrs(pmpt_attr, pmpt_attr, rpmpt_attr);
free(rpromptbuf);
rpromptbuf = new_rprompt;
} while (looping != reexpanding);

View File

@@ -208,7 +208,7 @@ int predisplaylen, postdisplaylen;
* and for ellipsis continuation markers.
*/
static zattr default_attr, special_attr, ellipsis_attr;
static zattr default_attr, special_attr, special_mask, ellipsis_attr;
/*
* Layer applied to highlighting for special characters
@@ -359,28 +359,33 @@ zle_set_highlight(void)
paste_attr_set = region_attr_set =
isearch_attr_set = suffix_attr_set = 1;
} else if (strpfx("default:", *atrs)) {
match_highlight(*atrs + 8, &default_attr, NULL);
match_highlight(*atrs + 8, &default_attr, NULL, NULL);
} else if (strpfx("special:", *atrs)) {
match_highlight(*atrs + 8, &special_attr, &special_layer);
match_highlight(*atrs + 8, &special_attr, &special_mask,
&special_layer);
special_attr_set = 1;
} else if (strpfx("region:", *atrs)) {
match_highlight(*atrs + 7, &(region_highlights[0].atr),
&(region_highlights[0].atrmask),
&(region_highlights[0].layer));
region_attr_set = 1;
} else if (strpfx("isearch:", *atrs)) {
match_highlight(*atrs + 8, &(region_highlights[1].atr),
&(region_highlights[1].atrmask),
&(region_highlights[1].layer));
isearch_attr_set = 1;
} else if (strpfx("suffix:", *atrs)) {
match_highlight(*atrs + 7, &(region_highlights[2].atr),
&(region_highlights[2].atrmask),
&(region_highlights[2].layer));
suffix_attr_set = 1;
} else if (strpfx("paste:", *atrs)) {
match_highlight(*atrs + 6, &(region_highlights[3].atr),
&(region_highlights[3].atrmask),
&(region_highlights[3].layer));
paste_attr_set = 1;
} else if (strpfx("ellipsis:", *atrs)) {
match_highlight(*atrs + 9, &ellipsis_attr, NULL);
match_highlight(*atrs + 9, &ellipsis_attr, NULL, NULL);
ellipsis_attr_set = 1;
}
}
@@ -388,15 +393,15 @@ zle_set_highlight(void)
/* Default attributes */
if (!special_attr_set)
special_attr = TXTSTANDOUT;
special_attr = special_mask = TXTSTANDOUT;
if (!region_attr_set)
region_highlights[0].atr = TXTSTANDOUT;
region_highlights[0].atr = region_highlights[0].atrmask = TXTSTANDOUT;
if (!isearch_attr_set)
region_highlights[1].atr = TXTUNDERLINE;
region_highlights[1].atr = region_highlights[1].atrmask = TXTUNDERLINE;
if (!suffix_attr_set)
region_highlights[2].atr = TXTBOLDFACE;
region_highlights[2].atr = region_highlights[2].atrmask = TXTBOLDFACE;
if (!paste_attr_set)
region_highlights[3].atr = TXTSTANDOUT;
region_highlights[3].atr = region_highlights[3].atrmask = TXTSTANDOUT;
if (!ellipsis_attr_set)
ellipsis_attr = TXTBGCOLOUR | ((zattr) 3 << TXT_ATTR_BG_COL_SHIFT) |
TXTFGCOLOUR | ((zattr) 4 << TXT_ATTR_FG_COL_SHIFT);
@@ -442,7 +447,7 @@ get_region_highlight(UNUSED(Param pm))
int offset;
const char memo_equals[] = " memo=";
int alloclen = sprintf(digbuf, "%d %d", rhp->start, rhp->end) +
output_highlight(rhp->atr, NULL) +
output_highlight(rhp->atr, rhp->atrmask, NULL) +
2; /* space and terminating NUL */
if (rhp->flags & ZRH_PREDISPLAY)
alloclen++; /* "P" */
@@ -461,7 +466,7 @@ get_region_highlight(UNUSED(Param pm))
offset = sprintf(*arrp, "%s%s ",
(rhp->flags & ZRH_PREDISPLAY) ? "P" : "",
digbuf);
(void)output_highlight(rhp->atr, *arrp + offset);
(void)output_highlight(rhp->atr, rhp->atrmask, *arrp + offset);
if (rhp->layer != 10)
strcat(*arrp, layerbuf);
@@ -537,7 +542,8 @@ set_region_highlight(UNUSED(Param pm), char **aval)
strp++;
rhp->layer = 10; /* default */
strp = (char*) match_highlight(strp, &rhp->atr, &rhp->layer);
strp = (char*) match_highlight(strp, &rhp->atr, &rhp->atrmask,
&rhp->layer);
while (inblank(*strp))
strp++;
@@ -1155,6 +1161,9 @@ zrefresh(void)
zputs("\n", shout); /* works with both hasam and !hasam */
/* lpromptbuf includes literal escapes so we need to update for it */
txtcurrentattrs = txtpendingattrs = pmpt_attr;
/* Unknown attributes remain so but if sequences were embedded
* directly in the prompt, let's not needlessly reset them. */
txtunknownattrs = 0;
}
if (clearflag) {
zputc(&zr_cr);
@@ -1197,7 +1206,7 @@ zrefresh(void)
rpms.s = nbuf[rpms.ln = 0] + lpromptw;
rpms.sen = *nbuf + winw;
for (t = tmpline, tmppos = 0; tmppos < tmpll; t++, tmppos++) {
zattr base_attr = mixattrs(default_attr, prompt_attr);
zattr base_attr = mixattrs(default_attr, default_attr, prompt_attr);
zattr all_attr = 0;
struct region_highlight *rhp;
int layer, nextlayer = 0;
@@ -1219,9 +1228,10 @@ zrefresh(void)
offset = predisplaylen; /* increment over it */
if (rhp->start + offset <= tmppos &&
tmppos < rhp->end + offset) {
base_attr = mixattrs(rhp->atr, base_attr);
base_attr = mixattrs(rhp->atr, rhp->atrmask, base_attr);
if (layer > special_layer)
all_attr = mixattrs(rhp->atr, all_attr);
all_attr = mixattrs(rhp->atr, rhp->atrmask,
all_attr);
}
} else if (rhp->layer > layer &&
(rhp->layer < nextlayer || nextlayer <= layer)) {
@@ -1229,7 +1239,7 @@ zrefresh(void)
}
}
if (special_layer == layer) {
all_attr = mixattrs(special_attr, base_attr);
all_attr = mixattrs(special_attr, special_mask, base_attr);
}
} while (nextlayer > layer);
@@ -2467,7 +2477,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs)
}
}
}
all_attr = mixattrs(special_attr, base_attr);
all_attr = mixattrs(special_attr, special_mask, base_attr);
if (t0 == tmpcs)
nvcs = vp - vbuf;

View File

@@ -2503,7 +2503,7 @@ printfmt(char *fmt, int n, int dopr, int doesc)
break;
case 'H':
if (p[1] == '{') {
p = parsehighlight(p + 2, '}', &atr);
p = parsehighlight(p + 2, '}', &atr, NULL);
--p;
if (atr != TXT_ERROR)
treplaceattrs(atr);

View File

@@ -277,7 +277,7 @@ zattrescape(zattr atr, int *len)
/* Parse the argument for %H */
/**/
mod_export char *
parsehighlight(char *arg, char endchar, zattr *atr)
parsehighlight(char *arg, char endchar, zattr *atr, zattr *mask)
{
static int entered = 0;
char *var = ".zle.hlgroups";
@@ -294,7 +294,7 @@ parsehighlight(char *arg, char endchar, zattr *atr)
if (ht && (node = (Param) ht->getnode(ht, arg))) {
attrs = node->gsu.s->getfn(node);
entered = 1;
if (match_highlight(attrs, atr, 0) == attrs)
if (match_highlight(attrs, atr, mask, NULL) == attrs)
*atr = TXT_ERROR;
} else
*atr = TXT_ERROR;
@@ -639,7 +639,7 @@ putpromptchar(int doprint, int endchar)
break;
case 'H':
if (bv->fm[1] == '{') {
bv->fm = parsehighlight(bv->fm + 2, '}', &atr);
bv->fm = parsehighlight(bv->fm + 2, '}', &atr, NULL);
--bv->fm;
if (atr != TXT_ERROR) {
treplaceattrs(atr);
@@ -1757,23 +1757,27 @@ tunsetattrs(zattr newattrs)
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. */
/* Merge two attribute sets.
* secondary is the background base attributes
* primary is attributes to be overlaid, taking precedence.
* mask indicates those attributes in primary that were explicitly
* set allowing an explicitly disabled attribute in primary to take
* precedence. */
/**/
mod_export zattr
mixattrs(zattr primary, zattr secondary)
mixattrs(zattr primary, zattr mask, 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;
zattr select = mask & TXT_ATTR_ALL;
if (mask & TXTFGCOLOUR)
select |= TXT_ATTR_FG_MASK;
if (mask & TXTBGCOLOUR)
select |= TXT_ATTR_BG_MASK;
if (mask & TXT_ATTR_FONT_WEIGHT)
select |= TXT_ATTR_FONT_WEIGHT;
return (primary & select) | (secondary & ~select);
}
/*****************************************************************************
@@ -1797,7 +1801,7 @@ struct highlight {
};
static const struct highlight highlights[] = {
{ "none", 0, TXT_ATTR_ALL },
{ "reset", 0, TXT_ATTR_ALL },
{ "bold", TXTBOLDFACE, TXTFAINT },
{ "faint", TXTFAINT, TXTBOLDFACE },
{ "standout", TXTSTANDOUT, 0 },
@@ -1923,15 +1927,17 @@ match_colour(const char **teststrp, int is_fg, int colour)
/*
* Match a set of highlights in the given teststr.
* Set *on_var to reflect the values found.
* Set *setmask to explicitly set attributes
* 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)
match_highlight(const char *teststr, zattr *on_var, zattr *setmask, int *layer)
{
int found = 1;
zattr mask = 0;
*on_var = 0;
while (found && *teststr) {
@@ -1941,7 +1947,7 @@ match_highlight(const char *teststr, zattr *on_var, int *layer)
found = 0;
if (strpfx("hl=", teststr)) {
teststr += 3;
teststr = parsehighlight((char *)teststr, ',', &atr);
teststr = parsehighlight((char *)teststr, ',', &atr, &mask);
if (atr != TXT_ERROR)
*on_var = atr;
found = 1;
@@ -1958,6 +1964,7 @@ match_highlight(const char *teststr, zattr *on_var, int *layer)
/* skip out of range colours but keep scanning attributes */
if (atr != TXT_ERROR)
*on_var |= atr;
mask |= is_fg ? TXTFGCOLOUR : TXTBGCOLOUR;
} else if (layer && strpfx("layer=", teststr)) {
teststr += 6;
*layer = (int) zstrtol(teststr, (char **) &teststr, 10);
@@ -1967,7 +1974,8 @@ match_highlight(const char *teststr, zattr *on_var, int *layer)
break;
found = 1;
} else {
for (hl = highlights; hl->name; hl++) {
int turn_off = 0;
for (hl = highlights; !found && hl->name; hl++) {
if (strpfx(hl->name, teststr)) {
const char *val = teststr + strlen(hl->name);
@@ -1976,14 +1984,25 @@ match_highlight(const char *teststr, zattr *on_var, int *layer)
else if (*val && *val != ' ')
break;
*on_var |= hl->mask_on;
*on_var &= ~hl->mask_off;
if (turn_off) {
*on_var &= ~hl->mask_on & ~hl->mask_off;
} else {
*on_var |= hl->mask_on;
*on_var &= ~hl->mask_off;
}
mask |= hl->mask_on | hl->mask_off;
teststr = val;
found = 1;
}
/* delayed this to the end of the first iteration because
* "noclear" isn't valid */
if (hl == highlights && (turn_off = strpfx("no", teststr)))
teststr += 2;
}
}
}
if (setmask)
*setmask = mask;
return teststr;
}
@@ -2038,22 +2057,28 @@ output_colour(int colour, int fg_bg, int truecol, char *buf)
/**/
mod_export int
output_highlight(zattr atr, char *buf)
output_highlight(zattr atr, zattr mask, 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 (mask == TXT_ATTR_ALL) {
zattr threebits = ~atr & TXT_ATTR_ALL;
threebits &= threebits - 1; /* can't be both bold and faint */
threebits &= threebits - 1; /* strip next bit to allow one "no" entry */
if (threebits) { /* more remain - shorter to start with "none" */
mask &= atr; /* mark unset bits from atr as done */
atrlen = 4;
if (buf) {
strcpy(ptr, "reset");
ptr += 5;
}
}
}
if (atr & TXTBGCOLOUR) {
if (mask & TXTFGCOLOUR) {
if (atrlen) {
atrlen++;
if (buf) {
@@ -2061,16 +2086,46 @@ output_highlight(zattr atr, char *buf)
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;
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;
} else {
atrlen += 10;
if (buf) {
strcpy(ptr, "fg=default");
ptr += 10;
}
}
}
if (mask & TXTBGCOLOUR) {
if (atrlen) {
atrlen++;
if (buf) {
strcpy(ptr, ",");
ptr++;
}
}
if (atr & TXTBGCOLOUR) {
len = output_colour(txtchangeget(atr, TXT_ATTR_BG_COL), COL_SEQ_BG,
(atr & TXT_ATTR_BG_24BIT), ptr);
atrlen += len;
if (buf)
ptr += len;
} else {
atrlen += 10;
if (buf) {
strcpy(ptr, "bg=default");
ptr += 10;
}
}
}
for (hp = highlights; hp->name; hp++) {
if (hp->mask_on & atr) {
if (hp->mask_on & mask && !(hp->mask_off & mask & atr)) {
mask &= ~hp->mask_off;
if (atrlen) {
atrlen++;
if (buf) {
@@ -2078,6 +2133,13 @@ output_highlight(zattr atr, char *buf)
ptr++;
}
}
if (!(hp->mask_on & atr)) {
atrlen += 2;
if (buf) {
strcpy(ptr, "no");
ptr += 2;
}
}
len = strlen(hp->name);
atrlen += len;
if (buf) {