fix: park cursor in composer; glitch stream frontier (0.9.26)
leaveok on chrome panes and always redraw input after side/log motion so the caret no longer sticks on the Seat Deck version line. Live assistant text briefly scrambles at the tip then settles to plaintext. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -961,6 +961,102 @@ static long ms_since(const struct timespec *a) {
|
||||
(long)(now.tv_nsec - a->tv_nsec) / 1000000;
|
||||
}
|
||||
|
||||
/* Stream frontier: trailing glyphs briefly scramble then settle to plaintext.
|
||||
* Returns a malloc'd ANSI copy, or NULL to use the source as-is. */
|
||||
#define GLITCH_FRONT 28
|
||||
|
||||
static unsigned glitch_mix(unsigned x) {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
return x;
|
||||
}
|
||||
|
||||
static char *glitch_stream_frontier(const char *src, int spin) {
|
||||
if (!src || !src[0])
|
||||
return NULL;
|
||||
size_t n = strlen(src);
|
||||
char *out = malloc(n * 3 + 64); /* room if we swap ASCII for UTF-8 glyphs */
|
||||
if (!out)
|
||||
return NULL;
|
||||
/* Collect starts of the last GLITCH_FRONT visible codepoints (skip SGR). */
|
||||
size_t starts[GLITCH_FRONT];
|
||||
size_t lens[GLITCH_FRONT];
|
||||
int nv = 0;
|
||||
size_t i = 0;
|
||||
while (i < n) {
|
||||
if (src[i] == '\033' && i + 1 < n && src[i + 1] == '[') {
|
||||
size_t j = i + 2;
|
||||
while (j < n && src[j] != 'm')
|
||||
j++;
|
||||
i = j < n ? j + 1 : n;
|
||||
continue;
|
||||
}
|
||||
wchar_t wc;
|
||||
size_t eaten;
|
||||
utf8_decode(src + i, n - i, &wc, &eaten);
|
||||
if (eaten == 0)
|
||||
break;
|
||||
if (wc != L'\n' && wc != L'\r') {
|
||||
if (nv < GLITCH_FRONT) {
|
||||
starts[nv] = i;
|
||||
lens[nv] = eaten;
|
||||
nv++;
|
||||
} else {
|
||||
memmove(starts, starts + 1, (GLITCH_FRONT - 1) * sizeof(starts[0]));
|
||||
memmove(lens, lens + 1, (GLITCH_FRONT - 1) * sizeof(lens[0]));
|
||||
starts[GLITCH_FRONT - 1] = i;
|
||||
lens[GLITCH_FRONT - 1] = eaten;
|
||||
}
|
||||
}
|
||||
i += eaten;
|
||||
}
|
||||
if (nv < 4) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
static const char *const uni_g[] = {
|
||||
"░", "▒", "▓", "█", "▄", "▀", "▌", "▐", "╳", "╬", "═", "║",
|
||||
"ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "#", "*", "+", "/"};
|
||||
static const char *const asc_g[] = {
|
||||
"#", "$", "%", "&", "*", "+", "=", "?", "/", "\\", "|", ":",
|
||||
".", ",", ";", "~", "^", "x", "o", "0", "1", "Z", "X", "/"};
|
||||
const char *const *glyphs = g_ascii ? asc_g : uni_g;
|
||||
const int ng = g_ascii ? (int)(sizeof(asc_g) / sizeof(asc_g[0]))
|
||||
: (int)(sizeof(uni_g) / sizeof(uni_g[0]));
|
||||
|
||||
size_t o = 0;
|
||||
size_t cursor = 0;
|
||||
for (int v = 0; v < nv; v++) {
|
||||
size_t st = starts[v];
|
||||
if (st > cursor) {
|
||||
memcpy(out + o, src + cursor, st - cursor);
|
||||
o += st - cursor;
|
||||
}
|
||||
/* Intensity: tip of stream most scrambled; older glyphs settle. */
|
||||
int age = nv - 1 - v; /* 0 = newest */
|
||||
unsigned h = glitch_mix((unsigned)spin * 131u + (unsigned)v * 17u +
|
||||
(unsigned)st);
|
||||
int scramble = age < 10 && (int)(h % 10) < (10 - age);
|
||||
if (scramble && age < 6) {
|
||||
const char *g = glyphs[h % (unsigned)ng];
|
||||
size_t gl = strlen(g);
|
||||
memcpy(out + o, g, gl);
|
||||
o += gl;
|
||||
} else {
|
||||
memcpy(out + o, src + st, lens[v]);
|
||||
o += lens[v];
|
||||
}
|
||||
cursor = st + lens[v];
|
||||
}
|
||||
if (cursor < n) {
|
||||
memcpy(out + o, src + cursor, n - cursor);
|
||||
o += n - cursor;
|
||||
}
|
||||
out[o] = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
static void live_begin(tui_t *t) {
|
||||
live_end_cleanup(t);
|
||||
t->md_cap = MD_INIT;
|
||||
@@ -1835,8 +1931,19 @@ static void build_display(tui_t *t, dlines_t *dl, int width) {
|
||||
block_to_lines_indented(&t->hist.items[i], width, t->details_all, dl);
|
||||
}
|
||||
g_dl_blk = -1;
|
||||
if (t->as_live)
|
||||
block_to_lines_indented(&t->live, width, t->details_all, dl);
|
||||
if (t->as_live) {
|
||||
block_t live = t->live;
|
||||
char *glitch = NULL;
|
||||
if (t->motion_on && t->busy) {
|
||||
const char *src =
|
||||
t->live.render ? t->live.render : (t->live.body ? t->live.body : "");
|
||||
glitch = glitch_stream_frontier(src, t->spin);
|
||||
if (glitch)
|
||||
live.render = glitch;
|
||||
}
|
||||
block_to_lines_indented(&live, width, t->details_all, dl);
|
||||
free(glitch);
|
||||
}
|
||||
}
|
||||
|
||||
/* Draws s at (y, x0) within maxw columns; returns the columns used. */
|
||||
@@ -3279,6 +3386,15 @@ static void layout(tui_t *t) {
|
||||
if (t->side)
|
||||
wbkgdset(t->side, COLOR_PAIR(CP_SIDE));
|
||||
}
|
||||
/* Side/status refreshes must not steal the terminal cursor from input. */
|
||||
if (t->side)
|
||||
leaveok(t->side, TRUE);
|
||||
if (t->status)
|
||||
leaveok(t->status, TRUE);
|
||||
if (t->header)
|
||||
leaveok(t->header, TRUE);
|
||||
if (t->todow)
|
||||
leaveok(t->todow, TRUE);
|
||||
scrollok(t->logw, FALSE);
|
||||
keypad(t->input, TRUE);
|
||||
wtimeout(t->input, 80);
|
||||
@@ -7436,9 +7552,11 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
if (busy) {
|
||||
t.spin++;
|
||||
draw_status(&t);
|
||||
draw_input(&t);
|
||||
if (t.motion_on && t.as_live)
|
||||
draw_log(&t); /* animate stream frontier glitch */
|
||||
if (t.motion_on && t.side)
|
||||
draw_side(&t);
|
||||
draw_input(&t); /* always last — park cursor in the composer */
|
||||
}
|
||||
|
||||
if (turn_done) {
|
||||
@@ -7472,6 +7590,7 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
if (!busy && t.motion_on && t.side && !t.home) {
|
||||
t.spin++;
|
||||
draw_side(&t);
|
||||
draw_input(&t); /* restore cursor after side refresh */
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user