diff options
| author | hachem <im@hachem.wtf> | 2026-10-03 21:40:00 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-03 21:40:00 +0200 |
| commit | d32aff437ca91784a10effad18bfcca541b5564b (patch) | |
| tree | cd22501287cdc6c66fcaa210f87148e06e30f246 | |
| parent | 419700c1bab1e37993260bd86ffe6fddc7d5df49 (diff) | |
fix: prose mode
| -rw-r--r-- | config.h | 21 | ||||
| -rw-r--r-- | todo.1 | 11 | ||||
| -rw-r--r-- | todo.c | 110 |
3 files changed, 119 insertions, 23 deletions
@@ -4,16 +4,16 @@ #define SUBTASK_INDENT 4 /* extra indent for subtasks */ #define WRAP_COLUMN 80 /* wrap item text past this column */ -/* colour theme (see todo.c for each): +/* + * colour theme (see `todo.c` for each): * - THEME_DEFAULT * - THEME_GRUVBOX * - THEME_NORD * - THEME_MONO - * - THEME_CUSTOM (the CUSTOM_THEME below) */ + * - THEME_CUSTOM (the CUSTOM_THEME below) + */ #define THEME THEME_DEFAULT - -/* used when THEME is THEME_CUSTOM */ -#define CUSTOM_THEME { \ +#define CUSTOM_THEME { /* used when THEME is THEME_CUSTOM */ \ .highlight = "\x1b[48;5;235m", .highlight_reset = "\x1b[49m", \ .section = "\x1b[1m", .section_reset = "\x1b[22m", \ .note = "\x1b[2;3m", .note_reset = "\x1b[22;23m", \ @@ -24,8 +24,10 @@ .important = "\x1b[1m", .important_reset = "\x1b[22m", \ } -/* checkbox glyphs (display only; the file always stores [ ] [-] [x]). - * OPT_* are for optional (*) tasks; any width is fine. */ +/* capitalize the first letter and add a trailing period */ +#define PROSE 1 + +/* checkbox glyphs (display only; the file always stores [ ] [-] [x]) */ #define MARK_TODO "[ ]" #define MARK_DOING "[-]" #define MARK_DONE "[x]" @@ -42,8 +44,3 @@ #define DATE_FORMAT "%a %d %b %Y" #define TIME_FORMAT "%H:%M" -/* limits (rarely need changing) */ -#define MAX_ITEMS 4096 /* most items in a list */ -#define MAX_TEXT 256 /* longest item text, in bytes */ -#define MAX_COMMAND 64 /* longest : command or / search */ -#define MAX_HISTORY 64 /* undo/redo depth */ @@ -21,6 +21,17 @@ The header shows the current date and time, localized through the environment. Items sort so that unfinished ones stay above finished ones within each group, and finished items are dimmed and struck through. .PP +With the prose option enabled at build time (see +.IR config.h ), +item text is rewritten so its first letter is capitalized and it ends with a +period; sections and important +.RB ( ! ) +items are capitalized but get no period. Text already capitalized or already +ending in +.BR . , " !" " or " ? +is left as is. The change is to the text itself and is written to the file when +you save. +.PP Nothing is written to disk until you save with .BR :w . .SH ITEMS @@ -18,6 +18,20 @@ #define TODO_PROGRAM "todo" #define TODO_VERSION "0.1.0" +#define MAX_ITEMS 4096 +#define MAX_TEXT 256 +#define MAX_COMMAND 64 +#define MAX_HISTORY 64 + +_Static_assert(MAX_ITEMS >= 1, "MAX_ITEMS must be positive"); +_Static_assert(MAX_TEXT >= 16, "MAX_TEXT too small"); +_Static_assert(MAX_COMMAND >= 8, "MAX_COMMAND too small"); +_Static_assert(MAX_HISTORY >= 1, "MAX_HISTORY must be positive"); +_Static_assert(LEFT_MARGIN >= 0, "LEFT_MARGIN must be non-negative"); +_Static_assert(SECTION_INDENT >= 0, "SECTION_INDENT must be non-negative"); +_Static_assert(SUBTASK_INDENT >= 0, "SUBTASK_INDENT must be non-negative"); +_Static_assert(WRAP_COLUMN > LEFT_MARGIN + SUBTASK_INDENT + 8, "WRAP_COLUMN too small for the indents"); + #define CLEAR_EOL "\x1b[K" #define CURSOR_HOME "\x1b[H" #define CURSOR_SHOW "\x1b[?25h" @@ -583,35 +597,56 @@ static int archive_done(struct List* list, const char* path) if (file == NULL) return -1; + bool ok = true; int archived = 0; - int kept = 0; - int i = 0; - while (i < list->count) + for (int i = 0; i < list->count; ) { int end = item_block_end(list, i); - bool drop = is_task(&list->items[i]) && list->items[i].state == STATE_DONE; - if (drop) + if (is_task(&list->items[i]) && list->items[i].state == STATE_DONE) for (int j = i; j < end; j++) { const struct Item* it = &list->items[j]; + int written; if (is_section(it) || is_note(it)) - (void) fprintf(file, "%s\n", it->text); + written = fprintf(file, "%s\n", it->text); else - (void) fprintf(file, "[%c] %s\n", state_char(it->state), it->text); + written = fprintf(file, "[%c] %s\n", state_char(it->state), it->text); + + if (written < 0) + ok = false; archived++; } - else + + i = end; + } + + if (ok && fflush(file) != 0) + ok = false; + + (void) fsync(fileno(file)); + + if (fclose(file) != 0) + ok = false; + + if (!ok) + return -1; + + int kept = 0; + for (int i = 0; i < list->count; ) + { + int end = item_block_end(list, i); + + if (!(is_task(&list->items[i]) && list->items[i].state == STATE_DONE)) for (int j = i; j < end; j++) list->items[kept++] = list->items[j]; i = end; } - (void) fclose(file); list->count = kept; return archived; } @@ -632,7 +667,10 @@ static int save(struct List* list, const char* path) { normalize_tasks(list); - FILE* file = fopen(path, "w"); + char tmp[4096]; + (void) snprintf(tmp, sizeof tmp, "%s.tmp", path); + + FILE* file = fopen(tmp, "w"); if (file == NULL) return -1; @@ -651,11 +689,22 @@ static int save(struct List* list, const char* path) ok = false; } + if (ok && fflush(file) != 0) + ok = false; + + (void) fsync(fileno(file)); + if (fclose(file) != 0) ok = false; + if (ok && rename(tmp, path) != 0) + ok = false; + if (!ok) + { + (void) remove(tmp); return -1; + } dirty = false; return 0; @@ -667,6 +716,42 @@ static void commit(struct List* list) sort_list(list); } +static void apply_prose(struct List* list, int skip) +{ + if (!PROSE) + return; + + for (int i = 0; i < list->count; i++) + { + if (i == skip) + continue; + + struct Item* item = &list->items[i]; + int len = (int) strlen(item->text); + + for (int c = 0; c < len; c++) + { + if (item->text[c] >= 'a' && item->text[c] <= 'z') + { + item->text[c] = (char) (item->text[c] - 32); + break; + } + if (item->text[c] >= 'A' && item->text[c] <= 'Z') + break; + } + + if (is_section(item) || is_important(item) || len == 0) + continue; + + char last = item->text[len - 1]; + if (last != '.' && last != '!' && last != '?' && len + 1 < (int) sizeof item->text) + { + item->text[len] = '.'; + item->text[len + 1] = '\0'; + } + } +} + struct Snapshot { struct Item* items; @@ -896,7 +981,8 @@ static void restore_terminal(void) static void on_signal(int signal_number) { (void) signal_number; - exit(EXIT_SUCCESS); + restore_terminal(); + _exit(EXIT_SUCCESS); } static int enter_raw_mode(void) @@ -2988,6 +3074,8 @@ static void run(struct List* list, const char* path) for (;;) { + apply_prose(list, is_editing(editor.mode) ? editor.cursor : -1); + if (editor.cursor >= list->count) editor.cursor = list->count - 1; if (editor.cursor < 0) |
