aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/design/001-first-component.md10
-rw-r--r--src/draw.c27
-rw-r--r--src/main.c70
-rw-r--r--src/screen.c8
-rw-r--r--src/vega.h7
-rw-r--r--tests/check.py51
6 files changed, 148 insertions, 25 deletions
diff --git a/docs/design/001-first-component.md b/docs/design/001-first-component.md
index 0161af9..fcfa3df 100644
--- a/docs/design/001-first-component.md
+++ b/docs/design/001-first-component.md
@@ -39,6 +39,12 @@ view tasks_list_focus = at.position
| CLI | `vega cli app.pmod < script` | 파이프용 평문. 목록은 `> ` 표시와 `[badge]`, 표는 표시 폭으로 정렬한 열. 터미널이고 `NO_COLOR`가 없을 때만 색 |
| NUI | `vega say app.pmod < script` | 문장. 이벤트마다 포커스 항목을 말하고(`Item 2 of 3: danger, milk, urgent.`), 실패하면 `Could not toggle.`, `show`에서 화면 전체 |
+포커스는 표면에서도 옮긴다. TUI에서 목록·표의 행을 클릭하면 그 행이 포커스가 되고, 휠은 포커스를 한 행씩 옮긴다
+(SGR 마우스 보고). 스크립트는 `point 보기 행`(0부터)과 `click 줄 칸`(마지막으로 보인 프레임, 1부터)으로 같은 일을 한다.
+앱에 코드가 필요 없다. `<표>_focus` 보기가 `at.position`처럼 인덱스 상태의 위치 그대로이면 Vega는 Pure에게 그 상태를
+묻고(`pure_module_position`) 행을 가리키는 이벤트로 옮긴다(`pure_app_point`). 키 이벤트처럼 저장되므로 다음 실행의 다른
+표면도 그 포커스에서 이어 간다. 포커스 보기가 다른 식이면(계산한 위치 등) 가리킬 상태가 없어 클릭은 아무 일도 하지 않는다.
+
TUI 입력: 앱에 text 입력이 있으면 처음에는 글자가 입력 줄로 가고 Enter가 확정한다(IME 확정분과 같은 의미).
Tab이 키 모드로 바꾸면 글자·숫자·Space가 `KeyA`·`Digit0`·`Space` 입력이 된다(앱이 Tab을 쓰면 Tab은 앱의 것).
화살표·Delete·Home 같은 키는 두 모드 모두 앱으로 간다. Ctrl-C나 Ctrl-Q로 끝내고 터미널을 되돌린다.
@@ -100,10 +106,10 @@ CLI나 문장 표면이 그대로 이어받는다. Vega에도 앱에도 저장 �
- aup와 Vega의 예제 11개를 세 표면에 무작위 입력으로 돌려 비정상 종료가 없다. sanitizer 빌드로도 같은 검사를 통과한다.
- 규약을 여덟 가지로 어긴 명세에서 `vega check`가 아홉 곳을 모두 정확한 문장으로 알리고, 예제에서는 아무것도 알리지
않는다.
+- 무작위 스크립트에 `point`(범위 밖 포함)와 TUI `click`을 섞어도 CLI·문장·프레임이 모델과 같다. 가상 터미널에서
+ 마우스로 행을 클릭하면 포커스가 옮겨지고, 저장소를 쓰는 다음 실행이 그 포커스에서 시작한다.
## 남은 것
-- 포커스를 표면에서 바꾸기(GUI 클릭, TUI 마우스). 지금은 앱의 키 핸들러만 포커스를 옮긴다. Pure에 표의 인덱스를
- 이벤트로 바꾸는 호스트 API가 필요하다.
- GUI 표면, 음성 합성으로 가는 NUI.
- 문장의 언어. 지금 NUI의 틀 문장은 영어이고 값은 앱의 text 그대로다.
diff --git a/src/draw.c b/src/draw.c
index bec3804..3c7a59d 100644
--- a/src/draw.c
+++ b/src/draw.c
@@ -193,11 +193,15 @@ void vega_say(FILE *out, PureApp *app, const VegaScreen *screen, bool all) {
bool vega_grid(VegaGrid *grid, unsigned width, unsigned height) {
size_t count = (size_t)width * height;
VegaCell *cells = calloc(count ? count : 1, sizeof(*cells)), *shown = calloc(count ? count : 1, sizeof(*shown));
- if (!cells || !shown) { free(cells); free(shown); return false; }
+ struct VegaRow *rows = calloc(height ? height : 1, sizeof(*rows));
+ if (!cells || !shown || !rows) { free(cells); free(shown); free(rows); return false; }
+ for (unsigned r = 0; r < height; ++r) rows[r].part = -1;
free(grid->cells);
free(grid->shown);
+ free(grid->rows);
grid->cells = cells;
grid->shown = shown;
+ grid->rows = rows;
grid->painted = false;
grid->width = width;
grid->height = height;
@@ -207,6 +211,7 @@ bool vega_grid(VegaGrid *grid, unsigned width, unsigned height) {
void vega_grid_free(VegaGrid *grid) {
free(grid->cells);
free(grid->shown);
+ free(grid->rows);
*grid = (VegaGrid){0};
}
@@ -272,11 +277,13 @@ static const char *key_symbol(const char *key) {
return key;
}
-static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, size_t first, unsigned top, unsigned height) {
+static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, int index, size_t first, unsigned top,
+ unsigned height) {
char text[VEGA_TEXT];
size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part);
for (unsigned r = 0; r < height && first + r < rows; ++r) {
size_t row = first + r;
+ grid->rows[top + r] = (struct VegaRow){index, row};
bool focused = row == focus;
VegaTone tone = vega_tone(app, part, row);
Pen pen = {grid, top + r, 0, grid->width, (uint8_t)tone, focused, false};
@@ -307,7 +314,8 @@ static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, size_t
}
}
-static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, size_t first, unsigned top, unsigned height) {
+static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, int index, size_t first, unsigned top,
+ unsigned height) {
const PureModule *m = app->module;
char text[VEGA_TEXT];
size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part);
@@ -338,6 +346,7 @@ static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, size_
for (unsigned r = 0; r < visible && first + r < rows; ++r) {
size_t row = first + r;
bool focused = row == focus;
+ grid->rows[top + 1 + r] = (struct VegaRow){index, row};
Pen pen = {grid, top + 1 + r, 0, grid->width, (uint8_t)vega_tone(app, part, row), focused, false};
write_cstr(&pen, focused ? "> " : " ");
for (unsigned c = 0; c < part->column_count; ++c) {
@@ -351,10 +360,18 @@ static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, size_
}
}
+bool vega_hit(const VegaGrid *grid, unsigned line, unsigned *part, size_t *item) {
+ if (line >= grid->height || grid->rows[line].part < 0) return false;
+ *part = (unsigned)grid->rows[line].part;
+ *item = grid->rows[line].item;
+ return true;
+}
+
void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const char *prompt, size_t prompt_length,
bool typing) {
const PureModule *m = app->module;
for (size_t i = 0; i < (size_t)grid->width * grid->height; ++i) grid->cells[i] = (VegaCell){' ', 0, 0, false, false};
+ for (unsigned r = 0; r < grid->height; ++r) grid->rows[r].part = -1;
grid->cursor_row = grid->cursor_column = -1;
if (!grid->width || !grid->height) return;
char text[VEGA_TEXT];
@@ -398,8 +415,8 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha
if (focus < *first) *first = focus;
if (visible && focus >= *first + visible) *first = focus - visible + 1;
if (*first + visible > rows) *first = rows > visible ? rows - visible : 0;
- if (part->kind == VEGA_LIST) draw_list(grid, app, part, *first, row + 1, area);
- else draw_table(grid, app, part, *first, row + 1, area);
+ if (part->kind == VEGA_LIST) draw_list(grid, app, part, (int)i, *first, row + 1, area);
+ else draw_table(grid, app, part, (int)i, *first, row + 1, area);
row += share;
}
/* Keys the app answers to, then the text being typed. */
diff --git a/src/main.c b/src/main.c
index 4f83780..4e5c79b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -20,13 +20,16 @@
* With --store the app's states live in that tree store: the app continues
* where it was, whichever surface runs it.
* Script commands, one per line: key KEYNAME | text HEX|- | type HEX | enter |
- * tab | frame [N] | show. key presses and releases the input bound to that key;
- * text commits text to the screen's text input; type, enter and tab act on the
- * TUI's input line. */
+ * tab | frame [N] | point VIEW ROW | click LINE COLUMN | show. key presses and
+ * releases the input bound to that key; text commits text to the screen's text
+ * input; type, enter and tab act on the TUI's input line; point focuses a row
+ * (0 first) of a list or table; click is a mouse click on the last shown TUI
+ * frame (1 first, as terminals report it). */
static PureModule module;
static PureApp app;
static VegaScreen screen;
+static VegaGrid *drawn; /* the TUI's last frame, which a click lands on */
static char entry[VEGA_TEXT];
static size_t entry_length;
static bool typing;
@@ -85,6 +88,27 @@ static bool commit(const char *text, size_t length) {
return pure_app_text(&app, (uint32_t)screen.prompt, (PureSpan){(const unsigned char *)text, length}) == PURE_OK;
}
+static const VegaPart *part_named(const char *name) {
+ for (unsigned i = 0; i < screen.count; ++i)
+ if (screen.parts[i].kind != VEGA_FIELD && !strcmp(module.view_names[screen.parts[i].view], name)) return &screen.parts[i];
+ return NULL;
+}
+
+/* A mouse button on a line of the last frame: a click focuses the row there;
+ * the wheel moves the focus of the list or table under it. */
+static bool mouse(unsigned button, unsigned line) {
+ unsigned index;
+ size_t item;
+ if (!drawn || !vega_hit(drawn, line, &index, &item)) return false;
+ const VegaPart *part = &screen.parts[index];
+ if (button == 64 || button == 65) {
+ size_t focus = vega_focus(&app, part), rows = vega_rows(&app, part);
+ if (button == 64 ? !focus : focus + 1 >= rows) return false;
+ item = button == 64 ? focus - 1 : focus + 1;
+ } else if (button) return false;
+ return vega_point(&app, part, item);
+}
+
static int hex(char c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; }
static size_t unhex(const char *digits, char *out, size_t size) {
@@ -221,7 +245,7 @@ static void on_resize(int signal_number) { (void)signal_number; resized = 1; }
static void restore_terminal(void) {
if (!raw_terminal) return;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal);
- const char *leave = "\033[0m\033[?25h\033[?1049l";
+ const char *leave = "\033[?1006l\033[?1000l\033[0m\033[?25h\033[?1049l";
if (write(STDOUT_FILENO, leave, strlen(leave)) < 0) { /* nothing left to tell */ }
raw_terminal = false;
}
@@ -238,7 +262,7 @@ static bool raw_mode(void) {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t)) return false;
raw_terminal = true;
atexit(restore_terminal);
- const char *enter = "\033[?1049h\033[?25l";
+ const char *enter = "\033[?1049h\033[?25l\033[?1000h\033[?1006h"; /* mouse buttons, reported as SGR */
return write(STDOUT_FILENO, enter, strlen(enter)) >= 0;
}
@@ -286,6 +310,19 @@ static bool handle_input(const unsigned char *in, size_t length) {
for (size_t at = 0; at < length;) {
unsigned char c = in[at];
if (c == 0x03 || c == 0x11) return false; /* Ctrl-C, Ctrl-Q */
+ if (c == 0x1B && at + 2 < length && in[at + 1] == '[' && in[at + 2] == '<') {
+ /* A mouse report: ESC [ < button ; column ; line, M on press, m on release. */
+ unsigned fields[3] = {0, 0, 0}, field = 0;
+ size_t end = at + 3;
+ for (; end < length && field < 3; ++end) {
+ if (in[end] >= '0' && in[end] <= '9') fields[field] = fields[field] * 10 + (unsigned)(in[end] - '0');
+ else if (in[end] == ';') ++field;
+ else break;
+ }
+ if (end < length && in[end] == 'M' && field == 2 && fields[2]) mouse(fields[0], fields[2] - 1);
+ at = end + 1;
+ continue;
+ }
if (c == 0x1B) {
size_t matched = 0;
for (size_t i = 0; i < sizeof(sequences) / sizeof(*sequences) && !matched; ++i) {
@@ -318,6 +355,7 @@ static int interactive(void) {
if (!raw_mode()) { fputs("vega: the tui needs a terminal (or use --dump WxH)\n", stderr); return 2; }
signal(SIGWINCH, on_resize);
VegaGrid grid = {0};
+ drawn = &grid;
bool ticking = frames();
for (;;) {
if (resized) {
@@ -338,6 +376,7 @@ static int interactive(void) {
if (got <= 0 || !handle_input(in, (size_t)got)) break;
}
restore_terminal();
+ drawn = NULL;
vega_grid_free(&grid);
return 0;
}
@@ -351,6 +390,8 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
static char line[1 << 16];
VegaGrid grid = {0};
if (surface == SURFACE_TUI && !vega_grid(&grid, width, height)) return 2;
+ bool shown = false;
+ drawn = surface == SURFACE_TUI ? &grid : NULL;
bool color = surface == SURFACE_CLI && isatty(STDOUT_FILENO) && !getenv("NO_COLOR");
unsigned long counted = vega_evaluations();
while (fgets(line, sizeof(line), stdin)) {
@@ -359,6 +400,7 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
if (sscanf(line, "%15s %65535s", command, argument) < 1) continue;
bool acted = true, ok = true;
int input = -1;
+ const char *verb = "do that";
if (!strcmp(command, "key")) {
ok = press(argument, &input);
if (input < 0) fprintf(stderr, "vega: no input answers %s\n", argument);
@@ -373,6 +415,19 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
} else if (!strcmp(command, "tab")) {
handle_key("Tab");
acted = false;
+ } else if (!strcmp(command, "point")) {
+ char name[PURE_MODULE_NAME];
+ long row = -1;
+ const VegaPart *part = sscanf(line, "%*s %31s %ld", name, &row) == 2 ? part_named(name) : NULL;
+ verb = "point";
+ if (!part) fprintf(stderr, "vega: no list or table %s\n", name);
+ ok = part && row >= 0 && vega_point(&app, part, (size_t)row);
+ } else if (!strcmp(command, "click")) {
+ unsigned at_line = 0, at_column = 0;
+ verb = "point";
+ if (surface != SURFACE_TUI) { fputs("vega: click needs the tui\n", stderr); continue; }
+ acted = sscanf(line, "%*s %u %u", &at_line, &at_column) == 2 && at_line && shown;
+ ok = acted && mouse(0, at_line - 1);
} else if (!strcmp(command, "frame")) {
long count = *argument ? strtol(argument, NULL, 10) : 1;
for (long i = 0; i < count && i < 600; ++i) ok = pure_app_frame(&app) == PURE_OK && ok;
@@ -380,7 +435,7 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
acted = false;
if (surface == SURFACE_CLI) vega_print(stdout, &app, &screen, color);
else if (surface == SURFACE_SAY) vega_say(stdout, &app, &screen, true);
- else if (painting) {
+ else if ((shown = true) && painting) {
Out out = {0};
vega_draw(&grid, &app, &screen, entry, entry_length, typing);
paint(&grid, &out);
@@ -400,11 +455,12 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
}
if (!ok && input >= 0) fprintf(stderr, "vega: %s failed\n", module.input_names[input]);
if (surface == SURFACE_SAY && acted) {
- if (!ok) printf("Could not %s.\n", input >= 0 ? module.input_names[input] : "do that");
+ if (!ok) printf("Could not %s.\n", input >= 0 ? module.input_names[input] : verb);
else vega_say(stdout, &app, &screen, false);
}
fflush(stdout);
}
+ drawn = NULL;
vega_grid_free(&grid);
return 0;
}
diff --git a/src/screen.c b/src/screen.c
index 2d4c6dc..3e8b2c4 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -32,12 +32,14 @@ void vega_screen(VegaScreen *screen, const PureModule *m) {
for (uint32_t i = 0; i < m->views && screen->count < VEGA_PARTS; ++i) {
if (m->view_table[i] || (int)i == screen->title || focus_view[i]) continue;
VegaPart *part = &screen->parts[screen->count++];
- *part = (VegaPart){.kind = VEGA_FIELD, .view = i, .focus = -1, .label = -1, .tone = -1, .detail = -1, .badge = -1};
+ *part = (VegaPart){.kind = VEGA_FIELD, .view = i, .focus = -1, .pointer = -1, .label = -1, .tone = -1, .detail = -1,
+ .badge = -1};
if (m->view_type[i] != PURE_VIEW_TABLE) continue;
char name[PURE_MODULE_NAME + 8];
snprintf(name, sizeof(name), "%s_focus", m->view_names[i]);
part->focus = pure_module_view(m, name);
if (part->focus >= 0 && !focus_view[part->focus]) part->focus = -1;
+ part->pointer = part->focus >= 0 ? pure_module_position(m, (uint32_t)part->focus) : -1;
part->label = column(m, i, "label", PURE_VIEW_TEXT);
part->tone = column(m, i, "tone", PURE_VIEW_NUM);
part->kind = part->label >= 0 ? VEGA_LIST : VEGA_TABLE;
@@ -155,6 +157,10 @@ size_t vega_focus(PureApp *app, const VegaPart *part) {
return focus >= (double)(rows - 1) ? rows - 1 : (size_t)focus;
}
+bool vega_point(PureApp *app, const VegaPart *part, size_t row) {
+ return part->pointer >= 0 && pure_app_point(app, (uint32_t)part->pointer, row) == PURE_OK;
+}
+
VegaTone vega_tone(PureApp *app, const VegaPart *part, size_t row) {
double tone;
if (part->tone < 0 || !vega_number(app, (uint32_t)part->tone, row, &tone)) return VEGA_NORMAL;
diff --git a/src/vega.h b/src/vega.h
index 2d0a29f..80e3f7d 100644
--- a/src/vega.h
+++ b/src/vega.h
@@ -20,6 +20,7 @@ typedef struct {
VegaKind kind;
uint32_t view;
int focus; /* number view with the focused row, or -1 */
+ int pointer; /* the index state that view shows, or -1: a surface may move it */
int label, tone, detail, badge; /* columns, or -1 */
uint32_t columns[PURE_MODULE_VIEWS]; /* a table's shown columns, in order */
unsigned column_count;
@@ -47,6 +48,9 @@ bool vega_number(PureApp *, uint32_t view, size_t row, double *out);
size_t vega_rows(const PureApp *, const VegaPart *);
/* The focused row, clamped to the rows; 0 without a focus view. */
size_t vega_focus(PureApp *, const VegaPart *);
+/* Focus a row, as a user pointing at it (a click); false when the part's
+ * focus is not a plain index position or the row is not there. */
+bool vega_point(PureApp *, const VegaPart *, size_t row);
VegaTone vega_tone(PureApp *, const VegaPart *, size_t row);
/* Values evaluated so far, for checking that surfaces stay lazy. */
unsigned long vega_evaluations(void);
@@ -77,6 +81,7 @@ typedef struct {
typedef struct {
unsigned width, height;
VegaCell *cells, *shown;
+ struct VegaRow { int part; size_t item; } *rows; /* the item each line shows, or part -1 */
bool painted; /* shown holds what the terminal shows */
size_t first[VEGA_PARTS];
int cursor_row, cursor_column; /* where the terminal cursor goes, or -1 */
@@ -86,4 +91,6 @@ void vega_grid_free(VegaGrid *);
/* prompt is the text being typed for the screen's text input; typing says
* whether keys go to it. */
void vega_draw(VegaGrid *, PureApp *, const VegaScreen *, const char *prompt, size_t prompt_length, bool typing);
+/* The part and item a line of the last drawn grid shows. */
+bool vega_hit(const VegaGrid *, unsigned line, unsigned *part, size_t *item);
#endif
diff --git a/tests/check.py b/tests/check.py
index d46698d..e0d5996 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -61,7 +61,13 @@ class Todo:
self.tasks, self.at = [], 0
def event(self, name, text=None):
- """Returns False when the event fails (a missing item)."""
+ """Returns False when the event fails (a missing item). point takes
+ the row as text: a surface focusing it, as a click would."""
+ if name == 'point':
+ if not 0 <= text < len(self.tasks):
+ return False
+ self.at = text
+ return True
if name == 'add':
self.tasks.append({'title': text, 'done': 0, 'urgent': 0})
self.at = len(self.tasks) - 1
@@ -127,7 +133,12 @@ def cli_and_say(vega, module, seeds, steps=300):
rng = random.Random(seed)
model, script, expected_cli, expected_say = Todo(), [], [], []
for _ in range(steps):
- if rng.random() < 0.25:
+ roll = rng.random()
+ if roll < 0.1:
+ row = rng.randrange(len(model.tasks) + 2)
+ script.append(f'point tasks_list {row}')
+ ok, name = model.event('point', row), 'point'
+ elif roll < 0.35:
word = rng.choice(WORDS)
script.append('text ' + (word.encode().hex() or '-'))
ok, name = model.event('add', word), 'add'
@@ -164,7 +175,12 @@ def stored_surfaces(vega, module, work, seeds, steps=240):
surface = ['cli', 'say'][number % 2]
script, expected = [], []
for _ in range(start, end):
- if rng.random() < 0.3:
+ roll = rng.random()
+ if roll < 0.1:
+ row = rng.randrange(len(model.tasks) + 1)
+ script.append(f'point tasks_list {row}')
+ ok, name = model.event('point', row), 'point'
+ elif roll < 0.4:
word = rng.choice(WORDS)
script.append('text ' + (word.encode().hex() or '-'))
ok, name = model.event('add', word), 'add'
@@ -201,9 +217,16 @@ def tui(vega, module, seeds, sizes, steps=300):
area = rows - 5 # title, field, list header, key bar, input line
for seed in seeds:
rng = random.Random(seed * 7 + columns)
- model, script, states = Todo(), [], []
- for _ in range(steps):
- if rng.random() < 0.3:
+ model, script, states, scroll = Todo(), [], [], 0
+ for step in range(steps):
+ roll = rng.random()
+ if roll < 0.12 and step:
+ # A click on a line of the last frame: an item line focuses that item.
+ line = rng.randrange(rows)
+ script.append(f'click {line + 1} 3')
+ if 3 <= line < 3 + area and scroll + line - 3 < len(model.tasks):
+ model.event('point', scroll + line - 3)
+ elif roll < 0.4:
word = rng.choice(WORDS)
script.append('text ' + (word.encode().hex() or '-'))
model.event('add', word)
@@ -212,6 +235,10 @@ def tui(vega, module, seeds, sizes, steps=300):
script.append(f'key {KEYS[name]}')
model.event(name)
script.append('show')
+ n = len(model.tasks)
+ if model.at < scroll: scroll = model.at
+ if model.at >= scroll + area: scroll = model.at - area + 1
+ if scroll + area > n: scroll = max(n - area, 0)
states.append(([dict(t) for t in model.tasks], model.at))
result = run(vega, 'tui', module, '--dump', f'{columns}x{rows}', stdin='\n'.join(script) + '\n')
assert result.returncode == 0, result.stderr
@@ -382,7 +409,8 @@ def interactive(vega, module, store=None):
return data
screen = read_for(0.3)
- for keys in (b'milk\r', 'b\u00e9ton\r'.encode(), b'\t', b'\x1b[A', b' ', b'u'):
+ # Then a click on line 5 (béton's row): a mouse report, press and release.
+ for keys in (b'milk\r', 'b\u00e9ton\r'.encode(), b'\t', b'\x1b[A', b' ', b'u', b'\x1b[<0;3;5M\x1b[<0;3;5m'):
os.write(fd, keys)
screen += read_for(0.2)
os.write(fd, b'\x03')
@@ -395,11 +423,13 @@ def interactive(vega, module, store=None):
# What the terminal ended up showing, from the bytes it was sent.
grid = [[[' ', 0, False] for _ in range(40)] for _ in range(10)]
replay(screen, grid)
- final = '\n'.join(shown(grid))
- assert 'milk' in final and 'béton' in final and '(2)' in final and 'done' in final, final
+ final = shown(grid)
+ assert 'milk' in '\n'.join(final) and '(2)' in final[2] and 'done' in final[3], final
+ assert final[4].startswith('>> béton') and final[3].startswith('| milk'), final # the click moved the focus
+ assert '\x1b[?1000h' in text and '\x1b[?1000l' in text, 'mouse reports on, then off'
if store: # what was typed is still there in the next session
again = run(vega, 'cli', module, '--store', store, stdin='show\n').stdout.splitlines()
- assert again == ['To do', 'left: 2 tasks', 'tasks_list (2)', '> milk [done] (muted)', ' béton'], again
+ assert again == ['To do', 'left: 2 tasks', 'tasks_list (2)', ' milk [done] (muted)', '> béton'], again
return 1
@@ -479,6 +509,7 @@ def robustness(vega, aup, work, rounds):
elif roll < 0.3: script.append('show')
elif roll < 0.35: script.append('frame 3')
elif roll < 0.4: script.append(rng.choice(['type 61', 'enter', 'tab']))
+ elif roll < 0.45: script.append(f'click {rng.randrange(1, 30)} {rng.randrange(1, 90)}')
else: script.append('key ' + rng.choice(keys))
text = '\n'.join(script) + '\nshow\n'
size = rng.choice(['80x24', '12x5', '1x1', '200x3'])