From 036ab6838fd7cc4bd95ca3253d96c721fa23fd54 Mon Sep 17 00:00:00 2001 From: hachem Date: Wed, 30 Sep 2026 07:55:30 +0200 Subject: feat: add optinal tasks + proper replace --- todo.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 109 insertions(+), 29 deletions(-) (limited to 'todo.c') diff --git a/todo.c b/todo.c index a4084a2..738d506 100644 --- a/todo.c +++ b/todo.c @@ -43,6 +43,7 @@ enum Mode { MODE_NORMAL, MODE_INSERT, + MODE_REPLACE, MODE_COMMAND, MODE_SEARCH, MODE_CONFIRM @@ -69,6 +70,7 @@ static const char* mode_name(enum Mode mode) { case MODE_NORMAL: return "NORMAL"; case MODE_INSERT: return "INSERT"; + case MODE_REPLACE: return "REPLACE"; case MODE_COMMAND: return "COMMAND"; case MODE_SEARCH: return "SEARCH"; case MODE_CONFIRM: return "CONFIRM"; @@ -77,6 +79,11 @@ static const char* mode_name(enum Mode mode) return "?"; } +static int is_editing(enum Mode mode) +{ + return mode == MODE_INSERT || mode == MODE_REPLACE; +} + static void todo_path(char* buffer, size_t size) { const char* env_path = getenv("TODO_FILE"); @@ -152,6 +159,22 @@ static int is_subtask(const struct Item* item) return item->text[0] == '-'; } +static int is_optional(const struct Item* item) +{ + return item->text[0] == '*'; +} + +static int task_group(const struct Item* item) +{ + if (item->state == STATE_DONE) + return 2; + + if (is_optional(item)) + return 1; + + return 0; +} + static int section_level(const struct Item* item) { int level = 0; @@ -219,7 +242,7 @@ static void sort_list(struct List* list) while (segment < list->count && !is_section(&list->items[segment])) segment++; - for (int pass = 0; pass < 2; pass++) + for (int group = 0; group < 3; group++) { int k = i; @@ -230,7 +253,7 @@ static void sort_list(struct List* list) while (block < segment && is_subtask(&list->items[block])) block++; - if ((list->items[k].state == STATE_DONE) == pass) + if (task_group(&list->items[k]) == group) { sorted[out++] = list->items[k]; @@ -399,6 +422,9 @@ static int yank_full; static enum Pending pending_action; +static char replace_original[MAX_TEXT]; +static int replace_start; + static void paste_item(struct List* list, int index, int* cursor) { if (list->count >= MAX_ITEMS || !yank_full) @@ -617,6 +643,8 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo int emitted = 0; int index = scroll; + int prev_group = -1; + int cursor_row = -1; while (emitted < visible && index < list->count) { @@ -636,10 +664,15 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo break; } + prev_group = -1; + + if (index == cursor) + cursor_row = 4 + emitted; + int level = section_level(item); int section_indent = LEFT_MARGIN + (level - 1) * SECTION_INDENT; - if (mode == MODE_INSERT && index == cursor) + if (is_editing(mode) && index == cursor) { length += snprintf(buffer + length, sizeof buffer - (size_t) length, "%s%*s\x1b[1m%s\x1b[22m\x1b[K%s\r\n", @@ -705,7 +738,7 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo int indent = base_indent + number_width + SUBTASK_INDENT; const char* name = item->text; - if (!(mode == MODE_INSERT && index == cursor)) + if (!(is_editing(mode) && index == cursor)) { name = item->text + 1; @@ -713,22 +746,54 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo name++; } + if (index == cursor) + cursor_row = 4 + emitted; + length += snprintf(buffer + length, sizeof buffer - (size_t) length, "%s%*s%s %s%s%s\x1b[K%s\r\n", background, indent, "", box, dim, name, dim_reset, background_reset); } else { + int group = task_group(item); + + if (prev_group >= 0 && group != prev_group) + { + length += snprintf(buffer + length, sizeof buffer - (size_t) length, "\x1b[K\r\n"); + emitted++; + + if (emitted >= visible) + break; + } + + prev_group = group; + int number = 0; for (int k = index; k >= 0 && !is_section(&list->items[k]); k--) if (!is_subtask(&list->items[k])) number++; + const char* task_box = is_optional(item) + ? (item->state == STATE_DONE ? "(x)" : item->state == STATE_DOING ? "(-)" : "( )") + : box; + const char* name = item->text; + + if (is_optional(item) && !(is_editing(mode) && index == cursor)) + { + name = item->text + 1; + + while (*name == ' ') + name++; + } + + if (index == cursor) + cursor_row = 4 + emitted; + length += snprintf(buffer + length, sizeof buffer - (size_t) length, "%s%*s\x1b[2m%*d.\x1b[22m %s %s%s%s\x1b[K%s\r\n", background, base_indent, "", number_width, number, - box, dim, item->text, dim_reset, background_reset); + task_box, dim, name, dim_reset, background_reset); } emitted++; @@ -762,24 +827,15 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo length += snprintf(buffer + length, sizeof buffer - (size_t) length, "\x1b[%d;%dH\x1b[6 q\x1b[?25h", rows, cursor_column); } - else if (mode != MODE_CONFIRM && cursor >= 0 && cursor < list->count) + else if (mode != MODE_CONFIRM && cursor >= 0 && cursor < list->count && cursor_row >= 0) { - int rows_before = 0; - - for (int i = scroll; i < cursor; i++) - rows_before += is_section(&list->items[i]) && i > scroll ? 2 : 1; - - if (is_section(&list->items[cursor]) && cursor > scroll) - rows_before++; - - int cursor_row = 4 + rows_before; int cursor_column; if (is_section(&list->items[cursor])) { int level = section_level(&list->items[cursor]); int section_indent = LEFT_MARGIN + (level - 1) * SECTION_INDENT; - cursor_column = mode == MODE_INSERT ? section_indent + 1 + edit_pos : section_indent + 1; + cursor_column = is_editing(mode) ? section_indent + 1 + edit_pos : section_indent + 1; } else { @@ -803,7 +859,9 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo cursor_column = text_start + edit_pos; } - const char* shape = mode == MODE_INSERT ? "\x1b[6 q" : "\x1b[2 q"; + const char* shape = mode == MODE_REPLACE ? "\x1b[4 q" + : mode == MODE_INSERT ? "\x1b[6 q" + : "\x1b[2 q"; length += snprintf(buffer + length, sizeof buffer - (size_t) length, "\x1b[%d;%dH%s\x1b[?25h", cursor_row, cursor_column, shape); @@ -1075,13 +1133,14 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit { insert_item(list, 0); *cursor = 0; + *edit_pos = 0; } if (*cursor >= 0 && *cursor < list->count) { - list->items[*cursor].text[0] = '\0'; - *edit_pos = 0; - *mode = MODE_INSERT; + (void) snprintf(replace_original, sizeof replace_original, "%s", list->items[*cursor].text); + replace_start = *edit_pos; + *mode = MODE_REPLACE; } break; @@ -1277,7 +1336,19 @@ static void insert_key(struct List* list, int* cursor, enum Mode* mode, int* edi if (key == 127 || key == 8) { - if (*edit_pos > 0) + if (*mode == MODE_REPLACE) + { + if (*edit_pos > replace_start) + { + (*edit_pos)--; + + if (*edit_pos < (int) strlen(replace_original)) + item->text[*edit_pos] = replace_original[*edit_pos]; + else + item->text[*edit_pos] = '\0'; + } + } + else if (*edit_pos > 0) { memmove(&item->text[*edit_pos - 1], &item->text[*edit_pos], length - (size_t) *edit_pos + 1); (*edit_pos)--; @@ -1286,10 +1357,18 @@ static void insert_key(struct List* list, int* cursor, enum Mode* mode, int* edi return; } - if (key >= 32 && key <= 126 && length + 1 < sizeof item->text) + if (key >= 32 && key != 127 && key < 256 && length + 1 < sizeof item->text) { - memmove(&item->text[*edit_pos + 1], &item->text[*edit_pos], length - (size_t) *edit_pos + 1); - item->text[*edit_pos] = (char) key; + if (*mode == MODE_REPLACE && *edit_pos < (int) length) + { + item->text[*edit_pos] = (char) key; + } + else + { + memmove(&item->text[*edit_pos + 1], &item->text[*edit_pos], length - (size_t) *edit_pos + 1); + item->text[*edit_pos] = (char) key; + } + (*edit_pos)++; } } @@ -1341,7 +1420,7 @@ static int command_key(struct List* list, const char* path, enum Mode* mode, cha return 1; } - if (key >= 32 && key <= 126 && length + 1 < MAX_COMMAND) + if (key >= 32 && key != 127 && key < 256 && length + 1 < MAX_COMMAND) { command[length] = (char) key; command[length + 1] = '\0'; @@ -1385,7 +1464,7 @@ static void search_key(const struct List* list, int* cursor, enum Mode* mode, ch return; } - if (key >= 32 && key <= 126 && length + 1 < MAX_COMMAND) + if (key >= 32 && key != 127 && key < 256 && length + 1 < MAX_COMMAND) { command[length] = (char) key; command[length + 1] = '\0'; @@ -1451,7 +1530,7 @@ static void run(struct List* list, const char* path) if (list->count > 0) { int text_length = (int) strlen(list->items[cursor].text); - int caret_max = mode == MODE_INSERT ? text_length : (text_length > 0 ? text_length - 1 : 0); + int caret_max = is_editing(mode) ? text_length : (text_length > 0 ? text_length - 1 : 0); if (edit_pos > caret_max) edit_pos = caret_max; @@ -1485,6 +1564,7 @@ static void run(struct List* list, const char* path) break; case MODE_INSERT: + case MODE_REPLACE: insert_key(list, &cursor, &mode, &edit_pos, key); break; @@ -1522,13 +1602,13 @@ static void print_help(void) "keys:\n" " h j k l / arrows move w / b words { / } sections\n" " gg / G top/bottom o new item i insert at cursor\n" - " A append text R replace text 3j 2dd counts\n" + " A append text R overtype (replace) 3j 2dd counts\n" " x toggle done space cycle state\n" " dd cut item D clear done yy p P yank/paste\n" " J / K move item or section / n N search u ctrl-r undo/redo\n" " :w :q :wq :x write / quit :reset all back to todo :clear empty\n" "\n" - "items: '= name' section, '== name' subsection, '- name' subtask, else a task.\n" + "items: '=' section, '==' subsection, '-' subtask, '*' optional, else a task.\n" "\n" "by hachem \n", TODO_PROGRAM, TODO_VERSION, TODO_PROGRAM, TODO_PROGRAM, TODO_PROGRAM); -- cgit v1.3