Compare commits

52 Commits
main ... v1.0.0

Author SHA1 Message Date
Zvonimir Rudinski
9414db11e1 add ncurses to requirements 2024-08-15 02:43:30 +02:00
Zvonimir Rudinski
6513a73ce4 change package name 2024-08-15 02:42:52 +02:00
Zvonimir Rudinski
5d32c49557 add msystem 2024-08-15 02:42:47 +02:00
Zvonimir Rudinski
1e1d6b9115 try out different command name 2024-08-15 02:38:06 +02:00
Zvonimir Rudinski
ec8d491e6f change to correct package name 2024-08-15 02:37:59 +02:00
Zvonimir Rudinski
278b11f1c1 add windows build again 2024-08-15 02:37:52 +02:00
Zvonimir Rudinski
677bd89362 add dirty check 2024-06-02 16:35:08 +02:00
Zvonimir Rudinski
537f57a3b2 remove windows build step 2024-06-02 07:13:36 +02:00
Zvonimir Rudinski
49c403cf63 add windows build step 2024-06-02 07:12:49 +02:00
Zvonimir Rudinski
31831d29d7 add mac build step 2024-06-02 07:12:08 +02:00
Zvonimir Rudinski
e35ba14b19 upload artifact 2024-06-02 07:11:32 +02:00
Zvonimir Rudinski
1c1ab9d29b fix mvwprintw string literal error 2024-06-02 07:09:39 +02:00
Zvonimir Rudinski
8a114acc71 remove setup gcc 2024-06-02 07:06:27 +02:00
Zvonimir Rudinski
aa25ef9a6a remove cache apt pkgs 2024-06-02 07:05:15 +02:00
Zvonimir Rudinski
6ec4aebf2a set version to string 2024-06-02 07:05:00 +02:00
Zvonimir Rudinski
481d853ebc add other steps 2024-06-02 07:04:39 +02:00
Zvonimir Rudinski
895abb1f0b generate workflow file 2024-06-02 07:02:12 +02:00
Zvonimir Rudinski
0ef406e59c start debugging workflow file 2024-06-02 07:01:12 +02:00
Zvonimir Rudinski
3e02cfe363 copy workflow into correct directory 2024-06-02 06:58:17 +02:00
Zvonimir Rudinski
05adbf5d33 add github build action 2024-06-02 06:57:46 +02:00
Zvonimir Rudinski
705f989737 take height into account 2024-06-02 06:50:19 +02:00
Zvonimir Rudinski
90991e7401 add view command 2024-06-02 06:49:24 +02:00
Zvonimir Rudinski
694a71362d add licenses 2024-06-02 06:40:15 +02:00
Zvonimir Rudinski
60f5e2af3e add proper scroll 2024-06-02 06:33:54 +02:00
Zvonimir Rudinski
ec9a455a49 load file on startup if it exists 2024-06-02 06:10:38 +02:00
Zvonimir Rudinski
40f2155a34 remove unused command 2024-06-02 06:05:31 +02:00
Zvonimir Rudinski
668dd69ac0 add install makefile command 2024-06-02 06:02:57 +02:00
Zvonimir Rudinski
758300b704 print key bindings on the footer 2024-06-02 05:59:43 +02:00
Zvonimir Rudinski
b73e47e590 add save file path to the header 2024-06-02 05:57:35 +02:00
Zvonimir Rudinski
c5ba5e8bfa store the default file in the home directory dotfile 2024-06-02 05:55:54 +02:00
Zvonimir Rudinski
0e709a08e2 add todo count in the bottom bar 2024-01-07 12:16:42 +01:00
Zvonimir Rudinski
1914119412 add space key for marking todos 2024-01-07 12:13:45 +01:00
Zvonimir Rudinski
2dd52012a7 allow arrow input 2024-01-07 12:11:30 +01:00
Zvonimir Rudinski
4c1d6a3f79 add a semi-functional TUI 2024-01-06 17:53:01 +01:00
Zvonimir Rudinski
691045186f fix build errors 2023-12-22 04:24:04 +01:00
Zvonimir Rudinski
87d220912e add cflags 2023-12-22 04:22:35 +01:00
Zvonimir Rudinski
4049d732bb update makefile 2023-12-22 04:20:16 +01:00
Zvonimir Rudinski
0efd48dd6f update readme 2023-12-22 04:17:47 +01:00
Zvonimir Rudinski
5fbf6bed11 format the code 2023-12-22 04:11:26 +01:00
Zvonimir Rudinski
8a649463c5 free todos after quit 2023-12-21 21:02:34 +01:00
Zvonimir Rudinski
aa3502dc7b update readme 2023-12-21 19:55:18 +01:00
Zvonimir Rudinski
44a7c3aacc implement loading from file 2023-12-21 19:49:34 +01:00
Zvonimir Rudinski
f0aa6758dc use stb like a normal person (totally didnt waste an hour) 2023-12-21 19:42:50 +01:00
Zvonimir Rudinski
13ebbcd049 remove llist 2023-12-21 19:25:34 +01:00
Zvonimir Rudinski
1a45b1e219 steal a linked list implementation 2023-12-21 18:26:44 +01:00
Zvonimir Rudinski
aa851fad3a add todo serialization 2023-12-21 18:06:50 +01:00
Zvonimir Rudinski
a89a330ede add makefile 2023-12-21 17:35:09 +01:00
Zvonimir Rudinski
38ca5234ca add ideas 2023-12-16 17:24:37 +01:00
Zvonimir Rudinski
a4319c5b38 add readme 2023-12-16 17:23:31 +01:00
Zvonimir Rudinski
f152a85d8a add remove command 2023-12-16 17:22:03 +01:00
Zvonimir Rudinski
dd7c5d0a1c separate files and fix newline bug 2023-12-16 17:12:25 +01:00
Zvonimir Rudinski
9e0ff21192 make stuff work 2023-12-16 17:03:00 +01:00
12 changed files with 2571 additions and 4 deletions

