rustfmt
This commit is contained in:
parent
e3ce612ca3
commit
5d3e69d679
33
src/app.rs
33
src/app.rs
@ -2,7 +2,7 @@
|
|||||||
// use int_enum::IntEnum;
|
// use int_enum::IntEnum;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::fs::{File};
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use tui_textarea::TextArea;
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ pub struct Project {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub filepath: String,
|
pub filepath: String,
|
||||||
pub selected_column_idx: usize,
|
pub selected_column_idx: usize,
|
||||||
pub columns: Vec<Column>
|
pub columns: Vec<Column>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@ -52,7 +52,7 @@ pub struct TaskState<'a> {
|
|||||||
pub title: TextArea<'a>,
|
pub title: TextArea<'a>,
|
||||||
pub description: TextArea<'a>,
|
pub description: TextArea<'a>,
|
||||||
pub focus: TaskEditFocus,
|
pub focus: TaskEditFocus,
|
||||||
pub is_edit: bool
|
pub is_edit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TaskState<'_> {
|
impl Default for TaskState<'_> {
|
||||||
@ -61,7 +61,7 @@ impl Default for TaskState<'_> {
|
|||||||
title: TextArea::default(),
|
title: TextArea::default(),
|
||||||
description: TextArea::default(),
|
description: TextArea::default(),
|
||||||
focus: TaskEditFocus::Title,
|
focus: TaskEditFocus::Title,
|
||||||
is_edit: false
|
is_edit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,13 +135,11 @@ impl<'a> Column {
|
|||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_task_state_from_curr_selected_task(&self) -> Option<TaskState<'a>> {
|
pub fn get_task_state_from_curr_selected_task(&self) -> Option<TaskState<'a>> {
|
||||||
self.get_selected_task().map(|t| {
|
self.get_selected_task().map(|t| TaskState {
|
||||||
TaskState {
|
title: TextArea::from(t.title.lines()),
|
||||||
title: TextArea::from(t.title.lines()),
|
description: TextArea::from(t.description.lines()),
|
||||||
description: TextArea::from(t.description.lines()),
|
focus: TaskEditFocus::Title,
|
||||||
focus: TaskEditFocus::Title,
|
is_edit: true,
|
||||||
is_edit: true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,10 +201,7 @@ impl Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_next_column(&mut self) -> &Column {
|
pub fn select_next_column(&mut self) -> &Column {
|
||||||
self.selected_column_idx = min(
|
self.selected_column_idx = min(self.selected_column_idx + 1, self.columns.len() - 1);
|
||||||
self.selected_column_idx + 1,
|
|
||||||
self.columns.len() - 1,
|
|
||||||
);
|
|
||||||
&self.columns[self.selected_column_idx]
|
&self.columns[self.selected_column_idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +240,9 @@ impl Project {
|
|||||||
pub fn move_task_up(&mut self) {
|
pub fn move_task_up(&mut self) {
|
||||||
let column = self.get_selected_column_mut();
|
let column = self.get_selected_column_mut();
|
||||||
if column.selected_task_idx > 0 {
|
if column.selected_task_idx > 0 {
|
||||||
column.tasks.swap(column.selected_task_idx, column.selected_task_idx - 1);
|
column
|
||||||
|
.tasks
|
||||||
|
.swap(column.selected_task_idx, column.selected_task_idx - 1);
|
||||||
column.selected_task_idx -= 1;
|
column.selected_task_idx -= 1;
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
@ -254,7 +251,9 @@ impl Project {
|
|||||||
pub fn move_task_down(&mut self) {
|
pub fn move_task_down(&mut self) {
|
||||||
let column = self.get_selected_column_mut();
|
let column = self.get_selected_column_mut();
|
||||||
if column.selected_task_idx < column.tasks.len() - 1 {
|
if column.selected_task_idx < column.tasks.len() - 1 {
|
||||||
column.tasks.swap(column.selected_task_idx, column.selected_task_idx + 1);
|
column
|
||||||
|
.tasks
|
||||||
|
.swap(column.selected_task_idx, column.selected_task_idx + 1);
|
||||||
column.selected_task_idx += 1;
|
column.selected_task_idx += 1;
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
|
129
src/input.rs
129
src/input.rs
@ -1,6 +1,6 @@
|
|||||||
|
use crate::app::{State, TaskEditFocus, TaskState};
|
||||||
use crossterm::event;
|
use crossterm::event;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
use crate::app::{TaskState, State, TaskEditFocus};
|
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
@ -14,81 +14,76 @@ pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> {
|
|||||||
// TODO: Extract this code to a separate function to avoid nesting
|
// TODO: Extract this code to a separate function to avoid nesting
|
||||||
match task.focus {
|
match task.focus {
|
||||||
// TODO: Handle wrapping around the enum rather than doing it manually
|
// TODO: Handle wrapping around the enum rather than doing it manually
|
||||||
TaskEditFocus::Title => {
|
TaskEditFocus::Title => match key.code {
|
||||||
match key.code {
|
KeyCode::Tab => task.focus = TaskEditFocus::Description,
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::Description,
|
KeyCode::BackTab => task.focus = TaskEditFocus::CancelBtn,
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::CancelBtn,
|
KeyCode::Enter => (),
|
||||||
KeyCode::Enter => (),
|
_ => {
|
||||||
_ => { task.title.input(key); }
|
task.title.input(key);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
TaskEditFocus::Description => {
|
TaskEditFocus::Description => match key.code {
|
||||||
match key.code {
|
KeyCode::Tab => task.focus = TaskEditFocus::ConfirmBtn,
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::ConfirmBtn,
|
KeyCode::BackTab => task.focus = TaskEditFocus::Title,
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::Title,
|
_ => {
|
||||||
_ => { task.description.input(key); }
|
task.description.input(key);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
TaskEditFocus::ConfirmBtn => {
|
TaskEditFocus::ConfirmBtn => match key.code {
|
||||||
match key.code {
|
KeyCode::Tab => task.focus = TaskEditFocus::CancelBtn,
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::CancelBtn,
|
KeyCode::BackTab => task.focus = TaskEditFocus::Description,
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::Description,
|
KeyCode::Enter => {
|
||||||
KeyCode::Enter => {
|
let title = task.title.clone().into_lines().join("\n");
|
||||||
let title = task.title.clone().into_lines().join("\n");
|
let description = task.description.clone().into_lines().join("\n");
|
||||||
let description = task.description.clone().into_lines().join("\n");
|
if task.is_edit {
|
||||||
if task.is_edit {
|
if let Some(selected_task) = column.get_selected_task_mut() {
|
||||||
if let Some(selected_task) = column.get_selected_task_mut() {
|
selected_task.title = title;
|
||||||
selected_task.title = title;
|
selected_task.description = description;
|
||||||
selected_task.description = description;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
column.add_task(title, description);
|
|
||||||
}
|
}
|
||||||
state.task_edit_state = None;
|
} else {
|
||||||
project.save();
|
column.add_task(title, description);
|
||||||
}
|
}
|
||||||
_ => (),
|
state.task_edit_state = None;
|
||||||
|
project.save();
|
||||||
}
|
}
|
||||||
}
|
_ => (),
|
||||||
TaskEditFocus::CancelBtn => {
|
},
|
||||||
match key.code {
|
TaskEditFocus::CancelBtn => match key.code {
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::Title,
|
KeyCode::Tab => task.focus = TaskEditFocus::Title,
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::ConfirmBtn,
|
KeyCode::BackTab => task.focus = TaskEditFocus::ConfirmBtn,
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
state.task_edit_state = None;
|
state.task_edit_state = None;
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
_ => (),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
None => {
|
None => match key.code {
|
||||||
match key.code {
|
KeyCode::Char('q') => state.quit = true,
|
||||||
KeyCode::Char('q') => state.quit = true,
|
KeyCode::Char('h') | KeyCode::Left => {
|
||||||
KeyCode::Char('h') |
|
project.select_previous_column();
|
||||||
KeyCode::Left => { project.select_previous_column(); },
|
|
||||||
KeyCode::Char('j') |
|
|
||||||
KeyCode::Down => column.select_next_task(),
|
|
||||||
KeyCode::Char('k') |
|
|
||||||
KeyCode::Up => column.select_previous_task(),
|
|
||||||
KeyCode::Char('l') |
|
|
||||||
KeyCode::Right => { project.select_next_column(); },
|
|
||||||
KeyCode::Char('g') => column.select_first_task(),
|
|
||||||
KeyCode::Char('G') => column.select_last_task(),
|
|
||||||
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(),
|
|
||||||
KeyCode::Char('D') => {
|
|
||||||
column.remove_task();
|
|
||||||
project.save();
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
KeyCode::Char('j') | KeyCode::Down => column.select_next_task(),
|
||||||
|
KeyCode::Char('k') | KeyCode::Up => column.select_previous_task(),
|
||||||
|
KeyCode::Char('l') | KeyCode::Right => {
|
||||||
|
project.select_next_column();
|
||||||
|
}
|
||||||
|
KeyCode::Char('g') => column.select_first_task(),
|
||||||
|
KeyCode::Char('G') => column.select_last_task(),
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
KeyCode::Char('D') => {
|
||||||
|
column.remove_task();
|
||||||
|
project.save();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
mod app;
|
mod app;
|
||||||
mod ui;
|
|
||||||
mod input;
|
mod input;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use ui::draw;
|
|
||||||
pub use input::handle;
|
pub use input::handle;
|
||||||
|
pub use ui::draw;
|
||||||
|
98
src/main.rs
98
src/main.rs
@ -1,23 +1,18 @@
|
|||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
use kanban_tui::{Project, State};
|
use clap::{Parser, ValueHint::FilePath};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, EnableMouseCapture},
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
terminal::{
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
disable_raw_mode,
|
|
||||||
enable_raw_mode,
|
|
||||||
EnterAlternateScreen,
|
|
||||||
LeaveAlternateScreen
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
use kanban_tui::{Project, State};
|
||||||
use std::{
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::{File, OpenOptions},
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
fs::{File, OpenOptions},
|
|
||||||
error::Error
|
|
||||||
};
|
};
|
||||||
use tui::backend::CrosstermBackend;
|
use tui::backend::CrosstermBackend;
|
||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
use clap::{Parser, ValueHint::FilePath};
|
|
||||||
|
|
||||||
const DEFAULT_DATABASE_NAME: &str = "kanban.json";
|
const DEFAULT_DATABASE_NAME: &str = "kanban.json";
|
||||||
|
|
||||||
@ -27,7 +22,7 @@ const DEFAULT_DATABASE_NAME: &str = "kanban.json";
|
|||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
#[arg(value_name="DATABASE", value_hint=FilePath, index=1)]
|
#[arg(value_name="DATABASE", value_hint=FilePath, index=1)]
|
||||||
/// Path to the
|
/// Path to the
|
||||||
pub filepath: Option<PathBuf>
|
pub filepath: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should just return a struct beacuse we should add a
|
// TODO: This should just return a struct beacuse we should add a
|
||||||
@ -42,54 +37,55 @@ fn prompt_project_init(default_name: &str) -> (String, io::Result<File>) {
|
|||||||
let result = io::stdin().read_line(&mut input);
|
let result = io::stdin().read_line(&mut input);
|
||||||
let input = input.trim();
|
let input = input.trim();
|
||||||
|
|
||||||
let filename =
|
let filename = match result {
|
||||||
match result {
|
Ok(b) if b == 0 => std::process::exit(0),
|
||||||
Ok(b) if b == 0 => std::process::exit(0),
|
Ok(b) if b > 0 && !input.is_empty() => input,
|
||||||
Ok(b) if b > 0 && !input.is_empty() => input,
|
_ => default_name,
|
||||||
_ => default_name
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: This might be a good time to prompt the user if they want
|
// TODO: This might be a good time to prompt the user if they want
|
||||||
// to change the default column names
|
// to change the default column names
|
||||||
|
|
||||||
(filename.to_string(),
|
(
|
||||||
OpenOptions::new()
|
filename.to_string(),
|
||||||
.write(true)
|
OpenOptions::new()
|
||||||
.read(true)
|
.write(true)
|
||||||
.create(true)
|
.read(true)
|
||||||
.open(filename))
|
.create(true)
|
||||||
|
.open(filename),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
#[async_std::main]
|
||||||
let (filepath, file) =
|
async fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
||||||
match CliArgs::parse() {
|
let (filepath, file) = match CliArgs::parse() {
|
||||||
CliArgs { filepath: Some(filepath) } => {
|
CliArgs {
|
||||||
let fpath = filepath.into_os_string().into_string().unwrap();
|
filepath: Some(filepath),
|
||||||
let file = OpenOptions::new()
|
} => {
|
||||||
.write(true)
|
let fpath = filepath.into_os_string().into_string().unwrap();
|
||||||
.read(true)
|
let file = OpenOptions::new().write(true).read(true).open(&fpath);
|
||||||
.open(&fpath);
|
|
||||||
|
|
||||||
if let Ok(f) = file {
|
if let Ok(f) = file {
|
||||||
(fpath, f)
|
(fpath, f)
|
||||||
} else {
|
} else {
|
||||||
let (fp, fname) = prompt_project_init(&fpath);
|
let (fp, fname) = prompt_project_init(&fpath);
|
||||||
(fp, fname.unwrap())
|
(fp, fname.unwrap())
|
||||||
}
|
|
||||||
},
|
|
||||||
CliArgs { filepath: None } => {
|
|
||||||
let file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.read(true)
|
|
||||||
.open(DEFAULT_DATABASE_NAME);
|
|
||||||
if let Ok(f) = file {
|
|
||||||
(DEFAULT_DATABASE_NAME.to_string(), f)
|
|
||||||
} else {
|
|
||||||
let (fp, fname) = prompt_project_init(DEFAULT_DATABASE_NAME);
|
|
||||||
(fp, fname.unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
CliArgs { filepath: None } => {
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.read(true)
|
||||||
|
.open(DEFAULT_DATABASE_NAME);
|
||||||
|
if let Ok(f) = file {
|
||||||
|
(DEFAULT_DATABASE_NAME.to_string(), f)
|
||||||
|
} else {
|
||||||
|
let (fp, fname) = prompt_project_init(DEFAULT_DATABASE_NAME);
|
||||||
|
(fp, fname.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut state = State::new(Project::load(filepath, &file)?);
|
let mut state = State::new(Project::load(filepath, &file)?);
|
||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
14
src/ui.rs
14
src/ui.rs
@ -19,7 +19,8 @@ fn draw_tasks<B: Backend>(f: &mut Frame<'_, B>, area: Rect, state: &State<'_>) {
|
|||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
for (i, column) in state.project.columns.iter().enumerate() {
|
for (i, column) in state.project.columns.iter().enumerate() {
|
||||||
let items: Vec<ListItem<'_>> = column.tasks
|
let items: Vec<ListItem<'_>> = column
|
||||||
|
.tasks
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(j, task)| {
|
.map(|(j, task)| {
|
||||||
@ -123,7 +124,7 @@ pub fn draw_task_popup<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>,
|
|||||||
[
|
[
|
||||||
Constraint::Percentage(80),
|
Constraint::Percentage(80),
|
||||||
Constraint::Min(10),
|
Constraint::Min(10),
|
||||||
Constraint::Min(10)
|
Constraint::Min(10),
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
@ -167,7 +168,8 @@ pub fn draw_task_popup<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>,
|
|||||||
task.title.set_block(b1);
|
task.title.set_block(b1);
|
||||||
if let TaskEditFocus::Title = task.focus {
|
if let TaskEditFocus::Title = task.focus {
|
||||||
task.title.set_style(Style::default().fg(Color::Yellow));
|
task.title.set_style(Style::default().fg(Color::Yellow));
|
||||||
task.title.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
task.title
|
||||||
|
.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
} else {
|
} else {
|
||||||
task.title.set_style(Style::default());
|
task.title.set_style(Style::default());
|
||||||
task.title.set_cursor_style(Style::default());
|
task.title.set_cursor_style(Style::default());
|
||||||
@ -176,8 +178,10 @@ pub fn draw_task_popup<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>,
|
|||||||
|
|
||||||
task.description.set_block(b2);
|
task.description.set_block(b2);
|
||||||
if let TaskEditFocus::Description = task.focus {
|
if let TaskEditFocus::Description = task.focus {
|
||||||
task.description.set_style(Style::default().fg(Color::Yellow));
|
task.description
|
||||||
task.description.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
.set_style(Style::default().fg(Color::Yellow));
|
||||||
|
task.description
|
||||||
|
.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
} else {
|
} else {
|
||||||
task.description.set_style(Style::default());
|
task.description.set_style(Style::default());
|
||||||
task.description.set_cursor_style(Style::default());
|
task.description.set_cursor_style(Style::default());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user