Run and fix pedantic clippy and 2018 idioms
This commit is contained in:
		
							parent
							
								
									db92090916
								
							
						
					
					
						commit
						c6343a4805
					
				
							
								
								
									
										44
									
								
								src/app.rs
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								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<Column>,
 | 
			
		||||
    pub task_edit_state: Option<TaskState<'a>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<TaskState<'a>> {
 | 
			
		||||
        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<Self, KanbanError> {
 | 
			
		||||
        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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
use super::*;
 | 
			
		||||
// use super::*;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/input.rs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								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(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								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<File>) {
 | 
			
		||||
    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<dyn Error>> {
 | 
			
		||||
                    .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<dyn Error>> {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    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<dyn Error>> {
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								src/ui.rs
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								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<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
 | 
			
		||||
fn draw_tasks<B: Backend>(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<ListItem> = column.tasks
 | 
			
		||||
        let items: Vec<ListItem<'_>> = column.tasks
 | 
			
		||||
            .iter()
 | 
			
		||||
            .enumerate()
 | 
			
		||||
            .map(|(j, task)| {
 | 
			
		||||
@ -55,16 +55,16 @@ fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn draw_task_info<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
 | 
			
		||||
fn draw_task_info<B: Backend>(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<B: Backend>(f: &mut Frame<B>, state: &mut AppState, popup_title: &str) {
 | 
			
		||||
pub fn draw_task_popup<B: Backend>(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<B: Backend>(f: &mut Frame<B>, state: &mut AppState, popup
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn draw<B: Backend>(f: &mut Frame<B>, state: &mut AppState) {
 | 
			
		||||
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>) {
 | 
			
		||||
    let main_layout = Layout::default()
 | 
			
		||||
        .direction(Direction::Vertical)
 | 
			
		||||
        .constraints(
 | 
			
		||||
@ -206,9 +206,9 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, 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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user