74
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: Build Todd
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libncurses5-dev
version: "1.0"
- name: make
run: make
- name: copy to build folder
run: |
mkdir -p build
cp ./todd build/
- uses: actions/upload-artifact@v4
with:
name: todd-linux
path: build
build-mac:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: make
run: make
- name: copy to build folder
run: |
mkdir -p build
cp ./todd build/
- uses: actions/upload-artifact@v4
with:
name: todd-mac
path: build
build-windows:
defaults:
run:
shell: msys2 {0}
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
msystem: mingw64
update: true
install: >-
gcc
make
ncurses-devel
- name: make
run: make
- name: copy to build folder
run: |
mkdir -p build
cp ./todd.exe build/
- uses: actions/upload-artifact@v4
with:
name: todd-windows
path: build

2
.gitignore vendored
View File

@@ -1 +1,3 @@
todd
*.o
*.todd

27
3rd-party/NCURSES-LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,27 @@
-------------------------------------------------------------------------------
-- Copyright (c) 1998-2004,2006 Free Software Foundation, Inc. --
-- --
-- Permission is hereby granted, free of charge, to any person obtaining a --
-- copy of this software and associated documentation files (the --
-- "Software"), to deal in the Software without restriction, including --
-- without limitation the rights to use, copy, modify, merge, publish, --
-- distribute, distribute with modifications, sublicense, and/or sell copies --
-- of the Software, and to permit persons to whom the Software is furnished --
-- to do so, subject to the following conditions: --
-- --
-- The above copyright notice and this permission notice shall be included --
-- in all copies or substantial portions of the Software. --
-- --
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS --
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF --
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN --
-- NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, --
-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR --
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE --
-- USE OR OTHER DEALINGS IN THE SOFTWARE. --
-- --
-- Except as contained in this notice, the name(s) of the above copyright --
-- holders shall not be used in advertising or otherwise to promote the --
-- sale, use or other dealings in this Software without prior written --
-- authorization. --
-------------------------------------------------------------------------------

16
3rd-party/STB-DS-LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,16 @@
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1895
3rd-party/stb-ds.h vendored Normal file

File diff suppressed because it is too large Load Diff

22
LICENSE.txt Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2024 Zvonimir Rudinski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

23
Makefile Normal file
View File

@@ -0,0 +1,23 @@
.DEFAULT_GOAL := all
CC=gcc
CFLAGS=-Wall -Wextra -Werror -pedantic -std=c99 -g
todo.o:
$(CC) $(CFLAGS) -c engine/todo.c -o todo.o
main.o:
$(CC) $(CFLAGS) -c main.c -o main.o
clean:
rm -f *.o
rm -f todd
rm /usr/local/bin/todd || true
todd: todo.o main.o
$(CC) $(CFLAGS) todo.o main.o -lncurses -o todd
all: todd
install: todd
cp todd /usr/local/bin/todd

