aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-20 10:24:18 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-20 10:24:18 +0200
commitae60b7d2c87cc9ed6878faa57cd4233f77f6f7c3 (patch)
tree918e717a5d226dcd54890901682a980c41d7e1d0
parent5097941fa96ce0c309e5f1af034993781a70fc66 (diff)
tui: pinned report headers, voucher detail headers, clamped pagingv0.1.48
-rw-r--r--clients/bokftui.c47
-rw-r--r--clients/tui.c116
-rw-r--r--clients/tui.h15
-rw-r--r--docs/PROTOCOL.md6
-rw-r--r--docs/STATE.md4
-rw-r--r--docs/TUI-GUIDELINES.md3
-rwxr-xr-xscripts/tui-golden.py42
-rw-r--r--tests/test_tui.c43
8 files changed, 222 insertions, 54 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c
index 05a845c..fef9100 100644
--- a/clients/bokftui.c
+++ b/clients/bokftui.c
@@ -29,6 +29,7 @@
#define MK_HEAD "\001"
#define MK_DIM "\002"
#define MK_RULE "\003"
+#define MK_STICKY "\004"
/* Menu item prefix for a non-selectable section header. */
#define MK_SECTION MK_HEAD
/* ^C quits the application; Esc is navigation only */
@@ -993,6 +994,10 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new)
series ? series : "", (long long)number, date ? date : "",
desc ? desc : "");
buf_append(&text, line, strlen(line));
+ int hdr = snprintf(
+ line, sizeof line, MK_HEAD " %-6s %-34s D %12s K %12s\n",
+ "Konto", "Benämning", "Debet", "Kredit");
+ buf_append(&text, line, (size_t)hdr);
size_t nrows = jarr_size(resp, "result.rows");
for (size_t i = 0; i < nrows; i++) {
char path[64];
@@ -1020,7 +1025,13 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new)
}
size_t natts = jarr_size(resp, "result.attachments");
if (natts) {
- buf_append(&text, "\nUnderlag:\n", 11);
+ buf_append(&text, "\n", 1);
+ line[0] = MK_RULE[0];
+ for (int i = 1; i <= 98; i++)
+ line[i] = '-';
+ line[99] = '\n';
+ buf_append(&text, line, 100);
+ buf_append(&text, MK_HEAD "Underlag:\n", 11);
for (size_t i = 0; i < natts; i++) {
char path[64];
snprintf(path, sizeof path, "result.attachments.%zu.filename",
@@ -1833,8 +1844,9 @@ static void accounts_report(struct app *a)
buf_init(&text);
char line[2048];
int hdr = snprintf(line, sizeof line,
- "%-6s %-*s %-12s %-5s %-6s %-8s\n", "Konto",
- name_w, "Namn", "Typ", "Aktiv", "SRU", "Moms");
+ MK_STICKY "%-6s %-*s %-12s %-5s %-6s %-8s\n",
+ "Konto", name_w, "Namn", "Typ", "Aktiv", "SRU",
+ "Moms");
buf_append(&text, line, (size_t)hdr);
int sep_w = 6 + 1 + name_w + 1 + 12 + 1 + 5 + 1 + 6 + 1 + 8;
for (int i = 0; i < sep_w && i < (int)sizeof line - 1; i++)
@@ -1976,10 +1988,11 @@ struct bs_sum {
int64_t ib, per, ub;
};
-static void bs_col_head(struct buf *t)
+static void bs_col_head(struct buf *t, int sticky)
{
- buf_line(t, " %5s %-48s %14s %14s %14s %14s\n", "", "", "Ing balans",
- "Ing saldo", "Period", "Utg balans");
+ buf_line(t, "%s %5s %-48s %14s %14s %14s %14s\n",
+ sticky ? MK_STICKY MK_HEAD : "", "", "", "Ing balans", "Ing saldo",
+ "Period", "Utg balans");
buf_rule(t, BS_RULE_W);
}
@@ -2065,7 +2078,7 @@ static void fmt_balans(struct buf *t, const struct app *a, yyjson_val *res)
struct bs_sum anl = { 0, 0, 0 }, oms = { 0, 0, 0 };
struct bs_sum eget = { 0, 0, 0 }, skuld = { 0, 0, 0 };
buf_line(t, MK_HEAD "TILLGÅNGAR\n");
- bs_col_head(t);
+ bs_col_head(t, 1);
buf_line(t, "\n" MK_HEAD "Anläggningstillgångar\n");
bs_rows(t, res, 1000, 1399, &anl);
bs_sum_line(t, "Summa anläggningstillgångar", &anl);
@@ -2087,7 +2100,7 @@ static void fmt_balans(struct buf *t, const struct app *a, yyjson_val *res)
bs_sum_line(t, "Summa tillgångar", &as);
buf_line(t, "\n" MK_HEAD "EGET KAPITAL OCH SKULDER\n");
- bs_col_head(t);
+ bs_col_head(t, 0);
buf_line(t, "\n" MK_HEAD "Eget kapital\n");
bs_group(t, res, "Bundet eget kapital", "Summa bundet eget kapital", 2000,
2090, &eget);
@@ -2280,8 +2293,8 @@ static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res,
tui_pad_hdr(h1, sizeof h1, 15, "Period");
tui_pad_hdr(h2, sizeof h2, 15, "Ackumulerat");
tui_pad_hdr(h3, sizeof h3, 15, "Föreg. per.");
- buf_line(t, MK_HEAD " %5s %s %s %s %s\n", "", lbl, h1, h2,
- h3);
+ buf_line(t, MK_STICKY MK_HEAD " %5s %s %s %s %s\n", "", lbl,
+ h1, h2, h3);
buf_rule(t, IS_RULE_W);
buf_line(t, "\n");
buf_line(t, MK_HEAD "%s\n", gr->title);
@@ -2362,8 +2375,8 @@ static void fmt_saldobalans(struct buf *t, const struct app *a,
tui_pad_hdr(h2, sizeof h2, 14, "Debet");
tui_pad_hdr(h3, sizeof h3, 14, "Kredit");
tui_pad_hdr(h4, sizeof h4, 14, "Utgående");
- buf_line(t, MK_HEAD " %5s %s %s %s %s %s\n", "Konto", nmh, h1, h2, h3,
- h4);
+ buf_line(t, MK_STICKY MK_HEAD " %5s %s %s %s %s %s\n", "Konto", nmh, h1,
+ h2, h3, h4);
buf_rule(t, BS_RULE_W);
yyjson_val *arr = yyjson_obj_get(res, "accounts");
size_t n = yyjson_arr_size(arr);
@@ -2541,8 +2554,8 @@ static void fmt_huvudbok(struct buf *t, const struct app *a, yyjson_val *res)
if (lv)
buf_line(t, MK_DIM "Senaste ver.: %s%lld\n\n", vstr(lv, "series"),
(long long)vint(lv, "number"));
- buf_line(t, MK_HEAD "%-8s %-58s %14s %14s %16s\n", "Konto", "", "Debet",
- "Kredit", "Saldo");
+ buf_line(t, MK_STICKY MK_HEAD "%-8s %-58s %14s %14s %16s\n", "Konto", "",
+ "Debet", "Kredit", "Saldo");
buf_rule(t, 114);
yyjson_val *accs = yyjson_obj_get(res, "accounts");
size_t n = yyjson_arr_size(accs);
@@ -2604,8 +2617,8 @@ static void fmt_verifikationslista(struct buf *t, const struct app *a,
(long long)vint(lv, "number"));
char nmh[64];
tui_pad_label(nmh, sizeof nmh, "Benämning", 48);
- buf_line(t, MK_HEAD "%-8s %-11s %s %14s %14s\n", "Ver.", "Datum/Konto",
- nmh, "Debet", "Kredit");
+ buf_line(t, MK_STICKY MK_HEAD "%-8s %-11s %s %14s %14s\n", "Ver.",
+ "Datum/Konto", nmh, "Debet", "Kredit");
buf_rule(t, 98);
yyjson_val *vs = yyjson_obj_get(res, "vouchers");
size_t n = yyjson_arr_size(vs);
@@ -5385,7 +5398,7 @@ static void strip_markup(const char *in, struct buf *out)
for (const char *p = in; *p;) {
const char *nl = strchr(p, '\n');
size_t len = nl ? (size_t)(nl - p) : strlen(p);
- if (len && p[0] >= TUI_MARK_HEADING && p[0] <= TUI_MARK_RULE) {
+ if (len && p[0] >= TUI_MARK_HEADING && p[0] <= TUI_MARK_STICKY) {
p++;
len--;
}
diff --git a/clients/tui.c b/clients/tui.c
index 06740d3..031fad0 100644
--- a/clients/tui.c
+++ b/clients/tui.c
@@ -126,6 +126,8 @@ const char *tui_markup(const char *line, int *style)
{
const char *p = line ? line : "";
int st = -1;
+ if (*p == TUI_MARK_STICKY)
+ p++;
if (*p == TUI_MARK_HEADING) {
st = TUI_HEADING;
p++;
@@ -141,6 +143,20 @@ const char *tui_markup(const char *line, int *style)
return p;
}
+int tui_sticky_split(char **lines, int n, char **head, int *nhead)
+{
+ int h = 0, b = 0;
+ for (int i = 0; i < n; i++) {
+ if (lines[i][0] == TUI_MARK_STICKY)
+ head[h++] = lines[i];
+ else
+ lines[b++] = lines[i];
+ }
+ if (nhead)
+ *nhead = h;
+ return b;
+}
+
void tui_keys_setup(void)
{
/* ^Enter has no control code; enable xterm modifyOtherKeys and bind the
@@ -1210,6 +1226,28 @@ int tui_pager(const char *title, const char *text, const char *action_hint)
action_hint ? TUI_PAGER_SAVE : 0, NULL, NULL);
}
+static void pager_line(int row, const char *line)
+{
+ move(row, 2);
+ clrtoeol();
+ if (!line)
+ return;
+ int st = -1;
+ const char *s = tui_markup(line, &st);
+ if (st == TUI_RULE) {
+ attrset(tui_style_attrs(TUI_RULE, g_colours, 0));
+ for (int x = 2; x < COLS - 1; x++)
+ mvaddch(row, x, ACS_HLINE);
+ attrset(A_NORMAL);
+ } else if (st >= 0) {
+ attrset(tui_style_attrs((enum tui_style)st, g_colours, 0));
+ mvaddnstr(row, 2, s, COLS - 4);
+ attrset(A_NORMAL);
+ } else {
+ mvaddnstr(row, 2, s, COLS - 4);
+ }
+}
+
int tui_pager_hook(const char *title, const char *text,
const char *extra_hint, unsigned flags,
int (*key)(void *ud, int ch), void *ud)
@@ -1232,29 +1270,24 @@ int tui_pager_hook(const char *title, const char *text,
break;
p = nl + 1;
}
+ char **head = xcalloc(nlines ? nlines : 1, sizeof(char *));
+ int nhead = 0;
+ int nb = tui_sticky_split(lines, n, head, &nhead);
int top = 0;
int view = LINES - 4;
+ int vbody = view - nhead;
+ if (vbody < 1)
+ vbody = 1;
for (;;) {
tui_frame(title);
- for (int i = 0; i < view; i++) {
- move(2 + i, 2);
- clrtoeol();
- if (top + i >= n)
- continue;
- int st = -1;
- const char *s = tui_markup(lines[top + i], &st);
- if (st == TUI_RULE) {
- attrset(tui_style_attrs(TUI_RULE, g_colours, 0));
- for (int x = 2; x < COLS - 1; x++)
- mvaddch(2 + i, x, ACS_HLINE);
- attrset(A_NORMAL);
- } else if (st >= 0) {
- attrset(tui_style_attrs((enum tui_style)st, g_colours, 0));
- mvaddnstr(2 + i, 2, s, COLS - 4);
- attrset(A_NORMAL);
- } else {
- mvaddnstr(2 + i, 2, s, COLS - 4);
- }
+ int row = 0;
+ for (int i = 0; i < nhead && row < view; i++, row++)
+ pager_line(2 + row, head[i]);
+ for (int i = 0; i < vbody && row < view; i++, row++) {
+ if (top + i < nb)
+ pager_line(2 + row, lines[top + i]);
+ else
+ pager_line(2 + row, NULL);
}
char hint[512];
if (extra_hint && *extra_hint)
@@ -1290,23 +1323,24 @@ int tui_pager_hook(const char *title, const char *text,
if (h == TUI_HOOK_STAY)
continue;
}
- if (ch == KEY_DOWN && top + view < n)
+ if (ch == KEY_DOWN && top + vbody < nb)
top++;
else if (ch == KEY_UP && top > 0)
top--;
else if (ch == KEY_NPAGE)
- top += view;
+ top += vbody;
else if (ch == KEY_PPAGE)
- top -= view;
+ top -= vbody;
else if (ch == KEY_HOME)
top = 0;
else if (ch == KEY_END)
- top = n > view ? n - view : 0;
- if (top + view > n)
- top = n > view ? n - view : 0;
+ top = nb > vbody ? nb - vbody : 0;
+ if (top + vbody > nb)
+ top = nb > vbody ? nb - vbody : 0;
if (top < 0)
top = 0;
}
+ free(head);
free(copy);
free(lines);
return ret;
@@ -1410,6 +1444,33 @@ int tui_form_act_first(int nf, const struct tui_form_action *acts, int na)
return -1;
}
+int tui_form_act_page(int focus, int nf, const struct tui_form_action *acts,
+ int na, int dir)
+{
+ if (nf < 0)
+ nf = 0;
+ if (na < 0)
+ na = 0;
+ int n = nf + na;
+ if (n <= 0)
+ return -1;
+ int pos = focus >= 0 && focus < n ? focus : 0;
+ for (int i = 0; i < TUI_FORM_PAGE; i++) {
+ int next = -1;
+ for (int p = pos + (dir >= 0 ? 1 : -1); p >= 0 && p < n;
+ p += (dir >= 0 ? 1 : -1)) {
+ if (p < nf || act_selectable(acts, p - nf, na)) {
+ next = p;
+ break;
+ }
+ }
+ if (next < 0)
+ break;
+ pos = next;
+ }
+ return pos;
+}
+
int tui_form_act_key(int sel, int nf, const struct tui_form_action *acts,
int na, int ch)
{
@@ -1429,10 +1490,7 @@ int tui_form_act_key(int sel, int nf, const struct tui_form_action *acts,
}
if (ch == KEY_NPAGE || ch == KEY_PPAGE) {
int dir = ch == KEY_NPAGE ? 1 : -1;
- int pos = sel;
- for (int i = 0; i < TUI_FORM_PAGE; i++)
- pos = tui_form_act_step(pos, nf, acts, na, dir);
- return pos;
+ return tui_form_act_page(sel, nf, acts, na, dir);
}
return tui_form_next(sel, nf, ch);
}
diff --git a/clients/tui.h b/clients/tui.h
index fcab5b7..6df27fb 100644
--- a/clients/tui.h
+++ b/clients/tui.h
@@ -37,13 +37,20 @@ void tui_style(enum tui_style s);
void tui_style_reset(void);
/* Pager line markup. A leading marker selects a style; \x03 is a rule and
- is drawn full width as ACS_HLINE. tui_markup returns the text after the
- marker and sets *style to the matching style, or -1 for plain text. */
+ is drawn full width as ACS_HLINE; \x04 pins the line as a column header
+ above the scrolling body. tui_markup returns the text after the marker(s)
+ and sets *style to the matching style, or -1 for plain text. */
#define TUI_MARK_HEADING 0x01
#define TUI_MARK_DIM 0x02
#define TUI_MARK_RULE 0x03
+#define TUI_MARK_STICKY 0x04
const char *tui_markup(const char *line, int *style);
+/* Moves sticky-marked lines from lines[] to head[] (both in input order),
+ compacts the remaining body lines in lines[] and returns the body count.
+ head must hold at least n pointers and must not alias lines. Pure. */
+int tui_sticky_split(char **lines, int n, char **head, int *nhead);
+
/* Key hooks: widgets call the screen's hook for keys they do not handle. */
#define TUI_HOOK_NONE 0 /* not handled: process the key normally */
#define TUI_HOOK_STAY 1 /* handled: redraw and keep the widget up */
@@ -213,6 +220,10 @@ int tui_form_focus_store(int *focus, int sel, int ret);
are skipped and the ring wraps. dir +1 = Tab/Down, -1 = Shift-Tab/Up. */
int tui_form_act_step(int focus, int nf, const struct tui_form_action *acts,
int na, int dir);
+/* Like tui_form_act_step, but pages (up to TUI_FORM_PAGE rows) and stops at
+ the first/last selectable row instead of wrapping. dir +1 = PgDn. */
+int tui_form_act_page(int focus, int nf, const struct tui_form_action *acts,
+ int na, int dir);
/* Same ring for one key, plus Home/End/PgUp/PgDn and the form return keys
(F5/^Enter/F9/Esc/q). Returns the new focus, a TUI_FORM_* code or
TUI_NAV_NONE. */
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md
index 8405ca5..6f26463 100644
--- a/docs/PROTOCOL.md
+++ b/docs/PROTOCOL.md
@@ -503,8 +503,9 @@ commands. Implemented screens (0.1.0-dev):
- **Inloggning** — server, user, password; org picker when several exist.
- **Dashboard** — status line with org, fiscal year, role and user.
-- **Verifikat** — list and detail view (rows, attachments, hash, link to
- corrected voucher); `c` posts an ändringsverifikat.
+- **Verifikat** — list and detail view (rows with column headers, an
+ underlag section separated by a rule, hash, link to corrected voucher); `c`
+ posts an ändringsverifikat.
- **Nytt verifikat** — row editor with live balance display, F5 dry-run
validation and F9 posting; one `client_ref` per form makes retries safe.
F4 applies a konteringsmall (prompts for template and `x`).
@@ -525,6 +526,7 @@ commands. Implemented screens (0.1.0-dev):
inkomstdeklaration (INK2/SRU), årsredovisning (K2 text draft) and
kontolista (all accounts with type, moms treatment, SRU and status).
`s` saves the SRU files, eSKD XML and the årsredovisning respectively.
+ Report tables keep their column-header row pinned while the body scrolls.
The draft asks for the board's proposed dividend, kept per fiscal year
with `fiscal_year.update`.
- **Bokslut** — periodiseringsfond and tax rate fields; F5 shows the posting
diff --git a/docs/STATE.md b/docs/STATE.md
index de87186..526c1a6 100644
--- a/docs/STATE.md
+++ b/docs/STATE.md
@@ -91,6 +91,10 @@ check.
saldo/Period/Utg balans and Beräknat resultat, Momsrapport ruta för ruta).
The TUI never shows report JSON. Amounts are Swedish formatted
(`1 234,56`); moms rutas are whole kronor truncated like Kapitas.
+ Report tables mark their column-header row with `\x04` (`TUI_MARK_STICKY`)
+ so the pager pins it above the scrolling body; the verifikat detail shows
+ column headers and separates the underlag section with a rule. PgUp/PgDn
+ page without wrapping and stop at the first/last row.
16. **Moms rules (schema v3)**: default seed covers 05 over 3000-3019,
3100-3199, 3300-3399, reverse-charge sales (32xx) in 41, EU purchases in
20/21, reverse-charge output VAT 2614/2624/2634 in 30/31/32, and box 48
diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md
index 3bd3c65..09605e9 100644
--- a/docs/TUI-GUIDELINES.md
+++ b/docs/TUI-GUIDELINES.md
@@ -24,7 +24,7 @@ there.
| `Ctrl+A` | Close or reopen the highlighted räkenskapsår (Räkenskapsår screen; asks for confirmation) |
| `1`–`9` | In lists: jump to that row. In menus: activate that item |
| `g` | Live goto: "Gå till rad/nummer:" updates the selection as you type digits (backspace steps back); Enter closes the prompt without opening anything |
-| arrows, PgUp/PgDn, Home/End | Move/scroll; selection always stays visible |
+| arrows, PgUp/PgDn, Home/End | Move/scroll; selection always stays visible. Pages stop at the first/last row, they never wrap |
| `e` | Edit the shown object (IB, where applicable) |
| `a` | Add/upload (Underlag) |
| `c` | Correct (voucher detail) |
@@ -193,6 +193,7 @@ formatters, interpreted by `tui_markup`):
| `\x01` | line is a heading (`TUI_HEADING`) |
| `\x02` | line is dimmed (`TUI_DIM`) |
| `\x03` | full-width `ACS_HLINE` rule (`TUI_RULE`); the rest of the line is ignored |
+| `\x04` | line is pinned as a column header: the pager keeps it visible above the scrolling body (combine with another marker, e.g. `\x04\x01`) |
Report formatters mark headings, sums/derived lines and rules before the
text reaches `tui_pager`; the markers never change column widths. Anything
diff --git a/scripts/tui-golden.py b/scripts/tui-golden.py
index 589b693..33f1d7c 100755
--- a/scripts/tui-golden.py
+++ b/scripts/tui-golden.py
@@ -88,6 +88,32 @@ SCENARIOS = [
"10. 20 range 4510-4529",
],
},
+ {
+ "name": "voucher-detail",
+ "screen": "vouchers",
+ "steps": [
+ {
+ "keys": ["enter"],
+ "expect": ["Benämning", "Debet", "Kredit", "Underlag",
+ "kvitto.txt"],
+ },
+ ],
+ },
+ {
+ "name": "kontolista-sticky",
+ "screen": "reports",
+ "expect": ["Rapporter"],
+ "steps": [
+ {
+ "keys": ["7"],
+ "expect": ["Konto", "Namn"],
+ },
+ {
+ "keys": ["end"],
+ "expect": ["Konto", "Namn", "konton, varav"],
+ },
+ ],
+ },
]
# --------------------------------------------------------------------------
@@ -558,6 +584,22 @@ def setup_rig(repo, tmp, env):
"--org", str(org_id), "fiscal_year.get",
json.dumps({"id": fy_id})], env)
fy = json.loads(out)["result"]
+ out = run_checked([str(bind / "bokfctl"), "--socket", str(sock),
+ "--user", "admin", "--password", env["BOKFD_PASSWORD"],
+ "--org", str(org_id), "voucher.post",
+ json.dumps({
+ "date": fy.get("start_date", "2026-01-01"),
+ "description": "Golden verifikat",
+ "rows": [{"account": "1930", "debit_ore": 12300},
+ {"account": "3001", "credit_ore": 12300}],
+ })], env)
+ vid = json.loads(out)["result"]["id"]
+ run_checked([str(bind / "bokfctl"), "--socket", str(sock),
+ "--user", "admin", "--password", env["BOKFD_PASSWORD"],
+ "--org", str(org_id), "attachment.put",
+ json.dumps({"filename": "kvitto.txt",
+ "content_base64": "a3ZpdHRv",
+ "voucher_id": vid})], env)
return daemon, sock, org_id, fy
diff --git a/tests/test_tui.c b/tests/test_tui.c
index 56880fa..84e610e 100644
--- a/tests/test_tui.c
+++ b/tests/test_tui.c
@@ -303,11 +303,39 @@ static void test_markup(void)
s = tui_markup("a\001b", &st);
CHECK(strcmp(s, "a\001b") == 0 && st == -1);
s = tui_markup("\004text", &st);
- CHECK(strcmp(s, "\004text") == 0 && st == -1);
+ CHECK(strcmp(s, "text") == 0 && st == -1);
+ s = tui_markup("\004\001Rubrik", &st);
+ CHECK(strcmp(s, "Rubrik") == 0 && st == TUI_HEADING);
s = tui_markup("\001x", NULL);
CHECK(strcmp(s, "x") == 0);
}
+static void test_sticky(void)
+{
+ char a[] = "alfa";
+ char h1[] = "\004RUBRIK 1";
+ char b[] = "beta";
+ char h2[] = "\004RUBRIK 2";
+ char c[] = "gamma";
+ char *lines[] = { a, h1, b, h2, c };
+ char *head[5];
+ int nh = 0;
+ int nb = tui_sticky_split(lines, 5, head, &nh);
+ CHECK(nb == 3 && nh == 2);
+ CHECK(strcmp(lines[0], "alfa") == 0);
+ CHECK(strcmp(lines[1], "beta") == 0);
+ CHECK(strcmp(lines[2], "gamma") == 0);
+ CHECK(strcmp(head[0], "\004RUBRIK 1") == 0);
+ CHECK(strcmp(head[1], "\004RUBRIK 2") == 0);
+ char *one[] = { a };
+ nb = tui_sticky_split(one, 1, head, &nh);
+ CHECK(nb == 1 && nh == 0);
+ char *none[] = { h1, h2 };
+ nb = tui_sticky_split(none, 2, head, &nh);
+ CHECK(nb == 0 && nh == 2);
+ CHECK(head[0] == h1 && head[1] == h2);
+}
+
static const unsigned char SECTION_FLAGS[5] = { 0, 1, 1, 0, 1 };
static void test_nav_sections(void)
@@ -417,8 +445,16 @@ static void test_form_actions(void)
CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_DOWN) == 0);
CHECK(tui_form_act_key(4, nf, ACTS, 6, KEY_HOME) == 0);
CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_END) == 8);
- CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_NPAGE) == 4);
- CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_PPAGE) == 4);
+ /* paging stops at the ends instead of wrapping */
+ CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_NPAGE) == 8);
+ CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_PPAGE) == 0);
+ CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_NPAGE) == 8);
+ CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_PPAGE) == 0);
+ CHECK(tui_form_act_page(0, nf, ACTS, 6, 1) == 8);
+ CHECK(tui_form_act_page(8, nf, ACTS, 6, -1) == 0);
+ CHECK(tui_form_act_page(4, nf, ACTS, 6, 1) == 8);
+ CHECK(tui_form_act_page(4, nf, ACTS, 6, -1) == 0);
+ CHECK(tui_form_act_page(0, 0, NULL, 0, 1) == -1);
CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_F(5)) == TUI_FORM_REFRESH);
CHECK(tui_form_act_key(5, nf, ACTS, 6, TUI_KEY_CTRL_ENTER) ==
TUI_FORM_SUBMIT);
@@ -687,6 +723,7 @@ int main(void)
test_date_field();
test_styles();
test_markup();
+ test_sticky();
test_nav();
test_nav_sections();
test_form_nav();