From 985ae80d7f82cef7c273fae953c73f8bd909ebef Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sat, 19 Sep 2026 23:17:45 +0200 Subject: tui: backspace deletes backwards while editing; esc restores the value --- clients/tui.c | 43 ++++++++++++++++++++++++------------------- docs/TUI-GUIDELINES.md | 3 +++ tests/test_tui.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/clients/tui.c b/clients/tui.c index 473cc0e..8f410c2 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -306,8 +306,7 @@ int tui_date_field_edit(char *buf, size_t cap, size_t *pos, int ch, } if (*fresh) { - int editing = ch == KEY_BACKSPACE || ch == 127 || ch == 8 || - ch == KEY_DC || (ch >= '0' && ch <= '9'); + int editing = ch >= '0' && ch <= '9'; int moving = ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END || ch == 1 || ch == 5; if (editing || moving) { @@ -394,8 +393,7 @@ int tui_field_edit(char *buf, size_t cap, size_t *pos, int ch, int *fresh) if (*pos != (size_t)-1 && *pos <= e.len) e.pos = *pos; if (*fresh) { - int editing = ch == KEY_BACKSPACE || ch == 127 || ch == 8 || - ch == KEY_DC || (ch >= 32 && ch < 256); + int editing = ch >= 32 && ch < 256; int moving = ch == KEY_LEFT || ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END || ch == 1 || ch == 5; if (editing || moving) { @@ -705,8 +703,9 @@ int tui_confirm(const char *title, const char *fmt, ...) int tui_prompt_into(char *buf, size_t cap, const char *label, const char *def, int mask) { - if (def && def != buf) - snprintf(buf, cap, "%s", def); + char scratch[512]; + size_t scap = cap < sizeof scratch ? cap : sizeof scratch; + snprintf(scratch, scap, "%s", def ? def : buf); int y = LINES - 3; move(y, 2); @@ -716,16 +715,19 @@ int tui_prompt_into(char *buf, size_t cap, const char *label, tui_style_reset(); refresh(); int prev = curs_set(1); - int rc = tui_edit_field(y, (int)strlen(label) + 3, buf, cap, mask); + int rc = tui_edit_field(y, (int)strlen(label) + 3, scratch, scap, mask); curs_set(prev == ERR ? 0 : prev); + if (rc != 0) + snprintf(buf, cap, "%s", scratch); return rc != 0; } int tui_date_prompt_into(char *buf, size_t cap, const char *label, const char *def) { - if (def && def != buf) - snprintf(buf, cap, "%s", def); + char scratch[16]; + size_t scap = cap < sizeof scratch ? cap : sizeof scratch; + snprintf(scratch, scap, "%s", def ? def : buf); int y = LINES - 3; move(y, 2); @@ -736,26 +738,29 @@ int tui_date_prompt_into(char *buf, size_t cap, const char *label, size_t pos = (size_t)-1; int fresh = 1; int prev = curs_set(1); + int rc = 0; for (;;) { move(y, (int)strlen(label) + 3); clrtoeol(); - addnstr(buf, (int)cap); + addnstr(scratch, (int)scap); clrtoeol(); - size_t slen = strlen(buf); + size_t slen = strlen(scratch); size_t cp = pos == (size_t)-1 || pos > slen ? slen : pos; - move(y, (int)strlen(label) + 3 + (int)tui_disp_width_n(buf, cp)); + move(y, (int)strlen(label) + 3 + (int)tui_disp_width_n(scratch, cp)); refresh(); int ch = in_key(); if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { - curs_set(prev == ERR ? 0 : prev); - return 1; - } - if (ch == 27) { - curs_set(prev == ERR ? 0 : prev); - return 0; + rc = 1; + break; } - tui_date_field_edit(buf, cap, &pos, ch, &fresh); + if (ch == 27) + break; + tui_date_field_edit(scratch, scap, &pos, ch, &fresh); } + curs_set(prev == ERR ? 0 : prev); + if (rc) + snprintf(buf, cap, "%s", scratch); + return rc; } int tui_amount_prompt_into(int64_t *out, const char *label, int64_t def_ore) diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md index 631a516..decca09 100644 --- a/docs/TUI-GUIDELINES.md +++ b/docs/TUI-GUIDELINES.md @@ -70,6 +70,9 @@ written compactly as `^N`, `^A`, `^C`, `^R` to save width. `Ctrl+U` clears it (see `field_edit`). - First keystroke in a freshly focused field replaces its content (`field_fresh`), so prefilled values like dates can be typed over. + Backspace/Del/`^U` still delete normally instead of replacing. +- A prompt edits a scratch copy: only `Enter` commits it to the field; + `Esc` leaves the field exactly as it was. - Date fields (`date_field_edit`, `date_prompt`) accept digits only and insert the dashes themselves: type `20260315` and the field shows `2026-03-15`. Backspace deletes a digit (with its separator), ←/→/Home/End move by digit, diff --git a/tests/test_tui.c b/tests/test_tui.c index f4405b4..93aece0 100644 --- a/tests/test_tui.c +++ b/tests/test_tui.c @@ -104,6 +104,42 @@ static void test_ledit(void) CHECK(tui_le_key(&e, KEY_UP) == 0); /* not an editing key */ } +static void test_field_fresh(void) +{ + char buf[16]; + size_t pos = (size_t)-1; + int fresh = 1; + + /* fresh + backspace deletes the last character, it does not clear */ + snprintf(buf, sizeof buf, "abc"); + CHECK(tui_field_edit(buf, sizeof buf, &pos, KEY_BACKSPACE, &fresh)); + CHECK(strcmp(buf, "ab") == 0); + + /* the first printable still replaces the whole prefilled value */ + snprintf(buf, sizeof buf, "abc"); + pos = (size_t)-1; + fresh = 1; + CHECK(tui_field_edit(buf, sizeof buf, &pos, 'x', &fresh)); + CHECK(strcmp(buf, "x") == 0); + + /* movement keeps the content, clears fresh; the next printable is + inserted at the caret */ + snprintf(buf, sizeof buf, "abc"); + pos = (size_t)-1; + fresh = 1; + CHECK(tui_field_edit(buf, sizeof buf, &pos, KEY_LEFT, &fresh)); + CHECK(strcmp(buf, "abc") == 0 && pos == 2 && fresh == 0); + CHECK(tui_field_edit(buf, sizeof buf, &pos, 'x', &fresh)); + CHECK(strcmp(buf, "abxc") == 0); + + /* fresh + delete removes the character at the caret, no clear */ + snprintf(buf, sizeof buf, "abc"); + pos = 0; + fresh = 1; + CHECK(tui_field_edit(buf, sizeof buf, &pos, KEY_DC, &fresh)); + CHECK(strcmp(buf, "bc") == 0); +} + static void date_feed(char *buf, size_t cap, const char *digits) { size_t pos = (size_t)-1; @@ -139,6 +175,18 @@ static void test_date_field(void) fresh = 0; tui_date_field_edit(buf, sizeof buf, &pos, '9', &fresh); CHECK(strcmp(buf, "2026-06-09") == 0); + + /* fresh + backspace deletes the last digit; fresh + ^U clears */ + snprintf(buf, sizeof buf, "2026-06-04"); + pos = (size_t)-1; + fresh = 1; + CHECK(tui_date_field_edit(buf, sizeof buf, &pos, KEY_BACKSPACE, &fresh)); + CHECK(strcmp(buf, "2026-06-0") == 0); + snprintf(buf, sizeof buf, "2026-06-04"); + pos = (size_t)-1; + fresh = 1; + CHECK(tui_date_field_edit(buf, sizeof buf, &pos, 21, &fresh)); + CHECK(strcmp(buf, "") == 0); } static void nav_init(struct tui_list_nav *v, int n) @@ -479,6 +527,7 @@ int main(void) test_formats(); test_parse_kr(); test_ledit(); + test_field_fresh(); test_date_field(); test_styles(); test_markup(); -- cgit v1.3