Skip to content

Commit

Permalink
State persistance
Browse files Browse the repository at this point in the history
  • Loading branch information
Deezzir committed Jan 16, 2023
1 parent 21c9b9c commit 1d46ec3
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 67 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ version = "0.1.0"
edition = "2021"

[dependencies]
termion = "2.0.1"
termion = "2.0.1"
regex = "1.3.9"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ cargo run

## Controls

| key | description |
| Key | Descritption |
|-----------------------------------------------------|--------------------|
| <kbd>w/↑</kbd>,<kbd>s/↓</kbd> | Move UP/DOWN |
| <kbd>ENTER</kbd> | Action |
| <kbd>Tab</kbd> | Switch between Todos and Dones |
| <kbd>q/ESC</kbd> | Quit |
7 changes: 7 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
TODO: Finish Work projects
TODO: Finist Sim2D project
TODO: Learn Rust
TODO: Write a Rust TODO app
TODO: Pet a cat
TODO: Have a lunch
TODO: Take a walk
212 changes: 150 additions & 62 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
extern crate regex;
extern crate termion;

use std::cmp::*;
use std::io::{stdin, stdout, Read, Write};
use std::cmp::min;
use std::env;
use std::fs::File;
use std::io::{self, stdin, stdout, BufRead, Read, Write};
use std::process;

use regex::Regex;

use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::{clear, color, cursor, style};

type Id = usize;
enum Status {
Todo,
Done,
}

