feat: implement left/right arrow navigation and inline editing in shell

This commit is contained in:
boreddevnl 2026-04-27 16:07:25 +02:00
parent 39088c7e8e
commit a43465e3d3
2 changed files with 92 additions and 22 deletions

View file

@ -866,11 +866,25 @@ static void prompt_write_with_right(const char *left_tmpl, const char *right_tmp
} }
} }
static void redraw_input(const char *prompt_tmpl, const char *line, int len) { static void redraw_input(const char *prompt_tmpl, const char *line, int len, int cursor) {
sys_write(1, "\r", 1); sys_write(1, "\r", 1);
prompt_write_with_right(prompt_tmpl, g_cfg.prompt_right); prompt_write_with_right(prompt_tmpl, g_cfg.prompt_right);
sys_write(1, line, len); sys_write(1, line, len);
sys_write(1, "\x1b[K", 3); sys_write(1, "\x1b[K", 3);
int diff = len - cursor;
if (diff > 0) {
int char_diff = text_strlen_utf8(line + cursor);
if (char_diff > 0) {
char num[16];
char seq[32];
itoa(char_diff, num);
str_copy(seq, "\x1b[", sizeof(seq));
str_append(seq, num, sizeof(seq));
str_append(seq, "D", sizeof(seq));
sys_write(1, seq, (int)strlen(seq));
}
}
} }
static void show_matches(const char *prompt_tmpl, const char *line, int len, char matches[][MAX_MATCH_LEN], int count) { static void show_matches(const char *prompt_tmpl, const char *line, int len, char matches[][MAX_MATCH_LEN], int count) {
@ -880,7 +894,7 @@ static void show_matches(const char *prompt_tmpl, const char *line, int len, cha
sys_write(1, " ", 2); sys_write(1, " ", 2);
} }
sys_write(1, "\n", 1); sys_write(1, "\n", 1);
redraw_input(prompt_tmpl, line, len); redraw_input(prompt_tmpl, line, len, len);
} }
static int pid_exists(int pid) { static int pid_exists(int pid) {
@ -1424,6 +1438,7 @@ static bool run_script(const char *path) {
static int read_line(char *out, int max_len, const char *prompt_tmpl) { static int read_line(char *out, int max_len, const char *prompt_tmpl) {
int len = 0; int len = 0;
int cursor = 0;
int hist_index = g_history_count; int hist_index = g_history_count;
bool search_mode = false; bool search_mode = false;
char saved_line[MAX_LINE]; char saved_line[MAX_LINE];
@ -1463,15 +1478,15 @@ 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 (cursor > 0) {
// Find previous UTF-8 character boundary const char *prev = text_prev_utf8(out, out + cursor);
const char *prev = text_prev_utf8(out, out + len); int shift = (int)(out + cursor - prev);
len = (int)(prev - out); for (int i = cursor; i <= len; i++) {
out[len] = 0; out[i - shift] = out[i];
}
// Send only ONE backspace sequence to the terminal cursor -= shift;
// because the terminal is now codepoint-based. len -= shift;
sys_write(1, "\b \b", 3); redraw_input(prompt_tmpl, out, len, cursor);
} }
search_mode = false; search_mode = false;
hist_index = g_history_count; hist_index = g_history_count;
@ -1524,7 +1539,8 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
out[token_start] = 0; out[token_start] = 0;
str_append(out, matches[0], max_len); str_append(out, matches[0], max_len);
len = (int)strlen(out); len = (int)strlen(out);
redraw_input(prompt_tmpl, out, len); cursor = len;
redraw_input(prompt_tmpl, out, len, cursor);
} else { } else {
int common_len = common_prefix_len(matches, match_count); int common_len = common_prefix_len(matches, match_count);
if (common_len > token_len) { if (common_len > token_len) {
@ -1535,9 +1551,11 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
out[token_start] = 0; out[token_start] = 0;
str_append(out, prefix_buf, max_len); str_append(out, prefix_buf, max_len);
len = (int)strlen(out); len = (int)strlen(out);
redraw_input(prompt_tmpl, out, len); cursor = len;
redraw_input(prompt_tmpl, out, len, cursor);
} else { } else {
show_matches(prompt_tmpl, out, len, matches, match_count); show_matches(prompt_tmpl, out, len, matches, match_count);
cursor = len;
} }
} }
search_mode = false; search_mode = false;
@ -1545,6 +1563,23 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
continue; continue;
} }
if (ch == 27) {
char seq[2];
int g1 = 0, g2 = 0;
int retries = 0;
while (g1 <= 0 && retries < 10) { g1 = sys_tty_read_in(&seq[0], 1); if (g1 <= 0) { sleep(1); retries++; } }
retries = 0;
while (g2 <= 0 && retries < 10) { g2 = sys_tty_read_in(&seq[1], 1); if (g2 <= 0) { sleep(1); retries++; } }
if (g1 > 0 && g2 > 0 && seq[0] == '[') {
if (seq[1] == 'A') ch = 17;
else if (seq[1] == 'B') ch = 18;
else if (seq[1] == 'C') ch = 19;
else if (seq[1] == 'D') ch = 20;
}
if (ch == 27) continue;
}
if (ch == 17) { // Up if (ch == 17) { // Up
if (g_history_count == 0) continue; if (g_history_count == 0) continue;
if (!search_mode) { if (!search_mode) {
@ -1565,7 +1600,8 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
hist_index = found; hist_index = found;
str_copy(out, g_history[hist_index], max_len); str_copy(out, g_history[hist_index], max_len);
len = (int)strlen(out); len = (int)strlen(out);
redraw_input(prompt_tmpl, out, len); cursor = len;
redraw_input(prompt_tmpl, out, len, cursor);
} }
continue; continue;
} }
@ -1583,20 +1619,44 @@ static int read_line(char *out, int max_len, const char *prompt_tmpl) {
hist_index = found; hist_index = found;
str_copy(out, g_history[hist_index], max_len); str_copy(out, g_history[hist_index], max_len);
len = (int)strlen(out); len = (int)strlen(out);
cursor = len;
} else { } else {
search_mode = false; search_mode = false;
hist_index = g_history_count; hist_index = g_history_count;
str_copy(out, saved_line, max_len); str_copy(out, saved_line, max_len);
len = (int)strlen(out); len = (int)strlen(out);
cursor = len;
}
redraw_input(prompt_tmpl, out, len, cursor);
continue;
}
if (ch == 20) { // Left
if (cursor > 0) {
const char *prev = text_prev_utf8(out, out + cursor);
if (prev) cursor = (int)(prev - out);
redraw_input(prompt_tmpl, out, len, cursor);
}
continue;
}
if (ch == 19) { // Right
if (cursor < len) {
const char *next = text_next_utf8(out + cursor);
if (next && *next) cursor = (int)(next - out);
else cursor = len;
redraw_input(prompt_tmpl, out, len, cursor);
} }
redraw_input(prompt_tmpl, out, len);
continue; continue;
} }
if (((unsigned char)ch >= 32 || (signed char)ch < 0) && len < max_len - 1) { if (((unsigned char)ch >= 32 || (signed char)ch < 0) && len < max_len - 1) {
out[len++] = ch; for (int i = len; i >= cursor; i--) {
out[len] = 0; out[i + 1] = out[i];
sys_write(1, &ch, 1); }
out[cursor++] = ch;
len++;
redraw_input(prompt_tmpl, out, len, cursor);
search_mode = false; search_mode = false;
hist_index = g_history_count; hist_index = g_history_count;
} }

