fix(input): update terminal and txtedit to use UTF-8 input subsystem.

This commit is contained in:
boreddevnl 2026-04-23 21:33:59 +02:00
parent 915e33434e
commit 8006a83449
3 changed files with 197 additions and 88 deletions

View file

@ -4,6 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <syscall.h> #include <syscall.h>
#include <stdbool.h> #include <stdbool.h>
#include "utf-8.h"
#define MAX_LINE 512 #define MAX_LINE 512
#define MAX_ARGS 32 #define MAX_ARGS 32
@ -576,9 +577,6 @@ static void get_time_string(char *out, int max_len) {
str_append(out, mm, max_len); str_append(out, mm, max_len);
} }
static void format_prompt(const char *tmpl, char *out, int max_len) {
render_prompt(tmpl, out, max_len, false);
}
static int split_args(char *line, char *argv[], int max_args) { static int split_args(char *line, char *argv[], int max_args) {
int argc = 0; int argc = 0;
@ -1466,8 +1464,13 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
if (ch == '\b' || ch == 127) { if (ch == '\b' || ch == 127) {
if (len > 0) { if (len > 0) {
len--; // Find previous UTF-8 character boundary
const char *prev = text_prev_utf8(out, out + len);
len = (int)(prev - out);
out[len] = 0; out[len] = 0;
// Send only ONE backspace sequence to the terminal
// because the terminal is now codepoint-based.
sys_write(1, "\b \b", 3); sys_write(1, "\b \b", 3);
} }
search_mode = false; search_mode = false;
@ -1590,7 +1593,7 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
continue; continue;
} }
if (ch >= 32 && len < max_len - 1) { if (((unsigned char)ch >= 32 || (signed char)ch < 0) && len < max_len - 1) {
out[len++] = ch; out[len++] = ch;
out[len] = 0; out[len] = 0;
sys_write(1, &ch, 1); sys_write(1, &ch, 1);

View file

@ -7,7 +7,7 @@
#include <syscall.h> #include <syscall.h>
#include "libc/libui.h" #include "libc/libui.h"
#include "libc/input.h" #include "libc/input.h"
#include <stdbool.h> #include "utf-8.h"
#include <stdint.h> #include <stdint.h>
#define DEFAULT_COLS 116 #define DEFAULT_COLS 116
@ -25,7 +25,7 @@ static int g_line_h = 10;
#define LINE_MAX 256 #define LINE_MAX 256
typedef struct { typedef struct {
char c; uint32_t c;
uint32_t color; uint32_t color;
} CharCell; } CharCell;
@ -52,6 +52,13 @@ typedef struct {
bool colors_enabled; bool colors_enabled;
// UTF-8 decoding state
uint32_t utf8_codepoint;
int utf8_expected;
int utf8_received;
int unacknowledged_chars;
// for color // for color
char current_input[LINE_MAX]; char current_input[LINE_MAX];
int input_len; int input_len;
@ -221,7 +228,7 @@ static void session_scroll(TerminalSession *s) {
s->cursor_col = 0; s->cursor_col = 0;
} }
static void session_put_char(TerminalSession *s, char c) { static void session_put_char(TerminalSession *s, uint32_t c) {
if (c == '\n') { if (c == '\n') {
s->cursor_row++; s->cursor_row++;
s->cursor_col = 0; s->cursor_col = 0;
@ -234,6 +241,7 @@ static void session_put_char(TerminalSession *s, char c) {
} }
if (c == '\b') { if (c == '\b') {
if (s->cursor_col > 0) s->cursor_col--; if (s->cursor_col > 0) s->cursor_col--;
if (s->unacknowledged_chars < 0) s->unacknowledged_chars++;
int idx = s->cursor_row * g_cols + s->cursor_col; int idx = s->cursor_row * g_cols + s->cursor_col;
if (idx >= 0 && idx < g_cols * g_rows) { if (idx >= 0 && idx < g_cols * g_rows) {
s->cells[idx].c = ' '; s->cells[idx].c = ' ';
@ -257,6 +265,7 @@ static void session_put_char(TerminalSession *s, char c) {
s->cells[idx].color = s->fg_color; s->cells[idx].color = s->fg_color;
} }
s->cursor_col++; s->cursor_col++;
if (s->unacknowledged_chars > 0) s->unacknowledged_chars--;
if (s->cursor_col >= g_cols) { if (s->cursor_col >= g_cols) {
s->cursor_col = 0; s->cursor_col = 0;
s->cursor_row++; s->cursor_row++;
@ -290,7 +299,7 @@ static void ansi_handle_sgr(TerminalSession *s) {
} }
} }
static void ansi_finalize(TerminalSession *s, char cmd) { static void ansi_finalize(TerminalSession *s, uint32_t cmd) {
if (cmd == 'm') { if (cmd == 'm') {
ansi_handle_sgr(s); ansi_handle_sgr(s);
} else if (cmd == 'J') { } else if (cmd == 'J') {
@ -339,36 +348,75 @@ static void ansi_finalize(TerminalSession *s, char cmd) {
} }
static void session_process_char(TerminalSession *s, char c) { static void session_process_char(TerminalSession *s, char c) {
uint8_t b = (uint8_t)c;
if (s->utf8_expected == 0) {
if (b < 128) {
s->utf8_codepoint = b;
s->utf8_expected = 0;
} else if ((b & 0xE0) == 0xC0) {
s->utf8_codepoint = b & 0x1F;
s->utf8_expected = 1;
s->utf8_received = 1;
return;
} else if ((b & 0xF0) == 0xE0) {
s->utf8_codepoint = b & 0x0F;
s->utf8_expected = 2;
s->utf8_received = 1;
return;
} else if ((b & 0xF8) == 0xF0) {
s->utf8_codepoint = b & 0x07;
s->utf8_expected = 3;
s->utf8_received = 1;
return;
} else {
return;
}
} else {
if ((b & 0xC0) == 0x80) {
s->utf8_codepoint = (s->utf8_codepoint << 6) | (b & 0x3F);
s->utf8_received++;
if (s->utf8_received <= s->utf8_expected) {
return;
}
} else {
s->utf8_expected = 0;
return;
}
}
uint32_t cp = s->utf8_codepoint;
s->utf8_expected = 0;
if (s->ansi_state == 0) { if (s->ansi_state == 0) {
if (c == 27) { if (cp == 27) {
s->ansi_state = 1; s->ansi_state = 1;
s->ansi_param_count = 0; s->ansi_param_count = 0;
s->ansi_params[0] = 0; s->ansi_params[0] = 0;
return; return;
} }
session_put_char(s, c); session_put_char(s, cp);
return; return;
} }
if (s->ansi_state == 1) { if (s->ansi_state == 1) {
if (c == '[') { if (cp == '[') {
s->ansi_state = 2; s->ansi_state = 2;
s->ansi_param_count = 0; s->ansi_param_count = 0;
s->ansi_params[0] = 0; s->ansi_params[0] = 0;
return; return;
} }
s->ansi_state = 0; s->ansi_state = 0;
session_put_char(s, c); session_put_char(s, cp);
return; return;
} }
if (s->ansi_state == 2) { if (s->ansi_state == 2) {
if (c >= '0' && c <= '9') { if (cp >= '0' && cp <= '9') {
int idx = s->ansi_param_count; int idx = s->ansi_param_count;
s->ansi_params[idx] = s->ansi_params[idx] * 10 + (c - '0'); s->ansi_params[idx] = s->ansi_params[idx] * 10 + (cp - '0');
return; return;
} }
if (c == ';') { if (cp == ';') {
if (s->ansi_param_count < 7) { if (s->ansi_param_count < 7) {
s->ansi_param_count++; s->ansi_param_count++;
s->ansi_params[s->ansi_param_count] = 0; s->ansi_params[s->ansi_param_count] = 0;
@ -376,7 +424,7 @@ static void session_process_char(TerminalSession *s, char c) {
return; return;
} }
s->ansi_param_count++; s->ansi_param_count++;
ansi_finalize(s, c); ansi_finalize(s, cp);
return; return;
} }
} }
@ -708,6 +756,9 @@ static void draw_session(TerminalSession *s) {
int total_lines = s->scroll_count + g_rows; int total_lines = s->scroll_count + g_rows;
int bottom_line = total_lines - 1 - s->scroll_offset; int bottom_line = total_lines - 1 - s->scroll_offset;
int top_line = bottom_line - (g_rows - 1); int top_line = bottom_line - (g_rows - 1);
int input_char_len = text_strlen_utf8(s->current_input);
int visible_len = input_char_len - s->unacknowledged_chars;
if (visible_len < 0) visible_len = 0;
for (int row = 0; row < g_rows; row++) { for (int row = 0; row < g_rows; row++) {
int line_index = top_line + row; int line_index = top_line + row;
@ -723,22 +774,24 @@ static void draw_session(TerminalSession *s) {
} }
} }
int input_start = s->cursor_col - s->input_len; int input_start = s->cursor_col - visible_len;
if (input_start < 0) input_start = 0; if (input_start < 0) input_start = 0;
if (input_start >= g_cols) input_start = g_cols - 1; if (input_start >= g_cols) input_start = g_cols - 1;
// lenght of a command char cmd[64];
int cmd_len = 0; int i = 0;
while (cmd_len < s->input_len && while (i < s->input_len && s->current_input[i] != ' ' && s->current_input[i] != '\t' && i < 63) {
s->current_input[cmd_len] != ' ') { cmd[i] = s->current_input[i];
cmd_len++; i++;
} }
cmd[i] = 0;
int cmd_char_len = text_strlen_utf8(cmd);
int input_end = input_start + cmd_len; int input_end = input_start + cmd_char_len;
if (input_end > g_cols) input_end = g_cols; if (input_end > g_cols) input_end = g_cols;
for (int col = 0; col < g_cols; col++) { for (int col = 0; col < g_cols; col++) {
char ch = ' '; uint32_t ch = ' ';
uint32_t color = s->fg_color; uint32_t color = s->fg_color;
if (line && col < line_cols) { if (line && col < line_cols) {
@ -753,10 +806,13 @@ static void draw_session(TerminalSession *s) {
} }
} }
char str[2] = { ch, 0 }; char out[5];
int len = text_encode_utf8(ch, out);
out[len] = 0;
int x = col * g_char_w; int x = col * g_char_w;
int y = base_y + row * g_line_h; int y = base_y + row * g_line_h;
ui_draw_string(g_win, x, y, str, color); ui_draw_string(g_win, x, y, out, color);
} }
} }
@ -784,6 +840,10 @@ static void tab_init(TerminalSession *s, int tty_id, int bsh_pid) {
s->input_len = 0; s->input_len = 0;
s->current_input[0] = 0; s->current_input[0] = 0;
s->utf8_codepoint = 0;
s->utf8_expected = 0;
s->utf8_received = 0;
s->unacknowledged_chars = 0;
session_reset_colors(s); session_reset_colors(s);
scrollback_init(s); scrollback_init(s);
@ -1024,30 +1084,11 @@ static void update_input_color(TerminalSession *s) {
static void handle_key(gui_event_t *ev) { static void handle_key(gui_event_t *ev) {
TerminalSession *s = &g_tabs[g_active_tab]; TerminalSession *s = &g_tabs[g_active_tab];
char c = (char)ev->arg1; int legacy = ev->arg1;
bool ctrl = ev->arg3 != 0; bool ctrl = ev->arg3 != 0;
uint32_t codepoint = (uint32_t)ev->arg4;
// create new tab with ctrl + t if (ctrl && (legacy == 'c' || legacy == 'C')) {
if (ctrl && c == 't') {
int idx = create_tab();
if (idx >= 0) g_active_tab = idx;
return;
}
// switch to tab right, with ctrl + arrow right
if (ctrl && c == KEY_RIGHT) {
if (g_tab_count > 0) g_active_tab = (g_active_tab + 1) % g_tab_count;
return;
}
// switch to tab left, with ctrl + arrow left
if (ctrl && c == KEY_LEFT) {
if (g_tab_count > 0) g_active_tab = (g_active_tab + g_tab_count - 1) % g_tab_count;
return;
}
// kill the processus with ctrl + c
if (ctrl && (c == 'c' || c == 'C')) {
int fg = sys_tty_get_fg(s->tty_id); int fg = sys_tty_get_fg(s->tty_id);
if (fg > 0) { if (fg > 0) {
sys_tty_kill_fg(s->tty_id); sys_tty_kill_fg(s->tty_id);
@ -1059,30 +1100,84 @@ static void handle_key(gui_event_t *ev) {
return; return;
} }
// create new tab with ctrl + t
if (ctrl && (legacy == 't' || legacy == 'T')) {
int idx = create_tab();
if (idx >= 0) g_active_tab = idx;
return;
}
// switch to tab right, with ctrl + arrow right
if (ctrl && legacy == KEY_RIGHT) {
if (g_tab_count > 0) g_active_tab = (g_active_tab + 1) % g_tab_count;
return;
}
// switch to tab left, with ctrl + arrow left
if (ctrl && legacy == KEY_LEFT) {
if (g_tab_count > 0) g_active_tab = (g_active_tab + g_tab_count - 1) % g_tab_count;
return;
}
if (legacy == KEY_LEFT) {
char seq[] = { 27, '[', 'D' };
sys_tty_write_in(s->tty_id, seq, 3);
return;
} else if (legacy == KEY_RIGHT) {
char seq[] = { 27, '[', 'C' };
sys_tty_write_in(s->tty_id, seq, 3);
return;
} else if (legacy == KEY_UP) {
char seq[] = { 27, '[', 'A' };
sys_tty_write_in(s->tty_id, seq, 3);
return;
} else if (legacy == KEY_DOWN) {
char seq[] = { 27, '[', 'B' };
sys_tty_write_in(s->tty_id, seq, 3);
return;
}
if (!ctrl) { if (!ctrl) {
if (c == KEY_BACKSPACE) { if (legacy == KEY_BACKSPACE) {
if (s->input_len > 0) { if (s->input_len > 0) {
s->input_len--; // Find previous UTF-8 character boundary
const char *prev = text_prev_utf8(s->current_input, s->current_input + s->input_len);
s->input_len = (int)(prev - s->current_input);
s->current_input[s->input_len] = 0; s->current_input[s->input_len] = 0;
} }
} else if (c >= 32 && c < 127) { } else if (codepoint >= 32 && codepoint != 127) {
if (s->input_len < LINE_MAX - 1) { char utf8[4];
s->current_input[s->input_len++] = c; int len = text_encode_utf8(codepoint, utf8);
if (len > 0 && s->input_len + len < LINE_MAX - 1) {
for (int i = 0; i < len; i++) {
s->current_input[s->input_len + i] = utf8[i];
}
s->input_len += len;
s->current_input[s->input_len] = 0; s->current_input[s->input_len] = 0;
s->unacknowledged_chars++;
} }
} else if (legacy == KEY_ENTER) {
s->input_color = 0xFFFFFFFF;
s->input_len = 0;
s->current_input[0] = 0;
s->unacknowledged_chars = 0;
} else if (legacy == KEY_BACKSPACE) {
s->unacknowledged_chars--;
} }
update_input_color(s); update_input_color(s);
} }
if (c == KEY_ENTER) { if (codepoint >= 32 && codepoint != 127) {
s->input_color = 0xFFFFFFFF; char utf8[4];
s->input_len = 0; int len = text_encode_utf8(codepoint, utf8);
s->current_input[0] = 0; if (len > 0) {
sys_tty_write_in(s->tty_id, utf8, len);
}
} else {
char c = (char)legacy;
sys_tty_write_in(s->tty_id, &c, 1);
} }
sys_tty_write_in(s->tty_id, &c, 1);
} }
int main(void) { int main(void) {

View file

@ -4,6 +4,8 @@
#include "libc/syscall.h" #include "libc/syscall.h"
#include "libc/libui.h" #include "libc/libui.h"
#include "libc/stdlib.h" #include "libc/stdlib.h"
#include "libc/input.h"
#include "utf-8.h"
#include <stddef.h> #include <stddef.h>
#define COLOR_DARK_PANEL 0xFF202020 #define COLOR_DARK_PANEL 0xFF202020
@ -17,7 +19,6 @@
#define EDITOR_MAX_LINES 128 #define EDITOR_MAX_LINES 128
#define EDITOR_MAX_LINE_LEN 256 #define EDITOR_MAX_LINE_LEN 256
static int editor_line_height = 16; static int editor_line_height = 16;
static int editor_char_width = 8;
typedef struct { typedef struct {
char content[EDITOR_MAX_LINE_LEN]; char content[EDITOR_MAX_LINE_LEN];
@ -176,11 +177,11 @@ static void editor_save_file(void) {
file_modified = 0; file_modified = 0;
} }
static void editor_insert_char(char ch) { static void editor_insert_char(int legacy, uint32_t codepoint) {
if (cursor_line >= EDITOR_MAX_LINES) return; if (cursor_line >= EDITOR_MAX_LINES) return;
EditorLine *line = &lines[cursor_line]; EditorLine *line = &lines[cursor_line];
if (ch == '\n') { if (legacy == '\n') {
if (line_count >= EDITOR_MAX_LINES) return; if (line_count >= EDITOR_MAX_LINES) return;
for (int j = line_count; j > cursor_line; j--) { for (int j = line_count; j > cursor_line; j--) {
lines[j] = lines[j - 1]; lines[j] = lines[j - 1];
@ -206,13 +207,17 @@ static void editor_insert_char(char ch) {
cursor_line++; cursor_line++;
cursor_col = 0; cursor_col = 0;
} else if (ch == '\b') { } else if (legacy == KEY_BACKSPACE) {
if (cursor_col > 0) { if (cursor_col > 0) {
for (int i = cursor_col - 1; i < line->length; i++) { const char *prev = text_prev_utf8(line->content, line->content + cursor_col);
line->content[i] = line->content[i + 1]; int char_len = (int)((line->content + cursor_col) - prev);
for (int i = cursor_col - char_len; i < line->length - char_len; i++) {
line->content[i] = line->content[i + char_len];
} }
line->length--; line->length -= char_len;
cursor_col--; cursor_col -= char_len;
line->content[line->length] = 0;
} else if (cursor_line > 0) { } else if (cursor_line > 0) {
EditorLine *prev = &lines[cursor_line - 1]; EditorLine *prev = &lines[cursor_line - 1];
int merge_point = prev->length; int merge_point = prev->length;
@ -235,14 +240,19 @@ static void editor_insert_char(char ch) {
cursor_col = merge_point; cursor_col = merge_point;
line_count--; line_count--;
} }
} else if (ch >= 32 && ch <= 126) { } else if (codepoint >= 32 && codepoint != 127) {
if (cursor_col < EDITOR_MAX_LINE_LEN - 1) { char utf8[4];
int len = text_encode_utf8(codepoint, utf8);
if (len > 0 && line->length + len < EDITOR_MAX_LINE_LEN) {
for (int i = line->length; i > cursor_col; i--) { for (int i = line->length; i > cursor_col; i--) {
line->content[i] = line->content[i - 1]; line->content[i + len - 1] = line->content[i - 1];
} }
line->content[cursor_col] = ch; for (int i = 0; i < len; i++) {
line->length++; line->content[cursor_col + i] = utf8[i];
cursor_col++; }
line->length += len;
cursor_col += len;
line->content[line->length] = 0;
} }
} }
file_modified = 1; file_modified = 1;
@ -386,7 +396,6 @@ static void editor_paint(ui_window_t win) {
int status_y = win_h - footer_h; int status_y = win_h - footer_h;
ui_draw_rounded_rect_filled(win, padding, status_y + 2, content_width, footer_h - 4, 6, COLOR_DARK_PANEL); ui_draw_rounded_rect_filled(win, padding, status_y + 2, content_width, footer_h - 4, 6, COLOR_DARK_PANEL);
char status_text[128];
ui_draw_string(win, padding + 15, status_y + 5, "Line:", COLOR_DKGRAY); ui_draw_string(win, padding + 15, status_y + 5, "Line:", COLOR_DKGRAY);
char line_str[32]; char line_str[32];
@ -409,36 +418,38 @@ static void editor_paint(ui_window_t win) {
ui_draw_string(win, padding + 160, status_y + 5, col_str, COLOR_DARK_TEXT); ui_draw_string(win, padding + 160, status_y + 5, col_str, COLOR_DARK_TEXT);
} }
static void editor_handle_key(char c, bool pressed) { static void editor_handle_key(int legacy, uint32_t codepoint, bool pressed) {
if (!pressed) return; if (!pressed) return;
if (c == 17) { // UP if (legacy == KEY_UP) { // UP
if (cursor_line > 0) { if (cursor_line > 0) {
cursor_line--; cursor_line--;
if (cursor_col > lines[cursor_line].length) cursor_col = lines[cursor_line].length; if (cursor_col > lines[cursor_line].length) cursor_col = lines[cursor_line].length;
if (cursor_line < scroll_top) scroll_top = cursor_line; if (cursor_line < scroll_top) scroll_top = cursor_line;
} }
} else if (c == 18) { // DOWN } else if (legacy == KEY_DOWN) { // DOWN
if (cursor_line < line_count - 1) { if (cursor_line < line_count - 1) {
cursor_line++; cursor_line++;
if (cursor_col > lines[cursor_line].length) cursor_col = lines[cursor_line].length; if (cursor_col > lines[cursor_line].length) cursor_col = lines[cursor_line].length;
editor_ensure_cursor_visible(); editor_ensure_cursor_visible();
} }
} else if (c == 19) { // LEFT } else if (legacy == KEY_LEFT) { // LEFT
if (cursor_col > 0) { if (cursor_col > 0) {
cursor_col--; const char *prev = text_prev_utf8(lines[cursor_line].content, lines[cursor_line].content + cursor_col);
cursor_col = (int)(prev - lines[cursor_line].content);
} else if (cursor_line > 0) { } else if (cursor_line > 0) {
cursor_line--; cursor_line--;
cursor_col = lines[cursor_line].length; cursor_col = lines[cursor_line].length;
} }
} else if (c == 20) { // RIGHT } else if (legacy == KEY_RIGHT) { // RIGHT
if (cursor_col < lines[cursor_line].length) { if (cursor_col < lines[cursor_line].length) {
cursor_col++; const char *next = text_next_utf8(lines[cursor_line].content + cursor_col);
cursor_col = (int)(next - lines[cursor_line].content);
} else if (cursor_line < line_count - 1) { } else if (cursor_line < line_count - 1) {
cursor_line++; cursor_line++;
cursor_col = 0; cursor_col = 0;
} }
} else { } else {
editor_insert_char(c); editor_insert_char(legacy, codepoint);
} }
} }
@ -477,11 +488,11 @@ int main(int argc, char **argv) {
editor_paint(win); editor_paint(win);
ui_mark_dirty(win, 0, 0, win_w, win_h); ui_mark_dirty(win, 0, 0, win_w, win_h);
} else if (ev.type == GUI_EVENT_KEY) { } else if (ev.type == GUI_EVENT_KEY) {
editor_handle_key((char)ev.arg1, true); editor_handle_key(ev.arg1, (uint32_t)ev.arg4, true);
editor_paint(win); editor_paint(win);
ui_mark_dirty(win, 0, 0, win_w, win_h); ui_mark_dirty(win, 0, 0, win_w, win_h);
} else if (ev.type == GUI_EVENT_KEYUP) { } else if (ev.type == GUI_EVENT_KEYUP) {
editor_handle_key((char)ev.arg1, false); editor_handle_key(ev.arg1, (uint32_t)ev.arg4, false);
} else if (ev.type == GUI_EVENT_CLOSE) { } else if (ev.type == GUI_EVENT_CLOSE) {
sys_exit(0); sys_exit(0);
} }