| 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 */ |
| 21 | struct 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 | |
| 37 | void report_line_init(struct report_line *l, char *buf, size_t cap, |
| 38 | size_t max_col); |
| 39 | void report_line_reset(struct report_line *l); |
| 40 | |
| 41 | void report_line_puts(struct report_line *l, const char *s); |
| 42 | cc_printf_like(2, 3) void report_line_printf(struct report_line *l, |
| 43 | const char *fmt, ...); |
| 44 | void 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 */ |
| 48 | void report_line_pad_to(struct report_line *l, size_t col); |
| 49 | void 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 */ |
| 53 | void report_line_field(struct report_line *l, const char *s, size_t width); |
| 54 | void report_line_right(struct report_line *l, const char *s); |
| 55 | |
| 56 | const char *report_line_str(const struct report_line *l); |
| 57 | size_t report_line_width(const struct report_line *l); |
| 58 | bool report_line_truncated(const struct report_line *l); |
| 59 | |
| 60 | /* Display columns a string would occupy */ |
| 61 | size_t report_strwidth(const char *s); |
| 62 | |
| 63 | struct report_panes; |
| 64 | struct 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 */ |
| 70 | struct 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 | |
| 78 | struct report_target report_console(void); |
| 79 | struct report_target report_console_indent(uint16_t indent); |
| 80 | struct report_target report_pane(struct report_panes *panes, uint32_t pane); |
| 81 | |
| 82 | /* Nesting indentation supported */ |
| 83 | struct report_target report_target_indent(struct report_target tgt, |
| 84 | uint16_t indent); |
| 85 | |
| 86 | static 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' */ |
| 91 | void report_puts(struct report_target *tgt, const char *s); |
| 92 | cc_printf_like(2, 3) void report_printf(struct report_target *tgt, |
| 93 | const char *fmt, ...); |
| 94 | void report_blank(struct report_target *tgt); |
| 95 | |
| 96 | void report_line_emit(struct report_target *tgt, struct report_line *l); |
| 97 | |
| 98 | /* --- TITLE ---- , to the target's width */ |
| 99 | void report_rule(struct report_target *tgt, const char *title); |
| 100 | void 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 */ |
| 106 | void report_wrap(struct report_target *tgt, const char *text); |
| 107 | cc_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 | */ |
| 117 | struct 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 */ |
| 125 | void 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 */ |
| 129 | cc_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 */ |
| 134 | struct report_target report_box_body(struct report_box *b); |
| 135 | void 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 */ |
| 139 | bool report_section_begin(const char *name); |
| 140 | bool report_section_begin_at(struct report_target *tgt, const char *name); |
| 141 | |
| 142 | /* No heading emitted, for callers that already have drawn one */ |
| 143 | bool report_section_claim(const char *name); |
| 144 | bool report_section_claim_at(struct report_target *tgt, const char *name); |
| 145 | void report_section_end(void); |
| 146 | const 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 */ |
| 152 | struct 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 | |
| 162 | void report_fields_begin(struct report_fields *g, struct report_target target, |
| 163 | uint32_t keyw, uint32_t valw); |
| 164 | cc_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 */ |
| 168 | cc_printf_like(3, 4) void report_field_full(struct report_fields *g, |
| 169 | const char *key, const char *fmt, |
| 170 | ...); |
| 171 | void 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 */ |
| 189 | struct 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 */ |
| 205 | void 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 */ |
| 209 | void report_panes_undivided(struct report_panes *panes); |
| 210 | |
| 211 | /* Heading for a pane, drawn as a segment of the pane's opening rule */ |
| 212 | void 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 | */ |
| 223 | void report_panes_top(struct report_panes *panes); |
| 224 | void 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()` */ |
| 236 | void report_panes_carry(struct report_panes *panes); |
| 237 | |
| 238 | /* Below REPORT_PANE_MIN_WIDTH, the panes stack vertically */ |
| 239 | void report_panes_flush(struct report_panes *panes); |
| 240 | uint16_t report_pane_rows(const struct report_panes *panes, uint32_t pane); |
| 241 | uint16_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 */ |
| 245 | struct 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 */ |
| 249 | void report_enter_panic(void); |
| 250 | |