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