From bbc11330984382e926e2a8e936e494193a0e2e23 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 13 Nov 2022 02:30:31 +0000 Subject: Fixed truncate_string_utf8. Implemented read_input_to_string_buffer to simplify getting input. --- main.c | 146 ++++++++++++++++++++++++++------------------------------------ readme.md | 3 +- 2 files changed, 63 insertions(+), 86 deletions(-) diff --git a/main.c b/main.c index 8e5bd9c..4d27c38 100644 --- a/main.c +++ b/main.c @@ -36,7 +36,8 @@ #include #define VERSION "1.0" // Use only 3 chars (to fit layouts). -#define MAX_TASK_NAME 58 // Maximum task name length (includes NUL). +#define TASK_NAME_LENGTH 57 // Task name length. +#define TASK_NAME_BYTES (TASK_NAME_LENGTH+1) #define FIRST_DAY_OF_WEEK 1 // (0-6, Sunday = 0). #define NUM_WEEK_DAYS 7 // Just to avoid magic numbers. @@ -47,7 +48,7 @@ typedef struct { int64_t times[NUM_WEEK_DAYS]; - char name[MAX_TASK_NAME]; + char name[TASK_NAME_BYTES]; } task_st; typedef struct { @@ -113,24 +114,33 @@ bool inline static is_equal_to_any(const char *to_compare, const char *test_a, c || strncmp(to_compare, test_b, strlen(test_b)+1) == 0; } -// Given an UTF8 encoded string, truncate it to length without breaking any UTF8 character. +// Given an UTF8 encoded string, truncate it to length bytes without breaking any UTF8 character. // The string should have capacity for at least length + 1. // The terminating null byte ('\0') is not included in length. -// Returns the amount of items discarded. -size_t truncate_string_utf8(char *string, size_t length) { +// Returns the truncated string length. +size_t truncate_string_utf8(char *string, size_t length) { // TODO Rename to truncate_utf_string + assert(string != NULL); - // Check for special cases where no truncation is required. - if (length == 0 || string[length] == '\0') { - return 0; - } - - // Search for a non-UTF8-sequence-item so we can truncate the string. + // Count continuation bytes. size_t idx = length; - while(idx > 0 && ((string[idx] & 0xC0) == 0x80)) { + while (idx > 0 && ((string[idx - 1] & 0xC0) == 0x80)) { idx--; } - string[idx] = '\0'; - return length - idx; + int continuation_bytes = length - idx; + + // Adjust length if missing continuation bytes. + if (idx > 0 // Not empty. + // Continuation bytes are not complete. + && (!(continuation_bytes == 0 && (string[idx - 1] & 0x80) == 0x00) + && !(continuation_bytes == 1 && (string[idx - 1] & 0xE0) == 0xC0) + && !(continuation_bytes == 2 && (string[idx - 1] & 0xF0) == 0xE0) + && !(continuation_bytes == 3 && (string[idx - 1] & 0xF8) == 0xF0)) + ) { + length -= (continuation_bytes + 1); + } + + string[length] = '\0'; + return length; } // Returns true when the string is empty or consists of white space characters. @@ -600,10 +610,10 @@ bool export_to_csv(const database_st *db, const char *path) { "task", "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ); - char name[MAX_TASK_NAME]; + char name[TASK_NAME_BYTES]; task_st *limit = db->tasks + db->count; - for (task_st *task = db->tasks; task < limit; task++) { - memcpy(name, task->name, MAX_TASK_NAME); + for (task_st *task = db->tasks; task < limit; task++) { // TODO Simplify for loop. + memcpy(name, task->name, TASK_NAME_BYTES); replace_char(name, ',', ' '); fprintf(file, "%s,%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 "\n", name, task->times[0], task->times[1], task->times[2], task->times[3], task->times[4], task->times[5], task->times[6] @@ -640,8 +650,8 @@ bool import_from_csv(database_st *db, const char *path) { continue; } size_t name_length = (name_delimiter - csv_buffer) + 1; - if (name_length >= MAX_TASK_NAME) { - name_length = MAX_TASK_NAME - 1; + if (name_length > TASK_NAME_LENGTH) { + name_length = TASK_NAME_LENGTH; } // Prepare new task. @@ -695,8 +705,8 @@ bool append_to_csv(task_st *task, const char *path) { fprintf(file, "\n"); } - char name[MAX_TASK_NAME]; - memcpy(name, task->name, MAX_TASK_NAME); + char name[TASK_NAME_BYTES]; + memcpy(name, task->name, TASK_NAME_BYTES); replace_char(name, ',', ' '); fprintf(file, "%s,%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 ",%" PRId64 "\n", name, task->times[0], task->times[1], task->times[2], task->times[3], task->times[4], task->times[5], task->times[6] @@ -1082,6 +1092,22 @@ void exit_gracefully(int signal) { ungetch('q'); } +void static read_input_to_string_buffer_with_space(int row, int column, int length, int space) { + snprintf(string_buffer, string_buffer_size, "%*s", space, ""); + attron(A_UNDERLINE); + mvaddstr(row, column, string_buffer); + echo(); + curs_set(1); + mvgetnstr(row, column, string_buffer, length); + truncate_string_utf8(string_buffer, length); + noecho(); + curs_set(0); + attroff(A_UNDERLINE); +} + +void static inline read_input_to_string_buffer(int row, int column, int length) { + read_input_to_string_buffer_with_space(row, column, length, length); +} int main(int argc, char *argv[]) { @@ -1240,7 +1266,7 @@ int main(int argc, char *argv[]) { clear(); getmaxyx(stdscr, size_y, size_x); is_terminal_too_small = size_x < 60 || size_y < 3; - size_t new_size = 2047 | MAX_TASK_NAME | (size_x + 1); + size_t new_size = 2047 | TASK_NAME_BYTES | (size_x + 1); if (string_buffer_size < new_size) { string_buffer_size = new_size; string_buffer = realloc(string_buffer, string_buffer_size); @@ -1268,12 +1294,11 @@ int main(int argc, char *argv[]) { // Set new task name. time_t now_utc = time(NULL); struct tm *now_local = localtime(&now_utc); - strftime(new_task->name, MAX_TASK_NAME, "%Y-%m-%d %H:%M:%S", now_local); + strftime(new_task->name, TASK_NAME_BYTES, "%Y-%m-%d %H:%M:%S", now_local); // Select new task. TODO Maybe do this on the database? selected_task = new_task; db->selected_task = selected_task - db->tasks; - trigger_autosave(); // Force rename action. @@ -1287,32 +1312,20 @@ int main(int argc, char *argv[]) { break; } - // Prepare row to input new task name. - attron(COLOR_PAIR(selected_task_theme) | A_BOLD | A_UNDERLINE); - snprintf(string_buffer, string_buffer_size, "%*s", size_x - 2, ""); - mvaddstr(selected_task_row, 1, string_buffer); - - // Get new task name. - echo(); - curs_set(1); - memset(string_buffer, 0, string_buffer_size); - mvgetnstr(selected_task_row, 1, string_buffer, MAX_TASK_NAME-1); - noecho(); - curs_set(0); + attron(COLOR_PAIR(selected_task_theme) | A_BOLD); + read_input_to_string_buffer_with_space(selected_task_row, 1, TASK_NAME_LENGTH, size_x - 2); attrset(A_NORMAL); - // Apply new task name. if (is_empty_string(string_buffer) == false) { replace_char(string_buffer, '\t', ' '); replace_char(string_buffer, '\v', ' '); replace_char(string_buffer, '\f', ' '); replace_char(string_buffer, '\r', ' '); - memcpy(selected_task->name, string_buffer, MAX_TASK_NAME); + memcpy(selected_task->name, string_buffer, TASK_NAME_BYTES); trigger_autosave(); } - break; } @@ -1351,11 +1364,8 @@ int main(int argc, char *argv[]) { break; } - int selected_day = key - '1'; - - attron(COLOR_PAIR(selected_task_theme) | A_BOLD); - // Prepare row to input new task name. + int selected_day = key - '1'; int input_width = layout->columns[L_DAYS_IDX + selected_day].width; int input_pos_x = 1 + layout->columns[L_TITLE_IDX].width; for (int col = 0; col < selected_day; col++) { @@ -1363,23 +1373,9 @@ int main(int argc, char *argv[]) { } input_pos_x++; - // Get time delta. - // TODO Maybe use that function referred in 'g' here? - { - attron(A_UNDERLINE); - - snprintf(string_buffer, string_buffer_size, "%*s", input_width, ""); - mvaddstr(selected_task_row, input_pos_x, string_buffer); - - echo(); - curs_set(1); - mvgetnstr(selected_task_row, input_pos_x, string_buffer, input_width); - noecho(); - curs_set(0); - attroff(A_UNDERLINE); - } - + attron(COLOR_PAIR(selected_task_theme) | A_BOLD); + read_input_to_string_buffer(selected_task_row, input_pos_x, input_width); attrset(A_NORMAL); // TODO Check if parsed OK. For that, I need to read the manual to know what strtoX returns. @@ -1443,7 +1439,6 @@ int main(int argc, char *argv[]) { break; } - // Make sure we sync before applying the changes. update_times(db); @@ -1492,18 +1487,11 @@ int main(int argc, char *argv[]) { move(selected_task_row, 1); addch(ACS_CKBOARD); addstr(" Move to: "); - int input_pos_x = getcurx(stdscr); - snprintf(string_buffer, string_buffer_size, "%*s", size_x - input_pos_x - 1, ""); - attron(A_UNDERLINE); - addstr(string_buffer); // Get line number. - echo(); - curs_set(1); - mvgetnstr(selected_task_row, input_pos_x, string_buffer, size_x - input_pos_x - 1); - noecho(); - curs_set(0); - + int input_pos_x = getcurx(stdscr); + int input_width = size_x - input_pos_x - 1; + read_input_to_string_buffer(selected_task_row, input_pos_x, input_width); attrset(A_NORMAL); char *parser; // TODO Rename var. @@ -1538,21 +1526,9 @@ int main(int argc, char *argv[]) { addstr(" Go to: "); // Get line number. - // TODO Maybe convert this code block on a function? - { - int input_pos_x = getcurx(stdscr); - snprintf(string_buffer, string_buffer_size, "%*s", size_x - input_pos_x - 1, ""); - attron(A_UNDERLINE); - addstr(string_buffer); - - echo(); - curs_set(1); - mvgetnstr(selected_task_row, input_pos_x, string_buffer, size_x - input_pos_x - 1); - noecho(); - curs_set(0); - attroff(A_UNDERLINE); - } - + int input_pos_x = getcurx(stdscr); + int input_width = size_x - input_pos_x - 1; + read_input_to_string_buffer(selected_task_row, input_pos_x, input_width); attrset(A_NORMAL); char *parser; diff --git a/readme.md b/readme.md index 973c8d2..b5b955c 100644 --- a/readme.md +++ b/readme.md @@ -62,7 +62,7 @@ Task Time Tracker - [ ] Compress code: - [x] Re-do sprint_time5_utf8: -12 delta LOC; - [x] Re-do truncate_string_utf8: 0 delta LOC; - - [ ] Get input using `get_input(char *input, size_t size, int row, int column)` (what does it returns???): + - [ ] Implement `read_input_to_string_buffer`: -24 delta LOC; - [ ] wrap malloc (and maybe others) in a function with error checking ```c static inline void *MallocOrDie(size_t MemSize) { void *AllocMem = malloc(MemSize); /* Some implementations return null on a 0 length alloc, * we may as well allow this as it increases compatibility * with very few side effects */ if(!AllocMem && MemSize) { printf("Could not allocate memory!"); exit(-1); } return AllocMem; } @@ -73,5 +73,6 @@ Task Time Tracker - `sect_active(database_st *db, task_st *task)` - [ ] Check if draw_tui may be simplified by drawing entire lines of tasks at once and draw columns separators after; - [ ] Rename `MAX_TASK_NAME` to `TASK_NAME_BUFFER_SIZE`; +- [ ] Try to fix flickering of ncurses; - [ ] Review all code for bugs related to auto-cast on ptrdiff_t/size_t (signed/unsigned); - [ ] Go over all `TODO` items; -- cgit v1.2.3