Fixed incorrect cursor position if prompt contains ANSI sequences

- And added delete key handling
This commit is contained in:
Aleksandr Lebedev 2026-04-21 15:05:17 +02:00
parent 1101c53bf6
commit ca08c9e3e5

61
main.c
View file

@ -161,6 +161,47 @@ size_t utf8_display_width(const char *buf, size_t len)
return width; return width;
} }
size_t visible_width(const char *s)
{
size_t i = 0;
size_t width = 0;
while (s[i]) {
// ANSI escape sequence
if (s[i] == '\x1b' && s[i+1] == '[') {
i += 2;
while (s[i] && !((s[i] >= '@' && s[i] <= '~')))
i++;
if (s[i]) i++;
continue;
}
unsigned char c = s[i];
if ((c & 0x80) == 0) {
i += 1;
width += 1;
}
else if ((c & 0xE0) == 0xC0) {
i += 2;
width += 1;
}
else if ((c & 0xF0) == 0xE0) {
i += 3;
width += 1;
}
else {
i += 4;
width += 1;
}
}
return width;
}
char* __readline_interactive(char* prompt) char* __readline_interactive(char* prompt)
{ {
enable_raw_mode(); enable_raw_mode();
@ -179,7 +220,7 @@ char* __readline_interactive(char* prompt)
write(STDOUT_FILENO, buf, len); write(STDOUT_FILENO, buf, len);
swrite(STDOUT_FILENO, "\x1b[K"); swrite(STDOUT_FILENO, "\x1b[K");
size_t prompt_width = utf8_display_width(prompt, strlen(prompt)); size_t prompt_width = visible_width(prompt);
size_t cell_cursor = utf8_display_width(buf, cursor); size_t cell_cursor = utf8_display_width(buf, cursor);
char seq[64]; char seq[64];
snprintf(seq, sizeof(seq), "\r\x1b[%zuC", prompt_width + cell_cursor); snprintf(seq, sizeof(seq), "\r\x1b[%zuC", prompt_width + cell_cursor);
@ -193,7 +234,7 @@ char* __readline_interactive(char* prompt)
if (clen <= 0) continue; if (clen <= 0) continue;
if (utf8[0] == '\x1b') { if (utf8[0] == '\x1b') {
char seq[2]; char seq[3];
if (read(STDIN_FILENO, &seq[0], 1) != 1) continue; if (read(STDIN_FILENO, &seq[0], 1) != 1) continue;
if (read(STDIN_FILENO, &seq[1], 1) != 1) continue; if (read(STDIN_FILENO, &seq[1], 1) != 1) continue;
@ -208,6 +249,22 @@ char* __readline_interactive(char* prompt)
case 'D': // Arrow left case 'D': // Arrow left
cursor = prev_char_start(buffer, cursor); cursor = prev_char_start(buffer, cursor);
break; break;
case '3':
if (read(STDIN_FILENO, &seq[2], 1) != 1) continue;
switch (seq[2])
{
case '~': // Delete
if (cursor < len) {
size_t next = next_char_start(buffer, len, cursor);
size_t diff = next - cursor;
memmove(buffer + cursor, buffer + next, len - next);
len -= diff;
}
break;
}
break;
} }
} }
} else if (utf8[0] == '\r') { // enter } else if (utf8[0] == '\r') { // enter