TUI: mouse drag selection with OSC 52 copy; Markdown emphasis follows CommonMark; v0.5.0
- Drag, double-click (word) and triple-click (line) selection in the log, auto-scroll at the edges, soft-wrapped lines joined on copy. - sashidown ANSI renderer: delimiter runs with flanking rules, so intraword underscores (test_apply_patch.c) stay literal; unmatched * _ and backticks are shown as-is; * and _ do not close each other. - tests: test_textsel, test_markdown.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include "cst_plugins.h"
|
||||
#include "cst_skills.h"
|
||||
#include "cst_subagent.h"
|
||||
#include "cst_textsel.h"
|
||||
#include "cst_perms.h"
|
||||
#include "cst_sandbox.h"
|
||||
#include "cst_tools.h"
|
||||
@@ -61,23 +62,7 @@ static int g_utf8 = 1; /* locale can show non-ASCII text */
|
||||
#ifndef KEY_RESIZE
|
||||
#define KEY_RESIZE 0632
|
||||
#endif
|
||||
/* MinGW often lacks wcwidth; simple East-Asian / ASCII heuristic. */
|
||||
static int cst_wcwidth(wchar_t wc) {
|
||||
if (wc == 0)
|
||||
return 0;
|
||||
if (wc < 32 || (wc >= 0x7f && wc < 0xa0))
|
||||
return -1;
|
||||
if (wc >= 0x1100 &&
|
||||
(wc <= 0x115f || wc == 0x2329 || wc == 0x232a ||
|
||||
(wc >= 0x2e80 && wc <= 0xa4cf && wc != 0x303f) ||
|
||||
(wc >= 0xac00 && wc <= 0xd7a3) || (wc >= 0xf900 && wc <= 0xfaff) ||
|
||||
(wc >= 0xfe10 && wc <= 0xfe19) || (wc >= 0xfe30 && wc <= 0xfe6f) ||
|
||||
(wc >= 0xff00 && wc <= 0xff60) || (wc >= 0xffe0 && wc <= 0xffe6) ||
|
||||
(wc >= 0x20000 && wc <= 0x2fffd) || (wc >= 0x30000 && wc <= 0x3fffd)))
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
#define wcwidth cst_wcwidth
|
||||
#define wcwidth cst_wcwidth /* MinGW often lacks wcwidth */
|
||||
#else
|
||||
#include <ncurses.h>
|
||||
#endif
|
||||
@@ -139,6 +124,12 @@ static int cst_clock_gettime_mono(struct timespec *ts) {
|
||||
#define KEY_KITTY_REPLY (KEY_OWN_BASE + 3)
|
||||
#define KEY_KITTY_BASE (KEY_OWN_BASE + 16)
|
||||
#define KITTY_CP_END 128
|
||||
/* Transcript selection states. */
|
||||
#define SEL_NONE 0
|
||||
#define SEL_PRESS 1 /* button down, not moved off the pressed cell yet */
|
||||
#define SEL_DRAG 2
|
||||
#define SEL_SHOWN 3 /* released; highlighted until the next input */
|
||||
#define MULTI_CLICK_MS 400
|
||||
|
||||
#ifdef NCURSES_VERSION
|
||||
static int g_kitty_on;
|
||||
@@ -301,6 +292,15 @@ typedef struct {
|
||||
int row_blk_cap;
|
||||
int cmd_x0, cmd_x1; /* "ctrl+p commands" in the status row */
|
||||
int mouse_on;
|
||||
int log_start; /* display line shown in logw row 0 */
|
||||
/* Mouse selection in the transcript: display line + logw column. */
|
||||
int sel_state; /* SEL_* */
|
||||
int sel_mode; /* 1 characters, 2 words, 3 lines */
|
||||
int sel_ay, sel_ax; /* anchor */
|
||||
int sel_y, sel_x; /* current end */
|
||||
int sel_press_y, sel_press_x; /* screen cell of the press */
|
||||
int sel_clicks, sel_click_y, sel_click_x;
|
||||
struct timespec sel_click_at;
|
||||
const char *tip;
|
||||
const char *sandbox_name; /* "landlock" / "seatbelt" / "rules" */
|
||||
char where[300]; /* ~/path:branch */
|
||||
@@ -404,26 +404,10 @@ static size_t utf8_prev_len(const char *s, size_t len) {
|
||||
}
|
||||
|
||||
static int utf8_decode(const char *s, size_t avail, wchar_t *wc, size_t *eaten) {
|
||||
if (avail == 0)
|
||||
return -1;
|
||||
mbstate_t st;
|
||||
memset(&st, 0, sizeof(st));
|
||||
size_t n = mbrtowc(wc, s, avail, &st);
|
||||
if (n == (size_t)-1 || n == (size_t)-2 || n == 0) {
|
||||
*wc = (unsigned char)s[0];
|
||||
*eaten = 1;
|
||||
return 0;
|
||||
}
|
||||
*eaten = n;
|
||||
return 0;
|
||||
return cst_utf8_decode(s, avail, wc, eaten);
|
||||
}
|
||||
|
||||
static int utf8_width(wchar_t wc) {
|
||||
int w = wcwidth(wc);
|
||||
if (w < 0)
|
||||
return 1;
|
||||
return w;
|
||||
}
|
||||
static int utf8_width(wchar_t wc) { return cst_utf8_width(wc); }
|
||||
|
||||
static int utf8_cols(const char *s, size_t n) {
|
||||
int cols = 0;
|
||||
@@ -454,20 +438,7 @@ static size_t utf8_fit_cols(const char *s, size_t n, int max_cols) {
|
||||
return i;
|
||||
}
|
||||
|
||||
static size_t ansi_skip(const char *s, size_t n) {
|
||||
if (n < 2 || (unsigned char)s[0] != 0x1b)
|
||||
return 0;
|
||||
if (s[1] == '[') {
|
||||
size_t i = 2;
|
||||
while (i < n) {
|
||||
unsigned char c = (unsigned char)s[i++];
|
||||
if (c >= 0x40 && c <= 0x7e)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
static size_t ansi_skip(const char *s, size_t n) { return cst_ansi_skip(s, n); }
|
||||
|
||||
static char *xstrdup(const char *s) {
|
||||
if (!s)
|
||||
@@ -1272,6 +1243,8 @@ typedef struct {
|
||||
int fill; /* pair painted across the whole row (0 = none) */
|
||||
int bar; /* pair for a "┃" in column 0 (0 = none) */
|
||||
int blk; /* hist block index, -1 for live / none */
|
||||
int cont; /* soft-wrapped continuation of the previous row */
|
||||
int cont_pad; /* leading columns of a continuation that only indent */
|
||||
} dline_t;
|
||||
|
||||
static int g_dl_blk = -1; /* block being flattened, stamped into dline_t */
|
||||
@@ -1311,6 +1284,8 @@ static int dlines_push(dlines_t *dl, const char *text, int pair, int bold,
|
||||
L->fill = 0;
|
||||
L->bar = 0;
|
||||
L->blk = g_dl_blk;
|
||||
L->cont = 0;
|
||||
L->cont_pad = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1341,10 +1316,10 @@ static void push_wrapped(dlines_t *dl, const char *prefix, const char *body,
|
||||
/* Walk body; for ANSI text, wrap on visible columns skipping SGR. */
|
||||
size_t n = strlen(body);
|
||||
size_t i = 0;
|
||||
int first = 1;
|
||||
int first = 1, soft = 0;
|
||||
while (i < n && dl->n < MAX_RENDER_LINES) {
|
||||
size_t start = i;
|
||||
int col = 0;
|
||||
int col = 0, pad = 0;
|
||||
while (i < n) {
|
||||
size_t sg = 0;
|
||||
if ((unsigned char)body[i] == 0x1b) {
|
||||
@@ -1387,7 +1362,7 @@ static void push_wrapped(dlines_t *dl, const char *prefix, const char *body,
|
||||
first = 0;
|
||||
} else {
|
||||
/* continuation indent matching prefix visual width */
|
||||
int pad = pref_cols;
|
||||
pad = pref_cols;
|
||||
if (pad < 0)
|
||||
pad = 0;
|
||||
if (pad > 40)
|
||||
@@ -1400,10 +1375,11 @@ static void push_wrapped(dlines_t *dl, const char *prefix, const char *body,
|
||||
line[pad] = 0;
|
||||
}
|
||||
}
|
||||
dlines_push(dl, line, pair, bold, dim);
|
||||
if (i < n && body[i - 1] != '\n' && col >= body_w) {
|
||||
/* soft wrap — already advanced */
|
||||
if (dlines_push(dl, line, pair, bold, dim) == 0 && soft) {
|
||||
dl->lines[dl->n - 1].cont = 1;
|
||||
dl->lines[dl->n - 1].cont_pad = pad;
|
||||
}
|
||||
soft = body[i - 1] != '\n';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2338,7 +2314,7 @@ static const char *const TIPS[] = {
|
||||
"/undo reverts the file changes of the last turn",
|
||||
"/provider switches provider; keys are saved per provider",
|
||||
"Click a tool card or Thought to expand it",
|
||||
"Shift+drag selects text while mouse support is on",
|
||||
"Drag over the transcript to copy it; double-click copies a word",
|
||||
};
|
||||
|
||||
static void draw_home(tui_t *t) {
|
||||
@@ -2548,6 +2524,57 @@ static void draw_side(tui_t *t) {
|
||||
wrefresh(s);
|
||||
}
|
||||
|
||||
/* Ordered selection [y1:x1, y2:x2] over display lines, snapped to words or
|
||||
* whole lines by the click count; x2 < 0 means the end of the line. */
|
||||
static void sel_range(const tui_t *t, const dlines_t *dl, int *y1, int *x1,
|
||||
int *y2, int *x2) {
|
||||
int ay = t->sel_ay, ax = t->sel_ax, by = t->sel_y, bx = t->sel_x;
|
||||
if (by < ay || (by == ay && bx < ax)) {
|
||||
int ty = ay, tx = ax;
|
||||
ay = by, ax = bx, by = ty, bx = tx;
|
||||
}
|
||||
if (t->sel_mode == 2) {
|
||||
int c1, c2;
|
||||
if (ay >= 0 && ay < dl->n) {
|
||||
cst_textsel_word(dl->lines[ay].text, dl->lines[ay].indent, ax, &c1, &c2);
|
||||
ax = c1;
|
||||
}
|
||||
if (by >= 0 && by < dl->n) {
|
||||
cst_textsel_word(dl->lines[by].text, dl->lines[by].indent, bx, &c1, &c2);
|
||||
bx = c2;
|
||||
}
|
||||
} else if (t->sel_mode == 3) {
|
||||
while (ay > 0 && ay < dl->n && dl->lines[ay].cont)
|
||||
ay--;
|
||||
while (by + 1 < dl->n && dl->lines[by + 1].cont)
|
||||
by++;
|
||||
ax = 0;
|
||||
bx = -1;
|
||||
}
|
||||
*y1 = ay, *x1 = ax, *y2 = by, *x2 = bx;
|
||||
}
|
||||
|
||||
static void draw_selection(tui_t *t, const dlines_t *dl, int h, int w) {
|
||||
if (t->sel_state != SEL_DRAG && t->sel_state != SEL_SHOWN)
|
||||
return;
|
||||
int y1, x1, y2, x2;
|
||||
sel_range(t, dl, &y1, &x1, &y2, &x2);
|
||||
for (int row = 0; row < h; row++) {
|
||||
int li = t->log_start + row;
|
||||
if (li < y1 || li > y2)
|
||||
continue;
|
||||
int a = li == y1 ? x1 : 0;
|
||||
int b = li == y2 && x2 >= 0 ? x2 : w - 1;
|
||||
if (li < dl->n)
|
||||
cst_textsel_snap(dl->lines[li].text, dl->lines[li].indent, &a, &b);
|
||||
if (b >= w)
|
||||
b = w - 1;
|
||||
if (a <= b)
|
||||
mvwchgat(t->logw, row, a, b - a + 1, A_REVERSE,
|
||||
HAS_COLOR() ? CP_PANEL : 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_log(tui_t *t) {
|
||||
if (!t->logw)
|
||||
return;
|
||||
@@ -2581,6 +2608,7 @@ static void draw_log(tui_t *t) {
|
||||
int start = total - h - t->log_scroll;
|
||||
if (start < 0)
|
||||
start = 0;
|
||||
t->log_start = start;
|
||||
|
||||
if (t->row_blk_cap < h) {
|
||||
int *nb = realloc(t->row_blk, (size_t)h * sizeof(int));
|
||||
@@ -2604,6 +2632,7 @@ static void draw_log(tui_t *t) {
|
||||
if (L->bar)
|
||||
put_wc(t->logw, row, 0, L'┃', HAS_COLOR() ? (int)COLOR_PAIR(L->bar) : 0);
|
||||
}
|
||||
draw_selection(t, &dl, h, w);
|
||||
if (t->log_scroll > 0 && h > 2) {
|
||||
char more[128];
|
||||
snprintf(more, sizeof(more),
|
||||
@@ -2777,8 +2806,17 @@ static int input_visual_rows(const tui_t *t) {
|
||||
}
|
||||
|
||||
static void apply_mouse(const tui_t *t) {
|
||||
mousemask(t->mouse_on ? (mmask_t)ALL_MOUSE_EVENTS : 0, NULL);
|
||||
mousemask(t->mouse_on ? (mmask_t)(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION)
|
||||
: 0,
|
||||
NULL);
|
||||
mouseinterval(0);
|
||||
#ifdef NCURSES_VERSION
|
||||
/* ncurses only asks for presses and releases (1000). 1002 also reports
|
||||
* motion while a button is held, for drag selection; terminals treat it as
|
||||
* replacing 1000, so it goes after mousemask and is turned off explicitly. */
|
||||
printf(t->mouse_on ? "\033[?1002h" : "\033[?1002l");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void free_windows(tui_t *t) {
|
||||
@@ -3919,7 +3957,7 @@ static void cmd_help(tui_t *t, char *arg, int *quit) {
|
||||
push_system_line(t, " Enter send · Esc cancel turn · Ctrl+C twice or Ctrl+D "
|
||||
"quit · Ctrl+P command palette");
|
||||
push_system_line(t, " Mouse: click cards / Thought to expand, wheel scrolls, "
|
||||
"Shift+drag selects · /mouse");
|
||||
"drag copies (double-click word, triple line) · /mouse");
|
||||
push_system_line(t, " Rebind in keybinds.json next to settings.json");
|
||||
push_system_line(t, "");
|
||||
}
|
||||
@@ -4346,8 +4384,10 @@ static void cmd_mouse(tui_t *t, char *arg, int *quit) {
|
||||
(void)quit;
|
||||
t->mouse_on = !t->mouse_on;
|
||||
apply_mouse(t);
|
||||
push_system_line(t, t->mouse_on ? "(mouse on · Shift+drag selects text)"
|
||||
: "(mouse off · the terminal selects text)");
|
||||
push_system_line(t, t->mouse_on
|
||||
? "(mouse on · drag selects and copies (OSC 52) · "
|
||||
"Shift+drag uses the terminal's own selection)"
|
||||
: "(mouse off · the terminal selects text)");
|
||||
}
|
||||
|
||||
static void cmd_max_steps(tui_t *t, char *arg, int *quit) {
|
||||
@@ -4533,25 +4573,16 @@ static void cmd_export(tui_t *t, char *arg, int *quit) {
|
||||
push_system_line(t, "(exported to %s)", path);
|
||||
}
|
||||
|
||||
static void cmd_copy(tui_t *t, char *arg, int *quit) {
|
||||
(void)arg;
|
||||
(void)quit;
|
||||
const char *text = NULL;
|
||||
for (int i = t->hist.n - 1; i >= 0 && !text; i--)
|
||||
if (t->hist.items[i].kind == BLK_ASSISTANT && t->hist.items[i].body)
|
||||
text = t->hist.items[i].body;
|
||||
if (!text || !text[0]) {
|
||||
push_system_line(t, "Nothing to copy yet");
|
||||
return;
|
||||
}
|
||||
/* Puts text on the terminal's clipboard; returns the bytes sent, 0 on
|
||||
* failure. */
|
||||
static size_t osc52_copy(const char *text, size_t n) {
|
||||
static const char b64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
size_t n = strlen(text);
|
||||
if (n > 75000)
|
||||
n = 75000; /* many terminals cap OSC 52 near 100 KB */
|
||||
char *enc = malloc((n + 2) / 3 * 4 + 1);
|
||||
if (!enc)
|
||||
return;
|
||||
return 0;
|
||||
size_t o = 0;
|
||||
for (size_t i = 0; i < n; i += 3) {
|
||||
unsigned v = (unsigned char)text[i] << 16;
|
||||
@@ -4571,8 +4602,24 @@ static void cmd_copy(tui_t *t, char *arg, int *quit) {
|
||||
printf("\033]52;c;%s\a", enc);
|
||||
fflush(stdout);
|
||||
free(enc);
|
||||
push_system_line(t, "(copied %zu bytes of the last reply via OSC 52 — the "
|
||||
"terminal must allow clipboard access)", n);
|
||||
return n;
|
||||
}
|
||||
|
||||
static void cmd_copy(tui_t *t, char *arg, int *quit) {
|
||||
(void)arg;
|
||||
(void)quit;
|
||||
const char *text = NULL;
|
||||
for (int i = t->hist.n - 1; i >= 0 && !text; i--)
|
||||
if (t->hist.items[i].kind == BLK_ASSISTANT && t->hist.items[i].body)
|
||||
text = t->hist.items[i].body;
|
||||
if (!text || !text[0]) {
|
||||
push_system_line(t, "Nothing to copy yet");
|
||||
return;
|
||||
}
|
||||
size_t n = osc52_copy(text, strlen(text));
|
||||
if (n)
|
||||
push_system_line(t, "(copied %zu bytes of the last reply via OSC 52 — the "
|
||||
"terminal must allow clipboard access)", n);
|
||||
}
|
||||
|
||||
static void cmd_init(tui_t *t, char *arg, int *quit) {
|
||||
@@ -4906,7 +4953,7 @@ static const slash_cmd_t SLASH[] = {
|
||||
1, cmd_approval},
|
||||
{"yolo", "", "", "toggle approving everything", 1, cmd_yolo},
|
||||
{"sidebar", "", "", "show or hide the session sidebar", 1, cmd_sidebar},
|
||||
{"mouse", "", "", "toggle mouse support (Shift+drag selects)", 1,
|
||||
{"mouse", "", "", "toggle mouse support (drag copies)", 1,
|
||||
cmd_mouse},
|
||||
{"max-steps", "", "[N]", "tool steps allowed per turn", 1, cmd_max_steps},
|
||||
{"status", "", "", "session, model, context and settings", 1, cmd_status},
|
||||
@@ -5437,6 +5484,145 @@ static int handle_click(tui_t *t, int y, int x, int busy) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Transcript cell under screen (y, x) as display line + column. Outside the
|
||||
* transcript returns 0, unless `clamp` pulls the point to its nearest edge. */
|
||||
static int log_cell(const tui_t *t, int y, int x, int clamp, int *li,
|
||||
int *col) {
|
||||
if (t->home || !t->logw)
|
||||
return 0;
|
||||
int lh, lw;
|
||||
getmaxyx(t->logw, lh, lw);
|
||||
int ry = y - t->log_y, rx = x - t->log_x;
|
||||
if (!clamp && (ry < 0 || ry >= lh || rx < 0 || rx >= lw))
|
||||
return 0;
|
||||
*li = t->log_start + clampi(ry, 0, lh - 1);
|
||||
*col = clampi(rx, 0, lw - 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void sel_clear(tui_t *t) {
|
||||
int shown = t->sel_state == SEL_DRAG || t->sel_state == SEL_SHOWN;
|
||||
t->sel_state = SEL_NONE;
|
||||
if (shown)
|
||||
draw_log(t);
|
||||
}
|
||||
|
||||
static void sel_copy(tui_t *t) {
|
||||
int h, w;
|
||||
getmaxyx(t->logw, h, w);
|
||||
(void)h;
|
||||
dlines_t dl = {0};
|
||||
build_display(t, &dl, w);
|
||||
int y1, x1, y2, x2;
|
||||
sel_range(t, &dl, &y1, &x1, &y2, &x2);
|
||||
cst_selbuf_t b = {0};
|
||||
for (int k = y1 < 0 ? 0 : y1; k <= y2 && k < dl.n; k++) {
|
||||
const dline_t *L = &dl.lines[k];
|
||||
int c1 = k == y1 ? x1 : 0, c2 = k == y2 ? x2 : -1;
|
||||
if (k > y1) {
|
||||
if (!L->cont)
|
||||
cst_selbuf_putc(&b, '\n');
|
||||
else if (c1 < L->indent + L->cont_pad)
|
||||
c1 = L->indent + L->cont_pad;
|
||||
}
|
||||
int joined = k < y2 && k + 1 < dl.n && dl.lines[k + 1].cont;
|
||||
cst_textsel_append(&b, L->text, L->indent, c1, c2, !joined);
|
||||
}
|
||||
dlines_free(&dl);
|
||||
if (b.n && osc52_copy(b.s, b.n)) {
|
||||
size_t chars = 0;
|
||||
for (size_t i = 0; i < b.n; i++)
|
||||
chars += ((unsigned char)b.s[i] & 0xC0) != 0x80;
|
||||
char msg[96];
|
||||
snprintf(msg, sizeof(msg), "Copied %zu characters (OSC 52)", chars);
|
||||
set_status(t, msg);
|
||||
draw_status(t);
|
||||
}
|
||||
cst_selbuf_free(&b);
|
||||
}
|
||||
|
||||
/* Drag while the button is held: extend the selection, scrolling the
|
||||
* transcript when the pointer is above or below it. */
|
||||
static void sel_motion(tui_t *t, int y, int x) {
|
||||
int lh, lw;
|
||||
getmaxyx(t->logw, lh, lw);
|
||||
(void)lw;
|
||||
int ry = y - t->log_y;
|
||||
if (ry < 0 || ry >= lh) {
|
||||
int before = t->log_scroll;
|
||||
t->log_scroll += ry < 0 ? 1 : -1;
|
||||
if (t->log_scroll < 0)
|
||||
t->log_scroll = 0;
|
||||
if (t->log_scroll != before)
|
||||
draw_log(t); /* updates log_start */
|
||||
}
|
||||
int li, col;
|
||||
if (!log_cell(t, y, x, 1, &li, &col))
|
||||
return;
|
||||
if (t->sel_state == SEL_PRESS && li == t->sel_ay && col == t->sel_ax)
|
||||
return;
|
||||
t->sel_state = SEL_DRAG;
|
||||
t->sel_y = li;
|
||||
t->sel_x = col;
|
||||
draw_log(t);
|
||||
}
|
||||
|
||||
/* Wheel, clicks and drag selection; -2 means quit. */
|
||||
static int on_mouse(tui_t *t, const MEVENT *ev, int busy) {
|
||||
mmask_t b = ev->bstate;
|
||||
if (b & BUTTON4_PRESSED) {
|
||||
t->log_scroll += SCROLL_SPEED;
|
||||
draw_log(t);
|
||||
return 0;
|
||||
}
|
||||
if (b & BUTTON5_PRESSED) {
|
||||
t->log_scroll -= SCROLL_SPEED;
|
||||
if (t->log_scroll < 0)
|
||||
t->log_scroll = 0;
|
||||
draw_log(t);
|
||||
return 0;
|
||||
}
|
||||
if (b & REPORT_MOUSE_POSITION) {
|
||||
if (t->sel_state == SEL_PRESS || t->sel_state == SEL_DRAG)
|
||||
sel_motion(t, ev->y, ev->x);
|
||||
return 0;
|
||||
}
|
||||
if (b & (BUTTON1_PRESSED | BUTTON1_CLICKED)) {
|
||||
sel_clear(t);
|
||||
int li, col;
|
||||
if (!(b & BUTTON1_PRESSED) || !log_cell(t, ev->y, ev->x, 0, &li, &col))
|
||||
return handle_click(t, ev->y, ev->x, busy);
|
||||
if (t->sel_clicks && since_ms(&t->sel_click_at) < MULTI_CLICK_MS &&
|
||||
li == t->sel_click_y && abs(col - t->sel_click_x) <= 1)
|
||||
t->sel_clicks = t->sel_clicks % 3 + 1;
|
||||
else
|
||||
t->sel_clicks = 1;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t->sel_click_at);
|
||||
t->sel_click_y = li;
|
||||
t->sel_click_x = col;
|
||||
t->sel_mode = t->sel_clicks;
|
||||
t->sel_ay = t->sel_y = li;
|
||||
t->sel_ax = t->sel_x = col;
|
||||
t->sel_press_y = ev->y;
|
||||
t->sel_press_x = ev->x;
|
||||
t->sel_state = t->sel_clicks > 1 ? SEL_DRAG : SEL_PRESS;
|
||||
if (t->sel_state == SEL_DRAG)
|
||||
draw_log(t);
|
||||
return 0;
|
||||
}
|
||||
if (b & BUTTON1_RELEASED) {
|
||||
if (t->sel_state == SEL_PRESS) {
|
||||
t->sel_state = SEL_NONE;
|
||||
return handle_click(t, t->sel_press_y, t->sel_press_x, busy);
|
||||
}
|
||||
if (t->sel_state == SEL_DRAG) {
|
||||
sel_copy(t);
|
||||
t->sel_state = SEL_SHOWN;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Tab completes after "/" at the start or an "@word" under the cursor. */
|
||||
static int wants_completion(const tui_t *t) {
|
||||
if (t->in_len && t->inbuf[0] == '/')
|
||||
@@ -6192,6 +6378,9 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
int rc = tui_wget_wch(t.input, &wch);
|
||||
if (rc == ERR)
|
||||
continue;
|
||||
if (t.sel_state == SEL_SHOWN &&
|
||||
!(rc == KEY_CODE_YES && (wch == KEY_MOUSE || wch == KEY_RESIZE)))
|
||||
sel_clear(&t);
|
||||
|
||||
if (!t.in_paste &&
|
||||
!(rc == KEY_CODE_YES &&
|
||||
@@ -6263,6 +6452,7 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
continue;
|
||||
}
|
||||
if (wch == KEY_RESIZE) {
|
||||
t.sel_state = SEL_NONE; /* display lines rewrap */
|
||||
layout(&t);
|
||||
wtimeout(t.input, 80);
|
||||
redraw_all(&t);
|
||||
@@ -6271,18 +6461,8 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
if (wch == KEY_MOUSE) {
|
||||
MEVENT ev;
|
||||
if (getmouse(&ev) == OK) {
|
||||
if (ev.bstate & BUTTON4_PRESSED) {
|
||||
t.log_scroll += SCROLL_SPEED;
|
||||
draw_log(&t);
|
||||
} else if (ev.bstate & BUTTON5_PRESSED) {
|
||||
t.log_scroll -= SCROLL_SPEED;
|
||||
if (t.log_scroll < 0)
|
||||
t.log_scroll = 0;
|
||||
draw_log(&t);
|
||||
} else if (ev.bstate & (BUTTON1_PRESSED | BUTTON1_CLICKED)) {
|
||||
if (handle_click(&t, ev.y, ev.x, busy) == -2)
|
||||
goto out;
|
||||
}
|
||||
if (on_mouse(&t, &ev, busy) == -2)
|
||||
goto out;
|
||||
draw_input(&t); /* keep the terminal cursor in the box */
|
||||
}
|
||||
continue;
|
||||
@@ -6495,7 +6675,7 @@ out:
|
||||
tui_free(&t);
|
||||
#ifdef NCURSES_VERSION
|
||||
kitty_restore();
|
||||
printf("\033[?2004l");
|
||||
printf("\033[?1002l\033[?2004l");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
endwin();
|
||||
|
||||
Reference in New Issue
Block a user