diff options
| -rw-r--r-- | docs/design/001-first-component.md | 10 | ||||
| -rw-r--r-- | src/draw.c | 27 | ||||
| -rw-r--r-- | src/main.c | 70 | ||||
| -rw-r--r-- | src/screen.c | 8 | ||||
| -rw-r--r-- | src/vega.h | 7 | ||||
| -rw-r--r-- | tests/check.py | 51 |
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 그대로다. @@ -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. */ @@ -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; @@ -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']) |
