v0.7.5: arrow keys, image request fix, deepseek-flash vision, musl CI
TUI keys: register both CSI and SS3 forms of the cursor/Home/End keys and
the xterm modifier sequences when terminfo lacks them, so an arrow sent in
the "other" form no longer turns into Esc + a typed letter. Up/Down move by
visual (soft-wrapped) row, and the draft survives a trip through history.
scripts/tui_keys drives the TUI in a pty to check all of this.
Images: text inside a multimodal content array is now a {"type":"text"}
part; DeepSeek rejected the bare string with HTTP 422. deepseek-flash
(and its deepseek-chat / deepseek-reasoner aliases) read images, so the
registry marks them vision; deepseek-v4-pro stays text only.
CI: models.c lost _DEFAULT_SOURCE in 0.7.0 and gmtime_r broke the musl
build; tests sleep with poll() instead of usleep for the same reason.
This commit is contained in:
@@ -378,6 +378,7 @@ typedef struct {
|
||||
char *prompt_hist[PROMPT_HIST_CAP];
|
||||
int prompt_hist_n;
|
||||
int hist_browse;
|
||||
char *hist_draft; /* the live input while browsing */
|
||||
|
||||
/* Busy-turn FIFO */
|
||||
char *queue[QUEUE_CAP];
|
||||
@@ -3163,28 +3164,16 @@ static size_t word_right(const tui_t *t, size_t at) {
|
||||
return at;
|
||||
}
|
||||
|
||||
/* Move to the previous/next logical line keeping the column (in chars).
|
||||
* Returns 0 when already on the first/last line. */
|
||||
static size_t input_hit(const tui_t *t, int want_row, int want_col);
|
||||
|
||||
/* Move to the previous/next visual row (soft-wrapped or not) keeping the
|
||||
* screen column. Returns 0 when already on the first/last row. */
|
||||
static int input_line_move(tui_t *t, int dir) {
|
||||
size_t ls = line_start(t, t->cur);
|
||||
int col = 0;
|
||||
for (size_t i = ls; i < t->cur; i += next_len(t, i))
|
||||
col++;
|
||||
size_t target;
|
||||
if (dir < 0) {
|
||||
if (ls == 0)
|
||||
return 0;
|
||||
target = line_start(t, ls - 1);
|
||||
} else {
|
||||
size_t le = line_end(t, t->cur);
|
||||
if (le >= t->in_len)
|
||||
return 0;
|
||||
target = le + 1;
|
||||
}
|
||||
size_t te = line_end(t, target);
|
||||
while (col-- > 0 && target < te)
|
||||
target += next_len(t, target);
|
||||
t->cur = target;
|
||||
int cr, cc;
|
||||
int rows = input_layout(t, NULL, NULL, &cr, &cc);
|
||||
if (cr + dir < 0 || cr + dir >= rows)
|
||||
return 0;
|
||||
t->cur = input_hit(t, cr + dir, cc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -5854,7 +5843,7 @@ static int submit_input(tui_t *t, int busy) {
|
||||
static size_t input_hit(const tui_t *t, int want_row, int want_col) {
|
||||
int row = 0, col = IN_COL;
|
||||
int right = (t->in_w > 0 ? t->in_w : t->cols) - 2;
|
||||
size_t i = 0, best = t->in_len;
|
||||
size_t i = 0, best = t->in_len, last = 0;
|
||||
while (i < t->in_len) {
|
||||
if (row == want_row && col >= want_col)
|
||||
return i;
|
||||
@@ -5873,12 +5862,14 @@ static size_t input_hit(const tui_t *t, int want_row, int want_col) {
|
||||
utf8_decode(t->inbuf + i, t->in_len - i, &wc, &eaten);
|
||||
int cw = utf8_width(wc == L'\t' ? L' ' : wc);
|
||||
if (col + cw > right && col > IN_COL) {
|
||||
/* offset i would be drawn at the start of the next row */
|
||||
if (row == want_row)
|
||||
return i;
|
||||
return last;
|
||||
row++;
|
||||
col = IN_COL;
|
||||
}
|
||||
best = i + eaten;
|
||||
last = i;
|
||||
col += cw;
|
||||
i += eaten;
|
||||
}
|
||||
@@ -6395,29 +6386,65 @@ static int g_k_alt_up, g_k_alt_down, g_k_ctrl_home, g_k_ctrl_end,
|
||||
g_k_ctrl_up, g_k_ctrl_down, g_k_ctrl_left, g_k_ctrl_right, g_k_alt_left,
|
||||
g_k_alt_right, g_k_ctrl_dc, g_k_alt_dc;
|
||||
|
||||
static int ext_key(const char *cap) {
|
||||
/* The terminfo key, else the xterm sequence bound to our own code `own`. */
|
||||
static int ext_key(const char *cap, const char *xterm, int own) {
|
||||
char *s = tigetstr(cap);
|
||||
if (!s || s == (char *)-1)
|
||||
return 0;
|
||||
int k = key_defined(s);
|
||||
return k > 0 ? k : 0;
|
||||
int k = s && s != (char *)-1 ? key_defined(s) : 0;
|
||||
if (k > 0)
|
||||
return k;
|
||||
#ifndef _WIN32
|
||||
k = key_defined(xterm);
|
||||
if (k > 0)
|
||||
return k;
|
||||
if (k == 0 && define_key(xterm, own) == OK)
|
||||
return own;
|
||||
#else
|
||||
(void)xterm, (void)own;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(NCURSES_VERSION) && !defined(_WIN32)
|
||||
/* Terminfo lists one form per key, but terminals disagree: cursor keys come
|
||||
* as CSI or SS3 whatever DECCKM says (tmux, mosh, a wrong TERM), Home/End as
|
||||
* CSI H, SS3 H or CSI 1~/7~. Unknown forms would be read as Esc + text. */
|
||||
static void vt_keys_init(void) {
|
||||
static const struct {
|
||||
const char *seq;
|
||||
int key;
|
||||
} K[] = {
|
||||
{"\033[A", KEY_UP}, {"\033OA", KEY_UP}, {"\033[B", KEY_DOWN},
|
||||
{"\033OB", KEY_DOWN}, {"\033[C", KEY_RIGHT}, {"\033OC", KEY_RIGHT},
|
||||
{"\033[D", KEY_LEFT}, {"\033OD", KEY_LEFT}, {"\033[H", KEY_HOME},
|
||||
{"\033OH", KEY_HOME}, {"\033[1~", KEY_HOME}, {"\033[7~", KEY_HOME},
|
||||
{"\033[F", KEY_END}, {"\033OF", KEY_END}, {"\033[4~", KEY_END},
|
||||
{"\033[8~", KEY_END}, {"\033[2~", KEY_IC}, {"\033[3~", KEY_DC},
|
||||
{"\033[5~", KEY_PPAGE}, {"\033[6~", KEY_NPAGE}, {"\033[Z", KEY_BTAB},
|
||||
{"\033[1;2A", KEY_SR}, {"\033[1;2B", KEY_SF}, {"\033[1;2C", KEY_SRIGHT},
|
||||
{"\033[1;2D", KEY_SLEFT},
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(K) / sizeof(K[0]); i++)
|
||||
if (key_defined(K[i].seq) == 0)
|
||||
define_key(K[i].seq, K[i].key);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void keys_init(void) {
|
||||
#ifdef NCURSES_VERSION
|
||||
g_k_alt_up = ext_key("kUP3");
|
||||
g_k_alt_down = ext_key("kDN3");
|
||||
g_k_ctrl_home = ext_key("kHOM5");
|
||||
g_k_ctrl_end = ext_key("kEND5");
|
||||
g_k_ctrl_up = ext_key("kUP5");
|
||||
g_k_ctrl_down = ext_key("kDN5");
|
||||
g_k_ctrl_left = ext_key("kLFT5");
|
||||
g_k_ctrl_right = ext_key("kRIT5");
|
||||
g_k_alt_left = ext_key("kLFT3");
|
||||
g_k_alt_right = ext_key("kRIT3");
|
||||
g_k_ctrl_dc = ext_key("kDC5");
|
||||
g_k_alt_dc = ext_key("kDC3");
|
||||
int own = KEY_OWN_BASE + 4; /* up to KEY_KITTY_BASE - 1 */
|
||||
g_k_alt_up = ext_key("kUP3", "\033[1;3A", own++);
|
||||
g_k_alt_down = ext_key("kDN3", "\033[1;3B", own++);
|
||||
g_k_ctrl_home = ext_key("kHOM5", "\033[1;5H", own++);
|
||||
g_k_ctrl_end = ext_key("kEND5", "\033[1;5F", own++);
|
||||
g_k_ctrl_up = ext_key("kUP5", "\033[1;5A", own++);
|
||||
g_k_ctrl_down = ext_key("kDN5", "\033[1;5B", own++);
|
||||
g_k_ctrl_left = ext_key("kLFT5", "\033[1;5D", own++);
|
||||
g_k_ctrl_right = ext_key("kRIT5", "\033[1;5C", own++);
|
||||
g_k_alt_left = ext_key("kLFT3", "\033[1;3D", own++);
|
||||
g_k_alt_right = ext_key("kRIT3", "\033[1;3C", own++);
|
||||
g_k_ctrl_dc = ext_key("kDC5", "\033[3;5~", own++);
|
||||
g_k_alt_dc = ext_key("kDC3", "\033[3;3~", own++);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -6630,6 +6657,7 @@ static void tui_free(tui_t *t) {
|
||||
queue_free_all(t);
|
||||
for (int i = 0; i < t->prompt_hist_n; i++)
|
||||
free(t->prompt_hist[i]);
|
||||
free(t->hist_draft);
|
||||
for (int i = 0; i < t->nfiles; i++)
|
||||
free(t->files[i]);
|
||||
free(t->files);
|
||||
@@ -6720,6 +6748,7 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
/* the Windows console driver reads key events, not VT sequences */
|
||||
define_key("\033[200~", KEY_PASTE_BEGIN);
|
||||
define_key("\033[201~", KEY_PASTE_END);
|
||||
vt_keys_init();
|
||||
printf("\033[?2004h");
|
||||
fflush(stdout);
|
||||
kitty_init();
|
||||
@@ -7032,15 +7061,22 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
if (t.prompt_hist_n <= 0)
|
||||
continue;
|
||||
int idx = t.hist_browse;
|
||||
if (idx < 0) {
|
||||
free(t.hist_draft);
|
||||
t.hist_draft = strdup(t.inbuf);
|
||||
}
|
||||
idx = idx < 0 ? t.prompt_hist_n - 1 : (idx > 0 ? idx - 1 : 0);
|
||||
hist_load_into_input(&t, idx);
|
||||
} else {
|
||||
if (t.hist_browse < 0)
|
||||
continue;
|
||||
if (t.hist_browse + 1 >= t.prompt_hist_n)
|
||||
input_clear(&t);
|
||||
else
|
||||
if (t.hist_browse + 1 >= t.prompt_hist_n) {
|
||||
input_set(&t, t.hist_draft);
|
||||
free(t.hist_draft);
|
||||
t.hist_draft = NULL;
|
||||
} else {
|
||||
hist_load_into_input(&t, t.hist_browse + 1);
|
||||
}
|
||||
}
|
||||
after_edit(&t);
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user