aboutsummaryrefslogtreecommitdiff
path: root/ttt.jai
diff options
context:
space:
mode:
authordam <dam@gudinoff>2023-04-14 09:03:25 +0100
committerdam <dam@gudinoff>2023-04-14 09:03:25 +0100
commit81ba4ca925c4e66b107bb7df737b8fdd51eee9ac (patch)
tree5cb9e812029f1dcbb7a77aade0ba220ab7e97b77 /ttt.jai
parent3bda0a6e82af3e09336b7f715de4d5aded28b78b (diff)
downloadtask-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.jai344
1 files changed, 134 insertions, 210 deletions
diff --git a/ttt.jai b/ttt.jai
index 454586d..ad93b3b 100644
--- a/ttt.jai
+++ b/ttt.jai
@@ -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.