From d8666da9de4842b454567798e900c64c723c5e60 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Wed, 12 Nov 2025 07:54:21 +0100 Subject: 54043, 54055: allow highlighing attributes to be turned back off --- ChangeLog | 6 +++ Doc/Zsh/zle.yo | 43 ++++++++++----- Src/Modules/hlgroup.c | 2 +- Src/Modules/watch.c | 2 +- Src/Zle/complist.c | 2 +- Src/Zle/zle.h | 4 +- Src/Zle/zle_main.c | 4 +- Src/Zle/zle_refresh.c | 44 ++++++++++------ Src/Zle/zle_tricky.c | 2 +- Src/prompt.c | 142 ++++++++++++++++++++++++++++++++++++-------------- 10 files changed, 175 insertions(+), 76 deletions(-) diff --git a/ChangeLog b/ChangeLog index e5b4e3486..10fca11de 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2025-11-12 Oliver Kiddle + * 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 diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index e20184121..29a8b8597 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -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: diff --git a/Src/Modules/hlgroup.c b/Src/Modules/hlgroup.c index 6c5a4bec4..1dc6ac2ef 100644 --- a/Src/Modules/hlgroup.c +++ b/Src/Modules/hlgroup.c @@ -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) { diff --git a/Src/Modules/watch.c b/Src/Modules/watch.c index acc499518..0d86142c1 100644 --- a/Src/Modules/watch.c +++ b/Src/Modules/watch.c @@ -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); } diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c index ea18434a3..4c87c15d7 100644 --- a/Src/Zle/complist.c +++ b/Src/Zle/complist.c @@ -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); } diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h index 91eefc9e5..bf479d4cc 100644 --- a/Src/Zle/zle.h +++ b/Src/Zle/zle.h @@ -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 */ diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index d7b2ca21c..46d0e07d2 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -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); diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c index 943eda672..8a89be333 100644 --- a/Src/Zle/zle_refresh.c +++ b/Src/Zle/zle_refresh.c @@ -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; diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c index aa3c71bc2..f399ebd00 100644 --- a/Src/Zle/zle_tricky.c +++ b/Src/Zle/zle_tricky.c @@ -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); diff --git a/Src/prompt.c b/Src/prompt.c index 801f1a727..83a7667cc 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -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) { -- cgit v1.2.3-70-g09d2