line-ed: add unicode support

This commit is contained in:
2026-05-17 16:44:54 +01:00
parent 2f200e8d1b
commit 5a23da4cfe
9 changed files with 107 additions and 70 deletions
+9 -6
View File
@@ -1,12 +1,15 @@
#include "buffer.h" #include "buffer.h"
#include "line-ed.h" #include "line-ed.h"
const char *line_start(struct line_ed *ed, size_t y) #include <fx/cstr.h>
const fx_wchar *line_start(struct line_ed *ed, size_t y)
{ {
const char *line = ed->l_buf; const fx_wchar *line = ed->l_buf;
for (size_t i = 0; i < y; i++) { for (size_t i = 0; i < y; i++) {
line += strcspn(line, "\n"); line += fx_wstrcspn(line, L"\n");
if (*line == '\n') { if (*line == '\n') {
line++; line++;
@@ -18,10 +21,10 @@ const char *line_start(struct line_ed *ed, size_t y)
size_t line_length(struct line_ed *ed, size_t y) size_t line_length(struct line_ed *ed, size_t y)
{ {
const char *line = ed->l_buf; const fx_wchar *line = ed->l_buf;
for (size_t i = 0; i < y; i++) { for (size_t i = 0; i < y; i++) {
line += strcspn(line, "\n"); line += fx_wstrcspn(line, L"\n");
if (*line == '\n') { if (*line == '\n') {
line++; line++;
} }
@@ -31,7 +34,7 @@ size_t line_length(struct line_ed *ed, size_t y)
return 0; return 0;
} }
size_t len = strcspn(line, "\n"); size_t len = fx_wstrcspn(line, L"\n");
if (line[len] == '\n') { if (line[len] == '\n') {
len++; len++;
} }
+2 -1
View File
@@ -1,13 +1,14 @@
#ifndef LINE_ED_BUFFER_H_ #ifndef LINE_ED_BUFFER_H_
#define LINE_ED_BUFFER_H_ #define LINE_ED_BUFFER_H_
#include <fx/encoding.h>
#include <stddef.h> #include <stddef.h>
struct line_ed; struct line_ed;
/* returns a pointer to the start of the line based on the given `y` /* returns a pointer to the start of the line based on the given `y`
* coordinate */ * coordinate */
extern const char *line_start(struct line_ed *ed, size_t y); extern const fx_wchar *line_start(struct line_ed *ed, size_t y);
/* returns the length of the line based on the given `y` coordinate. /* returns the length of the line based on the given `y` coordinate.
* for any line other than the last line in the buffer, this length * for any line other than the last line in the buffer, this length
* INCLUDES the trailing linefeed. */ * INCLUDES the trailing linefeed. */
+22 -12
View File
@@ -19,29 +19,39 @@ void alloc_empty_history_entry(struct line_ed *ed)
void save_buf_to_history(struct line_ed *ed) void save_buf_to_history(struct line_ed *ed)
{ {
fx_string *cur fx_string *cur = (fx_string *)fx_array_get(
= (fx_string *)fx_array_get(ed->l_history, ed->l_history_pos); ed->l_history,
fx_string_replace_all(cur, ed->l_buf); ed->l_history_pos);
fx_string_clear(cur);
fx_string_append_wstr(cur, ed->l_buf);
} }
void append_buf_to_history(struct line_ed *ed) void append_buf_to_history(struct line_ed *ed)
{ {
fx_string *cur fx_string *cur = (fx_string *)fx_array_get(
= (fx_string *)fx_array_get(ed->l_history, ed->l_history_pos); ed->l_history,
ed->l_history_pos);
char s[] = {'\n', 0}; char s[] = {'\n', 0};
fx_string_append_cstr(cur, s); fx_string_append_cstr(cur, s);
fx_string_append_cstr(cur, ed->l_buf); fx_string_append_wstr(cur, ed->l_buf);
} }
void load_buf_from_history(struct line_ed *ed) void load_buf_from_history(struct line_ed *ed)
{ {
fx_string *cur fx_string *cur = (fx_string *)fx_array_at(
= (fx_string *)fx_array_at(ed->l_history, ed->l_history_pos); ed->l_history,
size_t len ed->l_history_pos);
= MIN((size_t)(ed->l_buf_end - ed->l_buf - 1), size_t len = MIN(
fx_string_get_size(cur, FX_STRLEN_NORMAL)); (size_t)(ed->l_buf_end - ed->l_buf - 1),
fx_string_get_size(cur, FX_STRLEN_CODEPOINTS));
const char *src = fx_string_get_cstr(cur);
for (size_t i = 0; i < len; i++) {
fx_wchar ch_src = fx_wchar_utf8_codepoint_decode(src);
ed->l_buf[i] = ch_src;
src += fx_wchar_utf8_codepoint_stride(src);
}
memcpy(ed->l_buf, fx_string_get_cstr(cur), len);
ed->l_buf[len] = '\0'; ed->l_buf[len] = '\0';
unsigned int x = 0, y = 0; unsigned int x = 0, y = 0;
+35 -21
View File
@@ -8,7 +8,7 @@
#include <stdio.h> #include <stdio.h>
void put_char(struct line_ed *ed, char c) void put_char(struct line_ed *ed, fx_wchar c)
{ {
if (ed->l_buf_ptr > ed->l_line_end + 1) { if (ed->l_buf_ptr > ed->l_line_end + 1) {
return; return;
@@ -21,8 +21,8 @@ void put_char(struct line_ed *ed, char c)
size_t prev_cursor = ed->l_buf_ptr - ed->l_buf; size_t prev_cursor = ed->l_buf_ptr - ed->l_buf;
char *dest = ed->l_buf_ptr; fx_wchar *dest = ed->l_buf_ptr;
size_t len = ed->l_line_end - ed->l_buf_ptr + 1; size_t len = (ed->l_line_end - ed->l_buf_ptr + 1) * sizeof *dest;
if (dest < ed->l_line_end) { if (dest < ed->l_line_end) {
memmove(dest + 1, dest, len); memmove(dest + 1, dest, len);
@@ -55,8 +55,8 @@ static void backspace_simple(struct line_ed *ed)
size_t prev_cursor = ed->l_buf_ptr - ed->l_buf; size_t prev_cursor = ed->l_buf_ptr - ed->l_buf;
char *dest = ed->l_buf_ptr; fx_wchar *dest = ed->l_buf_ptr;
size_t len = ed->l_line_end - ed->l_buf_ptr + 1; size_t len = (ed->l_line_end - ed->l_buf_ptr + 1) * sizeof *dest;
memmove(dest - 1, dest, len); memmove(dest - 1, dest, len);
ed->l_cursor_x--; ed->l_cursor_x--;
@@ -78,8 +78,8 @@ static void backspace_nl(struct line_ed *ed)
.r_prev_line_len = prev_line_len, .r_prev_line_len = prev_line_len,
}; };
char *dest = ed->l_buf_ptr; fx_wchar *dest = ed->l_buf_ptr;
size_t len = ed->l_line_end - ed->l_buf_ptr + 1; size_t len = (ed->l_line_end - ed->l_buf_ptr + 1) * sizeof *dest;
memmove(dest - 1, dest, len); memmove(dest - 1, dest, len);
ed->l_cursor_x = prev_line_len - 1; ed->l_cursor_x = prev_line_len - 1;
@@ -112,7 +112,7 @@ void backspace(struct line_ed *ed)
void cursor_left(struct line_ed *ed) void cursor_left(struct line_ed *ed)
{ {
if (ed->l_cursor_x != 0) { if (ed->l_cursor_x != 0) {
//fputs("\010", stdout); // fputs("\010", stdout);
fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -1); fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, -1);
fflush(stdout); fflush(stdout);
ed->l_cursor_x--; ed->l_cursor_x--;
@@ -120,7 +120,8 @@ void cursor_left(struct line_ed *ed)
return; return;
} }
if (ed->l_cursor_y <= ed->l_continuations || ed->l_buf_ptr <= ed->l_buf) { if (ed->l_cursor_y <= ed->l_continuations
|| ed->l_buf_ptr <= ed->l_buf) {
return; return;
} }
@@ -134,9 +135,12 @@ void cursor_left(struct line_ed *ed)
size_t len = line_length(ed, ed->l_cursor_y); size_t len = line_length(ed, ed->l_cursor_y);
ed->l_cursor_x = len - 1; ed->l_cursor_x = len - 1;
//printf("\033[A\033[%dG", len + prompt_len); // printf("\033[A\033[%dG", len + prompt_len);
fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1); fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, -1);
fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, (int)(len + prompt_len)); fx_tty_move_cursor_x(
ed->l_tty,
FX_TTY_POS_START,
(int)(len + prompt_len));
fflush(stdout); fflush(stdout);
} }
@@ -150,7 +154,7 @@ void cursor_right(struct line_ed *ed)
if (*ed->l_buf_ptr != '\n') { if (*ed->l_buf_ptr != '\n') {
ed->l_cursor_x++; ed->l_cursor_x++;
ed->l_buf_ptr++; ed->l_buf_ptr++;
//fputs("\033[C", stdout); // fputs("\033[C", stdout);
fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, 1); fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_CURSOR, 1);
fflush(stdout); fflush(stdout);
return; return;
@@ -164,7 +168,7 @@ void cursor_right(struct line_ed *ed)
ed->l_cursor_x = 0; ed->l_cursor_x = 0;
ed->l_buf_ptr++; ed->l_buf_ptr++;
//printf("\033[B\033[G"); // printf("\033[B\033[G");
fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, 1); fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, 1);
fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, 0); fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, 0);
fflush(stdout); fflush(stdout);
@@ -177,13 +181,18 @@ void arrow_up(struct line_ed *ed)
} }
if (ed->l_cursor_y > 0) { if (ed->l_cursor_y > 0) {
//printf("\033[%uA", ed->l_cursor_y); // printf("\033[%uA", ed->l_cursor_y);
fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, (long long)ed->l_cursor_y); fx_tty_move_cursor_y(
ed->l_tty,
FX_TTY_POS_CURSOR,
(long long)ed->l_cursor_y);
} }
//printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); // printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1);
fx_tty_move_cursor_x( fx_tty_move_cursor_x(
ed->l_tty, FX_TTY_POS_START, (long long)prompt_length(ed, PROMPT_MAIN)); ed->l_tty,
FX_TTY_POS_START,
(long long)prompt_length(ed, PROMPT_MAIN));
fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR); fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR);
save_buf_to_history(ed); save_buf_to_history(ed);
@@ -201,13 +210,18 @@ void arrow_down(struct line_ed *ed)
} }
if (ed->l_cursor_y > 0) { if (ed->l_cursor_y > 0) {
//printf("\033[%uA", ed->l_cursor_y); // printf("\033[%uA", ed->l_cursor_y);
fx_tty_move_cursor_y(ed->l_tty, FX_TTY_POS_CURSOR, (int)ed->l_cursor_y); fx_tty_move_cursor_y(
ed->l_tty,
FX_TTY_POS_CURSOR,
(int)ed->l_cursor_y);
} }
//printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1); // printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1);
fx_tty_move_cursor_x( fx_tty_move_cursor_x(
ed->l_tty, FX_TTY_POS_START, prompt_length(ed, PROMPT_MAIN) + 1); ed->l_tty,
FX_TTY_POS_START,
prompt_length(ed, PROMPT_MAIN) + 1);
save_buf_to_history(ed); save_buf_to_history(ed);
ed->l_history_pos++; ed->l_history_pos++;
+3 -1
View File
@@ -1,9 +1,11 @@
#ifndef LINE_ED_INPUT_H_ #ifndef LINE_ED_INPUT_H_
#define LINE_ED_INPUT_H_ #define LINE_ED_INPUT_H_
#include <fx/encoding.h>
struct line_ed; struct line_ed;
extern void put_char(struct line_ed *ed, char c); extern void put_char(struct line_ed *ed, fx_wchar c);
extern void backspace(struct line_ed *ed); extern void backspace(struct line_ed *ed);
extern void cursor_left(struct line_ed *ed); extern void cursor_left(struct line_ed *ed);
extern void cursor_right(struct line_ed *ed); extern void cursor_right(struct line_ed *ed);
+15 -14
View File
@@ -6,6 +6,7 @@
#include "prompt.h" #include "prompt.h"
#include <ctype.h> #include <ctype.h>
#include <fx/cstr.h>
#include <fx/term/tty.h> #include <fx/term/tty.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -98,13 +99,13 @@ static void clear_buffer(struct line_ed *ed)
ed->l_continuations = 0; ed->l_continuations = 0;
} }
static void convert_continuation_feeds(char *s, size_t max) static void convert_continuation_feeds(fx_wchar *s, size_t max)
{ {
char *end = s + max; fx_wchar *end = s + max;
size_t len = strlen(s); size_t len = fx_wstrlen(s);
while (1) { while (1) {
size_t r = strcspn(s, "\\"); size_t r = fx_wstrcspn(s, L"\\");
if (s + r > end) { if (s + r > end) {
break; break;
} }
@@ -126,13 +127,13 @@ static void convert_continuation_feeds(char *s, size_t max)
} }
} }
static void remove_continuation_feeds(char *s, size_t max) static void remove_continuation_feeds(fx_wchar *s, size_t max)
{ {
char *end = s + max; fx_wchar *end = s + max;
size_t len = strlen(s); size_t len = fx_wstrlen(s);
while (1) { while (1) {
size_t r = strcspn(s, "\\"); size_t r = fx_wstrcspn(s, L"\\");
if (s + r > end) { if (s + r > end) {
break; break;
} }
@@ -156,9 +157,9 @@ static void remove_continuation_feeds(char *s, size_t max)
static bool input_is_empty(struct line_ed *ed) static bool input_is_empty(struct line_ed *ed)
{ {
const char *p = ed->l_buf; const fx_wchar *p = ed->l_buf;
while (p < ed->l_line_end) { while (p < ed->l_line_end) {
if (!isspace(*p)) { if (!fx_wchar_is_space(*p)) {
return false; return false;
} }
} }
@@ -188,7 +189,7 @@ size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out)
fputc('\r', stdout); fputc('\r', stdout);
} }
fputc(ed->l_buf[i], stdout); fx_stream_write_char(fx_stdout, ed->l_buf[i]);
} }
fflush(stdout); fflush(stdout);
@@ -257,7 +258,7 @@ size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out)
arrow_down(ed); arrow_down(ed);
break; break;
default: default:
if (iswgraph(key) || key == ' ') { if (fx_wchar_is_graph(key) || key == ' ') {
put_char(ed, key); put_char(ed, key);
} }
break; break;
@@ -278,7 +279,7 @@ size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out)
ed->l_history_pos = fx_array_size(ed->l_history) - 1; ed->l_history_pos = fx_array_size(ed->l_history) - 1;
const char *last = last_history_line(ed); const char *last = last_history_line(ed);
if (!last || strcmp(last, ed->l_buf) != 0) { if (!last || fx_wastrcmp(last, ed->l_buf) != 0) {
save_buf_to_history(ed); save_buf_to_history(ed);
} }
} }
@@ -294,7 +295,7 @@ size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out)
convert_continuation_feeds(ed->l_buf, sz); convert_continuation_feeds(ed->l_buf, sz);
} }
fx_stream_write_cstr(out, ed->l_buf, NULL); fx_stream_write_wstr(out, ed->l_buf, NULL);
fx_stream_write_char(out, '\n'); fx_stream_write_char(out, '\n');
return sz; return sz;
+2 -2
View File
@@ -49,9 +49,9 @@ struct line_ed {
/* input buffer, pointer to the buffer cell that corresponds to /* input buffer, pointer to the buffer cell that corresponds to
* the current cursor position, and pointer to the byte AFTER the last * the current cursor position, and pointer to the byte AFTER the last
* usable byte in the buffer */ * usable byte in the buffer */
char *l_buf, *l_buf_ptr, *l_buf_end; fx_wchar *l_buf, *l_buf_ptr, *l_buf_end;
/* pointer to the byte AFTER the last byte of the user's input */ /* pointer to the byte AFTER the last byte of the user's input */
char *l_line_end; fx_wchar *l_line_end;
/* 2-dimensional coordinates of the current cursor position. /* 2-dimensional coordinates of the current cursor position.
* this does NOT include any prompts that are visible on the terminal */ * this does NOT include any prompts that are visible on the terminal */
size_t l_cursor_x, l_cursor_y; size_t l_cursor_x, l_cursor_y;
+13 -12
View File
@@ -4,6 +4,7 @@
#include "cursor.h" #include "cursor.h"
#include "line-ed.h" #include "line-ed.h"
#include <fx/cstr.h>
#include <fx/term/tty.h> #include <fx/term/tty.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -15,19 +16,19 @@
* the (x, y) coordinates provided should be the data coordinates of the * the (x, y) coordinates provided should be the data coordinates of the
* first character in `s`. * first character in `s`.
*/ */
void print_text(struct line_ed *ed, size_t x, size_t y, const char *s) void print_text(struct line_ed *ed, size_t x, size_t y, const fx_wchar *s)
{ {
fx_tty *tty = ed->l_tty; fx_tty *tty = ed->l_tty;
for (size_t i = 0; s[i] != '\n' && s[i] != '\0'; i++) { for (size_t i = 0; s[i] != '\n' && s[i] != '\0'; i++) {
fputc(s[i], stdout); fx_stream_write_char(fx_stdout, s[i]);
} }
} }
void print_buffer(struct line_ed *ed) void print_buffer(struct line_ed *ed)
{ {
const char *line_buf = ed->l_buf; const fx_wchar *line_buf = ed->l_buf;
size_t line_len = strcspn(line_buf, "\n"); size_t line_len = fx_wstrcspn(line_buf, L"\n");
unsigned int y = 0; unsigned int y = 0;
@@ -44,7 +45,7 @@ void print_buffer(struct line_ed *ed)
} }
y++; y++;
line_len = strcspn(line_buf, "\n"); line_len = fx_wstrcspn(line_buf, L"\n");
fputc('\r', stdout); fputc('\r', stdout);
fputc('\n', stdout); fputc('\n', stdout);
@@ -74,8 +75,8 @@ void print_buffer(struct line_ed *ed)
void put_refresh(struct line_ed *ed, struct refresh_state *state) void put_refresh(struct line_ed *ed, struct refresh_state *state)
{ {
/* get the data for the line that is being refreshed */ /* get the data for the line that is being refreshed */
const char *line_buf = line_start(ed, ed->l_cursor_y); const fx_wchar *line_buf = line_start(ed, ed->l_cursor_y);
size_t line_len = strcspn(line_buf, "\n"); size_t line_len = fx_wstrcspn(line_buf, L"\n");
/* the index of the first char in line_buf that needs to be reprinted */ /* the index of the first char in line_buf that needs to be reprinted */
size_t start_x = state->r_prev_cursor_x; size_t start_x = state->r_prev_cursor_x;
@@ -128,8 +129,8 @@ void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state)
/* get the data for the line that is being refreshed. /* get the data for the line that is being refreshed.
* relative to where the cursor was before the linefeed was deleted, * relative to where the cursor was before the linefeed was deleted,
* this is the PREVIOUS line. */ * this is the PREVIOUS line. */
const char *line_buf = line_start(ed, ed->l_cursor_y); const fx_wchar *line_buf = line_start(ed, ed->l_cursor_y);
size_t line_len = strcspn(line_buf, "\n"); size_t line_len = fx_wstrcspn(line_buf, L"\n");
/* the index of the first char in line_buf that needs to be reprinted. /* the index of the first char in line_buf that needs to be reprinted.
* subtract one to account for the linefeed that has since been deleted. * subtract one to account for the linefeed that has since been deleted.
@@ -188,7 +189,7 @@ void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state)
line_buf + start_x); line_buf + start_x);
line_buf += line_len + 1; line_buf += line_len + 1;
line_len = strcspn(line_buf, "\n"); line_len = fx_wstrcspn(line_buf, L"\n");
start_x = 0; start_x = 0;
if (*line_buf == '\0') { if (*line_buf == '\0') {
@@ -218,8 +219,8 @@ void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state)
void backspace_simple_refresh(struct line_ed *ed, struct refresh_state *state) void backspace_simple_refresh(struct line_ed *ed, struct refresh_state *state)
{ {
/* get the data for the line that is being refreshed */ /* get the data for the line that is being refreshed */
const char *line_buf = line_start(ed, ed->l_cursor_y); const fx_wchar *line_buf = line_start(ed, ed->l_cursor_y);
size_t line_len = strcspn(line_buf, "\n"); size_t line_len = fx_wstrcspn(line_buf, L"\n");
/* the index of the first char in line_buf that needs to be reprinted */ /* the index of the first char in line_buf that needs to be reprinted */
size_t start_x = ed->l_cursor_x; size_t start_x = ed->l_cursor_x;
+6 -1
View File
@@ -1,6 +1,7 @@
#ifndef LINE_ED_REFRESH_H_ #ifndef LINE_ED_REFRESH_H_
#define LINE_ED_REFRESH_H_ #define LINE_ED_REFRESH_H_
#include <fx/encoding.h>
#include <stddef.h> #include <stddef.h>
struct line_ed; struct line_ed;
@@ -15,7 +16,11 @@ struct refresh_state {
size_t r_prev_line_len; size_t r_prev_line_len;
}; };
extern void print_text(struct line_ed *ed, size_t x, size_t y, const char *s); extern void print_text(
struct line_ed *ed,
size_t x,
size_t y,
const fx_wchar *s);
extern void print_buffer(struct line_ed *ed); extern void print_buffer(struct line_ed *ed);
extern void put_refresh(struct line_ed *ed, struct refresh_state *state); extern void put_refresh(struct line_ed *ed, struct refresh_state *state);
extern void backspace_nl_refresh( extern void backspace_nl_refresh(