12
README.md Normal file
View File

@@ -0,0 +1,12 @@
# Todd
`todd` is a to-do app written in C.
## Requirements
- GCC
- GNU Make
- ncurses development libraries
## Building
To build `todd` all you need to do is run `make`.
## 3rd Party
Todd uses the following dependencies:
- stb_ds - https://github.com/nothings/stb
- ncurses - https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/index.html

View File

@@ -1 +0,0 @@
cc main.c -o todd

56
engine/todo.c Normal file
View File

@@ -0,0 +1,56 @@
#include "todo.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Memory management
void todo_free_item(TodoItem *item) { free(item->title); }
// Item operations
TodoItem todo_create_item(char *title) {
TodoItem item;
item.title_length = strlen(title);
item.title = malloc(item.title_length + 1);
strcpy(item.title, title);
item.completed = false;
return item;
}
void todo_mark_item(TodoItem *item, bool_t completed) {
item->completed = completed;
}
void todo_print_item(TodoItem *item) {
printf("%s - ", item->title);
if (item->completed) {
printf("Completed\n");
} else {
printf("Not Completed\n");
}
}
// Serialization
char *todo_item_serialize(TodoItem *item, int *buffer_size_out) {
// Layout of the serialized item:
// title_length - 1 byte
// title - title_length bytes
// completed - 1 byte
unsigned char title_length = item->title_length;
unsigned char completed_length = sizeof(item->completed);
int buffer_size = sizeof(unsigned char) + title_length + completed_length;
char *buffer = malloc(buffer_size);
// copy title length
memcpy(buffer, &title_length, sizeof(unsigned char));
// copy title
memcpy(buffer + sizeof(unsigned char), item->title, title_length);
// copy completed
memcpy(buffer + sizeof(unsigned char) + title_length, &item->completed,
completed_length);
*buffer_size_out = buffer_size;
return buffer;
}

25
engine/todo.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef TODO_H
#define TODO_H
#define TODO_MAX_TITLE_LENGTH 255
#define true 1
#define false 0
typedef unsigned char bool_t;
typedef struct {
unsigned char title_length;
char *title;
bool_t completed;
} TodoItem;
// Memory management
void todo_free_item(TodoItem *item);
// Item operations
TodoItem todo_create_item(char *title);
void todo_mark_item(TodoItem *item, bool_t completed);
void todo_print_item(TodoItem *item);
// Serialization
char *todo_item_serialize(TodoItem *item, int *buffer_size_out);
#endif

418
main.c
View File

