hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-01 17:24:47 +0200
committerhachem <im@hachem.wtf>2026-10-01 17:24:47 +0200
commit7fe8c5ed9d741a58118f145a5d5ed14e533b9d39 (patch)
treeacbaaddbcc8e5f1ba5b799d72dba02fbd29ae0a1
parent036ab6838fd7cc4bd95ca3253d96c721fa23fd54 (diff)
fix: double characters on international keyboards
-rw-r--r--todo.c88
1 files changed, 71 insertions, 17 deletions
diff --git a/todo.c b/todo.c
index 738d506..fd26604 100644
--- a/todo.c
+++ b/todo.c
@@ -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);