ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
error_popup.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define TUI_ERROR_POPUP_WRAPPED_BUFFER_SIZE 65536
4#define TUI_ERROR_POPUP_STYLE_BUFFER_SIZE 128
5#define TUI_ERROR_POPUP_THREAD_BUFFER_SIZE 160
6#define TUI_ERROR_POPUP_FILE_BUFFER_SIZE 1024
7#define TUI_ERROR_POPUP_FUNCTION_BUFFER_SIZE 256
8#define TUI_ERROR_POPUP_LINE_BUFFER_SIZE 24
9#define TUI_ERROR_POPUP_ADDRESS_BUFFER_SIZE 40
10#define TUI_ERROR_POPUP_TITLE_BUFFER_SIZE 128
11#define TUI_ERROR_POPUP_HEADING_BUFFER_SIZE 256
12#define TUI_ERROR_POPUP_CODE_BUFFER_SIZE 512
13#define TUI_ERROR_POPUP_CELL_BUFFER_SIZE 1024
14#define TUI_ERROR_POPUP_FINAL_OUTPUT_BUFFER_SIZE 65536
15
16/* Existing buffer budgets in bytes, including NUL for text storage. */
17#define ERROR_POPUP_LINE_CAPACITY 2048
18
19#include <ascii-chat/otel.h>
25#include <ascii-chat/ui/popup.h>
27#include <ascii-chat/ui/text.h>
31#include <libplatform/compiler.h>
32#include <libplatform/system.h>
33#include <libplatform/terminal.h>
35#include <ascii-chat/source_root.h>
38#include <stdatomic.h>
39#include <string.h>
40
41// Only short value copies and navigation run under this guard, never rendering or symbolization.
42static atomic_flag g_guard = ATOMIC_FLAG_INIT;
44static size_t g_count, g_selected, g_scroll, g_body_rows = 1, g_page_rows = 1;
45static uint64_t g_latest, g_displayed_latest, g_dropped;
46
47static void lock_state(void) {
48 while (atomic_flag_test_and_set_explicit(&g_guard, memory_order_acquire)) {
49 }
50}
51static void unlock_state(void) {
52 atomic_flag_clear_explicit(&g_guard, memory_order_release);
53}
54
55static void refresh_locked(void) {
56 uint64_t selected_id = g_count ? g_entries[g_selected].error_id : 0;
57 g_count = errno_notifications_snapshot(g_entries, ASCIICHAT_ERRNO_HISTORY_SIZE, &g_dropped);
58 uint64_t latest = g_count ? g_entries[g_count - 1].error_id : 0;
59 if (latest != g_latest) {
60 g_selected = g_count ? g_count - 1 : 0;
61 g_scroll = 0;
62 g_latest = latest;
63 } else {
64 g_selected = 0;
65 for (size_t i = 0; i < g_count; ++i)
66 if (g_entries[i].error_id == selected_id)
67 g_selected = i;
68 }
69}
70
74
76 lock_state();
77 refresh_locked();
78 uint64_t revision = g_latest;
79 unlock_state();
80 return revision;
81}
82
83terminal_size_t ui_error_popup_minimum(void) {
84 return (terminal_size_t){.cols = 60, .rows = 14};
85}
86
88 lock_state();
89 refresh_locked();
90 uint64_t dismiss = 0;
91 size_t previous = g_selected;
92 size_t previous_scroll = g_scroll;
93 switch ((int)key) {
94 case KEY_ESCAPE:
95 case 'q':
96 case '\r':
97 case '\n':
98 /* Dismiss all notifications currently shown. The popup may receive the
99 * first key before its presentation-thread snapshot has been rendered. */
100 dismiss = UINT64_MAX;
101 break;
102 case KEY_LEFT:
103 if (g_selected)
104 --g_selected;
105 break;
106 case KEY_RIGHT:
107 if (g_selected + 1 < g_count)
108 ++g_selected;
109 break;
110 case KEY_HOME:
111 g_selected = 0;
112 break;
113 case KEY_END:
114 g_selected = g_count ? g_count - 1 : 0;
115 break;
116 case KEY_UP:
117 if (g_scroll)
118 --g_scroll;
119 break;
120 case KEY_DOWN:
121 ++g_scroll;
122 break;
123 case KEY_PAGEUP:
124 g_scroll = g_scroll > g_page_rows ? g_scroll - g_page_rows : 0;
125 break;
126 case KEY_PAGEDOWN:
127 g_scroll += g_page_rows;
128 break;
129 default:
130 break;
131 }
132 if (g_selected != previous)
133 g_scroll = 0;
134 size_t maximum = g_body_rows > g_page_rows ? g_body_rows - g_page_rows : 0;
135 if (g_scroll > maximum)
136 g_scroll = maximum;
137 bool selection_changed = previous != g_selected;
138 bool scrolled = previous_scroll != g_scroll && !selection_changed;
139 unlock_state();
140 if (selection_changed)
141 ascii_otel_ui_usage("error", "selected", 0);
142 if (scrolled)
143 ascii_otel_ui_usage("error", "details_scrolled", 0);
144 if (dismiss)
145 ascii_otel_ui_usage("error", "dismissed", 0);
146 if (dismiss)
149}
150
151// These caches belong exclusively to the presentation thread. Raw backtraces are copied,
152// symbolized once per selection, then freed; notification snapshots never own symbols.
153static uint64_t g_cached_id;
154static int g_cached_width;
155static bool g_cached_color;
156static char g_wrapped[TUI_ERROR_POPUP_WRAPPED_BUFFER_SIZE];
157static size_t g_lines[ERROR_POPUP_LINE_CAPACITY], g_line_count, g_trace_start;
158#ifdef ASCIICHAT_DIAGNOSTIC_UI
159static backtrace_text_t g_trace;
160#endif
161
162static void sanitize(char *out, size_t capacity, const char *text) {
163 size_t used = 0;
164 for (const unsigned char *p = (const unsigned char *)(text ? text : ""); *p && used + 1 < capacity; ++p) {
165 // Messages may contain paths or remote text. Never interpret terminal controls from them.
166 unsigned char c = *p;
167 if (c == 0xc2 && p[1] >= 0x80 && p[1] <= 0x9f) {
168 out[used++] = '?';
169 ++p;
170 continue;
171 }
172 if (c == '\t')
173 c = ' ';
174 else if ((c < 32 && c != '\n') || c == 127)
175 c = '?';
176 out[used++] = (char)c;
177 }
178 out[used] = '\0';
179}
180
181static void append_bytes(size_t *used, const char *text, size_t bytes) {
182 bytes = MIN(bytes, sizeof(g_wrapped) - 1 - *used);
183 memcpy(g_wrapped + *used, text, bytes);
184 *used += bytes;
185 g_wrapped[*used] = '\0';
186}
187
188static void wrap_body(const char *text, int width) {
189 size_t used = 0;
190 int cells = 0;
191 char style[TUI_ERROR_POPUP_STYLE_BUFFER_SIZE] = "";
192 g_line_count = 1;
193 g_lines[0] = 0;
194 g_wrapped[0] = '\0';
195 while (*text && used + 256 < sizeof(g_wrapped) && g_line_count < ARRAY_SIZE(g_lines)) {
196 if (text[0] == '\033' && text[1] == '[') {
197 const char *end = text + 2;
198 while (*end && !(*end >= '@' && *end <= '~'))
199 ++end;
200 if (*end != 'm') {
201 // The formatter supplies SGR only. Ignore any unexpected control sequence.
202 text = *end ? end + 1 : end;
203 continue;
204 }
205 size_t bytes = (size_t)(end + 1 - text);
206 append_bytes(&used, text, bytes);
207 if (bytes < sizeof(style)) {
208 memcpy(style, text, bytes);
209 style[bytes] = '\0';
210 }
211 text = end + 1;
212 continue;
213 }
214 int bytes = utf8_next_char_bytes(text, strlen(text));
215 if (bytes <= 0)
216 bytes = 1;
217 int columns = utf8_display_width_n(text, (size_t)bytes);
218 if (*text == '\n' || (cells && cells + columns > width)) {
219 if (style[0])
220 append_bytes(&used, "\033[0m", 4);
221 g_lines[g_line_count++] = used + 1;
222 append_bytes(&used, "\n", 1);
223 append_bytes(&used, style, strlen(style));
224 cells = 0;
225 if (*text == '\n')
226 ++text;
227 continue;
228 }
229 append_bytes(&used, text, (size_t)bytes);
230 cells += MAX(columns, 0);
231 text += bytes;
232 }
233}
234
235static void build_body(const asciichat_error_context_t *error, int width, bool colors) {
236 g_trace_start = 0;
240 sanitize(message, sizeof(message), error->context_message);
241 named_metadata_t metadata = {0};
242 bool named = named_metadata_copy((uintptr_t)error->thread_id, &metadata) && metadata.name[0];
243 if (named)
244 sanitize(thread, sizeof(thread), metadata.name);
245 else
246 safe_snprintf(thread, sizeof(thread), "0x%llx", (unsigned long long)error->thread_id);
248 if (!body) {
249 wrap_body("Error details unavailable (out of memory)", width);
250 return;
251 }
252 const char *grey = colors ? ui_text_color_prefix(LOG_COLOR_GREY) : "";
253 const char *reset = colors ? "\033[0m" : "";
254 frame_buffer_printf(body, "Details: %s%s\n\n%sThread: %s", message[0] ? message : "No additional details",
255 error->message_truncated ? " [truncated]" : "", grey,
256 colors ? colored_string(named ? LOG_COLOR_WARN : LOG_COLOR_FATAL, thread) : thread);
257 if (named) {
259 safe_snprintf(address, sizeof(address), "0x%llx", (unsigned long long)error->thread_id);
260 frame_buffer_printf(body, " (%s)", colors ? colored_string(LOG_COLOR_FATAL, address) : address);
261 }
262 frame_buffer_printf(body, "%s\n", reset);
263 if (error->has_system_error)
264 frame_buffer_printf(body, "System error: %d (%s)\n", error->system_errno, SAFE_STRERROR(error->system_errno));
265 if (error->has_wsa_error)
266 frame_buffer_printf(body, "Socket error: %d\n", error->wsa_error);
267#ifdef ASCIICHAT_DIAGNOSTIC_UI
268 sanitize(file, sizeof(file), error->file ? extract_project_relative_path(error->file, ASCIICHAT_OWNED_SOURCE_DIRS) : "?");
269 sanitize(function, sizeof(function), error->function);
270 safe_snprintf(line, sizeof(line), "%d", error->line);
271 frame_buffer_printf(body, "%sSource: %s %s:%s%s\n", grey, colors ? colored_string(LOG_COLOR_DEBUG, file) : file,
272 colors ? colored_string(LOG_COLOR_DEV, function) : function,
273 colors ? colored_string(LOG_COLOR_GREY, line) : line, reset);
274 if (g_cached_id != error->error_id) {
275 memset(&g_trace, 0, sizeof(g_trace));
276 backtrace_t trace = error->backtrace;
277 trace.symbols = NULL;
278 trace.tried_symbolize = false;
279 if (trace.count) {
280 backtrace_symbolize(&trace, ASCIICHAT_OWNED_SOURCE_DIRS);
281 if (trace.symbols)
282 backtrace_format_styled(&g_trace, "Backtrace", &trace, ERROR_BACKTRACE_INTERNAL_FRAMES, 0,
283 backtrace_filter_error_internal);
284 backtrace_t_free(&trace);
285 }
286 }
287 wrap_body(frame_buffer_get_content(body), width);
288 if (g_trace.plain[0])
289 g_trace_start = g_line_count + 1;
290 const char *trace_text = g_trace.plain[0] ? (colors ? g_trace.colored : g_trace.plain) : "Backtrace unavailable";
291 size_t trace_length = strlen(trace_text);
292 if (trace_length && trace_text[trace_length - 1] == '\n')
293 --trace_length;
294 frame_buffer_printf(body, "\n%.*s", (int)trace_length, trace_text);
295#else
296 (void)file;
297 (void)function;
298 (void)line;
299#endif
300 wrap_body(frame_buffer_get_content(body), width);
302 g_cached_id = error->error_id;
303 g_cached_width = width;
304 g_cached_color = colors;
305}
306
307static void format_error_heading(char *buffer, size_t capacity, const asciichat_error_context_t *error) {
308 safe_snprintf(buffer, capacity, "%s (%d): %s", asciichat_error_name(error->code), error->code,
309 asciichat_error_string(error->code));
310}
311
312void ui_error_popup_render(frame_buffer_t *out, terminal_size_t size) {
314 lock_state();
315 refresh_locked();
316 size_t count = g_count, selected = g_selected;
317 uint64_t displayed_latest = g_latest, dropped = g_dropped;
318 if (count)
319 error = g_entries[selected];
320 unlock_state();
321 if (!count)
322 return;
323 terminal_size_t minimum = ui_error_popup_minimum();
324 frame_buffer_append(out, "\033[0m\033[?25l", 10);
325 if (ui_too_small(size, minimum)) {
326 ui_too_small_render(out, size, minimum);
327 return;
328 }
329 lock_state();
330 g_displayed_latest = displayed_latest;
331 unlock_state();
332 bool colors = !GET_OPTION(strip_ansi) &&
333 terminal_should_color_output(frame_buffer_get_screen_output_fd(), options_terminal_settings());
334 int width = MIN(size.cols - 4, 100);
335 int body_width = width - 6;
336 if (g_cached_id != error.error_id || g_cached_width != body_width || g_cached_color != colors)
337 build_body(&error, body_width, colors);
338 size_t desired_rows = g_trace_start ? MIN(g_line_count, g_trace_start + 12) : g_line_count;
339 int body_rows = MIN(size.rows - 8, MIN(22, (int)desired_rows));
340 size_t fixed_rows = g_trace_start && g_trace_start < (size_t)body_rows ? g_trace_start : 0;
341 if (g_trace_start && !fixed_rows)
342 body_rows = MIN(body_rows, 12);
343 int height = body_rows + 6;
344 int top = (size.rows - height) / 2 + 1, left = (size.cols - width) / 2 + 1;
345 size_t scroll_rows = g_line_count - fixed_rows;
346 size_t page_rows = (size_t)body_rows - fixed_rows;
347 lock_state();
348 g_body_rows = scroll_rows;
349 g_page_rows = page_rows;
350 size_t maximum = scroll_rows > page_rows ? scroll_rows - page_rows : 0;
351 g_scroll = MIN(g_scroll, maximum);
352 size_t scroll = g_scroll;
353 unlock_state();
356 safe_snprintf(title, sizeof(title), "Error (%zu) %zu of %zu%s", count, selected + 1, count,
357 dropped ? " [older errors dropped]" : "");
359 ui_text_cell(heading, sizeof(heading), title, width - 4, UI_ALIGN_LEFT);
360 frame_buffer_printf(out, "\033[%d;%dH%s", top + 1, left + 2,
361 colors ? colored_string(LOG_COLOR_ERROR, heading) : heading);
363 format_error_heading(code, sizeof(code), &error);
364 ui_text_cell(cell, sizeof(cell), code, width - 4, UI_ALIGN_LEFT);
365 frame_buffer_printf(out, "\033[%d;%dH%s", top + 2, left + 2, cell);
366 for (int i = 0; i < body_rows; ++i) {
367 size_t index = (size_t)i < fixed_rows ? (size_t)i : (size_t)i + scroll;
368 if (index >= g_line_count)
369 break;
370 const char *start = g_wrapped + g_lines[index];
371 const char *end = strchr(start, '\n');
372 frame_buffer_printf(out, "\033[%d;%dH%.*s\033[0m", top + 4 + i, left + 2,
373 (int)(end ? end - start : (ptrdiff_t)strlen(start)), start);
374 }
375 ui_scrollbar_render(out, top + 4 + (int)fixed_rows, left + width - 3, (int)page_rows, scroll_rows, page_rows, scroll);
376 const char *orange = colors ? ui_text_color_prefix(LOG_COLOR_DEV) : "";
377 const char *reset = colors ? "\033[0m" : "";
378 frame_buffer_printf(out, "\033[%d;%dH%s?%s Help", top + height - 2, left + 2, orange, reset);
379 ui_button_t button = {.label = "Dismiss all", .shortcut = '\r'};
380 ui_buttons_render(out, top + height - 2, left + 10, width - 20, &button, 1, 0);
381}
382
384 lock_state();
385 g_count = g_selected = g_scroll = 0;
386 g_latest = g_displayed_latest = g_dropped = 0;
387 unlock_state();
388 g_cached_id = 0;
389}
390
391static char g_final_output[TUI_ERROR_POPUP_FINAL_OUTPUT_BUFFER_SIZE];
392static size_t g_final_length;
393static int g_final_fd;
394
395void ui_error_popup_preserve(int fd, terminal_size_t size) {
397 return;
399 size_t count = errno_notifications_snapshot(entries, ARRAY_SIZE(entries), NULL);
400 if (!count)
401 return;
402 bool previous = errno_notifications_suppress(true);
403 frame_buffer_t *output = frame_buffer_create(0, 0);
404 if (output) {
405 bool colors = !GET_OPTION(strip_ansi) && terminal_should_color_output(fd, options_terminal_settings());
406 bool unicode = terminal_should_use_utf8(GET_OPTION(force_utf8));
407 int width = MIN(size.cols > 0 ? size.cols - 1 : 79, 100);
408 width = MAX(width, 8);
409 int body_width = width - 4;
410 size_t first = count > 4 ? count - 4 : 0;
411 frame_buffer_append(output, "\033[0m\033[?25h\r\n", 12);
412 if (first)
413 frame_buffer_printf(output, "%zu pending notifications; showing the latest 4.\n", count);
414 for (size_t i = first; i < count; ++i) {
416 const char *side = unicode ? "│" : "|";
417 ui_popup_rule(output, width, UI_POPUP_TOP, false, unicode, LOG_COLOR_RESET);
418 frame_buffer_append(output, "\n", 1);
419 ui_text_cell(cell, sizeof(cell), "ERROR", body_width, UI_ALIGN_LEFT);
420 frame_buffer_printf(output, "%s %s %s\n", side, colors ? colored_string(LOG_COLOR_ERROR, cell) : cell, side);
421 format_error_heading(code, sizeof(code), &entries[i]);
422 ui_text_cell(cell, sizeof(cell), code, body_width, UI_ALIGN_LEFT);
423 frame_buffer_printf(output, "%s %s %s\n", side, cell, side);
424 build_body(&entries[i], body_width, colors);
425 for (size_t line = 0; line < g_line_count; ++line) {
426 const char *start = g_wrapped + g_lines[line];
427 const char *end = strchr(start, '\n');
429 size_t length = end ? (size_t)(end - start) : strlen(start);
430 length = MIN(length, sizeof(line_text) - 1);
431 memcpy(line_text, start, length);
432 line_text[length] = '\0';
433 ui_text_cell(cell, sizeof(cell), line_text, body_width, UI_ALIGN_LEFT);
434 frame_buffer_printf(output, "%s %s%s %s\n", side, cell, colors ? "\033[0m" : "", side);
435 }
436 ui_popup_rule(output, width, UI_POPUP_BOTTOM, false, unicode, LOG_COLOR_RESET);
437 frame_buffer_append(output, "\n", 1);
438 }
439 if (frame_buffer_get_length(output) <= sizeof(g_final_output)) {
440 g_final_length = frame_buffer_get_length(output);
441 memcpy(g_final_output, frame_buffer_get_content(output), g_final_length);
442 g_final_fd = fd;
443 } else {
444 char message[ASCIICHAT_ERRNO_MESSAGE_MAX];
445 sanitize(message, sizeof(message), entries[count - 1].context_message);
446 int length = safe_snprintf(g_final_output, sizeof(g_final_output), "\nERROR\n%s (%d): %s\nDetails: %s\n",
447 asciichat_error_name(entries[count - 1].code), entries[count - 1].code,
448 asciichat_error_string(entries[count - 1].code), message);
449 g_final_length = length > 0 ? MIN((size_t)length, sizeof(g_final_output) - 1) : 0;
450 g_final_fd = fd;
451 }
452 }
453 if (output)
454 frame_buffer_destroy(output);
456}
457
459 if (g_final_length)
460 platform_write_all(g_final_fd, g_final_output, g_final_length);
461 g_final_length = 0;
462}
void errno_notifications_dismiss(uint64_t through_id)
bool errno_notifications_pending(void)
size_t errno_notifications_snapshot(asciichat_error_context_t *out, size_t capacity, uint64_t *dropped)
bool errno_notifications_suppress(bool suppress)
#define ASCIICHAT_ERRNO_MESSAGE_MAX
#define ASCIICHAT_ERRNO_HISTORY_SIZE
void ui_buttons_render(frame_buffer_t *out, int row, int left, int width, const ui_button_t *b, size_t n, size_t selected)
Definition buttons.c:29
Named object registry for debugging — log identifiable resource names.
#define ERROR_POPUP_LINE_CAPACITY
Definition error_popup.c:17
#define TUI_ERROR_POPUP_FUNCTION_BUFFER_SIZE
Definition error_popup.c:7
bool ui_error_popup_visible(void)
Definition error_popup.c:71
#define TUI_ERROR_POPUP_WRAPPED_BUFFER_SIZE
Definition error_popup.c:3
#define TUI_ERROR_POPUP_FILE_BUFFER_SIZE
Definition error_popup.c:6
#define TUI_ERROR_POPUP_ADDRESS_BUFFER_SIZE
Definition error_popup.c:9
#define TUI_ERROR_POPUP_STYLE_BUFFER_SIZE
Definition error_popup.c:4
#define TUI_ERROR_POPUP_FINAL_OUTPUT_BUFFER_SIZE
Definition error_popup.c:14
void ui_error_popup_key(keyboard_key_t key)
Definition error_popup.c:87
#define TUI_ERROR_POPUP_THREAD_BUFFER_SIZE
Definition error_popup.c:5
#define TUI_ERROR_POPUP_HEADING_BUFFER_SIZE
Definition error_popup.c:11
#define TUI_ERROR_POPUP_CELL_BUFFER_SIZE
Definition error_popup.c:13
void ui_error_popup_preserve(int fd, terminal_size_t size)
void ui_error_popup_flush(void)
uint64_t ui_error_popup_revision(void)
Definition error_popup.c:75
#define TUI_ERROR_POPUP_CODE_BUFFER_SIZE
Definition error_popup.c:12
void ui_error_popup_reset(void)
void ui_error_popup_render(frame_buffer_t *out, terminal_size_t size)
#define TUI_ERROR_POPUP_TITLE_BUFFER_SIZE
Definition error_popup.c:10
#define TUI_ERROR_POPUP_LINE_BUFFER_SIZE
Definition error_popup.c:8
terminal_size_t ui_error_popup_minimum(void)
Definition error_popup.c:83
char cells[(512 *256 *2)]
Definition fft.c:21
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
unsigned int height
Definition fft.c:23
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.
void frame_buffer_destroy(frame_buffer_t *buf)
Destroy a frame buffer.
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.
int frame_buffer_get_screen_output_fd(void)
Get current output file descriptor for terminal screens.
struct frame_buffer frame_buffer_t
Opaque frame buffer handle.
#define MIN(a, b)
Return the minimum of two values.
Definition common.h:170
#define ARRAY_SIZE(arr)
Calculate the number of elements in an array.
Definition common.h:180
#define SAFE_STRERROR(errnum)
Definition common.h:480
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)
@ 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_ERROR
Definition log/log.h:152
@ LOG_COLOR_DEV
Definition log/log.h:148
@ LOG_COLOR_DEBUG
Definition log/log.h:149
@ LOG_COLOR_WARN
Definition log/log.h:151
#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.
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
keyboard_key_t
Options for interactive line editing.
Definition keyboard.h:53
@ KEY_UP
Up arrow key.
Definition keyboard.h:57
@ KEY_ESCAPE
Escape key (ESC).
Definition keyboard.h:55
@ KEY_PAGEDOWN
Definition keyboard.h:71
@ KEY_PAGEUP
Definition keyboard.h:70
@ KEY_LEFT
Left arrow key.
Definition keyboard.h:59
@ KEY_RIGHT
Right arrow key.
Definition keyboard.h:60
@ KEY_END
End key (move to end of line).
Definition keyboard.h:63
@ KEY_DOWN
Down arrow key.
Definition keyboard.h:58
@ KEY_HOME
Home key (move to start of line).
Definition keyboard.h:62
int utf8_display_width_n(const char *str, size_t max_bytes)
Calculate terminal display width of a UTF-8 string up to byte limit.
Definition utf8.c:102
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
Definition string.c:284
const char * extract_project_relative_path(const char *file, const char *const *owned_dirs)
Extract a path relative to the source root embedded by CMake.
int utf8_next_char_bytes(const char *str, size_t max_bytes)
Get byte length of next UTF-8 character.
Definition utf8.c:177
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
void ascii_otel_ui_usage(const char *screen, const char *event, uint64_t duration_ns)
Definition otel.c:434
📂 Path Manipulation Utilities
asciichat_error_t ui_popup_frame(frame_buffer_t *out, int row, int col, int width, int height, log_color_t color)
Definition popup.c:28
asciichat_error_t ui_popup_rule(frame_buffer_t *out, int width, ui_popup_edge_t edge, bool double_border, bool unicode, log_color_t color)
Definition popup.c:12
@ UI_POPUP_BOTTOM
Definition popup.h:9
@ UI_POPUP_TOP
Definition popup.h:9
static bool size_t index
Definition queries.h:8
static bool size_t gotel_query_t * out
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
🔤 String Manipulation and Shell Escaping Utilities
The useful part of named metadata is in its fields: together they describe what is pending,...
const char * ui_text_color_prefix(log_color_t color)
Definition text.c:18
size_t ui_text_cell(char *out, size_t cap, const char *text, int width, ui_alignment_t align)
Definition text.c:48
@ UI_ALIGN_LEFT
Definition text.h:9
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_redraw(void)
🔤 UTF-8 Encoding and Decoding Utilities