diff options
| author | hachem <im@hachem.wtf> | 2026-10-01 17:24:47 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-01 17:24:47 +0200 |
| commit | 7fe8c5ed9d741a58118f145a5d5ed14e533b9d39 (patch) | |
| tree | acbaaddbcc8e5f1ba5b799d72dba02fbd29ae0a1 | |
| parent | 036ab6838fd7cc4bd95ca3253d96c721fa23fd54 (diff) | |
fix: double characters on international keyboards
| -rw-r--r-- | todo.c | 88 |
1 files changed, 71 insertions, 17 deletions
@@ -84,6 +84,47 @@ static int is_editing(enum Mode mode) return mode == MODE_INSERT || mode == MODE_REPLACE; } +/* utf-8 helpers: edit_pos is a byte offset, but the cursor and h/l/backspace + * work in whole characters. a continuation byte has its top two bits as 10. */ +static int utf8_width(const char* text, int bytes) +{ + int width = 0; + + for (int i = 0; i < bytes && text[i] != '\0'; i++) + if (((unsigned char) text[i] & 0xC0) != 0x80) + width++; + + return width; +} + +static int utf8_left(const char* text, int pos) +{ + if (pos <= 0) + return 0; + + pos--; + + while (pos > 0 && ((unsigned char) text[pos] & 0xC0) == 0x80) + pos--; + + return pos; +} + +static int utf8_right(const char* text, int pos) +{ + int length = (int) strlen(text); + + if (pos >= length) + return length; + + pos++; + + while (pos < length && ((unsigned char) text[pos] & 0xC0) == 0x80) + pos++; + + return pos; +} + static void todo_path(char* buffer, size_t size) { const char* env_path = getenv("TODO_FILE"); @@ -830,12 +871,13 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo else if (mode != MODE_CONFIRM && cursor >= 0 && cursor < list->count && cursor_row >= 0) { int cursor_column; + int caret = utf8_width(list->items[cursor].text, edit_pos); if (is_section(&list->items[cursor])) { int level = section_level(&list->items[cursor]); int section_indent = LEFT_MARGIN + (level - 1) * SECTION_INDENT; - cursor_column = is_editing(mode) ? section_indent + 1 + edit_pos : section_indent + 1; + cursor_column = is_editing(mode) ? section_indent + 1 + caret : section_indent + 1; } else { @@ -856,7 +898,7 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo else text_start = base_indent + number_width + 7; - cursor_column = text_start + edit_pos; + cursor_column = text_start + caret; } const char* shape = mode == MODE_REPLACE ? "\x1b[4 q" @@ -999,12 +1041,18 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit case 'h': case KEY_LEFT: - *edit_pos -= count; + if (*cursor >= 0 && *cursor < list->count) + for (int repeat = 0; repeat < count; repeat++) + *edit_pos = utf8_left(list->items[*cursor].text, *edit_pos); + break; case 'l': case KEY_RIGHT: - *edit_pos += count; + if (*cursor >= 0 && *cursor < list->count) + for (int repeat = 0; repeat < count; repeat++) + *edit_pos = utf8_right(list->items[*cursor].text, *edit_pos); + break; case 'w': @@ -1320,17 +1368,13 @@ static void insert_key(struct List* list, int* cursor, enum Mode* mode, int* edi if (key == KEY_LEFT) { - if (*edit_pos > 0) - (*edit_pos)--; - + *edit_pos = utf8_left(item->text, *edit_pos); return; } if (key == KEY_RIGHT) { - if (*edit_pos < (int) length) - (*edit_pos)++; - + *edit_pos = utf8_right(item->text, *edit_pos); return; } @@ -1340,18 +1384,24 @@ static void insert_key(struct List* list, int* cursor, enum Mode* mode, int* edi { if (*edit_pos > replace_start) { - (*edit_pos)--; + int start = utf8_left(item->text, *edit_pos); + int original_length = (int) strlen(replace_original); - if (*edit_pos < (int) strlen(replace_original)) - item->text[*edit_pos] = replace_original[*edit_pos]; + if (*edit_pos <= original_length) + for (int i = start; i < *edit_pos; i++) + item->text[i] = replace_original[i]; else - item->text[*edit_pos] = '\0'; + memmove(&item->text[start], &item->text[*edit_pos], + length - (size_t) *edit_pos + 1); + + *edit_pos = start; } } else if (*edit_pos > 0) { - memmove(&item->text[*edit_pos - 1], &item->text[*edit_pos], length - (size_t) *edit_pos + 1); - (*edit_pos)--; + int start = utf8_left(item->text, *edit_pos); + memmove(&item->text[start], &item->text[*edit_pos], length - (size_t) *edit_pos + 1); + *edit_pos = start; } return; @@ -1529,7 +1579,8 @@ static void run(struct List* list, const char* path) if (list->count > 0) { - int text_length = (int) strlen(list->items[cursor].text); + const char* text = list->items[cursor].text; + int text_length = (int) strlen(text); int caret_max = is_editing(mode) ? text_length : (text_length > 0 ? text_length - 1 : 0); if (edit_pos > caret_max) @@ -1537,6 +1588,9 @@ static void run(struct List* list, const char* path) if (edit_pos < 0) edit_pos = 0; + + while (edit_pos > 0 && ((unsigned char) text[edit_pos] & 0xC0) == 0x80) + edit_pos--; } render(list, cursor, scroll, mode, edit_pos, command); |
