diff options
| author | dam <dam@gudinoff> | 2023-04-14 09:03:25 +0100 |
|---|---|---|
| committer | dam <dam@gudinoff> | 2023-04-14 09:03:25 +0100 |
| commit | 81ba4ca925c4e66b107bb7df737b8fdd51eee9ac (patch) | |
| tree | 5cb9e812029f1dcbb7a77aade0ba220ab7e97b77 /ttt.jai | |
| parent | 3bda0a6e82af3e09336b7f715de4d5aded28b78b (diff) | |
| download | task-time-tracker-81ba4ca925c4e66b107bb7df737b8fdd51eee9ac.tar.zst task-time-tracker-81ba4ca925c4e66b107bb7df737b8fdd51eee9ac.zip | |
Using resizable array to hold tasks. Implemented create new task, rename task. Fixed case break bug.
Diffstat (limited to 'ttt.jai')
| -rw-r--r-- | ttt.jai | 344 |
1 files changed, 134 insertions, 210 deletions
@@ -17,7 +17,7 @@ // - release : jai ttt.jai -import_dir . -quiet -x64 -release // - debug : jai ttt.jai -import_dir . -quiet -x64 -#import "Basic"()(MEMORY_DEBUGGER=true); +#import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. #import "System"; #import "Math"; #import "POSIX"; @@ -57,8 +57,7 @@ Database :: struct { active_idx : s64 = -1; selected_idx : s64 = -1; total_times : [NUM_WEEK_DAYS] s64; - tasks : [] Task; - capacity : s64; + tasks : [..] Task; } SIZE_OF_TASK :: #run size_of(Task); @@ -327,96 +326,14 @@ contains_task :: inline (db: *Database, task: *Task) -> bool { return task >= db.tasks.data && task - db.tasks.data < db.tasks.count; } -// Adds a task to the database. +// Adds a task to the database and returns it. // If necessary, expands database capacity. -// Returns success. -add_task :: (db: *Database, task: Task) -> success: bool { +add_task :: (db: *Database, task: Task = .{}) -> task: *Task { assert(db != null, "Parameter 'db' is null."); - - // TODO Remove old implementation... or use the dynamics arrays? -// idx := db.tasks.count; -// maybe_grow(*db.tasks); // TODO Check for errors? -// db.tasks.count += 1; -// db.tasks[idx] = task; - - if (db.tasks.count == S64_MAX) { - print_error("Database reached maximum capacity."); - return false; - } - - // If necessary, expand database capacity. - current_capacity := db.capacity; - if ((db.tasks.count + 1) > current_capacity) { - new_capacity := - ifx current_capacity == 0 then 8 else - ifx current_capacity > (S64_MAX >> 1) then S64_MAX else - current_capacity << 1; - - new_tasks := realloc(db.tasks.data, new_capacity * SIZE_OF_TASK, current_capacity * SIZE_OF_TASK); - if (new_tasks == null) { - print_error("Failed to expand database."); - return false; - } - - db.tasks.data = new_tasks; - db.capacity = new_capacity; - } - - db.tasks.count += 1; - db.tasks[db.tasks.count-1] = task; - - // Adjust selected task. - if (db.selected_idx < 0) { - db.selected_idx = db.tasks.count-1; - } - - return true; + array_add(*db.tasks, task); + return *db.tasks[db.tasks.count-1]; } /* -// Creates new task stored at location given by task pointer. -// If necessary, expands database capacity. -// Returns success. -bool create_task(database_st *db, task_st **task) { - assert(db != NULL); - assert(task != NULL); - - if (db->count >= MAX_DATABASE_TASKS) { - print_error("Database reached maximum capacity."); - return false; - } - - // If necessary, expand database capacity. - size_t current_capacity = db->capacity; - if ((db->count + 1) > current_capacity) { - size_t new_capacity = - current_capacity == 0 ? 2 : - current_capacity > (MAX_DATABASE_TASKS >> 1) ? MAX_DATABASE_TASKS : - current_capacity << 1; - - task_st *new_tasks = realloc(db->tasks, new_capacity * SIZEOF_TASK_ST); - if (new_tasks == NULL) { - print_error("Failed to expand database."); - return false; - } - - db->capacity = new_capacity; - db->tasks = new_tasks; - } - - // Prepare new task. - *task = &db->tasks[db->count]; - memset(*task, 0, SIZEOF_TASK_ST); - - db->count++; - - // Adjust selected task. - if (db->selected_task < 0) { - db->selected_task = db->count-1; - } - - return true; -} - // Duplicates the given task. Duplicated task is appended to the database. // Returns success. bool duplicate_task(database_st *db, task_st *task) { @@ -449,6 +366,7 @@ bool duplicate_task(database_st *db, task_st *task) { // If possible, shrinks the database capacity. // Returns success. delete_task :: (db: *Database, task: *Task) -> bool { + assert(db != null); assert(task != null); assert(contains_task(db, task)); @@ -460,7 +378,8 @@ delete_task :: (db: *Database, task: *Task) -> bool { // Move tasks after the index position to their new positions. index := task - db.tasks.data; - memmove(task, task + 1, (db.tasks.count - index - 1) * SIZE_OF_TASK); // FIXME memmove is not implemented + for index..db.tasks.count-2 + db.tasks[it] = db.tasks[it+1]; db.tasks.count -= 1; // Adjust selected task. @@ -477,16 +396,31 @@ delete_task :: (db: *Database, task: *Task) -> bool { } // If possible, shrink database capacity. - current_capacity := db.capacity; - if (db.tasks.count <= (current_capacity >> 2)) { - new_capacity := current_capacity >> 1; - new_tasks := realloc(db.tasks.data, new_capacity * SIZEOF_TASK_ST); - if (new_tasks == null && new_capacity > 0) { - print_error("Failed to shrink database."); - return false; + // TODO Do we really want to make this fuss? + current_capacity := db.tasks.allocated; + if (db.tasks.count < (current_capacity >> 2)) { + new_capacity := 1 << (get_msb(db.tasks.count) + 2); + my_array_reserve_nonpoly(xx *db.tasks, new_capacity, SIZE_OF_TASK); + } + + get_msb :: (value: s64) -> msb: s64, found: bool { + result: s64 = ---; + #asm { + mov val: gpr, value; + bsr result, val; } - db.capacity = new_capacity; - db.tasks.data = new_tasks; + return result * xx cast(bool)value, xx value; // If value is zero: return `0, false`. + } + + my_array_reserve_nonpoly :: (array: *[..] *void, desired_items: s64, size: s64) -> success: bool { + if !array.allocator.proc remember_allocators(array); + + new_array_data := realloc(array.data, desired_items * size, array.allocated * size, array.allocator); + if new_array_data == null return false; + + array.data = new_array_data; + array.allocated = desired_items; + return true; } return true; @@ -698,11 +632,13 @@ load_database :: (db: *Database, path: string) -> success: bool { assert(read_success == true, "Failed to read database info from '%'.", path); // Reserve database capacity for tasks. - db.tasks.data = alloc(db.tasks.count * SIZE_OF_TASK); - db.capacity = db.tasks.count; + tasks_count := db.tasks.count; + Initialize(*db.tasks); // Cleanup whatever was read from file. + array_reserve(*db.tasks, tasks_count); // Read database tasks. - file_read(file, db.tasks.data, SIZE_OF_TASK * db.tasks.count); + file_read(file, db.tasks.data, SIZE_OF_TASK * tasks_count); + db.tasks.count = tasks_count; // Make sure we are reading all the file. buffer: u8; @@ -744,17 +680,16 @@ export_to_csv :: (db: Database, path: string) -> success: bool { // Imports CSV file into database. // Returns success. import_from_csv :: (db: *Database, path: string) -> bool { - - // TODO WIP - + // TODO Review code. assert(db != null, "Parameter 'db' is null."); assert(xx path, "Parameter 'path' is empty."); error_code: s64; - // Check file size - file_info: stat_t; - error_code = sys_stat(path, *file_info); // TODO Check for error. - size := file_info.st_size; + // Check file size TODO Read based on file size + //file_info: stat_t; + //error_code = sys_stat(path, *file_info); // TODO Check for error. + //size := file_info.st_size; + size := 0; success: bool; map: Map_File_Info; @@ -828,35 +763,37 @@ import_from_csv :: (db: *Database, path: string) -> bool { return "", false; } - // Parse CSV lines. - line := csv; - while (true) { - line, success := consume_next_line(*csv); -// line, success := next_line(*csv); - if success == false break; - - task: Task; - csv_values := split(line, ","); // TODO Temporary memory... if file is too big this may break. - - // Import task name. - name_length := min(task.name.count, csv_values[0].count); - memcpy(task.name.data, csv_values[0].data, name_length); - truncate_string(xx task.name, name_length); - - advance(*csv_values); - for csv_values { - parsed_value := string_to_int(it); - task.times[it_index] = parsed_value; - db.total_times[it_index] = add_int64(db.total_times[it_index], parsed_value); + { // Parse CSV lines. + auto_release_temp(); // TODO Needs to be tested. + line := csv; + while (true) { + line, success := consume_next_line(*csv); + // line, success := next_line(*csv); + if success == false break; + + task: Task; + csv_values := split(line, ","); // TODO Temporary memory... if file is too big this may break. + + // Import task name. + name_length := min(task.name.count, csv_values[0].count); + memcpy(task.name.data, csv_values[0].data, name_length); + truncate_string(xx task.name, name_length); + + advance(*csv_values); + for csv_values { + parsed_value := string_to_int(it); + task.times[it_index] = parsed_value; + db.total_times[it_index] = add_int64(db.total_times[it_index], parsed_value); + } + + add_task(db, *task); + // TODO Check this old code and remove it if not necessary. + // if context.temporary_storage.total_bytes_occupied > (100<<20) { + // print("temp: %\n", context.temporary_storage.total_bytes_occupied >> 20); + // reset_temporary_storage(); + // } } - - add_task(db, *task); -// if context.temporary_storage.total_bytes_occupied > (100<<20) { -// print("temp: %\n", context.temporary_storage.total_bytes_occupied >> 20); -// reset_temporary_storage(); -// } } - reset_temporary_storage(); return true; } @@ -928,13 +865,11 @@ set_active_task :: (db: *Database, task: *Task) { update_times(db); db.active_idx = ifx (task == null) then -1 else task - db.tasks.data; } -/* + // Returns true when database is full. -bool is_database_full(database_st *db) { - assert(db != NULL); - return db->count >= MAX_DATABASE_TASKS; +is_database_full :: inline (db: Database) -> bool { + return db.tasks.count >= MAX_DATABASE_TASKS; } -*/ INPUT_TIMEOUT_MS :: 1000; INPUT_AWAIT_INF :: -1; @@ -1243,23 +1178,22 @@ free_memory :: () { free(ar_file_path); //reset_temporary_storage(); } -/* -void read_input_to_string_buffer_with_space(int row, int column, int style, int length, int space) { - assert(length < string_buffer_size); - assert(space < string_buffer_size); - + +read_input_with_space :: (row: int, column: int, style: s32, length: int, space: int) -> string { + str := talloc_string(length); + memset(str.data, 0, str.count); attron(style | A_UNDERLINE); - mvprintw(row, column, "%*s", space, ""); + mvprintw(xx row, xx column, "%*s", space, ""); echo(); curs_set(1); - memset(string_buffer, 0, string_buffer_size); - mvgetnstr(row, column, string_buffer, length); - truncate_string_utf8(string_buffer, length); + mvgetnstr(xx row, xx column, str.data, xx length); + truncate_string(str, length); noecho(); curs_set(0); attrset(A_NORMAL); + return str; } - +/* void read_input_to_string_buffer(int row, int column, int style, int length) { read_input_to_string_buffer_with_space(row, column, style, length, length); } @@ -1306,20 +1240,23 @@ read_enter_confirmation :: (row: int, style: int, message: string) -> bool { return getch() == #char "\n"; } - main :: () { - defer report_memory_leaks(); // TODO DEBUG + defer report_memory_leaks(); // TODO Remove after final debug sessions. defer free_memory(); { // Initialize app directory. + auto_release_temp(); home_dir, success_dir := get_home_directory(); // Returns system owned memory. if success_dir == false { home_dir = "."; } - home_path, success_path := get_absolute_path(home_dir); // Returns temporary memory. TODO LEAK + home_path, success_path := get_absolute_path(home_dir); // Returns temporary memory. + + + if success_path == false { print_error("Failed to find home directory '%'.", home_dir); exit(1); @@ -1331,9 +1268,9 @@ main :: () { make_directory_if_it_does_not_exist(app_directory, recursive = true); } - + { // Initialize database and archive files if needed. - + auto_release_temp(); if (file_exists(db_file_path) == false) { if (store_database(database, db_file_path) == false) { print_error("Failed to initialize database."); @@ -1475,14 +1412,28 @@ main :: () { initialize_tui(); - layout := *layouts[Layouts.COMPACT]; db := *database; + layout := *layouts[Layouts.COMPACT]; flushinp(); ungetch(KEY_RESIZE); while (true) { + + if (is_terminal_too_small) { + INVALID_WINDOW_MESSAGE :: "Terminal is too small: minimum 60x3."; + mvaddstr(size_y / 2, (size_x - xx INVALID_WINDOW_MESSAGE.count) / 2, INVALID_WINDOW_MESSAGE); + } + else { + draw_tui(db, layout); + draw_error_window(); + } + + reset_temporary_storage(); + timeout(INPUT_TIMEOUT_MS); key := getch(); if key == #char "q" || key == #char "Q" break; + update_times(*database); + timeout(INPUT_AWAIT_INF); active_task := get_active_task(db); selected_task := get_selected_task(db); @@ -1494,9 +1445,6 @@ main :: () { else ifx (db.selected_idx < 0) then 1 else (db.selected_idx % layout_tasks_rows) + NUM_HEADER_ROWS; - timeout(INPUT_AWAIT_INF); - update_times(*database); - if key == { // When getch() times out. @@ -1530,24 +1478,20 @@ main :: () { //} update_layout(); layout = *layouts[ifx size_x > 100 then Layouts.NORMAL else Layouts.COMPACT]; -/* - case 'n': - case 'N':{ - if (is_database_full(db)) { + + case #char "n"; #through; + case #char "N"; + if is_database_full(db) { read_enter_confirmation(selected_task_row, error_style, " Unable to create entry: database is full. "); - break; + continue; } // Create new task. - task_st *new_task; - if (create_task(db, &new_task) == false) { - break; - } - - // Set new task name. - time_t now_utc = time(NULL); - struct tm *now_local = localtime(&now_utc); - strftime(new_task->name, TASK_NAME_BYTES, "%Y-%m-%d %H:%M:%S", now_local); + now_utc := current_time_consensus(); + now_local := to_calendar(now_utc, .LOCAL); + name := calendar_to_iso_string(now_local); + new_task := add_task(db); + memcpy(new_task.name.data, name.data, min(Task.name.count, name.count)); // Select new task. select_task(db, new_task); @@ -1557,28 +1501,21 @@ main :: () { // Force rename action. flushinp(); ungetch(KEY_F(2)); - break; - } - - case KEY_F(2): { - if (selected_task == NULL) { - break; - } + + case KEY_F2; + if (selected_task == null) continue; - read_input_to_string_buffer_with_space(selected_task_row, 1, action_style, TASK_NAME_LENGTH, size_x - 2); - 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, TASK_NAME_BYTES); + // Change task name. + // TODO remove TASK_NAME_LENGTH + input := read_input_with_space(selected_task_row, 1, action_style, TASK_NAME_LENGTH, size_x - 2); + if is_empty_string(input) == false { + replace_chars(input, "\t\x0B\x0C\r", #char " "); + memcpy(selected_task.name.data, input.data, min(Task.name.count, input.count)); trigger_autosave(); } - break; - } - */ + case KEY_BACKSPACE; - if (selected_task == null) break; // BUG + if (selected_task == null) continue; if (read_enter_confirmation(selected_task_row, action_style, " Press enter to reset task. ") == true) { reset_task_times(db, selected_task); @@ -1586,7 +1523,7 @@ main :: () { } case KEY_DC; // Delete - if (selected_task == null || selected_task == active_task) break; // BUG + if (selected_task == null || selected_task == active_task) continue; if (read_enter_confirmation(selected_task_row, action_style, " Press enter to delete task. ") == true) { delete_task(db, selected_task); @@ -1736,13 +1673,12 @@ main :: () { case #char "t"; #through; case #char "T"; - if (active_task == null) break; // BUG + if (active_task == null) continue; select_task(db, active_task); case #char "\n"; #through; case #char " "; - if (db != *database || selected_task == null) - break; // BUG The break does not work inside an if_case. Review this and all other usages. + if (db != *database || selected_task == null) continue; set_active_task(db, ifx (active_task == selected_task) then null else selected_task); active_task = get_active_task(db); trigger_autosave(); @@ -1752,14 +1688,14 @@ main :: () { if (import_from_csv(*archive, ar_file_path) == false) { reset_database(*archive); print_error("Failed to load archive."); - break; + continue; } db = *archive; } else { if (export_to_csv(*archive, ar_file_path) == false) { print_error("Failed to store archive."); - break; + continue; } reset_database(*archive); db = *database; @@ -1815,18 +1751,6 @@ main :: () { case KEY_NPAGE; select_task_by_delta(db, layout_tasks_rows); } - - if (is_terminal_too_small) { - INVALID_WINDOW_MESSAGE :: "Terminal is too small: minimum 60x3."; - mvaddstr(size_y / 2, (size_x - xx INVALID_WINDOW_MESSAGE.count) / 2, INVALID_WINDOW_MESSAGE); - } - else { - draw_tui(db, layout); - draw_error_window(); - } - - timeout(INPUT_TIMEOUT_MS); - } // Save any unsaved changes. |