@@ -1,7 +1,423 @@
#include "engine/todo.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#define MAX_TODOS UINT_MAX
#define MAX_PATH_LENGTH 1024
#define STB_DS_IMPLEMENTATION
#define BORDERS_PAIR 1
#define DEFAULT_PAIR 2
#define COMPLETED_PAIR 3
#include "3rd-party/stb-ds.h"
#define KEY_BINDINGS " [a]dd [v]view [m]ark [d]elete [w]rite [l]oad [q]uit"
bool dirty = false;
char SAVE_FILE[MAX_PATH_LENGTH] = "";
TodoItem *todos = NULL;
enum Command {
ADD = 'a',
VIEW = 'v',
MARK = 'm',
REMOVE = 'd',
QUIT = 'q',
WRITE_TO_FILE = 'w',
LOAD_FROM_FILE = 'l',
UP = 'k',
DOWN = 'j',
UP_ARROW = KEY_UP,
DOWN_ARROW = KEY_DOWN,
SPACE = ' '
};
void clear_todos(void) {
for (int i = 0; i < arrlen(todos); i++) {
todo_free_item(&todos[i]);
}
arrfree(todos);
}
void initialize_curses(int *width, int *height) {
initscr();
curs_set(0);
int tw, th;
getmaxyx(stdscr, th, tw);
*width = tw;
*height = th;
start_color();
init_pair(BORDERS_PAIR, COLOR_BLACK, COLOR_WHITE);
init_pair(DEFAULT_PAIR, COLOR_WHITE, COLOR_BLACK);
init_pair(COMPLETED_PAIR, COLOR_BLACK, COLOR_GREEN);
// turn off echoing of keys, and enter cbreak mode,
// where no buffering is performed on keyboard input
cbreak();
noecho();
keypad(stdscr, TRUE);
}
void draw_header(int width) {
attron(COLOR_PAIR(BORDERS_PAIR));
move(0, 0);
addstr(" Todd - ");
addstr(SAVE_FILE);
for (int i = strlen(" Todd - ") + strlen(SAVE_FILE); i < width - 1; i++) {
addch(' ');
}
}
void draw_footer(int width, int height) {
move(height - 1, 0);
addch(' ');
int todos_count = arrlen(todos);
int marked_count = 0;
for (int i = 0; i < todos_count; i++) {
if (todos[i].completed) {
marked_count++;
}
}
// get the length of the string
int length = snprintf(NULL, 0, "%d/%d", marked_count, todos_count);
// print the string
printw("%d/%d", marked_count, todos_count);
// print the rest of the line but leave space for the key bindings
for (unsigned long i = length; i < width - strlen(KEY_BINDINGS) - 1; i++) {
addch(' ');
}
// print the key bindings
printw(KEY_BINDINGS);
attroff(COLOR_PAIR(BORDERS_PAIR));
}
char alert(const char *message, int terminal_width, int terminal_height) {
int window_height = 5;
int window_width = terminal_width - 4;
int window_y = terminal_height / 2 - 1;
int window_x = 2;
int message_x = (terminal_width - 4 - strlen(message)) / 2;
int message_y = 2;
// create a new window
WINDOW *alert_window = newwin(window_height, window_width, window_y, window_x);
// draw a border around the window
box(alert_window, 0, 0);
// print the message in the middle of the window
mvwprintw(alert_window, message_y, message_x, "%s", message);
// refresh the window
wrefresh(alert_window);
// wait for a key press
char key = getch();
// delete the window
delwin(alert_window);
return key;
}
void input_string(int terminal_width, int terminal_height, char *buffer, int buffer_size) {
int window_height = 5;
int window_width = terminal_width - 4;
int window_y = terminal_height / 2 - 1;
int window_x = 2;
int message_x = strlen("Enter todo title: ") + 1;
int message_y = 2;
curs_set(1);
// create a new window
WINDOW *input_window = newwin(window_height, window_width, window_y, window_x);
// draw a border around the window
box(input_window, 0, 0);
// print the message in the middle of the window
mvwprintw(input_window, message_y, message_x, "Enter todo title: ");
// refresh the window
wrefresh(input_window);
// wait for a key press
echo();
mvwgetnstr(input_window, message_y, message_x + strlen("Enter todo title: "), buffer, buffer_size);
noecho();
// delete the window
delwin(input_window);
curs_set(0);
}
void display_todos(int width, int height, int current_line) {
int current_y = 1;
int current_index = 0;
const char *padding = "...";
unsigned long title_length = 0;
unsigned long padding_length = strlen(padding);
char *title = (char *)malloc(width);
attron(COLOR_PAIR(DEFAULT_PAIR));
// calculate the offset
if (height - 4 <= current_line) {
// ignore the first n todos
current_index = current_line - (height - 4);
}
// while we have space to print todos
while (current_y < height - 2) {
// check if we're out of bounds
if (current_index >= arrlen(todos)) {
break;
}
// copy the title to the buffer
title_length = strlen(todos[current_index].title);
strncpy(title, todos[current_index].title, width - padding_length - 1);
// append the padding if needed
if (title_length > strlen(title)) {
strcat(title, padding);
}
// move to the correct line
move(current_y, 0);
// if the current todo is completed, print it as green
if (todos[current_index].completed) {
attroff(COLOR_PAIR(DEFAULT_PAIR));
attron(COLOR_PAIR(COMPLETED_PAIR));
}
// highlight the current line
if (current_index == current_line) {
attron(A_UNDERLINE);
}
printw("%d. %s", current_index + 1, title);
// increment the current y and index
current_y++;
current_index++;
attroff(A_UNDERLINE);
attroff(COLOR_PAIR(COMPLETED_PAIR));
attron(COLOR_PAIR(DEFAULT_PAIR));
}
free(title);
}
// Command handlers
void add_command_handler(int width, int height) {
char title[TODO_MAX_TITLE_LENGTH];
input_string(width, height, title, TODO_MAX_TITLE_LENGTH);
TodoItem item = todo_create_item(title);
arrput(todos, item);
// set the dirty flag
dirty = true;
}
void view_command_handler(int width, int height, int index) {
TodoItem item = todos[index];
// create an alert in the middle of the screen
alert(item.title, width, height);
}
void mark_command_handler(int index) {
TodoItem item = todos[index];
todo_mark_item(&item, !item.completed);
todos[index] = item;
// set the dirty flag
dirty = true;
}
void remove_command_handler(int index) {
arrdel(todos, index);
// set the dirty flag
dirty = true;
}
void write_to_file_handler(void) {
FILE *file = fopen(SAVE_FILE, "wb");
int bs = 0;
int todos_count = arrlen(todos);
// write a magic value "TODD" to the file
fwrite("TODD", sizeof(char), 4, file);
// write the number of todos to the file
fwrite(&todos_count, sizeof(int), 1, file);
// write each todo to the file
for (int i = 0; i < todos_count; i++) {
char *buffer = todo_item_serialize(&todos[i], &bs);
fwrite(buffer, sizeof(char), bs, file);
free(buffer);
}
fclose(file);
// reset the dirty flag
dirty = false;
}
void load_from_file_handler(void) {
// clear the current todos
clear_todos();
FILE *file = fopen(SAVE_FILE, "rb");
// if unable to open the file, return
if (file == NULL) {
return;
}
char magic[5] = {'\0'};
int todos_count = 0;
// check the "TODD" (without the \0) magic value
fread(magic, sizeof(char), 4, file);
if (strcmp(magic, "TODD") != 0) {
printf("Invalid file format\nExpected TODD, got %s\n", magic);
return;
}
// read the number of todos
fread(&todos_count, sizeof(int), 1, file);
// read each todo
for (int i = 0; i < todos_count; i++) {
// read the title length
unsigned char title_length = 0;
fread(&title_length, sizeof(unsigned char), 1, file);
// read the title
char *title = malloc(title_length + 1);
fread(title, sizeof(char), title_length, file);
title[title_length] = '\0';
// read the completed flag
bool_t completed = false;
fread(&completed, sizeof(bool_t), 1, file);
// create the todo item
TodoItem item = todo_create_item(title);
// mark the item as completed if needed
if (completed) {
todo_mark_item(&item, true);
}
// add the item to the list
arrput(todos, item);
}
// reset the dirty flag
dirty = false;
}
int main(int argc, char **argv) {
printf("Hello, world!\n");
int terminal_width = 0;
int terminal_height = 0;
int key = 0;
int running = 1;
int current_line = 0;
// set the save file path if provided
if (argc > 1 && strlen(argv[1]) < MAX_PATH_LENGTH) {
strncpy(SAVE_FILE, argv[1], MAX_PATH_LENGTH);
} else {
// load the default save file which is "~/.todos.todd"
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
// check if we have enough space to store the path
assert(strlen(homedir) + strlen("/.todos.todd") < MAX_PATH_LENGTH);
// create the path
snprintf(SAVE_FILE, MAX_PATH_LENGTH, "%s/.todos.todd", homedir);
}
// if file exists, load it
load_from_file_handler();
initialize_curses(&terminal_width, &terminal_height);
while (running) {
clear();
draw_header(terminal_width);
draw_footer(terminal_width, terminal_height);
display_todos(terminal_width, terminal_height, current_line);
refresh();
// wait for a key press
key = getch();
// handle the key press
switch (key) {
case LOAD_FROM_FILE:
load_from_file_handler();
alert("Loaded!", terminal_width, terminal_height);
break;
case WRITE_TO_FILE:
write_to_file_handler();
alert("Saved!", terminal_width, terminal_height);
break;
case ADD:
add_command_handler(terminal_width, terminal_height);
break;
case VIEW:
view_command_handler(terminal_width, terminal_height, current_line);
break;
case MARK:
case SPACE:
mark_command_handler(current_line);
break;
case REMOVE:
remove_command_handler(current_line);
if (current_line >= arrlen(todos)) {
current_line = arrlen(todos) - 1;
}
break;
case QUIT:
if (dirty) {
key = alert("You have unsaved changes. Save changes? [y/n]", terminal_width, terminal_height);
if (key == 'y') {
write_to_file_handler();
}
}
running = 0;
break;
case UP:
case UP_ARROW:
current_line--;
if (current_line < 0) {
current_line = 0;
}
break;
case DOWN:
case DOWN_ARROW:
current_line++;
if (current_line >= arrlen(todos)) {
current_line = arrlen(todos) - 1;
}
break;
}
}
endwin();
return 0;
}