View file

@ -62,6 +62,8 @@ typedef struct {
// for color // for color
char current_input[LINE_MAX]; char current_input[LINE_MAX];
int input_len; int input_len;
int input_start_col;
int input_start_row;
} TerminalSession; } TerminalSession;
static ui_window_t g_win; static ui_window_t g_win;
@ -774,9 +776,10 @@ static void draw_session(TerminalSession *s) {
} }
} }
int input_start = s->cursor_col - visible_len; int input_start = -1;
if (input_start < 0) input_start = 0; if (s->input_len > 0 && row == s->input_start_row) {
if (input_start >= g_cols) input_start = g_cols - 1; input_start = s->input_start_col;
}
char cmd[64]; char cmd[64];
int i = 0; int i = 0;
@ -800,7 +803,7 @@ static void draw_session(TerminalSession *s) {
color = line[col].color; color = line[col].color;
} }
if (s->scroll_offset == 0 && row == s->cursor_row) { if (s->scroll_offset == 0 && row == s->input_start_row && input_start >= 0) {
if (col >= input_start && col < input_end) { if (col >= input_start && col < input_end) {
color = s->input_color; color = s->input_color;
} }
@ -839,6 +842,8 @@ static void tab_init(TerminalSession *s, int tty_id, int bsh_pid) {
s->saved_col = 0; s->saved_col = 0;
s->input_len = 0; s->input_len = 0;
s->input_start_col = 0;
s->input_start_row = 0;
s->current_input[0] = 0; s->current_input[0] = 0;
s->utf8_codepoint = 0; s->utf8_codepoint = 0;
s->utf8_expected = 0; s->utf8_expected = 0;
@ -1095,6 +1100,7 @@ static void handle_key(gui_event_t *ev) {
sys_tty_set_fg(s->tty_id, 0); sys_tty_set_fg(s->tty_id, 0);
return; return;
} }
s->input_len = 0;
char ch = 3; char ch = 3;
sys_tty_write_in(s->tty_id, &ch, 1); sys_tty_write_in(s->tty_id, &ch, 1);
return; return;
@ -1149,6 +1155,10 @@ static void handle_key(gui_event_t *ev) {
char utf8[4]; char utf8[4];
int len = text_encode_utf8(codepoint, utf8); int len = text_encode_utf8(codepoint, utf8);
if (len > 0 && s->input_len + len < LINE_MAX - 1) { if (len > 0 && s->input_len + len < LINE_MAX - 1) {
if (s->input_len == 0) {
s->input_start_col = s->cursor_col;
s->input_start_row = s->cursor_row;
}
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
s->current_input[s->input_len + i] = utf8[i]; s->current_input[s->input_len + i] = utf8[i];
} }