diff options
| author | hachem <im@hachem.wtf> | 2026-09-29 18:49:30 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-29 18:49:30 +0200 |
| commit | a172a638591614794a39c494daaba35eb78a91c3 (patch) | |
| tree | a9ba704093984d98d5298e4654d411840cbf5fa5 | |
| parent | 7a61791c5e87796b1984d13d70d5bafc1953bbb3 (diff) | |
feat: add command mode
| -rw-r--r-- | todo.c | 252 |
1 files changed, 211 insertions, 41 deletions
@@ -1,3 +1,4 @@ +#include <locale.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -12,6 +13,7 @@ #define MAX_ITEMS 4096 #define MAX_TEXT 256 +#define MAX_COMMAND 64 #define LEFT_MARGIN 2 #define SECTION_INDENT 2 @@ -39,7 +41,8 @@ struct List enum Mode { MODE_NORMAL, - MODE_INSERT + MODE_INSERT, + MODE_COMMAND }; enum Key @@ -56,6 +59,7 @@ static const char* mode_name(enum Mode mode) { case MODE_NORMAL: return "NORMAL"; case MODE_INSERT: return "INSERT"; + case MODE_COMMAND: return "COMMAND"; } return "?"; @@ -136,6 +140,16 @@ static int is_subtask(const struct Item* item) return item->text[0] == '-'; } +static int section_level(const struct Item* item) +{ + int level = 0; + + while (item->text[level] == '=') + level++; + + return level; +} + static void normalize_tasks(struct List* list) { for (int i = 0; i < list->count; i++) @@ -169,6 +183,64 @@ static void normalize_tasks(struct List* list) } } +static void sort_list(struct List* list) +{ + struct Item* sorted = malloc((size_t) list->count * sizeof *sorted); + + if (sorted == NULL) + return; + + int out = 0; + int i = 0; + + while (i < list->count) + { + if (is_section(&list->items[i])) + { + sorted[out++] = list->items[i]; + i++; + continue; + } + + int segment = i; + + while (segment < list->count && !is_section(&list->items[segment])) + segment++; + + for (int pass = 0; pass < 2; pass++) + { + int k = i; + + while (k < segment) + { + int block = k + 1; + + while (block < segment && is_subtask(&list->items[block])) + block++; + + if ((list->items[k].state == STATE_DONE) == pass) + { + sorted[out++] = list->items[k]; + + for (int sub_pass = 0; sub_pass < 2; sub_pass++) + for (int s = k + 1; s < block; s++) + if ((list->items[s].state == STATE_DONE) == sub_pass) + sorted[out++] = list->items[s]; + } + + k = block; + } + } + + i = segment; + } + + for (int x = 0; x < list->count; x++) + list->items[x] = sorted[x]; + + free(sorted); +} + static int block_end(const struct List* list, int start) { int i = start + 1; @@ -218,6 +290,12 @@ static void save(struct List* list, const char* path) (void) fclose(file); } +static void commit(struct List* list) +{ + normalize_tasks(list); + sort_list(list); +} + static void insert_item(struct List* list, int index) { if (list->count >= MAX_ITEMS) @@ -326,7 +404,7 @@ static void to_roman(int number, char* buffer, size_t size) } } -static void render(const struct List* list, int cursor, int scroll, enum Mode mode, int edit_pos) +static void render(const struct List* list, int cursor, int scroll, enum Mode mode, int edit_pos, const char* command) { int rows = screen_rows(); int visible = content_rows(); @@ -391,31 +469,46 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo break; } + int level = section_level(item); + int section_indent = LEFT_MARGIN + (level - 1) * SECTION_INDENT; + if (mode == MODE_INSERT && index == cursor) { length += snprintf(buffer + length, sizeof buffer - (size_t) length, "%s%*s\x1b[1m%s\x1b[22m\x1b[K%s\r\n", - background, LEFT_MARGIN, "", item->text, background_reset); + background, section_indent, "", item->text, background_reset); } else { int section_number = 0; - for (int k = 0; k <= index; k++) + for (int k = index; k >= 0; k--) if (is_section(&list->items[k])) - section_number++; + { + int other = section_level(&list->items[k]); + + if (other == level) + section_number++; + else if (other < level) + break; + } char roman[16]; to_roman(section_number, roman, sizeof roman); - const char* name = item->text + 1; + if (level >= 2) + for (char* letter = roman; *letter != '\0'; letter++) + if (*letter >= 'A' && *letter <= 'Z') + *letter = (char) (*letter + 32); + + const char* name = item->text + level; while (*name == ' ') name++; length += snprintf(buffer + length, sizeof buffer - (size_t) length, "%s%*s\x1b[1m%s. %s\x1b[22m\x1b[K%s\r\n", - background, LEFT_MARGIN, "", roman, name, background_reset); + background, section_indent, "", roman, name, background_reset); } emitted++; @@ -423,12 +516,16 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo continue; } - int scan = index - 1; + int heading_level = 0; - while (scan >= 0 && !is_section(&list->items[scan])) - scan--; + for (int k = index - 1; k >= 0; k--) + if (is_section(&list->items[k])) + { + heading_level = section_level(&list->items[k]); + break; + } - int in_section = scan >= 0; + int base_indent = LEFT_MARGIN + heading_level * SECTION_INDENT; const char* box = item->state == STATE_DONE ? "[x]" : item->state == STATE_DOING ? "[-]" @@ -438,7 +535,7 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo if (is_subtask(item)) { - int indent = LEFT_MARGIN + (in_section ? SECTION_INDENT : 0) + SUBTASK_INDENT; + int indent = base_indent + SUBTASK_INDENT; const char* name = item->text; if (!(mode == MODE_INSERT && index == cursor)) @@ -461,11 +558,9 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo if (!is_subtask(&list->items[k])) number++; - int item_indent = LEFT_MARGIN + (in_section ? SECTION_INDENT : 0); - 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, item_indent, "", number_width, number, + background, base_indent, "", number_width, number, box, dim, item->text, dim_reset, background_reset); } @@ -479,11 +574,22 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo emitted++; } - length += snprintf(buffer + length, sizeof buffer - (size_t) length, - "\x1b[%d;1H%*s\x1b[2m-- %s -- %d/%d done\x1b[0m\x1b[K", - rows, LEFT_MARGIN, "", mode_name(mode), done, total_items); + if (mode == MODE_COMMAND) + length += snprintf(buffer + length, sizeof buffer - (size_t) length, + "\x1b[%d;1H%*s:%s\x1b[K", rows, LEFT_MARGIN, "", command); + else + length += snprintf(buffer + length, sizeof buffer - (size_t) length, + "\x1b[%d;1H%*s\x1b[2m[%s] %d/%d done\x1b[0m\x1b[K", + rows, LEFT_MARGIN, "", mode_name(mode), done, total_items); + + if (mode == MODE_COMMAND) + { + int cursor_column = LEFT_MARGIN + 2 + (int) strlen(command); - if (cursor >= 0 && cursor < list->count) + length += snprintf(buffer + length, sizeof buffer - (size_t) length, + "\x1b[%d;%dH\x1b[6 q\x1b[?25h", rows, cursor_column); + } + else if (cursor >= 0 && cursor < list->count) { int rows_before = 0; @@ -498,22 +604,28 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo if (is_section(&list->items[cursor])) { - cursor_column = mode == MODE_INSERT ? LEFT_MARGIN + 1 + edit_pos : LEFT_MARGIN + 1; + 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; } else { - int scan = cursor - 1; + int heading_level = 0; - while (scan >= 0 && !is_section(&list->items[scan])) - scan--; + for (int k = cursor - 1; k >= 0; k--) + if (is_section(&list->items[k])) + { + heading_level = section_level(&list->items[k]); + break; + } - int item_indent = LEFT_MARGIN + (scan >= 0 ? SECTION_INDENT : 0); + int base_indent = LEFT_MARGIN + heading_level * SECTION_INDENT; int text_start; if (is_subtask(&list->items[cursor])) - text_start = item_indent + SUBTASK_INDENT + 5; + text_start = base_indent + SUBTASK_INDENT + 5; else - text_start = item_indent + number_width + 7; + text_start = base_indent + number_width + 7; cursor_column = text_start + edit_pos; } @@ -567,13 +679,18 @@ static int read_key(void) return 27; } -static int normal_key(struct List* list, const char* path, int* cursor, enum Mode* mode, int* edit_pos, int key) +static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit_pos, char* command, int key) { switch (key) { case 'q': return 0; + case ':': + command[0] = '\0'; + *mode = MODE_COMMAND; + break; + case 'j': case KEY_DOWN: (*cursor)++; @@ -599,7 +716,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod { struct Item* item = &list->items[*cursor]; item->state = (enum State) ((item->state + 1) % 3); - save(list, path); + commit(list); } break; @@ -609,7 +726,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod { struct Item* item = &list->items[*cursor]; item->state = item->state == STATE_DONE ? STATE_TODO : STATE_DONE; - save(list, path); + commit(list); } break; @@ -714,7 +831,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod if (read(STDIN_FILENO, &next, 1) == 1 && next == 'd') { delete_item(list, *cursor); - save(list, path); + commit(list); } break; @@ -736,7 +853,6 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod reverse_items(list, end, next_end); reverse_items(list, start, next_end); *cursor = start + (next_end - end); - save(list, path); } } else if (*cursor + 1 < list->count && !is_section(&list->items[*cursor + 1])) @@ -745,7 +861,6 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod list->items[*cursor] = list->items[*cursor + 1]; list->items[*cursor + 1] = moved; (*cursor)++; - save(list, path); } break; @@ -771,7 +886,6 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod reverse_items(list, start, end); reverse_items(list, previous_start, end); *cursor = previous_start; - save(list, path); } } else if (*cursor > 0 && !is_section(&list->items[*cursor - 1])) @@ -780,7 +894,6 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod list->items[*cursor] = list->items[*cursor - 1]; list->items[*cursor - 1] = moved; (*cursor)--; - save(list, path); } break; @@ -794,7 +907,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod list->items[kept++] = list->items[i]; list->count = kept; - save(list, path); + commit(list); break; } @@ -805,7 +918,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod return 1; } -static void insert_key(struct List* list, const char* path, int* cursor, enum Mode* mode, int* edit_pos, int key) +static void insert_key(struct List* list, int* cursor, enum Mode* mode, int* edit_pos, int key) { struct Item* item = &list->items[*cursor]; size_t length = strlen(item->text); @@ -823,14 +936,13 @@ static void insert_key(struct List* list, const char* path, int* cursor, enum Mo *cursor = 0; } - save(list, path); + normalize_tasks(list); *mode = MODE_NORMAL; return; } if (key == '\r' || key == '\n') { - save(list, path); insert_item(list, *cursor + 1); (*cursor)++; *edit_pos = 0; @@ -872,17 +984,67 @@ static void insert_key(struct List* list, const char* path, int* cursor, enum Mo } } +static int command_key(struct List* list, const char* path, enum Mode* mode, char* command, int key) +{ + size_t length = strlen(command); + + if (key == 27) + { + command[0] = '\0'; + *mode = MODE_NORMAL; + return 1; + } + + if (key == '\r' || key == '\n') + { + int write = strchr(command, 'w') != NULL || strcmp(command, "x") == 0; + int quit = strchr(command, 'q') != NULL || strcmp(command, "x") == 0; + + *mode = MODE_NORMAL; + + if (write) + save(list, path); + + command[0] = '\0'; + + if (quit) + return 0; + + return 1; + } + + if (key == 127 || key == 8) + { + if (length > 0) + command[length - 1] = '\0'; + else + *mode = MODE_NORMAL; + + return 1; + } + + if (key >= 32 && key <= 126 && length + 1 < MAX_COMMAND) + { + command[length] = (char) key; + command[length + 1] = '\0'; + } + + return 1; +} + static void run(struct List* list, const char* path) { if (enter_raw_mode() != 0) exit(EXIT_FAILURE); normalize_tasks(list); + sort_list(list); enum Mode mode = MODE_NORMAL; int cursor = 0; int scroll = 0; int edit_pos = 0; + char command[MAX_COMMAND] = ""; for (;;) { @@ -912,7 +1074,7 @@ static void run(struct List* list, const char* path) edit_pos = 0; } - render(list, cursor, scroll, mode, edit_pos); + render(list, cursor, scroll, mode, edit_pos, command); int key = read_key(); @@ -922,13 +1084,19 @@ static void run(struct List* list, const char* path) switch (mode) { case MODE_NORMAL: - if (normal_key(list, path, &cursor, &mode, &edit_pos, key) == 0) + if (normal_key(list, &cursor, &mode, &edit_pos, command, key) == 0) return; break; case MODE_INSERT: - insert_key(list, path, &cursor, &mode, &edit_pos, key); + insert_key(list, &cursor, &mode, &edit_pos, key); + break; + + case MODE_COMMAND: + if (command_key(list, path, &mode, command, key) == 0) + return; + break; } } @@ -936,6 +1104,8 @@ static void run(struct List* list, const char* path) int main(void) { + (void) setlocale(LC_ALL, ""); + char path[4096]; todo_path(path, sizeof path); |
