diff --git a/src/app.rs b/src/app.rs index 9424124..002aeb2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,21 +17,12 @@ pub struct Column { } // #[derive(Deserialize, Serialize, Debug, Clone, Copy)] -#[derive(Deserialize, Serialize, Debug)] +#[derive(Default, Deserialize, Serialize, Debug)] pub struct Task { pub title: String, pub description: String, } -impl Default for Task { - fn default() -> Self { - Task { - title: String::new(), - description: String::new(), - } - } -} - /// Type used mainly for serialization at this time #[derive(Deserialize, Serialize, Debug)] pub struct Project { @@ -75,16 +66,17 @@ impl Default for TaskState<'_> { } } -pub struct AppState<'a> { +pub struct State<'a> { pub project: Project, pub quit: bool, pub columns: Vec, pub task_edit_state: Option>, } -impl AppState<'_> { +impl State<'_> { + #[must_use] pub fn new(project: Project) -> Self { - AppState { + State { quit: false, task_edit_state: None, project, @@ -94,6 +86,7 @@ impl AppState<'_> { } impl<'a> Column { + #[must_use] pub fn new(name: &str) -> Self { Column { name: name.to_owned(), @@ -113,6 +106,7 @@ impl<'a> Column { self.select_next_task(); } + #[must_use] pub fn get_selected_task(&self) -> Option<&Task> { self.tasks.get(self.selected_task_idx) } @@ -123,18 +117,19 @@ impl<'a> Column { pub fn select_previous_task(&mut self) { let task_idx = &mut self.selected_task_idx; - *task_idx = task_idx.saturating_sub(1) + *task_idx = task_idx.saturating_sub(1); } pub fn select_next_task(&mut self) { let task_idx = &mut self.selected_task_idx; - *task_idx = min(*task_idx + 1, self.tasks.len().saturating_sub(1)) + *task_idx = min(*task_idx + 1, self.tasks.len().saturating_sub(1)); } pub fn select_last_task(&mut self) { self.selected_task_idx = self.tasks.len() - 1; } + #[must_use] pub fn get_task_state_from_curr_selected_task(&self) -> Option> { self.get_selected_task().map(|t| { TaskState { @@ -148,6 +143,7 @@ impl<'a> Column { } impl Project { + #[must_use] pub fn new(name: &str, filepath: String) -> Self { Project { name: name.to_owned(), @@ -166,6 +162,9 @@ impl Project { serde_json::from_str(json).map_err(|_| KanbanError::BadJson) } + /// # Errors + /// + /// Will return `Err` if `file` contains json that doesn't match State schema pub fn load(path: String, mut file: &File) -> Result { let mut json = String::new(); file.read_to_string(&mut json)?; @@ -176,11 +175,16 @@ impl Project { } } + /// # Panics + /// + /// Will panic if there's an error serializing the Json or there's an issue + /// writing the file pub fn save(&self) { let json = serde_json::to_string_pretty(&self).unwrap(); std::fs::write(&self.filepath, json).unwrap(); } + #[must_use] pub fn get_selected_column(&self) -> &Column { &self.columns[self.selected_column_idx] } @@ -211,7 +215,7 @@ impl Project { } else { col_idx > 0 }; - if cond && column.tasks.len() > 0 { + if cond && !column.tasks.is_empty() { let t = column.tasks.remove(column.selected_task_idx); column.select_previous_task(); if move_next { @@ -227,18 +231,18 @@ impl Project { } pub fn move_task_previous_column(&mut self) { - self.move_task_to_column(false) + self.move_task_to_column(false); } pub fn move_task_next_column(&mut self) { - self.move_task_to_column(true) + self.move_task_to_column(true); } pub fn move_task_up(&mut self) { let column = self.get_selected_column_mut(); if column.selected_task_idx > 0 { column.tasks.swap(column.selected_task_idx, column.selected_task_idx - 1); - column.selected_task_idx = column.selected_task_idx - 1; + column.selected_task_idx -= 1; self.save(); } } @@ -247,7 +251,7 @@ impl Project { let column = self.get_selected_column_mut(); if column.selected_task_idx < column.tasks.len() - 1 { column.tasks.swap(column.selected_task_idx, column.selected_task_idx + 1); - column.selected_task_idx = column.selected_task_idx + 1; + column.selected_task_idx += 1; self.save(); } } diff --git a/src/app/tests.rs b/src/app/tests.rs index 9148c04..35ba390 100644 --- a/src/app/tests.rs +++ b/src/app/tests.rs @@ -1 +1 @@ -use super::*; \ No newline at end of file +// use super::*; diff --git a/src/input.rs b/src/input.rs index 20e03d1..9ef0477 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,11 +1,11 @@ -#![allow(unused_imports)] use crossterm::event; use crossterm::event::{Event, KeyCode}; -use crate::app::{TaskState, AppState, TaskEditFocus}; -use std::io::{stdout, Write}; -use tui_textarea::TextArea; +use crate::app::{TaskState, State, TaskEditFocus}; -pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> { +/// # Errors +/// +/// Crossterm `event::read()` might return an error +pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> { let project = &mut state.project; let column = project.get_selected_column_mut(); if let Event::Key(key) = event::read()? { @@ -35,7 +35,7 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> { KeyCode::BackTab => task.focus = TaskEditFocus::Description, KeyCode::Enter => { let title = task.title.clone().into_lines().join("\n"); - let description = task.description.clone().into_lines().clone().join("\n"); + let description = task.description.clone().into_lines().join("\n"); if task.is_edit { if let Some(selected_task) = column.get_selected_task_mut() { selected_task.title = title; @@ -55,7 +55,7 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> { KeyCode::Tab => task.focus = TaskEditFocus::Title, KeyCode::BackTab => task.focus = TaskEditFocus::ConfirmBtn, KeyCode::Enter => { - state.task_edit_state = None + state.task_edit_state = None; } _ => (), } @@ -73,14 +73,10 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> { KeyCode::Up => column.select_previous_task(), KeyCode::Char('l') | KeyCode::Right => { project.select_next_column(); }, - KeyCode::Char('<') | - KeyCode::Char('H') => project.move_task_previous_column(), - KeyCode::Char('>') | - KeyCode::Char('L') => project.move_task_next_column(), - KeyCode::Char('=') | - KeyCode::Char('J') => project.move_task_down(), - KeyCode::Char('-') | - KeyCode::Char('K') => project.move_task_up(), + KeyCode::Char('<' | 'H') => project.move_task_previous_column(), + KeyCode::Char('>' | 'L') => project.move_task_next_column(), + KeyCode::Char('=' | 'J') => project.move_task_down(), + KeyCode::Char('-' | 'K') => project.move_task_up(), KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()), KeyCode::Char('e') => state.task_edit_state = column.get_task_state_from_curr_selected_task(), @@ -95,4 +91,3 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> { } Ok(()) } - diff --git a/src/lib.rs b/src/lib.rs index bbf5681..289aad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ -// #![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms)] mod app; mod ui; mod input; pub use app::*; pub use ui::draw; -pub use input::handle_input; +pub use input::handle; diff --git a/src/main.rs b/src/main.rs index d30759d..ca4b2d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ #![deny(rust_2018_idioms)] -#![allow(unused_imports)] -#![allow(dead_code)] -use kanban_tui::*; +use kanban_tui::{Project, State}; use crossterm::{ - event::*, + event::{DisableMouseCapture, EnableMouseCapture}, terminal::{ disable_raw_mode, enable_raw_mode, @@ -13,7 +11,6 @@ use crossterm::{ }; use std::{ io::{self, Write}, - env, path::PathBuf, fs::{File, OpenOptions}, error::Error @@ -37,7 +34,7 @@ fn prompt_project_init(default_name: &str) -> (String, io::Result) { let mut input = String::new(); println!("Database not found, select the name of the database if it exists or enter a name to start a new project"); - print!("Database name (default: {}): ", default_name); + print!("Database name (default: {default_name}): "); io::stdout().flush().unwrap(); let result = io::stdin().read_line(&mut input); @@ -71,12 +68,11 @@ fn main() -> anyhow::Result<(), Box> { .read(true) .open(&fpath); - match file { - Ok(f) => (fpath, f), - Err(_) => { - let (fp, fname) = prompt_project_init(&fpath); - (fp, fname.unwrap()) - } + if let Ok(f) = file { + (fpath, f) + } else { + let (fp, fname) = prompt_project_init(&fpath); + (fp, fname.unwrap()) } }, CliArgs { filepath: None } => { @@ -92,7 +88,7 @@ fn main() -> anyhow::Result<(), Box> { } } }; - let mut state = AppState::new(Project::load(filepath, &file)?); + let mut state = State::new(Project::load(filepath, &file)?); enable_raw_mode()?; let mut stdout = io::stdout(); @@ -102,7 +98,7 @@ fn main() -> anyhow::Result<(), Box> { while !state.quit { terminal.draw(|f| kanban_tui::draw(f, &mut state))?; - kanban_tui::handle_input(&mut state)?; + kanban_tui::handle(&mut state)?; } // state.project.save(); diff --git a/src/ui.rs b/src/ui.rs index 2931e24..352ca1d 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,25 +1,25 @@ -use crate::app::*; +use crate::app::{State, TaskEditFocus}; use tui::backend::Backend; -use tui::layout::*; +use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use tui::style::{Color, Modifier, Style}; use tui::text::{Span, Spans}; -use tui::widgets::*; +use tui::widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap}; use tui::Frame; -fn draw_tasks(f: &mut Frame, area: &Rect, state: &AppState) { +fn draw_tasks(f: &mut Frame<'_, B>, area: Rect, state: &State<'_>) { let columns = Layout::default() .direction(Direction::Horizontal) .constraints( vec![ - Constraint::Percentage(100 / state.project.columns.len() as u16); + Constraint::Percentage(100 / u16::try_from(state.project.columns.len()).unwrap()); state.project.columns.len() ] .as_ref(), ) - .split(*area); + .split(area); for (i, column) in state.project.columns.iter().enumerate() { - let items: Vec = column.tasks + let items: Vec> = column.tasks .iter() .enumerate() .map(|(j, task)| { @@ -55,16 +55,16 @@ fn draw_tasks(f: &mut Frame, area: &Rect, state: &AppState) { } } -fn draw_task_info(f: &mut Frame, area: &Rect, state: &AppState) { +fn draw_task_info(f: &mut Frame<'_, B>, area: Rect, state: &State<'_>) { let block = Block::default().title("TASK INFO").borders(Borders::ALL); if let Some(task) = state.project.get_selected_column().get_selected_task() { let p = Paragraph::new(task.description.as_str()) .block(block) .wrap(Wrap { trim: true }); - f.render_widget(p, *area); + f.render_widget(p, area); } else { let p = Paragraph::new("No tasks for this column").block(block); - f.render_widget(p, *area); + f.render_widget(p, area); } } @@ -94,7 +94,7 @@ fn centered_rect_for_popup(percent_x: u16, percent_y: u16, r: Rect) -> Rect { .split(popup_layout[1])[1] } -pub fn draw_task_popup(f: &mut Frame, state: &mut AppState, popup_title: &str) { +pub fn draw_task_popup(f: &mut Frame<'_, B>, state: &mut State<'_>, popup_title: &str) { let area = centered_rect_for_popup(45, 60, f.size()); let block = Block::default() .title(popup_title) @@ -189,7 +189,7 @@ pub fn draw_task_popup(f: &mut Frame, state: &mut AppState, popup } } -pub fn draw(f: &mut Frame, state: &mut AppState) { +pub fn draw(f: &mut Frame<'_, B>, state: &mut State<'_>) { let main_layout = Layout::default() .direction(Direction::Vertical) .constraints( @@ -206,9 +206,9 @@ pub fn draw(f: &mut Frame, state: &mut AppState) { let block = Block::default().title("KANBAN BOARD").borders(Borders::ALL); f.render_widget(block, main_layout[0]); - draw_tasks(f, &main_layout[1], &state); + draw_tasks(f, main_layout[1], state); - draw_task_info(f, &main_layout[2], &state); + draw_task_info(f, main_layout[2], state); let block = Block::default().title("KEYBINDINGS").borders(Borders::TOP);