commit - f3d1acfbd1e73e25b5c6dd5ac6e2e9c7c9ddfd35
commit + 3a70517b1b3371da4e8c33fc991db7fea4f84caf
blob - 37bf30d1809764ae9daa9373ae61d981ff86ae69
blob + 798f9e90dbe3a9a6332e8b66247410dc489e689f
--- regress/editor/editor_test.c
+++ regress/editor/editor_test.c
CHECK(ed_feed(&e, K_PGUP, 0, NULL, 0) == R_PAGE_UP);
ed_free(&e);
+ /* Mouse-wheel events scroll the transcript without recalling history. */
+ ed_init(&e);
+ ed_history_add(&e, "old command", 11);
+ type(&e, "draft");
+ CHECK(ed_feed(&e, K_WHEEL_UP, 0, NULL, 0) == R_SCROLL_LINE_UP);
+ CHECK(text_is(&e, "draft"));
+ CHECK(ed_feed(&e, K_WHEEL_DOWN, 0, NULL, 0) == R_SCROLL_LINE_DOWN);
+ CHECK(text_is(&e, "draft"));
+ ed_free(&e);
+
REGRESS_END();
}
blob - c40e8d28c1b55f1f46af4bb9b948aeba1739b689
blob + b562dc7a99bb00a01cdce4f625ba2c7ffab55a09
--- regress/term/term_test.c
+++ regress/term/term_test.c
CHECK(one_key("\x1b[6~", 4, NULL) == K_PGDN);
CHECK(one_key("\x1b[Z", 3, NULL) == K_BTAB);
+ /* ---- SGR mouse wheel: distinct from editor-history arrows ---- */
+ CHECK(one_key("\x1b[<64;12;7M", sizeof("\x1b[<64;12;7M") - 1,
+ NULL) == K_WHEEL_UP);
+ CHECK(one_key("\x1b[<65;12;7M", sizeof("\x1b[<65;12;7M") - 1,
+ NULL) == K_WHEEL_DOWN);
+ /* Ctrl-modified wheel still uses the low button bits. */
+ CHECK(one_key("\x1b[<80;12;7M", sizeof("\x1b[<80;12;7M") - 1,
+ NULL) == K_WHEEL_UP);
+ {
+ struct term_input ti;
+ struct ti_key k;
+
+ ti_init(&ti);
+ ti_feed(&ti, (const unsigned char *)"\x1b[<64;12", 8);
+ CHECK(ti_next(&ti, &k) == TI_NONE);
+ ti_feed(&ti, (const unsigned char *)";7M", 3);
+ CHECK(ti_next(&ti, &k) == TI_KEY && k.key == K_WHEEL_UP);
+ ti_free(&ti);
+ }
+ /* Unused mouse events are consumed whole; coordinates never become
+ * editor input, and the next ordinary key remains available. */
+ {
+ struct term_input ti;
+ struct ti_key k;
+
+ ti_init(&ti);
+ ti_feed(&ti, (const unsigned char *)"\x1b[<0;12;7Mx",
+ sizeof("\x1b[<0;12;7Mx") - 1);
+ CHECK(ti_next(&ti, &k) == TI_KEY && k.key == K_CHAR &&
+ k.cp == 'x');
+ CHECK(ti_next(&ti, &k) == TI_NONE);
+ ti_free(&ti);
+ }
+ CHECK(one_key("\x1b[<64;12;7m", sizeof("\x1b[<64;12;7m") - 1,
+ NULL) == -1);
+
/* ---- SS3 arrows ---- */
CHECK(one_key("\x1bOA", 3, NULL) == K_UP);
CHECK(one_key("\x1bOF", 3, NULL) == K_END);
blob - 67abed12fae8a686c29bd7db01941150df10884b
blob + 5409a30504055bbd8724f303b025b81677ac02d2
--- src/fugu-tty/editor.c
+++ src/fugu-tty/editor.c
case K_BTAB:
do_complete(e, 0);
return (R_NONE);
+ case K_WHEEL_UP:
+ return (R_SCROLL_LINE_UP);
+ case K_WHEEL_DOWN:
+ return (R_SCROLL_LINE_DOWN);
case K_PGUP:
return (R_PAGE_UP);
case K_PGDN:
case K_DOWN:
recall_next(e);
return (R_NONE);
+ case K_WHEEL_UP:
+ return (R_SCROLL_LINE_UP);
+ case K_WHEEL_DOWN:
+ return (R_SCROLL_LINE_DOWN);
case K_PGUP:
return (R_PAGE_UP);
case K_PGDN:
blob - 90a435e08fdadb892bf4903e1095c85f24c25c06
blob + e1002aeb280f617e8da7ed1cef8f8a3bc4fcd134
--- src/fugu-tty/editor.h
+++ src/fugu-tty/editor.h
K_END,
K_PGUP,
K_PGDN,
+ K_WHEEL_UP,
+ K_WHEEL_DOWN,
K_CTRL_C,
K_CTRL_D,
K_CTRL_L,
blob - 9d083687063c4e4c052a31aa87a8db4a17ec48d2
blob + 8253851f7ef29861121167ec0be2a7fdb8bde858
--- src/fugu-tty/term_input.c
+++ src/fugu-tty/term_input.c
return (0);
}
+/*
+ * Parse an xterm SGR mouse frame: CSI < Cb ; Cx ; Cy M/m. Basic mouse
+ * tracking reports wheel motion as button 64/65; modifier bits do not alter
+ * the low button bits. Every complete mouse frame is consumed, including
+ * clicks, releases, and horizontal-wheel events that fugu does not use, so
+ * their coordinates can never leak into the editor as typed text.
+ */
+static enum ti_result
+step_sgr_mouse(struct term_input *ti, struct ti_key *out, int *again)
+{
+ size_t i = 3;
+ unsigned int button = 0;
+ int field;
+ unsigned char final;
+
+ for (field = 0; field < 3; field++) {
+ if (i >= ti->len)
+ return (TI_NONE);
+ if (ti->buf[i] < '0' || ti->buf[i] > '9')
+ goto malformed;
+ while (i < ti->len && ti->buf[i] >= '0' &&
+ ti->buf[i] <= '9') {
+ if (field == 0) {
+ if (button <= 255)
+ button = button * 10 +
+ (ti->buf[i] - '0');
+ if (button > 255)
+ button = 256;
+ }
+ i++;
+ if (i > 32)
+ goto malformed;
+ }
+ if (i >= ti->len)
+ return (TI_NONE);
+ if (field < 2) {
+ if (ti->buf[i] != ';')
+ goto malformed;
+ i++;
+ }
+ }
+
+ if (i >= ti->len)
+ return (TI_NONE);
+ final = ti->buf[i];
+ if (final != 'M' && final != 'm')
+ goto malformed;
+ consume(ti, i + 1);
+ *again = 1;
+ if (final != 'M' || (button & 64) == 0)
+ return (TI_NONE);
+ if ((button & 3) == 0) {
+ out->key = K_WHEEL_UP;
+ return (TI_KEY);
+ }
+ if ((button & 3) == 1) {
+ out->key = K_WHEEL_DOWN;
+ return (TI_KEY);
+ }
+ return (TI_NONE);
+
+malformed:
+ /* Make progress on a malformed bounded frame without swallowing the
+ * ordinary byte that follows it. */
+ consume(ti, i + 1);
+ *again = 1;
+ return (TI_NONE);
+}
+
/* Parse one CSI/SS3 sequence at buf[0]. */
static enum ti_result
step_escape(struct term_input *ti, struct ti_key *out, int *again)
*again = 1;
return (TI_NONE);
}
+ if (ti->len >= 3 && ti->buf[2] == '<')
+ return (step_sgr_mouse(ti, out, again));
{
size_t i = 2;
int param = 0, haveparam = 0;
blob - 33e812698b9756cd4f898ba089f68dfac855b13c
blob + b52badcb7e0f7370c675ffac5a63db6c48a127fc
--- src/fugu-tty/term_input.h
+++ src/fugu-tty/term_input.h
/*
* The terminal input decoder (behavior.md 2.1): a pure state machine
* turning a raw byte stream from the terminal into editor key events --
- * UTF-8 assembly, CSI/SS3 escape sequences to key symbols, and xterm
- * bracketed paste (ESC[200~ ... ESC[201~) collected as one atomic event.
+ * UTF-8 assembly, CSI/SS3 escape sequences to key symbols, xterm SGR mouse
+ * wheel reports, and bracketed paste (ESC[200~ ... ESC[201~) collected as
+ * one atomic event.
* It is fed bytes incrementally (input arrives in bursts) and drained one
* event at a time, so nothing about input handling depends on wgetch and
* the whole thing is unit-tested headless.