diff options
| author | hachem <im@hachem.wtf> | 2026-09-29 19:12:57 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-29 19:12:57 +0200 |
| commit | cdc7348d5fee6a82036ad0b1cb5b3038591cf7ee (patch) | |
| tree | 197905d5fa7c5402f683b4c636929164836008d4 | |
| parent | a172a638591614794a39c494daaba35eb78a91c3 (diff) | |
feat: more qol
| -rw-r--r-- | todo.c | 213 |
1 files changed, 192 insertions, 21 deletions
@@ -1,3 +1,4 @@ +#include <fcntl.h> #include <locale.h> #include <signal.h> #include <stdio.h> @@ -296,6 +297,90 @@ static void commit(struct List* list) sort_list(list); } +#define MAX_HISTORY 64 + +struct Snapshot +{ + struct Item* items; + int count; +}; + +static struct Snapshot undo_stack[MAX_HISTORY]; +static struct Snapshot redo_stack[MAX_HISTORY]; +static int undo_count; +static int redo_count; + +static struct Snapshot snapshot_take(const struct List* list) +{ + struct Snapshot snap; + snap.count = list->count; + snap.items = malloc((size_t) list->count * sizeof *snap.items); + + if (snap.items != NULL) + memcpy(snap.items, list->items, (size_t) list->count * sizeof *snap.items); + + return snap; +} + +static void snapshot_apply(struct List* list, const struct Snapshot* snap) +{ + list->count = snap->count; + memcpy(list->items, snap->items, (size_t) snap->count * sizeof *snap->items); +} + +static void stack_push(struct Snapshot* stack, int* count, struct Snapshot snap) +{ + if (*count == MAX_HISTORY) + { + free(stack[0].items); + memmove(&stack[0], &stack[1], (size_t) (MAX_HISTORY - 1) * sizeof *stack); + (*count)--; + } + + stack[(*count)++] = snap; +} + +static void stack_clear(struct Snapshot* stack, int* count) +{ + while (*count > 0) + free(stack[--(*count)].items); +} + +static void history_record(struct List* list) +{ + struct Snapshot snap = snapshot_take(list); + + if (snap.items == NULL) + return; + + stack_push(undo_stack, &undo_count, snap); + stack_clear(redo_stack, &redo_count); +} + +static void history_undo(struct List* list) +{ + if (undo_count == 0) + return; + + stack_push(redo_stack, &redo_count, snapshot_take(list)); + + struct Snapshot snap = undo_stack[--undo_count]; + snapshot_apply(list, &snap); + free(snap.items); +} + +static void history_redo(struct List* list) +{ + if (redo_count == 0) + return; + + stack_push(undo_stack, &undo_count, snapshot_take(list)); + + struct Snapshot snap = redo_stack[--redo_count]; + snapshot_apply(list, &snap); + free(snap.items); +} + static void insert_item(struct List* list, int index) { if (list->count >= MAX_ITEMS) @@ -383,7 +468,7 @@ static int screen_rows(void) static int content_rows(void) { - int rows = screen_rows() - 3; + int rows = screen_rows() - 4; return rows < 1 ? 1 : rows; } @@ -436,17 +521,22 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo number_width++; char date[64]; + char clock[32]; time_t now = time(NULL); struct tm* local = localtime(&now); if (local == NULL || strftime(date, sizeof date, "%a %d %b %Y", local) == 0) date[0] = '\0'; + if (local == NULL || strftime(clock, sizeof clock, "%H:%M", local) == 0) + clock[0] = '\0'; + char buffer[64 * 1024]; int length = 0; length += snprintf(buffer + length, sizeof buffer - (size_t) length, - "\x1b[H%*s\x1b[2m%s\x1b[0m\x1b[K\r\n\x1b[K\r\n", LEFT_MARGIN, "", date); + "\x1b[H%*s\x1b[2m%s\x1b[0m\x1b[K\r\n%*s\x1b[2m%s\x1b[0m\x1b[K\r\n\x1b[K\r\n", + LEFT_MARGIN, "", date, LEFT_MARGIN, "", clock); int emitted = 0; int index = scroll; @@ -599,7 +689,7 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo if (is_section(&list->items[cursor]) && cursor > scroll) rows_before++; - int cursor_row = 3 + rows_before; + int cursor_row = 4 + rows_before; int cursor_column; if (is_section(&list->items[cursor])) @@ -645,6 +735,15 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo static int read_key(void) { + static int pending = -1; + + if (pending != -1) + { + int key = pending; + pending = -1; + return key; + } + char character; if (read(STDIN_FILENO, &character, 1) != 1) @@ -653,29 +752,40 @@ static int read_key(void) if (character != 27) return (unsigned char) character; - struct termios current; - tcgetattr(STDIN_FILENO, ¤t); + /* an escape: peek for an arrow sequence without blocking. a byte that is + * not part of a sequence is pushed back so a lone escape followed by a + * command (e.g. :wq) never swallows the following keys. */ + int flags = fcntl(STDIN_FILENO, F_GETFL); + (void) fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); - struct termios timed = current; - timed.c_cc[VMIN] = 0; - timed.c_cc[VTIME] = 1; - (void) tcsetattr(STDIN_FILENO, TCSANOW, &timed); + char first; - char sequence[2]; - ssize_t first = read(STDIN_FILENO, &sequence[0], 1); - ssize_t second = first == 1 ? read(STDIN_FILENO, &sequence[1], 1) : 0; + if (read(STDIN_FILENO, &first, 1) != 1) + { + (void) fcntl(STDIN_FILENO, F_SETFL, flags); + return 27; + } - (void) tcsetattr(STDIN_FILENO, TCSANOW, ¤t); + if (first == '[') + { + char second; + ssize_t got = read(STDIN_FILENO, &second, 1); + (void) fcntl(STDIN_FILENO, F_SETFL, flags); - if (first == 1 && second == 1 && sequence[0] == '[') - switch (sequence[1]) - { - case 'A': return KEY_UP; - case 'B': return KEY_DOWN; - case 'C': return KEY_RIGHT; - case 'D': return KEY_LEFT; - } + if (got == 1) + switch (second) + { + case 'A': return KEY_UP; + case 'B': return KEY_DOWN; + case 'C': return KEY_RIGHT; + case 'D': return KEY_LEFT; + } + return 27; + } + + (void) fcntl(STDIN_FILENO, F_SETFL, flags); + pending = (unsigned char) first; return 27; } @@ -711,9 +821,56 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit (*edit_pos)++; break; + case 'w': + if (*cursor >= 0 && *cursor < list->count) + { + const char* text = list->items[*cursor].text; + int length = (int) strlen(text); + int position = *edit_pos; + + while (position < length && text[position] != ' ') + position++; + + while (position < length && text[position] == ' ') + position++; + + *edit_pos = position; + } + + break; + + case 'b': + if (*cursor >= 0 && *cursor < list->count) + { + const char* text = list->items[*cursor].text; + int position = *edit_pos; + + if (position > 0) + position--; + + while (position > 0 && text[position] == ' ') + position--; + + while (position > 0 && text[position - 1] != ' ') + position--; + + *edit_pos = position; + } + + break; + + case 'u': + history_undo(list); + break; + + case 18: + history_redo(list); + break; + case ' ': if (*cursor >= 0 && *cursor < list->count && !is_section(&list->items[*cursor])) { + history_record(list); struct Item* item = &list->items[*cursor]; item->state = (enum State) ((item->state + 1) % 3); commit(list); @@ -724,6 +881,7 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit case 'x': if (*cursor >= 0 && *cursor < list->count && !is_section(&list->items[*cursor])) { + history_record(list); struct Item* item = &list->items[*cursor]; item->state = item->state == STATE_DONE ? STATE_TODO : STATE_DONE; commit(list); @@ -733,6 +891,7 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit case 'a': case 'o': + history_record(list); insert_item(list, *cursor + 1); *cursor = *cursor + 1 < list->count ? *cursor + 1 : list->count - 1; *edit_pos = 0; @@ -740,6 +899,8 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit break; case 'A': + history_record(list); + if (list->count == 0) { insert_item(list, 0); @@ -757,6 +918,8 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit case 'i': if (*cursor >= 0 && *cursor < list->count) { + history_record(list); + if (is_section(&list->items[*cursor])) { int position = 1; @@ -773,6 +936,8 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit break; case 'R': + history_record(list); + if (list->count == 0) { insert_item(list, 0); @@ -830,6 +995,7 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit if (read(STDIN_FILENO, &next, 1) == 1 && next == 'd') { + history_record(list); delete_item(list, *cursor); commit(list); } @@ -841,6 +1007,8 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit if (*cursor < 0 || *cursor >= list->count) break; + history_record(list); + if (is_section(&list->items[*cursor])) { int start = *cursor; @@ -869,6 +1037,8 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit if (*cursor < 0 || *cursor >= list->count) break; + history_record(list); + if (is_section(&list->items[*cursor])) { int start = *cursor; @@ -900,6 +1070,7 @@ static int normal_key(struct List* list, int* cursor, enum Mode* mode, int* edit case 'D': { + history_record(list); int kept = 0; for (int i = 0; i < list->count; i++) |
