1/* @title: Console report composer */
2#pragma once
3#include <compiler.h>
4#include <console/term.h>
5#include <stdarg.h>
6#include <stdbool.h>
7#include <stddef.h>
8#include <stdint.h>
9
10/* Compose a report as a stream of lines, all statically allocated
11 * with no locks taken, primarily useful for panicking */
12
13/* Bytes for a composed line. Full width rules of box drawing glyphs cost
14 * three bytes a column, so we use this to bound terminal width */
15#define REPORT_LINE_MAX 1024
16
17/* Build a line into a caller supplied buffer, counting display columns
18 *
19 * `max_col` bounds layout and `cap` bounds buffer, and partial writes
20 * are not allowed, and overflow uses ellipsis */
21struct report_line {
22 char *buf;
23 size_t cap;
24 size_t len; /* bytes used */
25 size_t col; /* display columns used */
26 size_t max_col; /* display column bound/budget */
27 size_t glyph_at; /* byte offset of the last visible glyph */
28 bool full; /* budget exhausted */
29 bool truncated; /* something was cut somewhere on this line */
30};
31
32#define REPORT_LINE(name, width) \
33 char name##_storage[REPORT_LINE_MAX]; \
34 struct report_line name; \
35 report_line_init(&name, name##_storage, sizeof(name##_storage), (width))
36
37void report_line_init(struct report_line *l, char *buf, size_t cap,
38 size_t max_col);
39void report_line_reset(struct report_line *l);
40
41void report_line_puts(struct report_line *l, const char *s);
42__printf_like(2, 3) void report_line_printf(struct report_line *l,
43 const char *fmt, ...);
44void report_line_vprintf(struct report_line *l, const char *fmt, va_list ap);
45
46/* Spaces out to a display column */
47void report_line_pad_to(struct report_line *l, size_t col);
48void report_line_repeat(struct report_line *l, const char *glyph, size_t n);
49
50/* take up exactly `width` display columns, and truncate if too long/pad
51 * if too short */
52void report_line_field(struct report_line *l, const char *s, size_t width);
53void report_line_right(struct report_line *l, const char *s);
54
55const char *report_line_str(const struct report_line *l);
56size_t report_line_width(const struct report_line *l);
57bool report_line_truncated(const struct report_line *l);
58
59/* Display columns a string would occupy */
60size_t report_strwidth(const char *s);
61
62struct report_panes;
63struct report_box;
64
65/* Where a finished line actually goes and how wide it is, either the
66 * console or the pane of a buffered region
67 *
68 * `col` means a display column, a pane is the vertical panel */
69struct report_target {
70 struct report_panes *panes; /* NULL means the console/a box */
71 struct report_box *box; /* non-NULL = lines come out bordered */
72 uint16_t width; /* display columns */
73 uint8_t indent;
74 uint8_t pane; /* which pane of `panes` */
75};
76
77struct report_target report_console(void);
78struct report_target report_console_indent(uint16_t indent);
79struct report_target report_pane(struct report_panes *panes, uint32_t pane);
80
81/* Nesting indentation supported */
82struct report_target report_target_indent(struct report_target tgt,
83 uint16_t indent);
84
85static inline uint16_t report_target_width(const struct report_target *tgt) {
86 return tgt->width;
87}
88
89/* Emit a line, don't pass '\n' */
90void report_puts(struct report_target *tgt, const char *s);
91__printf_like(2, 3) void report_printf(struct report_target *tgt,
92 const char *fmt, ...);
93void report_blank(struct report_target *tgt);
94
95void report_line_emit(struct report_target *tgt, struct report_line *l);
96
97/* --- TITLE ---- , to the target's width */
98void report_rule(struct report_target *tgt, const char *title);
99void report_rule_sev(struct report_target *tgt, enum term_sev sev,
100 const char *title);
101
102/* Word wrap to `tgt`'s width, emitting however many lines it takes
103 *
104 * Break at spaces, honor newlines, count display columns */
105void report_wrap(struct report_target *tgt, const char *text);
106__printf_like(2, 3) void report_wrap_printf(struct report_target *tgt,
107 const char *fmt, ...);
108
109/* A box, such as
110 *
111 * ┌─ title ─────────────┐
112 * │ this is my box data │
113 * └─────────────────────┘
114 *
115 */
116struct report_box {
117 struct report_target target;
118 uint16_t inner; /* content columns, i.e.
119 * everything but borders and padding */
120};
121
122/* inner == 0 fills target, size with `report_strwidth()`, when the box
123 * should hug rather than span the terminal, content wraps */
124void report_box_open(struct report_box *b, struct report_target target,
125 const char *title, uint16_t inner);
126
127/* This wraps, different from all the other _printf functions here */
128__printf_like(2, 3) void report_box_printf(struct report_box *b,
129 const char *fmt, ...);
130
131/* A target whose lines get bordered, so anything that draws
132 * to a target works in a box, its width is the box's inner width */
133struct report_target report_box_body(struct report_box *b);
134void report_box_close(struct report_box *b);
135
136/* Gives a header to a region we're inside of it, with faults being recorded
137 * to avoid nested panics breaking the report */
138bool report_section_begin(const char *name);
139bool report_section_begin_at(struct report_target *tgt, const char *name);
140
141/* No heading emitted, for callers that already have drawn one */
142bool report_section_claim(const char *name);
143bool report_section_claim_at(struct report_target *tgt, const char *name);
144void report_section_end(void);
145const char *report_section_current(void);
146
147#define REPORT_FIELD_GAP 2
148
149/* packs kv pairs, as many per row as target is wide, flushing as it fills,
150 * holding a line writer pointing into its storage */
151struct report_fields {
152 struct report_target target;
153 uint8_t per_row;
154 uint8_t in_row;
155 uint8_t keyw;
156 uint8_t valw;
157 struct report_line line;
158 char storage[REPORT_LINE_MAX];
159};
160
161void report_fields_begin(struct report_fields *g, struct report_target target,
162 uint32_t keyw, uint32_t valw);
163__printf_like(3, 4) void report_field(struct report_fields *g, const char *key,
164 const char *fmt, ...);
165
166/* End row and take the full line, for values carrying annotations too long */
167__printf_like(3, 4) void report_field_full(struct report_fields *g,
168 const char *key, const char *fmt,
169 ...);
170void report_fields_end(struct report_fields *g);
171
172/* Storage is MAX * LINES * BYTES */
173#define REPORT_PANES_MAX 3
174
175/* Max rows for a pane */
176#define REPORT_PANE_ROWS 48
177
178/* Bytes stored per line */
179#define REPORT_PANE_LINE_MAX 384
180#define REPORT_PANE_MIN_WIDTH 24
181#define REPORT_PANE_GAP 3
182
183/* Vertical panels side by side, only 2D thing here, and the only buffered one
184 *
185 * Panes can't stream, and lines are stored per-pane and interleaved at flush
186 *
187 * This is statically allocated */
188struct report_panes {
189 uint8_t n;
190 uint16_t width[REPORT_PANES_MAX];
191 const char *title[REPORT_PANES_MAX];
192 uint8_t title_sev[REPORT_PANES_MAX];
193 uint16_t nrows[REPORT_PANES_MAX];
194 uint32_t dropped[REPORT_PANES_MAX];
195 bool stacked; /* too narrow to sit side by side */
196 bool undivided; /* no vertical rule */
197 char text[REPORT_PANES_MAX][REPORT_PANE_ROWS][REPORT_PANE_LINE_MAX];
198
199 /* Which rows are rules */
200 bool rule[REPORT_PANES_MAX][REPORT_PANE_ROWS];
201};
202
203/* `weights` == NULL is even split */
204void report_panes_begin(struct report_panes *panes, uint32_t n,
205 const uint8_t *weights);
206
207/* Remove the vertical rule between panes, leaving only the gap */
208void report_panes_undivided(struct report_panes *panes);
209
210/* Heading for a pane, drawn as a segment of the pane's opening rule */
211void report_panes_title(struct report_panes *panes, uint32_t pane,
212 enum term_sev sev, const char *title);
213
214/* Framing rules with dividers:
215 *
216 * -- text -----+--- more ------
217 * ...
218 * -- data -----+---------------
219 *
220 * The opening rule is segmented, and closing rule can double as a title
221 */
222void report_panes_top(struct report_panes *panes);
223void report_panes_bottom(struct report_panes *panes, enum term_sev sev,
224 const char *title);
225
226/* Hand this region's divider positions to next opening rule, so one
227 * rule can close the region above and open one below, such as
228 *
229 * -----------+------
230 *
231 * -- text ---+------
232 *
233 * Call after flushing and before `begin()` changes widths, consumed by
234 * the next `report_panes_top()` */
235void report_panes_carry(struct report_panes *panes);
236
237/* Below REPORT_PANE_MIN_WIDTH, the panes stack vertically */
238void report_panes_flush(struct report_panes *panes);
239uint16_t report_pane_rows(const struct report_panes *panes, uint32_t pane);
240uint16_t report_panes_rows_max(const struct report_panes *panes);
241
242/* Reserved for panic paths, so callers don't need to own the storage, regions
243 * get used one at a time with `begin()` resetting it */
244struct report_panes *report_panes_panic(void);
245
246/* Enter panic report path, so writes are lock free and the screen
247 * is given back, used in panic handler and tracks nested panics */
248void report_enter_panic(void);
249