summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--Doc/Zsh/params.yo18
-rw-r--r--Doc/Zsh/zle.yo61
-rw-r--r--Src/Zle/termquery.c224
-rw-r--r--Src/Zle/zle.h26
-rw-r--r--Src/Zle/zle_refresh.c5
-rw-r--r--Src/Zle/zle_vi.c1
-rw-r--r--Src/init.c2
-rw-r--r--Src/zsh.h4
9 files changed, 320 insertions, 25 deletions
diff --git a/ChangeLog b/ChangeLog
index 5371ac3e5..7f62eb9a6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
2025-11-10 Oliver Kiddle <opk@zsh.org>
+ * 53438: Doc/Zsh/params.yo, Doc/Zsh/zle.yo, Src/Zle/termquery.c,
+ Src/Zle/zle.h, Src/Zle/zle_refresh.c, Src/Zle/zle_vi.c, Src/init.c,
+ Src/zsh.h: support for changing terminal cursor shape and colour
+
* 53404: Doc/Zsh/params.yo, Src/Zle/termquery.c, Src/Zle/zle_main.c,
Src/builtin.c, Src/init.c, Src/input.c, Src/loop.c, Src/prompt.c,
Src/subst.c, Src/utils.c, Src/zsh.h, Test/X04zlehighlight.ztst,
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 6cb9167d5..76185efca 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -1749,6 +1749,12 @@ inserted instead of invoking editor commands. Furthermore, pasted text forms a
single undo event and if the region is active, pasted text will replace the
region.
)
+item(tt(cursor-color) <E>)(
+Support for changing the color of the cursor.
+)
+item(tt(cursor-shape) <E>)(
+Support for changing the shape of the cursor.
+)
item(tt(integration-output) <E>)(
This provides the terminal with semantic information regarding where the output
from commands start and finish. Some terminals use this information to make it
@@ -1769,6 +1775,11 @@ item(tt(query-bg) <E>)(
Query the terminal background color which is used for tt(.term.bg) and
tt(.term.mode).
)
+item(tt(query-cursor) <E>)(
+Query the cursor color. This facilitates restoring the cursor to its original
+color if it has been configured via tt(zle_cursorform). The color is also
+assigned to tt(.term.cursor).
+)
item(tt(query-fg) <E>)(
Query the terminal foreground color which is used for tt(.term.fg).
)
@@ -1900,6 +1911,13 @@ parameter has the effect of ensuring that bracketed paste remains disabled.
However, see also the tt(.term.extensions) parameter which provides a single
place to enable or disable terminal features.
)
+vindex(zle_cursorform)
+item(tt(zle_cursorform))(
+An array describing contexts in which ZLE should change the shape and color
+of the cursor.
+See ifzman(em(Cursor Form) in zmanref(zshzle))\
+ifnzman(noderef(Cursor Form)).
+)
vindex(zle_highlight)
item(tt(zle_highlight))(
An array describing contexts in which ZLE should highlight the input text.
diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index 4c5965c6f..669e6927b 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -2855,3 +2855,64 @@ special array parameter tt(region_highlight); see
ifnzman(noderef(Zle Widgets))\
ifzman(above).
+texinode(Cursor Form)()()(Character Highlighting)
+subsect(Cursor Form)
+cindex(cursor form)
+
+vindex(zle_cursorform, setting)
+Some terminals support the ability to change the shape and color of the cursor.
+On such terminals, the line editor will use cursor styles appropriate to
+different contexts. This is controlled via the array parameter
+tt(zle_cursorform). To disable all cursor changes, see the tt(.term.extensions)
+parameter.
+
+Each element of the array should consist of a word indicating a context
+followed by a colon, then a comma-separated list of properties describing the
+shape and color to apply to the cursor.
+
+The available contexts follow with the default cursor form shown in
+parentheses. Where no default is given, the terminal's default is applied:
+
+startitem()
+item(tt(command))(
+Used for vi normal mode.
+)
+item(tt(edit))(
+The default form used in the line editor and for editing text in emacs
+mode.
+)
+item(tt(insert) (tt(bar)))(
+Used for vi editing mode.
+)
+item(tt(overwrite) (tt(underline)))(
+Used when editing text in overwrite mode or with the vi replace command.
+)
+item(tt(pending) (tt(underline)))(
+Used where the line editor is waiting for a single key press such as the vi
+operator pending mode widget.
+)
+item(tt(region))(
+Applied for both tt(regionstart) and tt(regionend) contexts.
+)
+item(tt(regionstart))(
+Used when the region is active and the cursor is positioned at the start of the
+region. The region includes the text under the cursor when it is positioned at
+the start so it is best to choose a cursor form that does not obscure this fact.
+)
+item(tt(regionend))(
+Used when the region is active and the cursor is positioned at the end of the
+region. Note that when this is the case, the region does not include the cursor
+position.
+)
+item(tt(visual))(
+Used when vi visual mode is active. The visual selection always includes the
+cursor position so the same advice as for tt(regionstart) applies.
+)
+enditem()
+
+The available cursor forms are tt(none), tt(bar), tt(block), tt(underline) and
+tt(hidden). Additionally, you can specify either tt(blink) or tt(steady) to
+indicate whether the cursor should flash and specify a color as an RGB triplet
+in hexadecimal format with with tt(color=)var(#xxxxxx). The value tt(none)
+applies the terminal's default cursor form. Note that on many terminals, this
+may be different to the initial cursor state from when the shell started.
diff --git a/Src/Zle/termquery.c b/Src/Zle/termquery.c
index c9fd22588..335ea51ed 100644
--- a/Src/Zle/termquery.c
+++ b/Src/Zle/termquery.c
@@ -132,8 +132,7 @@ typedef const unsigned char seqstate_t;
static char *EXTVAR = ".term.extensions";
static char *IDVAR = ".term.id";
static char *VERVAR = ".term.version";
-static char *BGVAR = ".term.bg";
-static char *FGVAR = ".term.fg";
+static char *COLORVAR[] = { ".term.fg", ".term.bg", ".term.cursor" };
static char *MODEVAR = ".term.mode";
/* Query sequences
@@ -144,6 +143,7 @@ static char *MODEVAR = ".term.mode";
* because tmux will need to pass these on. */
#define TQ_BGCOLOR "\033]11;?\033\\"
#define TQ_FGCOLOR "\033]10;?\033\\"
+#define TQ_CURSOR "\033]12;?\033\\"
/* Kitty / fixterms keyboard protocol which allows wider support for keys
* and modifiers. This clears the screen in terminology. */
@@ -420,32 +420,33 @@ probe_terminal(const char *tquery, seqstate_t *states,
settyinfo(&torig);
}
+static unsigned memo_cursor;
+
static void
handle_color(int bg, int red, int green, int blue)
{
char *colour;
- switch (bg) {
- case 1: /* background color */
- /* scale by Rec.709 coefficients for lightness */
- setsparam(MODEVAR, ztrdup(
- 0.2126f * red + 0.7152f * green + 0.0722f * blue <= 127 ?
- "dark" : "light"));
- /* fall-through */
- case 0:
- colour = zalloc(8);
- sprintf(colour, "#%02x%02x%02x", red, green, blue);
- setsparam(bg ? BGVAR : FGVAR, colour);
- break;
- default: break;
+ if (bg == 1) { /* background color */
+ /* scale by Rec.709 coefficients for lightness */
+ setsparam(MODEVAR, ztrdup(
+ 0.2126f * red + 0.7152f * green + 0.0722f * blue <= 127 ?
+ "dark" : "light"));
}
+
+ if (bg == 2) /* cursor color */
+ memo_cursor = (red << 24) | (green << 16) | (blue << 8);
+
+ colour = zalloc(8);
+ sprintf(colour, "#%02x%02x%02x", red, green, blue);
+ setsparam(COLORVAR[bg], colour);
}
/* roughly corresponding feature names */
static const char *features[] =
- { "bg", "fg", "modkeys-kitty", "truecolor", "id" };
+ { "bg", "fg", "cursor", "modkeys-kitty", "truecolor", "id" };
static const char *queries[] =
- { TQ_BGCOLOR, TQ_FGCOLOR, TQ_KITTYKB, TQ_RGB, TQ_XTVERSION, TQ_DA };
+ { TQ_BGCOLOR, TQ_FGCOLOR, TQ_CURSOR, TQ_KITTYKB, TQ_RGB, TQ_XTVERSION, TQ_DA };
static void
handle_query(int sequence, int *numbers, int len, char *capture, int clen,
@@ -460,12 +461,12 @@ handle_query(int sequence, int *numbers, int len, char *capture, int clen,
break;
case 2: /* kitty keyboard */
feat = zshcalloc(2 * sizeof(char *));
- *feat = ztrdup(features[2]);
+ *feat = ztrdup(features[3]);
assignaparam(EXTVAR, feat, ASSPM_WARN|ASSPM_AUGMENT);
break;
case 3: /* truecolor */
feat = zshcalloc(2 * sizeof(char *));
- *feat = ztrdup(features[3]);
+ *feat = ztrdup(features[4]);
assignaparam(EXTVAR, feat, ASSPM_WARN|ASSPM_AUGMENT);
break;
case 4: /* id */
@@ -480,7 +481,7 @@ handle_query(int sequence, int *numbers, int len, char *capture, int clen,
/**/
void
query_terminal(void) {
- char tquery[sizeof(TQ_BGCOLOR TQ_FGCOLOR TQ_KITTYKB TQ_RGB TQ_XTVERSION TQ_DA)];
+ char tquery[sizeof(TQ_BGCOLOR TQ_FGCOLOR TQ_CURSOR TQ_KITTYKB TQ_RGB TQ_XTVERSION TQ_DA)];
char *tqend = tquery;
static seqstate_t states[] = QUERY_STATES;
char **f, **flist = getaparam(EXTVAR);
@@ -504,7 +505,7 @@ query_terminal(void) {
/* if termcap indicates 24-bit color, assume support - even
* though this is only based on the initial $TERM
* failing that, check $COLORTERM */
- if (i == 3 && (tccolours == 1 << 24 ||
+ if (i == 4 && (tccolours == 1 << 24 ||
((cterm = getsparam("COLORTERM")) &&
(!strcmp(cterm, "truecolor") ||
!strcmp(cterm, "24bit")))))
@@ -624,7 +625,7 @@ extension_enabled(const char *class, const char *ext, unsigned clen, int def)
if (strncmp(*e + negate, class, clen))
continue;
- if (!*(*e + negate + clen) || !strcmp(*e + negate + clen, ext))
+ if (!*(*e + negate + clen) || !strcmp(*e + negate + clen + 1, ext))
return !negate;
}
return def;
@@ -703,7 +704,7 @@ end_edit(void)
const char **
prompt_markers(void)
{
- static unsigned aid = 0;
+ static unsigned int aid = 0;
static char pre[] = "\033]133;A;cl=m;aid=zZZZZZZ\033\\"; /* before the prompt */
static const char *const PR = "\033]133;P;k=i\033\\"; /* primary (PS1) */
static const char *const SE = "\033]133;P;k=s\033\\"; /* secondary (PS2) */
@@ -754,3 +755,180 @@ notify_pwd(void)
write_loop(SHTTY, url, ulen);
write_loop(SHTTY, "\033\\", 2);
}
+
+static unsigned int *cursor_forms;
+static unsigned int cursor_enabled_mask;
+
+static void
+match_cursorform(const char *teststr, unsigned int *cursor_form)
+{
+ static const struct {
+ const char *name;
+ unsigned char value, mask;
+ } shapes[] = {
+ { "none", 0, 0xff },
+ { "underline", CURF_UNDERLINE, CURF_SHAPE_MASK },
+ { "bar", CURF_BAR, CURF_SHAPE_MASK },
+ { "block", CURF_BLOCK, CURF_SHAPE_MASK },
+ { "blink", CURF_BLINK, CURF_STEADY },
+ { "steady", CURF_STEADY, CURF_BLINK },
+ { "hidden", CURF_HIDDEN, 0 }
+ };
+
+ *cursor_form = 0;
+ while (*teststr) {
+ size_t s;
+ int found = 0;
+
+ if (strpfx("color=#", teststr)) {
+ char *end;
+ teststr += 7;
+ zlong col = zstrtol(teststr, &end, 16);
+ if (end - teststr == 4) {
+ unsigned int red = col >> 8;
+ unsigned int green = (col & 0xf0) >> 4;
+ unsigned int blue = (col & 0xf);
+ *cursor_form &= 0xff; /* clear color */
+ *cursor_form |= CURF_COLOR |
+ ((red << 4 | red) << CURF_RED_SHIFT) |
+ ((green << 4 | green) << CURF_GREEN_SHIFT) |
+ ((blue << 4 | blue) << CURF_BLUE_SHIFT);
+ found = 1;
+ } else if (end - teststr == 6) {
+ *cursor_form |= (col << 8) | CURF_COLOR;
+ found = 1;
+ }
+ teststr = end;
+ }
+ for (s = 0; !found && s < sizeof(shapes) / sizeof(*shapes); s++) {
+ if (strpfx(shapes[s].name, teststr)) {
+ teststr += strlen(shapes[s].name);
+ *cursor_form &= ~shapes[s].mask;
+ *cursor_form |= shapes[s].value;
+ found = 1;
+ }
+ }
+ if (!found) /* skip an unknown component */
+ teststr = strchr(teststr, ',');
+ if (!teststr || *teststr != ',')
+ break;
+ teststr++;
+ }
+}
+
+/**/
+void
+zle_set_cursorform(void)
+{
+ char **atrs = getaparam("zle_cursorform");
+ static int setup = 0;
+ size_t i;
+ static const char *contexts[] = {
+ "edit:",
+ "command:",
+ "insert:",
+ "overwrite:",
+ "pending:",
+ "regionstart:",
+ "regionend:",
+ "visual:"
+ };
+
+ if (!cursor_forms)
+ cursor_forms = zalloc(CURC_DEFAULT * sizeof(*cursor_forms));
+ memset(cursor_forms, 0, CURC_DEFAULT * sizeof(*cursor_forms));
+ cursor_forms[CURC_INSERT] = CURF_BAR;
+ cursor_forms[CURC_OVERWRITE] = CURF_UNDERLINE;
+ cursor_forms[CURC_PENDING] = CURF_UNDERLINE;
+
+ for (; atrs && *atrs; atrs++) {
+ if (strpfx("region:", *atrs)) {
+ match_cursorform(*atrs + 7, &cursor_forms[CURC_REGION_END]);
+ cursor_forms[CURC_REGION_START] = cursor_forms[CURC_REGION_END];
+ continue;
+ }
+ for (i = 0; i < sizeof(contexts) / sizeof(*contexts); i++) {
+ if (strpfx(contexts[i], *atrs)) {
+ match_cursorform(*atrs + strlen(contexts[i]), &cursor_forms[i]);
+ break;
+ }
+ }
+ }
+
+ if (!setup || trashedzle) {
+ cursor_enabled_mask = 0;
+ setup = 1;
+ if (!extension_enabled("cursor", "shape", 6, 1))
+ cursor_enabled_mask |= CURF_SHAPE_MASK | CURF_BLINK | CURF_STEADY;
+ if (!extension_enabled("cursor", "color", 6, 1))
+ cursor_enabled_mask |= CURF_COLOR_MASK;
+ }
+}
+
+/**/
+void
+free_cursor_forms(void)
+{
+ if (cursor_forms)
+ zfree(cursor_forms, CURC_DEFAULT * sizeof(*cursor_form));
+ cursor_forms = 0;
+}
+
+/**/
+void
+cursor_form(void)
+{
+ char seq[31];
+ char *s = seq;
+ unsigned int want, changed;
+ static unsigned int state = CURF_DEFAULT;
+ enum cursorcontext context = CURC_DEFAULT;
+
+ if (!cursor_forms)
+ return;
+
+ if (trashedzle) {
+ ;
+ } else if (!insmode) {
+ context = CURC_OVERWRITE;
+ } else if (vichgflag == 2) {
+ context = CURC_PENDING;
+ } else if (region_active) {
+ if (invicmdmode()) {
+ context = CURC_VISUAL;
+ } else {
+ context = mark > zlecs ? CURC_REGION_START : CURC_REGION_END;
+ }
+ } else
+ context = invicmdmode() ? CURC_COMMAND : (vichgflag ? CURC_INSERT : CURC_EDIT);
+ want = (context == CURC_DEFAULT) ? CURF_DEFAULT : cursor_forms[context];
+ if (!(changed = (want ^ state) & ~cursor_enabled_mask))
+ return;
+
+ if (changed & CURF_HIDDEN)
+ tcout(want & CURF_HIDDEN ? TCCURINV : TCCURVIS);
+ if (changed & CURF_SHAPE_MASK) {
+ char c = '0';
+ switch (want & CURF_SHAPE_MASK) {
+ case CURF_BAR: c += 2;
+ case CURF_UNDERLINE: c += 2;
+ case CURF_BLOCK:
+ c += 2 - !!(want & CURF_BLINK);
+ changed &= ~(CURF_BLINK | CURF_STEADY);
+ }
+ s += sprintf(s, "\033[%c q", c);
+ }
+ if (changed & (CURF_BLINK | CURF_STEADY)) {
+ s += sprintf(s, "\033[?12%c", (want & CURF_BLINK) ? 'h' : 'l');
+ }
+ if (changed & CURF_COLOR_MASK) {
+ if (!(want & CURF_COLOR_MASK))
+ want = memo_cursor | (want & 0xff);
+ s += sprintf(s, "\033]12;rgb:%02x00/%02x00/%02x00\033\\",
+ want >> CURF_RED_SHIFT, (want >> CURF_GREEN_SHIFT) & 0xff,
+ (want >> CURF_BLUE_SHIFT) & 0xff);
+ }
+ if (s - seq)
+ write_loop(SHTTY, seq, s - seq);
+ state = want;
+}
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
index 5bb9e7a5e..91eefc9e5 100644
--- a/Src/Zle/zle.h
+++ b/Src/Zle/zle.h
@@ -470,6 +470,32 @@ struct region_highlight {
* interaction in Doc/Zsh/zle.yo. */
#define N_SPECIAL_HIGHLIGHTS (4)
+/* Terminal cursor contexts */
+enum cursorcontext {
+ CURC_EDIT,
+ CURC_COMMAND,
+ CURC_INSERT,
+ CURC_OVERWRITE,
+ CURC_PENDING,
+ CURC_REGION_START,
+ CURC_REGION_END,
+ CURC_VISUAL,
+ CURC_DEFAULT
+};
+
+#define CURF_DEFAULT 0
+#define CURF_UNDERLINE 1
+#define CURF_BAR 2
+#define CURF_BLOCK 3
+#define CURF_SHAPE_MASK 3
+#define CURF_BLINK (1 << 2)
+#define CURF_STEADY (1 << 3)
+#define CURF_HIDDEN (1 << 4)
+#define CURF_COLOR (1 << 5)
+#define CURF_COLOR_MASK ((0xffffffu << 8) | CURF_COLOR)
+#define CURF_RED_SHIFT 24
+#define CURF_GREEN_SHIFT 16
+#define CURF_BLUE_SHIFT 8
#ifdef MULTIBYTE_SUPPORT
/*
diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c
index f076bdd61..f27191114 100644
--- a/Src/Zle/zle_refresh.c
+++ b/Src/Zle/zle_refresh.c
@@ -1024,6 +1024,8 @@ zrefresh(void)
tmpalloced = 0;
}
+ zle_set_cursorform();
+
/* this will create region_highlights if it's still NULL */
zle_set_highlight();
@@ -1666,6 +1668,7 @@ individually */
/* move to the new cursor position */
moveto(rpms.nvln, rpms.nvcs);
+ cursor_form();
/* swap old and new buffers - better than freeing/allocating every time */
bufswap();
@@ -2706,4 +2709,6 @@ zle_refresh_finish(void)
region_highlights = NULL;
n_region_highlights = 0;
}
+
+ free_cursor_forms();
}
diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c
index 6692df830..667063774 100644
--- a/Src/Zle/zle_vi.c
+++ b/Src/Zle/zle_vi.c
@@ -186,6 +186,7 @@ getvirange(int wf)
virangeflag = 1;
wordflag = wf;
mark = -1;
+ cursor_form();
/* use operator-pending keymap if one exists */
Keymap km = openkeymap("viopp");
if (km)
diff --git a/Src/init.c b/Src/init.c
index dea52202e..20b8cc7fd 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -754,7 +754,7 @@ static char *tccapnams[TC_COUNT] = {
"cl", "le", "LE", "nd", "RI", "up", "UP", "do",
"DO", "dc", "DC", "ic", "IC", "cd", "ce", "al", "dl", "ta",
"md", "mh", "so", "us", "ZH", "me", "se", "ue", "ZR", "ch",
- "ku", "kd", "kl", "kr", "sc", "rc", "bc", "AF", "AB"
+ "ku", "kd", "kl", "kr", "sc", "rc", "bc", "AF", "AB", "vi", "ve"
};
/**/
diff --git a/Src/zsh.h b/Src/zsh.h
index 4d791cc7c..b82c35aea 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2674,7 +2674,9 @@ struct ttyinfo {
#define TCBACKSPACE 34
#define TCFGCOLOUR 35
#define TCBGCOLOUR 36
-#define TC_COUNT 37
+#define TCCURINV 37
+#define TCCURVIS 38
+#define TC_COUNT 39
#define tccan(X) (tclen[X])