struct UI {
cur_id: Option<Id>,
Expand All @@ -17,6 +27,94 @@ struct UI {
col: u16,
}

fn main() {
let mut args = env::args();
args.next().unwrap();

let file_path = match args.next() {
Some(file_path) => file_path,
None => {
eprintln!("Usage: todo <file>");
eprintln!("[ERROR]: No file specified");
process::exit(1);
}
};

let mut quit: bool = false;
let mut tab = Status::Todo;

let mut todos: Vec<String> = Vec::<String>::new();
let mut cur_todo: Id = 0;
let mut dones: Vec<String> = Vec::<String>::new();
let mut cur_done: Id = 0;

parse_items(&file_path, &mut todos, &mut dones).unwrap();

let mut stdin = stdin();
let mut ui: UI = UI::new();

while !quit {
ui.begin(0, 0);
{
match tab {
Status::Todo => {
ui.label("[TODO] DONE ");
ui.label("------------");
ui.begin_list(cur_todo);
for (id, todo) in todos.iter().enumerate() {
ui.list_item(&format!("- [ ] {}", todo), id);
}
ui.end_list();
}
Status::Done => {
ui.label(" TODO [DONE]");
ui.label("------------");
ui.begin_list(cur_done);
for (id, done) in dones.iter().enumerate() {
ui.list_item(&format!("- [X] {}", done), id);
}
ui.end_list();
}
}
}
ui.end();

if let Some(Ok(key)) = stdin.by_ref().keys().next() {
match key {
Key::Esc | Key::Char('q') => quit = true,
Key::Up | Key::Char('w') => match tab {
Status::Todo => list_up(&todos, &mut cur_todo),
Status::Done => list_up(&dones, &mut cur_done),
},
Key::Down | Key::Char('s') => match tab {
Status::Todo => list_down(&todos, &mut cur_todo),
Status::Done => list_down(&dones, &mut cur_done),
},
Key::Char('\n') => match tab {
Status::Todo => list_move(&mut todos, &mut dones, &mut cur_todo),
Status::Done => list_move(&mut dones, &mut todos, &mut cur_done),
},
Key::Char('\t') => {
tab = tab.togle();
}
_ => {}
}
}
}

ui.clear();
dump_items(&file_path, &todos, &dones).unwrap();
}

impl Status {
fn togle(&self) -> Status {
match self {
Status::Todo => Status::Done,
Status::Done => Status::Todo,
}
}
}

impl UI {
fn new() -> UI {
let mut term = stdout().into_raw_mode().unwrap();
Expand All @@ -40,8 +138,8 @@ impl UI {
self.term,
"{}{}{}",
cursor::Hide,
clear::All,
cursor::Goto(1, 1)
cursor::Goto(1, 1),
clear::AfterCursor
)
.unwrap();
}
Expand Down Expand Up @@ -97,69 +195,59 @@ fn term_reset<W: Write>(s: &mut W) {
s.flush().unwrap();
}

fn main() {
let mut stdin = stdin();
let mut ui: UI = UI::new();
let mut cur_todo: Id = 0;
let _cur_done: Id = 0;
let mut quit: bool = false;
fn list_up(list: &Vec<String>, cur: &mut Id) {
if *cur > 0 && list.len() > 0 {
*cur -= 1;
}
}

let mut todos: Vec<String> = vec![
"Finish Scancore".to_string(),
"Make a cup of tea".to_string(),
"Write a Rust TODO app".to_string(),
];
let mut dones: Vec<String> = vec!["Pet a cat".to_string(), "Have a lunch".to_string()];
fn list_down(list: &Vec<String>, cur: &mut Id) {
if list.len() > 0 {
*cur = min(*cur + 1, list.len() - 1)
}
}

while !quit {
ui.begin(0, 0);
{
ui.label("TODO:");
ui.label("------------------------------");
ui.begin_list(cur_todo);
for (id, todo) in todos.iter().enumerate() {
ui.list_item(&format!("- [ ] {}", todo), id);
}
ui.end_list();

ui.label("");
ui.label("DONE:");
ui.label("------------------------------");
ui.begin_list(0);
for (id, done) in dones.iter().enumerate() {
ui.list_item(&format!("- [X] {}", done), id + 1);
}
ui.end_list();
fn list_move(from: &mut Vec<String>, to: &mut Vec<String>, cur: &mut Id) {
if *cur < from.len() {
to.push(from.remove(*cur));
if from.len() > 0 {
*cur = min(*cur, from.len() - 1);
} else {
*cur = 0;
}
ui.end();
}
}

if let Some(Ok(key)) = stdin.by_ref().keys().next() {
match key {
Key::Esc | Key::Char('q') => quit = true,
Key::Up | Key::Char('w') => {
if cur_todo > 0 {
cur_todo -= 1;
}
}
Key::Down | Key::Char('s') => {
if todos.len() > 0 {
cur_todo = min(cur_todo + 1, todos.len() - 1)
}
}
Key::Char('\n') => {
if cur_todo < todos.len() {
dones.push(todos.remove(cur_todo));
if todos.len() > 0 {
cur_todo = min(cur_todo, todos.len() - 1);
} else {
cur_todo = 0;
}
}
}
_ => {}
}
fn parse_items(
file_path: &str,
todos: &mut Vec<String>,
dones: &mut Vec<String>,
) -> std::io::Result<()> {
let file = File::open(file_path)?;
let re_todo = Regex::new(r"^TODO: (.*)$").unwrap();
let re_done = Regex::new(r"^DONE: (.*)$").unwrap();

for (id, line) in io::BufReader::new(file).lines().enumerate() {
let line = line?;
if let Some(caps) = re_todo.captures(&line) {
todos.push(caps[1].to_string());
} else if let Some(caps) = re_done.captures(&line) {
dones.push(caps[1].to_string());
} else {
eprintln!("[ERROR]: {}:{}: invalid format in the line", file_path, id + 1);
process::exit(1);
}
}
Ok(())
}

ui.clear();
fn dump_items(file_path: &str, todos: &Vec<String>, dones: &Vec<String>) -> std::io::Result<()> {
let mut file = File::create(file_path)?;
for todo in todos.iter() {
writeln!(file, "TODO: {}", todo).unwrap();
}
for done in dones.iter() {
writeln!(file, "DONE: {}", done).unwrap();
}
Ok(())
}
3 changes: 0 additions & 3 deletions src/todo.rs

This file was deleted.

0 comments on commit 1d46ec3

Please sign in to comment.