ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
errno_stacks.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define TUI_ERRNO_STACKS_FOOTER_BUFFER_SIZE 512
4#define TUI_ERRNO_STACKS_HEADER_BUFFER_SIZE 768
5#define TUI_ERRNO_STACKS_TABS_BUFFER_SIZE 256
6#define TUI_ERRNO_STACKS_COLUMNS_BUFFER_SIZE 256
7#define TUI_ERRNO_STACKS_THREAD_NAME_BUFFER_SIZE 256
8#define TUI_ERRNO_STACKS_THREAD_BUFFER_SIZE 256
9#define TUI_ERRNO_STACKS_MESSAGE_BUFFER_SIZE 256
10#define TUI_ERRNO_STACKS_USER_BUFFER_SIZE 256
11#define TUI_ERRNO_STACKS_AGE_BUFFER_SIZE 96
12#define TUI_ERRNO_STACKS_OUTCOMES_BUFFER_SIZE 128
13#define TUI_ERRNO_STACKS_CODE_BUFFER_SIZE 48
14#define TUI_ERRNO_STACKS_LINE_BUFFER_SIZE 2048
15
17#include <ascii-chat/ui/text.h>
18#include <ascii-chat/ui/table.h>
27#include <ascii-chat/log/log.h>
29#include <libplatform/compiler.h>
30#include <libplatform/system.h>
31#include <libplatform/terminal.h>
32#include <libplatform/mutex.h>
37#include <libplatform/lifecycle.h>
38#include <string.h>
39#include <stdlib.h>
40
41#define ERRNO_TUI_MIN_COLS 88
42#define ERRNO_TUI_MIN_ROWS 10
43#define ERRNO_TUI_MAX_ROWS (ASCIICHAT_ERRNO_MAX_FRAMES + ASCIICHAT_ERRNO_HISTORY_SIZE)
44#define ERRNO_TUI_TABLE_BASE_WIDTH 114
45#define ERRNO_TUI_THREAD_BASE_WIDTH 14
46#define ERRNO_TUI_THREAD_MAX_WIDTH 24
47#define ERRNO_TUI_MESSAGE_BASE_WIDTH 23
48#define ERRNO_TUI_MESSAGE_MAX_WIDTH 38
49
61
82
83static errno_tui_state_t g_state;
84static mutex_t g_mutex;
85static lifecycle_t g_lifecycle = LIFECYCLE_INIT;
86
87static bool state_lock(void) {
88 if (lifecycle_init_once(&g_lifecycle)) {
89 if (mutex_init(&g_mutex, "errno_tui") != 0) {
90 lifecycle_init_abort(&g_lifecycle);
91 return false;
92 }
93 lifecycle_init_commit(&g_lifecycle);
94 }
95 if (!lifecycle_is_initialized(&g_lifecycle))
96 return false;
97 mutex_lock(&g_mutex);
98 return true;
99}
100
101static void state_unlock(void) {
102 mutex_unlock(&g_mutex);
103}
104
105static int by_created_desc(const void *left, const void *right) {
106 const errno_tui_row_t *a = left, *b = right;
107 return a->context.created_ns < b->context.created_ns ? 1 : a->context.created_ns > b->context.created_ns ? -1 : 0;
108}
109
110static int by_resolved_desc(const void *left, const void *right) {
111 const errno_tui_row_t *a = left, *b = right;
112 return a->resolved_ns < b->resolved_ns ? 1 : a->resolved_ns > b->resolved_ns ? -1 : 0;
113}
114
115static int by_stack_desc(const void *left, const void *right) {
116 const errno_tui_row_t *a = left, *b = right;
117 if (a->group_id != b->group_id)
118 return a->group_timestamp < b->group_timestamp ? 1 : a->group_timestamp > b->group_timestamp ? -1 : 0;
120 uint64_t bt = b->historical ? b->resolved_ns : b->context.created_ns;
121 return at < bt ? 1 : at > bt ? -1 : 0;
122}
123
124static size_t snapshot_current(errno_tui_row_t *rows, bool stack_sort, size_t capacity, size_t *stacks) {
126 size_t thread_count = errno_threads_snapshot(threads, ASCIICHAT_ERRNO_MAX_THREADS);
127 size_t count = 0;
128 *stacks = 0;
129 for (size_t t = 0; t < thread_count && t < ASCIICHAT_ERRNO_MAX_THREADS; ++t) {
131 size_t depth = errno_snapshot(threads[t], frames, ASCIICHAT_ERRNO_MAX_DEPTH).copied;
132 if (!depth)
133 continue;
134 ++*stacks;
135 for (size_t i = 0; i < depth && i < ASCIICHAT_ERRNO_MAX_DEPTH && count < capacity; ++i) {
136 rows[count++] =
137 (errno_tui_row_t){.context = frames[i], .group_id = threads[t], .group_timestamp = frames[0].created_ns};
138 }
139 }
140 if (!stack_sort)
141 qsort(rows, count, sizeof(*rows), by_created_desc);
142 else {
143 /* The newest frame in each per-thread snapshot orders the group; frames remain together. */
144 qsort(rows, count, sizeof(*rows), by_stack_desc);
145 }
146 return count;
147}
148
149static size_t snapshot_history(errno_tui_row_t *rows, bool stack_sort, size_t capacity) {
152 if (!history)
153 return 0;
155 size_t count = total < capacity ? total : capacity;
156 for (size_t i = 0; i < count; ++i) {
157 rows[i] = (errno_tui_row_t){.context = history[i].context,
158 .resolved_ns = history[i].resolved_ns,
159 .outcome = history[i].outcome,
160 .historical = true,
161 .group_id = history[i].context.cause_id ? history[i].context.cause_id
162 : history[i].context.error_id};
163 }
164 for (size_t i = 0; i < count; ++i) {
165 uint64_t group = rows[i].group_id;
166 for (size_t depth = 0; depth < count; ++depth) {
167 bool found_parent = false;
168 for (size_t j = 0; j < count; ++j) {
169 if (rows[j].context.error_id == group && rows[j].context.cause_id) {
170 group = rows[j].context.cause_id;
171 found_parent = true;
172 break;
173 }
174 }
175 if (!found_parent)
176 break;
177 }
178 rows[i].group_id = group;
179 rows[i].group_timestamp = rows[i].resolved_ns;
180 if (stack_sort)
181 for (size_t j = 0; j < count; ++j)
182 if (rows[j].group_id == group && rows[j].resolved_ns > rows[i].group_timestamp)
183 rows[i].group_timestamp = rows[j].resolved_ns;
184 }
185 qsort(rows, count, sizeof(*rows), stack_sort ? by_stack_desc : by_resolved_desc);
186 SAFE_FREE(history);
187 return count;
188}
189
190static void outcome_counts(const asciichat_errno_history_t *history, size_t count, unsigned code, unsigned *resolved,
191 unsigned *dismissed, unsigned *handled) {
192 *resolved = 0;
193 *handled = 0;
194 *dismissed = 0;
195 for (size_t i = 0; i < count && i < ASCIICHAT_ERRNO_HISTORY_SIZE; ++i) {
196 if ((unsigned)history[i].context.code != code)
197 continue;
198 ++*resolved;
199 if (history[i].outcome == ASCIICHAT_ERRNO_HANDLED)
200 ++*handled;
201 else if (history[i].outcome == ASCIICHAT_ERRNO_DISMISSED)
202 ++*dismissed;
203 }
204}
205
207static void format_thread_name(uint64_t thread_id, char *output, size_t output_size) {
208 named_metadata_t metadata = {0};
209 if (named_metadata_copy((uintptr_t)thread_id, &metadata) && metadata.name[0]) {
210 safe_snprintf(output, output_size, "%s", metadata.name);
211 return;
212 }
213 safe_snprintf(output, output_size, "0x%llx", (unsigned long long)thread_id);
214}
215
216static void put_color(frame_buffer_t *buffer, log_color_t color, bool enabled) {
217 frame_buffer_printf(buffer, "%s", (enabled ? ui_text_color_prefix(color) : ""));
218}
219
220static size_t duration_number_length(const char *duration) {
221 size_t length = 0;
222 while (duration[length] && ((duration[length] >= '0' && duration[length] <= '9') || duration[length] == '.'))
223 ++length;
224 return length;
225}
226
227static void render_footer(frame_buffer_t *buffer, int width, bool colors) {
228 const char *orange = (colors ? ui_text_color_prefix(LOG_COLOR_DEV) : "");
229 const char *grey = (colors ? ui_text_color_prefix(LOG_COLOR_GREY) : "");
230 const char *reset = (colors ? ui_text_color_prefix(LOG_COLOR_RESET) : "");
233 footer, sizeof(footer),
234 "%sTab%s%s: tab %ss%s%s: sort %sUp%s/%sk%s/%sDown%s/%sj%s%s: move %sx%s%s: clear %s'%s/%sEsc%s%s: close",
235 orange, reset, grey, orange, reset, grey, orange, reset, orange, reset, orange, reset, orange, reset, grey,
236 orange, reset, grey, orange, reset, orange, reset, grey);
237 frame_buffer_render_centered(buffer, footer, width);
238}
239
240static void render_table_line(frame_buffer_t *buffer, const char *line, int width) {
241 int table_width = width - 4;
242 int left_padding = (width - table_width) / 2;
243 frame_buffer_printf(buffer, "%*s%s\n", left_padding, "", line);
244}
245
246static int table_width_for_terminal(int width) {
247 return width - 4;
248}
249
250static int growing_column_width(int table_width, int base_width, int max_width, int divisor) {
251 int extra_width = MAX(table_width - ERRNO_TUI_TABLE_BASE_WIDTH, 0);
252 return MIN(base_width + extra_width / divisor, max_width);
253}
254
256static void render_errno_stacks(terminal_size_t size, const void *unused) {
257 (void)unused;
258 errno_tui_state_t state;
259 if (!state_lock())
260 return;
261 state = g_state;
262 state_unlock();
263
264 frame_buffer_t *buffer = frame_buffer_create(size.rows, size.cols);
265 if (!buffer)
266 return;
267 frame_buffer_append(buffer, "\033[0m", 4);
269 const terminal_size_t minimum = {.cols = ERRNO_TUI_MIN_COLS, .rows = ERRNO_TUI_MIN_ROWS};
270 int output_fd = state.output_fd >= 0 ? state.output_fd : STDOUT_FILENO;
271 if (ui_too_small(size, minimum)) {
272 ui_too_small_render(buffer, size, minimum);
274 frame_buffer_destroy(buffer);
275 return;
276 }
277
278 bool colors = !GET_OPTION(strip_ansi) && terminal_should_color_output(output_fd, options_terminal_settings());
280 errno_tui_row_t *current_rows = SAFE_CALLOC(ERRNO_TUI_MAX_ROWS, sizeof(*current_rows), errno_tui_row_t *);
281 if (!rows || !current_rows) {
282 SAFE_FREE(rows);
283 SAFE_FREE(current_rows);
284 frame_buffer_destroy(buffer);
285 return;
286 }
287 size_t stacks = 0;
288 size_t current_count = snapshot_current(current_rows, state.stack_sort, ERRNO_TUI_MAX_ROWS, &stacks);
289 size_t count = 0;
290 if (state.tab == ERRNO_TUI_CURRENT) {
291 memcpy(rows, current_rows, current_count * sizeof(*rows));
292 count = current_count;
293 } else {
294 count = snapshot_history(rows, state.stack_sort, ERRNO_TUI_MAX_ROWS);
295 }
296 ui_layout_t layout;
297 if (ui_layout_measure(size, 4, 1, 2, &layout) != ASCIICHAT_OK) {
298 SAFE_FREE(current_rows);
299 SAFE_FREE(rows);
300 frame_buffer_destroy(buffer);
301 return;
302 }
303 int list_rows = layout.body_rows;
304 if (list_rows < 1)
305 list_rows = 1;
306 if (state_lock()) {
307 g_state.rows = list_rows;
308 g_state.count = (int)count;
309 ui_selectable_list_keep_visible(&g_state.list, (int)count, list_rows);
310 g_state.selected_current =
311 state.tab == ERRNO_TUI_CURRENT && g_state.list.selected >= 0 && g_state.list.selected < (int)count;
312 if (g_state.selected_current)
313 g_state.selected_context = rows[g_state.list.selected].context;
314 state = g_state;
315 state_unlock();
316 }
317
318 int width = size.cols - 1;
319 bool utf8 = utf8_output_enabled();
323 size_t old_count = history ? errno_history_snapshot(history, ASCIICHAT_ERRNO_HISTORY_SIZE) : 0;
324 uint64_t latest = 0;
325 for (size_t i = 0; i < current_count; ++i)
326 if (current_rows[i].context.created_ns > latest)
327 latest = current_rows[i].context.created_ns;
328 for (size_t i = 0; i < old_count; ++i)
329 if (history[i].resolved_ns > latest)
330 latest = history[i].resolved_ns;
332 const char *blue = (colors ? ui_text_color_prefix(LOG_COLOR_DEBUG) : "");
333 const char *purple = (colors ? ui_text_color_prefix(LOG_COLOR_FATAL) : "");
334 const char *reset = (colors ? ui_text_color_prefix(LOG_COLOR_RESET) : "");
335 int header_length =
336 safe_snprintf(header, sizeof(header), "%serrno stacks%s errors %s%zu%s stacks %s%zu%s old %s%zu%s", blue,
337 reset, purple, current_count, reset, purple, stacks, reset, purple, old_count, reset);
338 if (latest && header_length > 0 && (size_t)header_length < sizeof(header)) {
340 time_pretty(time_get_ns() - latest, -1, ago, sizeof(ago));
341 size_t value_length = duration_number_length(ago);
342 safe_snprintf(header + header_length, sizeof(header) - (size_t)header_length, " latest %s%.*s%s%s ago", purple,
343 (int)value_length, ago, reset, ago + value_length);
344 }
345 frame_buffer_render_centered(buffer, header, width);
348 safe_snprintf(tabs, sizeof(tabs), "Current | Past %s sort", state.stack_sort ? "stack" : "recent");
349 frame_buffer_render_centered(buffer, tabs, width);
350 int table_width = table_width_for_terminal(width);
351 int thread_width = growing_column_width(table_width, ERRNO_TUI_THREAD_BASE_WIDTH, ERRNO_TUI_THREAD_MAX_WIDTH, 4);
352 int message_width = growing_column_width(table_width, ERRNO_TUI_MESSAGE_BASE_WIDTH, ERRNO_TUI_MESSAGE_MAX_WIDTH, 3);
353 int user_width = table_width - 28 - thread_width - message_width;
355 safe_snprintf(columns, sizeof(columns), "%sAgo R/D/H Code %-*s %-*s User message%s", blue, thread_width,
356 "Thread", message_width, "Standard message", reset);
357 render_table_line(buffer, columns, width);
358
359 for (int i = 0; i < list_rows; ++i) {
360 int index = state.list.first_visible + i;
361 if (index >= (int)count) {
362 frame_buffer_printf(buffer, "\n");
363 continue;
364 }
365 const errno_tui_row_t *row = &rows[index];
369 uint64_t timestamp = row->historical ? row->resolved_ns : row->context.created_ns;
370 time_pretty(time_get_ns() - timestamp, -1, ago, sizeof(ago));
371 size_t ago_value_length = duration_number_length(ago);
372 format_thread_name(row->context.thread_id, thread_name, sizeof(thread_name));
373 truncate_with_ellipsis(thread_name, thread, sizeof(thread), thread_width);
374 truncate_with_ellipsis(asciichat_error_string(row->context.code), message, sizeof(message), message_width);
375 truncate_with_ellipsis(row->context.context_message, user, sizeof(user), user_width);
376 unsigned resolved, dismissed, handled;
377 outcome_counts(history, old_count, (unsigned)row->context.code, &resolved, &dismissed, &handled);
380 safe_snprintf(age, sizeof(age), "%s%.*s%s%s", purple, (int)ago_value_length, ago, reset, ago + ago_value_length);
381 safe_snprintf(outcomes, sizeof(outcomes), "%s%u%s/%s%u%s/%s%u%s", purple, resolved, reset, purple, dismissed, reset,
382 purple, handled, reset);
383 safe_snprintf(code, sizeof(code), "%s%d%s", purple, row->context.code, reset);
384 const ui_table_column_t table_columns[] = {{"Ago", 10, UI_ALIGN_LEFT, false},
385 {"R/D/H", 8, UI_ALIGN_LEFT, false},
386 {"Code", 5, UI_ALIGN_LEFT, false},
387 {"Thread", thread_width, UI_ALIGN_LEFT, true},
388 {"Standard message", message_width, UI_ALIGN_LEFT, true},
389 {"User message", user_width, UI_ALIGN_LEFT, true}};
390 const char *cells[] = {age, outcomes, code, thread, message, user};
391 ui_table_row(line, sizeof(line), table_columns, cells, 6, table_width, true, index == state.list.selected);
392 render_table_line(buffer, line, width);
393 }
394 ui_scrollbar_render(buffer, 5, width - 1, list_rows, count, (size_t)list_rows, (size_t)state.list.first_visible);
396 render_footer(buffer, width, colors);
397 put_color(buffer, LOG_COLOR_RESET, colors);
399 SAFE_FREE(history);
400 SAFE_FREE(current_rows);
401 SAFE_FREE(rows);
402 frame_buffer_destroy(buffer);
403}
404
405static void publish(void) {
406 const unsigned marker = 1;
407 int fd = STDOUT_FILENO;
408 if (state_lock()) {
409 fd = g_state.output_fd >= 0 ? g_state.output_fd : STDOUT_FILENO;
410 state_unlock();
411 }
413 (terminal_size_t){.cols = ERRNO_TUI_MIN_COLS, .rows = ERRNO_TUI_MIN_ROWS},
414 render_errno_stacks, &marker, sizeof(marker)) != ASCIICHAT_OK)
415 CLEAR_ERRNO();
416}
417
419#if defined(ASCIICHAT_DIAGNOSTIC_UI)
420 if (!state_lock())
421 return;
422 g_state.visible = !g_state.visible;
423 if (g_state.visible)
424 ui_selectable_list_reset(&g_state.list);
425 if (g_state.visible)
426 g_state.output_fd = ui_controller_active_fd();
427 bool visible = g_state.visible;
428 state_unlock();
429 if (visible)
430 publish();
431 else
433#endif
434}
435
437 if (!state_lock())
438 return;
439 g_state.visible = false;
440 state_unlock();
442}
443
445 if (!state_lock())
446 return false;
447 bool visible = g_state.visible;
448 state_unlock();
449 return visible;
450}
451
454 return false;
455 if (!state_lock())
456 return false;
457 if (key == KEY_BACKTICK || key == KEY_ESCAPE) {
458 g_state.visible = false;
459 state_unlock();
461 return true;
462 }
463 if (key == '\t') {
464 g_state.tab = g_state.tab == ERRNO_TUI_CURRENT ? ERRNO_TUI_HISTORY : ERRNO_TUI_CURRENT;
465 ui_selectable_list_reset(&g_state.list);
466 } else if (key == 's' || key == 'S') {
467 g_state.stack_sort = !g_state.stack_sort;
468 ui_selectable_list_reset(&g_state.list);
469 } else if (key == KEY_UP || key == 'k') {
470 ui_selectable_list_move(&g_state.list, g_state.count, g_state.rows, -1);
471 } else if (key == KEY_DOWN || key == 'j') {
472 ui_selectable_list_move(&g_state.list, g_state.count, g_state.rows, 1);
473 } else if (key == 'x' || key == 'X') {
474 errno_tui_state_t state = g_state;
475 state_unlock();
476 if (state.selected_current)
477 errno_clear_if_current(state.selected_context.thread_id, state.selected_context.generation,
478 state.selected_context.error_id);
479 publish();
480 return true;
481 } else {
482 state_unlock();
483 return false;
484 }
485 state_unlock();
486 publish();
487 return true;
488}
bool errno_clear_if_current(uint64_t id, uint64_t generation, uint64_t error_id)
size_t errno_threads_snapshot(uint64_t *out, size_t capacity)
asciichat_errno_snapshot_t errno_snapshot(uint64_t id, asciichat_error_context_t *out, size_t capacity)
size_t errno_history_snapshot(asciichat_errno_history_t *out, size_t capacity)
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define ASCIICHAT_ERRNO_MAX_THREADS
#define ASCIICHAT_ERRNO_HISTORY_SIZE
#define CLEAR_ERRNO()
asciichat_errno_outcome_t
@ ASCIICHAT_ERRNO_HANDLED
@ ASCIICHAT_ERRNO_DISMISSED
#define ASCIICHAT_ERRNO_MAX_DEPTH
Named object registry for debugging — log identifiable resource names.
#define ERRNO_TUI_MESSAGE_BASE_WIDTH
#define TUI_ERRNO_STACKS_MESSAGE_BUFFER_SIZE
Definition errno_stacks.c:9
#define ERRNO_TUI_MIN_COLS
#define TUI_ERRNO_STACKS_HEADER_BUFFER_SIZE
Definition errno_stacks.c:4
#define TUI_ERRNO_STACKS_USER_BUFFER_SIZE
#define ERRNO_TUI_THREAD_MAX_WIDTH
#define TUI_ERRNO_STACKS_AGE_BUFFER_SIZE
#define TUI_ERRNO_STACKS_FOOTER_BUFFER_SIZE
Definition errno_stacks.c:3
#define ERRNO_TUI_MIN_ROWS
#define ERRNO_TUI_TABLE_BASE_WIDTH
void ui_errno_stacks_toggle(void)
#define ERRNO_TUI_THREAD_BASE_WIDTH
#define TUI_ERRNO_STACKS_CODE_BUFFER_SIZE
#define TUI_ERRNO_STACKS_THREAD_NAME_BUFFER_SIZE
Definition errno_stacks.c:7
bool ui_errno_stacks_visible(void)
#define TUI_ERRNO_STACKS_COLUMNS_BUFFER_SIZE
Definition errno_stacks.c:6
errno_tui_tab_t
@ ERRNO_TUI_CURRENT
@ ERRNO_TUI_HISTORY
#define ERRNO_TUI_MESSAGE_MAX_WIDTH
#define TUI_ERRNO_STACKS_THREAD_BUFFER_SIZE
Definition errno_stacks.c:8
void ui_errno_stacks_hide(void)
bool ui_errno_stacks_handle_key(keyboard_key_t key)
#define TUI_ERRNO_STACKS_TABS_BUFFER_SIZE
Definition errno_stacks.c:5
#define ERRNO_TUI_MAX_ROWS
#define TUI_ERRNO_STACKS_LINE_BUFFER_SIZE
#define TUI_ERRNO_STACKS_OUTCOMES_BUFFER_SIZE
Error and exit codes - unified status values (0-255).
char cells[(512 *256 *2)]
Definition fft.c:21
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
unsigned int width
Definition fft.c:23
void frame_buffer_append(frame_buffer_t *buf, const char *str, size_t len)
Append raw bytes to the buffer.
int frame_buffer_render_centered(frame_buffer_t *buf, const char *text, int cols)
Render centered text (used by both splash and status screens).
void frame_buffer_destroy(frame_buffer_t *buf)
Destroy a frame buffer.
void frame_buffer_render_divider(frame_buffer_t *buf, int width, bool use_utf8, bool use_colors)
size_t frame_buffer_get_length(const frame_buffer_t *buf)
Get the current content length of the frame buffer.
const char * frame_buffer_get_content(const frame_buffer_t *buf)
Get a pointer to the buffer content.
frame_buffer_t * frame_buffer_create(int rows, int cols)
Create a new frame buffer.
void frame_buffer_printf(frame_buffer_t *buf, const char *fmt,...)
Printf-style append to the buffer.
void frame_buffer_clear_screen(frame_buffer_t *buf)
Append clear screen and home escape codes (\033[2J\033[H).
Buffered terminal frame rendering.
struct frame_buffer frame_buffer_t
Opaque frame buffer handle.
bool enabled
Is filtering active?
Definition grep.c:62
#define MIN(a, b)
Return the minimum of two values.
Definition common.h:170
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned long long uint64_t
Definition common.h:63
#define MAX(a, b)
Return the maximum of two values.
Definition common.h:175
bool named_metadata_copy(uintptr_t key, named_metadata_t *output)
@ ASCIICHAT_OK
Definition error_codes.h:46
log_color_t
Color enum for logging - indexes into color arrays.
Definition log/log.h:147
@ LOG_COLOR_RESET
Definition log/log.h:155
@ LOG_COLOR_GREY
Definition log/log.h:154
@ LOG_COLOR_FATAL
Definition log/log.h:153
@ LOG_COLOR_DEV
Definition log/log.h:148
@ LOG_COLOR_DEBUG
Definition log/log.h:149
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as a compact duration with configurable sub-minute precision.
Definition time.c:374
#define TIME_DURATION_BUFFER_SIZE
Definition time.h:40
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
#define options_terminal_settings()
Definition options.h:1296
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
keyboard_key_t
Options for interactive line editing.
Definition keyboard.h:53
@ KEY_UP
Up arrow key.
Definition keyboard.h:57
@ KEY_BACKTICK
'`' key - print lock state (debug builds)
Definition keyboard.h:84
@ KEY_ESCAPE
Escape key (ESC).
Definition keyboard.h:55
@ KEY_DOWN
Down arrow key.
Definition keyboard.h:58
bool utf8_output_enabled(void)
Return whether terminal output should use UTF-8 according to –utf8.
Definition utf8.c:22
void truncate_with_ellipsis(const char *input, char *output, size_t output_size, int max_width)
Truncate a string to a maximum display width, with ellipsis.
Definition string.c:370
🖥️ Display Width and Terminal Centering Utilities
📝 Logging API with multiple log levels and terminal output control
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
static bool size_t index
Definition queries.h:8
void ui_scrollbar_render(frame_buffer_t *out, int row, int col, int height, size_t total, size_t visible, size_t offset)
Definition scrollbar.c:40
void ui_selectable_list_move(ui_selectable_list_t *list, int count, int rows, int delta)
void ui_selectable_list_reset(ui_selectable_list_t *list)
void ui_selectable_list_keep_visible(ui_selectable_list_t *list, int count, int rows)
🔤 String Manipulation and Shell Escaping Utilities
uint64_t created_ns
Monotonic time, for age.
uint64_t group_timestamp
asciichat_error_context_t context
uint64_t resolved_ns
asciichat_errno_outcome_t outcome
uint64_t group_id
The useful part of errno tui row is in context, resolved_ns, group_id, group_timestamp: together they...
ui_selectable_list_t list
errno_tui_tab_t tab
asciichat_error_context_t selected_context
The useful part of named metadata is in its fields: together they describe what is pending,...
int body_rows
Definition ui/layout.h:5
ui selectable list keeps the values that move through the subsystem: selected, first_visible....
size_t ui_table_row(char *out, size_t cap, const ui_table_column_t *cols, const char *const *cells, size_t count, int width, bool selectable, bool selected)
When code needs to work with ui table, it keeps columns, column_count, row_count, viewport here inste...
Definition table.c:18
const char * ui_text_color_prefix(log_color_t color)
Definition text.c:18
@ UI_ALIGN_LEFT
Definition text.h:9
⏱️ High-precision timing utilities using sokol_time.h and uthash
void ui_too_small_render(frame_buffer_t *buffer, terminal_size_t actual, terminal_size_t minimum)
Definition too_small.c:38
bool ui_too_small(terminal_size_t actual, terminal_size_t minimum)
Definition too_small.c:10
void ui_controller_remove(ui_screen_t screen)
asciichat_error_t ui_controller_submit(ui_screen_t screen, int fd, terminal_size_t minimum, ui_render_fn render, const void *snapshot, size_t bytes)
int ui_controller_current_screen(void)
asciichat_error_t ui_controller_write(int fd, const char *data, size_t len)
int ui_controller_active_fd(void)
@ UI_SCREEN_ERRNO
asciichat_error_t ui_layout_measure(terminal_size_t size, int header, int detail, int footer, ui_layout_t *out)
Definition ui/layout.c:3
🔤 UTF-8 Encoding and Decoding Utilities