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