ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
ui/input.c
Go to the documentation of this file.
4#include <string.h>
6asciichat_error_t ui_input_render(frame_buffer_t *buffer, int row, int col, int width, int height, const char *text,
7 size_t cursor, bool preserve_quotes) {
8 if (!buffer || !text || row < 1 || col < 1 || width < 5 || height < 1 || cursor > strlen(text) ||
9 !utf8_is_valid(text) || (cursor && ((unsigned char)text[cursor] & 0xc0) == 0x80))
10 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid input field text, cursor or geometry");
11 int lines = 1;
12 size_t cursor_line = 0;
13 for (size_t i = 0; text[i]; ++i)
14 if (text[i] == '\n') {
15 ++lines;
16 if (i < cursor)
17 ++cursor_line;
18 }
19 int shown = lines < height ? lines : height;
20 int first = cursor_line >= (size_t)shown ? (int)cursor_line - shown + 1 : 0;
21 const char *line = text;
22 int number = 0, cursor_col = col + 2;
23 while (line && number < first + shown) {
24 const char *end = strchr(line, '\n');
25 if (!end)
26 end = line + strlen(line);
27 if (number >= first) {
28 const char *start = line;
29 bool active_line = (size_t)number == cursor_line;
30 bool quoted = preserve_quotes && end - line >= 2 && (*line == '"' || *line == '\'') && end[-1] == *line;
31 const char *anchor = active_line ? text + cursor : end;
32 int prefix = 0;
33 if (quoted && utf8_display_width_n(line, (size_t)(anchor - line)) > width - 1) {
34 // Keep the opening quote visible while scrolling the path's interior.
35 // The tail includes the closing quote when the cursor is at the end.
36 start = line + 1;
37 prefix = 4;
38 while (start < anchor && utf8_display_width_n(start, (size_t)(anchor - start)) > width - prefix - 1)
39 start += utf8_next_char_bytes(start, (size_t)(end - start));
40 } else if (active_line) {
41 while (start < anchor && utf8_display_width_n(start, (size_t)(anchor - start)) > width - 1)
42 start += utf8_next_char_bytes(start, (size_t)(end - start));
43 }
44 if (active_line)
45 cursor_col = col + 2 + prefix + utf8_display_width_n(start, (size_t)(anchor - start));
46 frame_buffer_printf(buffer, "\033[%d;%dH%s", row + number - first, col, active_line ? "> " : " ");
47 if (prefix)
48 frame_buffer_printf(buffer, "%c...", *line);
49 int used = prefix;
50 while (start < end) {
51 int bytes = utf8_next_char_bytes(start, (size_t)(end - start));
52 if (bytes <= 0)
53 break;
54 int cells = utf8_display_width_n(start, (size_t)bytes);
55 if (used + cells > width)
56 break;
57 frame_buffer_append(buffer, start, (size_t)bytes);
58 used += cells;
59 start += bytes;
60 }
61 frame_buffer_printf(buffer, "%*s", width - used, "");
62 }
63 ++number;
64 line = *end ? end + 1 : NULL;
65 }
66 frame_buffer_printf(buffer, "\033[%d;%dH\033[?25h", row + (int)cursor_line - first, cursor_col);
67 return ASCIICHAT_OK;
68}
69
70static bool word_space(unsigned char byte) {
71 return byte == ' ' || (byte >= '\t' && byte <= '\r');
72}
73
74static size_t previous(const char *text, size_t cursor) {
75 if (cursor) {
76 do {
77 --cursor;
78 } while (cursor && ((unsigned char)text[cursor] & 0xc0) == 0x80);
79 }
80 return cursor;
81}
82
83void ui_input_edit_key(char *buffer, size_t capacity, size_t *length, size_t *position, keyboard_key_t key) {
84 if (!buffer || !capacity || !length || !position || *length >= capacity || *position > *length ||
85 strnlen(buffer, capacity) != *length || !utf8_is_valid(buffer) ||
86 (*position && ((unsigned char)buffer[*position] & 0xc0) == 0x80)) {
87 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid line editor storage, UTF-8 or cursor");
88 return;
89 }
90 size_t len = *length, cursor = *position, start = cursor, end = cursor;
91 if (key == KEY_HOME)
92 cursor = 0;
93 else if (key == KEY_END)
94 cursor = len;
95 else if (key == KEY_LEFT)
96 cursor = previous(buffer, cursor);
97 else if (key == KEY_RIGHT && cursor < len)
98 cursor += (size_t)utf8_next_char_bytes(buffer + cursor, len - cursor);
99 else if (key == 8 || key == 127)
100 start = previous(buffer, cursor);
101 else if (key == KEY_DELETE && cursor < len)
102 end += (size_t)utf8_next_char_bytes(buffer + cursor, len - cursor);
103 else if (key == 23) {
104 while (start && word_space((unsigned char)buffer[start - 1]))
105 start = previous(buffer, start);
106 while (start && !word_space((unsigned char)buffer[start - 1]))
107 start = previous(buffer, start);
108 } else if (key == KEY_CTRL_DELETE) {
109 while (end < len && !word_space((unsigned char)buffer[end]))
110 end += (size_t)utf8_next_char_bytes(buffer + end, len - end);
111 while (end < len && word_space((unsigned char)buffer[end]))
112 ++end;
113 } else {
114 char text[UTF8_BUFFER_SIZE];
115 if (key == '\t' || (key >= 32 && key != 127)) {
116 if (keyboard_text_encode(key, text))
117 (void)ui_input_insert(buffer, capacity, length, position, text);
118 }
119 return;
120 }
121 if (start != end) {
122 memmove(buffer + start, buffer + end, len - end + 1);
123 len -= end - start;
124 cursor = start;
125 }
126 *length = len;
127 *position = cursor;
128}
129
130asciichat_error_t ui_input_insert(char *buffer, size_t capacity, size_t *length, size_t *cursor, const char *text) {
131 if (!buffer || !length || !cursor || !text || *length >= capacity || *cursor > *length ||
132 strnlen(buffer, capacity) != *length || !utf8_is_valid(text) || !utf8_is_valid(buffer) ||
133 (*cursor && ((unsigned char)buffer[*cursor] & 0xc0) == 0x80))
134 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid input insertion storage, text or cursor");
135 size_t bytes = strlen(text);
136 if (bytes >= capacity - *length)
137 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Input insertion exceeds field capacity");
138 memmove(buffer + *cursor + bytes, buffer + *cursor, *length - *cursor + 1);
139 memcpy(buffer + *cursor, text, bytes);
140 *cursor += bytes;
141 *length += bytes;
142 return ASCIICHAT_OK;
143}
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define SET_ERRNO(code,...)
Common buffer size definitions.
char cells[(512 *256 *2)]
Definition fft.c:21
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_printf(frame_buffer_t *buf, const char *fmt,...)
Printf-style append to the buffer.
struct frame_buffer frame_buffer_t
Opaque frame buffer handle.
#define UTF8_BUFFER_SIZE
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
keyboard_key_t
Options for interactive line editing.
Definition keyboard.h:53
@ 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_HOME
Home key (move to start of line).
Definition keyboard.h:62
@ KEY_DELETE
Delete key (forward delete).
Definition keyboard.h:61
@ KEY_CTRL_DELETE
Ctrl+Delete (delete word forward).
Definition keyboard.h:64
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
bool utf8_is_valid(const char *str)
Check if a string is valid UTF-8.
Definition utf8.c:128
int utf8_next_char_bytes(const char *str, size_t max_bytes)
Get byte length of next UTF-8 character.
Definition utf8.c:177
size_t keyboard_text_encode(keyboard_key_t key, char text[UTF8_BUFFER_SIZE])
🔤 String Manipulation and Shell Escaping Utilities
asciichat_error_t ui_input_insert(char *buffer, size_t capacity, size_t *length, size_t *cursor, const char *text)
Definition ui/input.c:130
void ui_input_edit_key(char *buffer, size_t capacity, size_t *length, size_t *position, keyboard_key_t key)
Definition ui/input.c:83
asciichat_error_t ui_input_render(frame_buffer_t *buffer, int row, int col, int width, int height, const char *text, size_t cursor, bool preserve_quotes)
Definition ui/input.c:6
🔤 UTF-8 Encoding and Decoding Utilities