diff options
| author | hachem <im@hachem.wtf> | 2026-09-29 17:43:37 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-29 17:43:37 +0200 |
| commit | 571d9a642b10c77c4ec5b91c0ccc7597ea7f4490 (patch) | |
| tree | 79f1d03acf9353d7a6909309b077bf856a63fe38 | |
| parent | a90c210784ec9e3b4115b9f46c1fc3664340f0ae (diff) | |
feat: add sections
| -rw-r--r-- | todo.c | 210 |
1 files changed, 170 insertions, 40 deletions
@@ -8,11 +8,14 @@ #include <time.h> #include <unistd.h> -#define todo_program "todo" -#define todo_version "0.1.0" +#define TODO_PROGRAM "todo" +#define TODO_VERSION "0.1.0" -#define max_items 4096 -#define max_text 256 +#define MAX_ITEMS 4096 +#define MAX_TEXT 256 + +#define LEFT_MARGIN 2 +#define SECTION_INDENT 2 enum State { @@ -24,12 +27,12 @@ enum State struct Item { enum State state; - char text[max_text]; + char text[MAX_TEXT]; }; struct List { - struct Item items[max_items]; + struct Item items[MAX_ITEMS]; int count; }; @@ -82,9 +85,9 @@ static void load(struct List* list, const char* path) if (file == NULL) return; - char line[max_text + 8]; + char line[MAX_TEXT + 8]; - while (list->count < max_items && fgets(line, sizeof line, file) != NULL) + while (list->count < MAX_ITEMS && fgets(line, sizeof line, file) != NULL) { strip_newline(line); @@ -115,6 +118,11 @@ static void load(struct List* list, const char* path) (void) fclose(file); } +static int is_section(const struct Item* item) +{ + return item->text[0] == '='; +} + static void save(const struct List* list, const char* path) { FILE* file = fopen(path, "w"); @@ -124,6 +132,12 @@ static void save(const struct List* list, const char* path) for (int i = 0; i < list->count; i++) { + if (is_section(&list->items[i])) + { + (void) fprintf(file, "%s\n", list->items[i].text); + continue; + } + char marker = list->items[i].state == STATE_DONE ? 'x' : list->items[i].state == STATE_DOING ? '-' : ' '; @@ -136,7 +150,7 @@ static void save(const struct List* list, const char* path) static void insert_item(struct List* list, int index) { - if (list->count >= max_items) + if (list->count >= MAX_ITEMS) return; if (index < 0) @@ -164,17 +178,15 @@ static void delete_item(struct List* list, int index) list->count--; } -static int is_separator(const struct Item* item) -{ - return item->text[0] == '\0'; -} static struct termios saved_termios; static void restore_terminal(void) { + static const char reset[] = "\x1b[?25h\x1b[0 q\x1b[?1049l"; + (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_termios); - (void) write(STDOUT_FILENO, "\x1b[?25h\x1b[?1049l", 14); + (void) write(STDOUT_FILENO, reset, sizeof reset - 1); } static void on_signal(int signal_number) @@ -187,7 +199,7 @@ static int enter_raw_mode(void) { if (!isatty(STDIN_FILENO)) { - (void) fprintf(stderr, "%s: not a terminal\n", todo_program); + (void) fprintf(stderr, "%s: not a terminal\n", TODO_PROGRAM); return -1; } @@ -228,20 +240,52 @@ static int content_rows(void) return rows < 1 ? 1 : rows; } +static void to_roman(int number, char* buffer, size_t size) +{ + static const int values[] = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; + static const char* const symbols[] = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" }; + + size_t length = 0; + buffer[0] = '\0'; + + for (int i = 0; i < 13; i++) + while (number >= values[i] && length + 2 < size) + { + length += (size_t) snprintf(buffer + length, size - length, "%s", symbols[i]); + number -= values[i]; + } +} + static void render(const struct List* list, int cursor, int scroll, enum Mode mode) { int rows = screen_rows(); int visible = content_rows(); - int number_width = 1; + int total_items = 0; + int done = 0; - for (int total = list->count; total >= 10; total /= 10) - number_width++; + for (int i = 0; i < list->count; i++) + if (!is_section(&list->items[i])) + { + total_items++; + done += list->items[i].state == STATE_DONE; + } - int done = 0; + int max_run = 0; + int run = 0; for (int i = 0; i < list->count; i++) - done += list->items[i].state == STATE_DONE; + { + if (is_section(&list->items[i])) + run = 0; + else if (++run > max_run) + max_run = run; + } + + int number_width = 1; + + for (int count = max_run; count >= 10; count /= 10) + number_width++; char date[64]; time_t now = time(NULL); @@ -254,22 +298,72 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo int length = 0; length += snprintf(buffer + length, sizeof buffer - (size_t) length, - "\x1b[H\x1b[2m%s\x1b[0m\x1b[K\r\n\x1b[K\r\n", date); + "\x1b[H%*s\x1b[2m%s\x1b[0m\x1b[K\r\n\x1b[K\r\n", LEFT_MARGIN, "", date); - for (int row = 0; row < visible; row++) + int emitted = 0; + int index = scroll; + + while (emitted < visible && index < list->count) { - int index = scroll + row; + const struct Item* item = &list->items[index]; - if (index >= list->count) + const char* background = index == cursor ? "\x1b[48;5;235m" : ""; + const char* background_reset = index == cursor ? "\x1b[49m" : ""; + + if (is_section(item)) { - length += snprintf(buffer + length, sizeof buffer - (size_t) length, "\x1b[K\r\n"); + if (emitted > 0) + { + length += snprintf(buffer + length, sizeof buffer - (size_t) length, "\x1b[K\r\n"); + emitted++; + + if (emitted >= visible) + break; + } + + 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); + } + else + { + int section_number = 0; + + for (int k = 0; k <= index; k++) + if (is_section(&list->items[k])) + section_number++; + + char roman[16]; + to_roman(section_number, roman, sizeof roman); + + const char* name = item->text + 1; + + 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); + } + + emitted++; + index++; continue; } - const struct Item* item = &list->items[index]; + int number = 0; + int scan = index; + + while (scan >= 0 && !is_section(&list->items[scan])) + { + number++; + scan--; + } + + int item_indent = LEFT_MARGIN + (scan >= 0 ? SECTION_INDENT : 0); - const char* background = index == cursor ? "\x1b[48;5;235m" : ""; - const char* background_reset = index == cursor ? "\x1b[49m" : ""; const char* box = item->state == STATE_DONE ? "[x]" : item->state == STATE_DOING ? "[-]" : "[ ]"; @@ -277,21 +371,57 @@ static void render(const struct List* list, int cursor, int scroll, enum Mode mo const char* dim_reset = item->state == STATE_DONE ? "\x1b[22m" : ""; length += snprintf(buffer + length, sizeof buffer - (size_t) length, - "%s\x1b[2m%*d.\x1b[22m %s %s%s%s\x1b[K%s\r\n", - background, number_width, index + 1, + "%s%*s\x1b[2m%*d.\x1b[22m %s %s%s%s\x1b[K%s\r\n", + background, item_indent, "", number_width, number, box, dim, item->text, dim_reset, background_reset); + emitted++; + index++; + } + + while (emitted < visible) + { + length += snprintf(buffer + length, sizeof buffer - (size_t) length, "\x1b[K\r\n"); + emitted++; } length += snprintf(buffer + length, sizeof buffer - (size_t) length, - "\x1b[%d;1H\x1b[2m-- %s -- %d/%d done\x1b[0m\x1b[K", rows, mode_name(mode), done, list->count); + "\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_INSERT && cursor >= 0 && cursor < list->count) + if (cursor >= 0 && cursor < list->count) { - int cursor_row = cursor - scroll + 3; - int cursor_column = number_width + 7 + (int) strlen(list->items[cursor].text); + 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 = 3 + rows_before; + int text_length = (int) strlen(list->items[cursor].text); + int cursor_column; + + if (is_section(&list->items[cursor])) + { + cursor_column = mode == MODE_INSERT ? LEFT_MARGIN + text_length + 1 : LEFT_MARGIN + 1; + } + else + { + int scan = cursor - 1; + + while (scan >= 0 && !is_section(&list->items[scan])) + scan--; + + int item_indent = LEFT_MARGIN + (scan >= 0 ? SECTION_INDENT : 0); + int text_start = item_indent + number_width + 7; + cursor_column = mode == MODE_INSERT ? text_start + text_length : text_start; + } + + const char* shape = mode == MODE_INSERT ? "\x1b[6 q" : "\x1b[2 q"; length += snprintf(buffer + length, sizeof buffer - (size_t) length, - "\x1b[%d;%dH\x1b[?25h", cursor_row, cursor_column); + "\x1b[%d;%dH%s\x1b[?25h", cursor_row, cursor_column, shape); } else { @@ -317,7 +447,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod break; case ' ': - if (*cursor >= 0 && *cursor < list->count) + if (*cursor >= 0 && *cursor < list->count && !is_section(&list->items[*cursor])) { struct Item* item = &list->items[*cursor]; item->state = (enum State) ((item->state + 1) % 3); @@ -327,7 +457,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod break; case 'x': - if (*cursor >= 0 && *cursor < list->count) + if (*cursor >= 0 && *cursor < list->count && !is_section(&list->items[*cursor])) { struct Item* item = &list->items[*cursor]; item->state = item->state == STATE_DONE ? STATE_TODO : STATE_DONE; @@ -389,7 +519,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod { int next = *cursor + 1; - while (next < list->count && !is_separator(&list->items[next])) + while (next < list->count && !is_section(&list->items[next])) next++; *cursor = next < list->count ? next : list->count - 1; @@ -400,7 +530,7 @@ static int normal_key(struct List* list, const char* path, int* cursor, enum Mod { int previous = *cursor - 1; - while (previous >= 0 && !is_separator(&list->items[previous])) + while (previous >= 0 && !is_section(&list->items[previous])) previous--; *cursor = previous >= 0 ? previous : 0; @@ -566,7 +696,7 @@ int main(void) if (list == NULL) { - (void) fprintf(stderr, "%s: out of memory\n", todo_program); + (void) fprintf(stderr, "%s: out of memory\n", TODO_PROGRAM); return EXIT_FAILURE; } |
