Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: alternate views on .zle.hlgroups



This patch adds a hlgroup module which defines two extra special
variables - .zle.esc and .zle.sgr. These are readonly associative
arrays.

The purpose of these is to make is easier to apply the attributes
defined in .zle.hlgroups to other commands. The same effect can already
be achieved using ${(%):-%H\{...\}} but this seemed like a friendlier
approach.

.zle.esc contains literal escape sequences
.zle.sgr is stripped down to just the "Select Graphic Rendition" number
sequence. This is useful, with, e.g. GREP_COLORS, LSCOLORS, the
list-colors style and some other tools like jq and ag.

This implementation uses dupstring() to return memory from the parameter
string get function. I don't see existing cases to confirm that this
is safe but it appears to work fine in testing. Would be good to have
confirmation from someone who has a clearer understanding of what the
lifetime of the memory pools is.

Naming (hlgroup module in singular, .zle.hlgroups in plural) is chosen
for consistency with the existing parameter module. I might otherwise
have opted for singular in both cases. But I'm open to other suggestions
or completely different ideas on naming.

I had two further ideas for things that might be included in the module.
I briefly considered what it would involve to support a hook to allow
dynamic updates to variables like GREP_COLORS in response to changes to
.zle.hlgroups. This didn't seem trivial, especially given that it is
currently useful to define it as a nameref (to a variable that works in
older zsh).

My second thought was support for parsing terminal responses to the
escape sequence for getting the background colour. This is just about
doable in shell code but not trivial and error-prone. But it can be
very helpful to avoid unreadable colour combinations. What form should
this best take? A builtin? Having a key bound to \e]11;rgb: may be
the best way to avoid interference with a type-ahead buffer but that
inconveniently move the setup code to a zle widget. ungetc() in C may
work better than my current print -z solution, especially where some of
the typeahead text is not even intended for zsh.

Oliver

diff --git a/Src/Modules/hlgroup.c b/Src/Modules/hlgroup.c
new file mode 100644
index 000000000..fcf8cfc81
--- /dev/null
+++ b/Src/Modules/hlgroup.c
@@ -0,0 +1,182 @@
+#include "hlgroup.mdh"
+#include "hlgroup.pro"
+
+#define GROUPVAR ".zle.hlgroups"
+
+static const struct gsu_scalar pmesc_gsu =
+{ strgetfn, nullstrsetfn, nullunsetfn };
+
+/**/
+static char *
+convertattr(char *attrstr, int sgr)
+{
+    zattr atr;
+    char *r, *s;
+    int len;
+
+    match_highlight(attrstr, &atr, NULL);
+    s = zattrescape(atr, sgr ? NULL : &len);
+
+    if (sgr) {
+	char *c = s, *t = s - 1;
+
+	while (c[0] == '\033' && c[1] == '[') {
+	    c += 2;
+	    while (isdigit(*c) || *c == ';')
+		*++t = *c++;
+	    t++;
+	    if (*c != 'm')
+		break;
+	    *t = ';';
+	    c++;
+	}
+	*t = '\0';
+	len = t - s;
+    }
+
+    r = dupstring_wlen(s, len);
+    free(s);
+    return r;
+}
+
+/**/
+static HashNode
+getgroup(const char *name, int sgr)
+{
+    Param pm = NULL;
+    HashNode hn;
+    HashTable hlg;
+    Value v;
+    struct value vbuf;
+    char *var = GROUPVAR;
+
+    pm = (Param) hcalloc(sizeof(struct param));
+    pm->gsu.s = &pmesc_gsu;
+    pm->node.nam = dupstring(name);
+    pm->node.flags = PM_SCALAR|PM_SPECIAL;
+
+    if (!(v = getvalue(&vbuf, &var, 0)) ||
+	     PM_TYPE(v->pm->node.flags) != PM_HASHED ||
+	     !(hlg = v->pm->gsu.h->getfn(v->pm)) ||
+	     !(hn = gethashnode2(hlg, name)))
+    {
+	pm->u.str = dupstring("");
+	pm->node.flags |= PM_UNSET;
+    } else {
+	pm->u.str = convertattr(((Param) hn)->u.str, sgr);
+    }
+
+    return &pm->node;
+}
+
+/**/
+static void
+scangroup(ScanFunc func, int flags, int sgr)
+{
+    struct param pm;
+    int i;
+    HashNode hn;
+    HashTable hlg;
+    Value v;
+    struct value vbuf;
+    char *var = GROUPVAR;
+
+    if (!(v = getvalue(&vbuf, &var, 0)) ||
+	     PM_TYPE(v->pm->node.flags) != PM_HASHED)
+	return;
+    hlg = v->pm->gsu.h->getfn(v->pm);
+
+    memset((void *)&pm, 0, sizeof(struct param));
+    pm.node.flags = PM_SCALAR;
+    pm.gsu.s = &pmesc_gsu;
+
+    for (i = 0; i < hlg->hsize; i++)
+	for (hn = hlg->nodes[i]; hn; hn = hn->next) {
+	    pm.u.str = convertattr(((Param) hn)->u.str, sgr);
+	    pm.node.nam = hn->nam;
+	    func(&pm.node, flags);
+	}
+}
+/**/
+static HashNode
+getpmesc(UNUSED(HashTable ht), const char *name)
+{
+    return getgroup(name, 0);
+}
+
+/**/
+static void
+scanpmesc(UNUSED(HashTable ht), ScanFunc func, int flags)
+{
+    return scangroup(func, flags, 0);
+}
+
+/**/
+static HashNode
+getpmsgr(UNUSED(HashTable ht), const char *name)
+{
+    return getgroup(name, 1);
+}
+
+/**/
+static void
+scanpmsgr(UNUSED(HashTable ht), ScanFunc func, int flags)
+{
+    return scangroup(func, flags, 1);
+}
+
+static struct paramdef partab[] = {
+    SPECIALPMDEF(".zle.esc", PM_READONLY_SPECIAL, 0, getpmesc, scanpmesc),
+    SPECIALPMDEF(".zle.sgr", PM_READONLY_SPECIAL, 0, getpmsgr, scanpmsgr)
+};
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    0
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
+{
+    return 0;
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
diff --git a/Src/Modules/hlgroup.mdd b/Src/Modules/hlgroup.mdd
new file mode 100644
index 000000000..ee3ba7260
--- /dev/null
+++ b/Src/Modules/hlgroup.mdd
@@ -0,0 +1,7 @@
+name=zsh/hlgroup
+link=either
+load=yes
+
+autofeatures="p:.zle.esc p:.zle.sgr"
+
+objects="hlgroup.o"
diff --git a/Src/prompt.c b/Src/prompt.c
index 0d674ceab..7acbe0e47 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -241,6 +241,34 @@ promptexpand(char *s, int ns, char *rs, char *Rs)
     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 */
 static char *
 parsehighlight(char *arg, char endchar, zattr *atr)




Messages sorted by: Reverse Date, Date, Thread, Author