Compare commits
No commits in common. "a8e3a448168089d8a94e7cb80ec858a9919420c0" and "e3ce612ca375732d1af7d38f67eaa363a2897e9c" have entirely different histories.
a8e3a44816
...
e3ce612ca3
1157
Cargo.lock
generated
1157
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,4 @@ serde_json = "1.0.89"
|
|||||||
int-enum = "0.5.0"
|
int-enum = "0.5.0"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls", "sqlite" ] }
|
|
||||||
async-std = { version = "1", features = [ "attributes" ] }
|
|
||||||
clap = { version = "4.3.2" , features = [ "derive" ] }
|
clap = { version = "4.3.2" , features = [ "derive" ] }
|
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,11 +135,13 @@ 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| TaskState {
|
self.get_selected_task().map(|t| {
|
||||||
title: TextArea::from(t.title.lines()),
|
TaskState {
|
||||||
description: TextArea::from(t.description.lines()),
|
title: TextArea::from(t.title.lines()),
|
||||||
focus: TaskEditFocus::Title,
|
description: TextArea::from(t.description.lines()),
|
||||||
is_edit: true,
|
focus: TaskEditFocus::Title,
|
||||||
|
is_edit: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +203,10 @@ 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 + 1, self.columns.len() - 1);
|
self.selected_column_idx = min(
|
||||||
|
self.selected_column_idx + 1,
|
||||||
|
self.columns.len() - 1,
|
||||||
|
);
|
||||||
&self.columns[self.selected_column_idx]
|
&self.columns[self.selected_column_idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,9 +245,7 @@ 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
|
column.tasks.swap(column.selected_task_idx, column.selected_task_idx - 1);
|
||||||
.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();
|
||||||
}
|
}
|
||||||
@ -251,9 +254,7 @@ 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
|
column.tasks.swap(column.selected_task_idx, column.selected_task_idx + 1);
|
||||||
.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,76 +14,81 @@ 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 => match key.code {
|
TaskEditFocus::Title => {
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::Description,
|
match key.code {
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::CancelBtn,
|
KeyCode::Tab => task.focus = TaskEditFocus::Description,
|
||||||
KeyCode::Enter => (),
|
KeyCode::BackTab => task.focus = TaskEditFocus::CancelBtn,
|
||||||
_ => {
|
KeyCode::Enter => (),
|
||||||
task.title.input(key);
|
_ => { task.title.input(key); }
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
TaskEditFocus::Description => match key.code {
|
TaskEditFocus::Description => {
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::ConfirmBtn,
|
match key.code {
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::Title,
|
KeyCode::Tab => task.focus = TaskEditFocus::ConfirmBtn,
|
||||||
_ => {
|
KeyCode::BackTab => task.focus = TaskEditFocus::Title,
|
||||||
task.description.input(key);
|
_ => { task.description.input(key); }
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
TaskEditFocus::ConfirmBtn => match key.code {
|
TaskEditFocus::ConfirmBtn => {
|
||||||
KeyCode::Tab => task.focus = TaskEditFocus::CancelBtn,
|
match key.code {
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::Description,
|
KeyCode::Tab => task.focus = TaskEditFocus::CancelBtn,
|
||||||
KeyCode::Enter => {
|
KeyCode::BackTab => task.focus = TaskEditFocus::Description,
|
||||||
let title = task.title.clone().into_lines().join("\n");
|
KeyCode::Enter => {
|
||||||
let description = task.description.clone().into_lines().join("\n");
|
let title = task.title.clone().into_lines().join("\n");
|
||||||
if task.is_edit {
|
let description = task.description.clone().into_lines().join("\n");
|
||||||
if let Some(selected_task) = column.get_selected_task_mut() {
|
if task.is_edit {
|
||||||
selected_task.title = title;
|
if let Some(selected_task) = column.get_selected_task_mut() {
|
||||||
selected_task.description = description;
|
selected_task.title = title;
|
||||||
|
selected_task.description = description;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
column.add_task(title, description);
|
||||||
}
|
}
|
||||||
} else {
|
state.task_edit_state = None;
|
||||||
column.add_task(title, description);
|
project.save();
|
||||||
}
|
}
|
||||||
state.task_edit_state = None;
|
_ => (),
|
||||||
project.save();
|
|
||||||
}
|
}
|
||||||
_ => (),
|
}
|
||||||
},
|
TaskEditFocus::CancelBtn => {
|
||||||
TaskEditFocus::CancelBtn => match key.code {
|
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 => match key.code {
|
None => {
|
||||||
KeyCode::Char('q') => state.quit = true,
|
match key.code {
|
||||||
KeyCode::Char('h') | KeyCode::Left => {
|
KeyCode::Char('q') => state.quit = true,
|
||||||
project.select_previous_column();
|
KeyCode::Char('h') |
|
||||||
|
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 input;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod input;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use input::handle;
|
|
||||||
pub use ui::draw;
|
pub use ui::draw;
|
||||||
|
pub use input::handle;
|
||||||
|
113
src/main.rs
113
src/main.rs
@ -1,19 +1,23 @@
|
|||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
use clap::{Parser, ValueHint::FilePath};
|
use kanban_tui::{Project, State};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, EnableMouseCapture},
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{
|
||||||
|
disable_raw_mode,
|
||||||
|
enable_raw_mode,
|
||||||
|
EnterAlternateScreen,
|
||||||
|
LeaveAlternateScreen
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use kanban_tui::{Project, State};
|
|
||||||
use sqlx::sqlite::SqlitePool;
|
|
||||||
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";
|
||||||
|
|
||||||
@ -23,7 +27,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
|
||||||
@ -38,69 +42,54 @@ 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 = match result {
|
let filename =
|
||||||
Ok(b) if b == 0 => std::process::exit(0),
|
match result {
|
||||||
Ok(b) if b > 0 && !input.is_empty() => input,
|
Ok(b) if b == 0 => std::process::exit(0),
|
||||||
_ => default_name,
|
Ok(b) if b > 0 && !input.is_empty() => input,
|
||||||
};
|
_ => 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(),
|
||||||
filename.to_string(),
|
OpenOptions::new()
|
||||||
OpenOptions::new()
|
.write(true)
|
||||||
.write(true)
|
.read(true)
|
||||||
.read(true)
|
.create(true)
|
||||||
.create(true)
|
.open(filename))
|
||||||
.open(filename),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::main]
|
fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
||||||
async fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
let (filepath, file) =
|
||||||
let (filepath, file) = match CliArgs::parse() {
|
match CliArgs::parse() {
|
||||||
CliArgs {
|
CliArgs { filepath: Some(filepath) } => {
|
||||||
filepath: Some(filepath),
|
let fpath = filepath.into_os_string().into_string().unwrap();
|
||||||
} => {
|
let file = OpenOptions::new()
|
||||||
let fpath = filepath.into_os_string().into_string().unwrap();
|
.write(true)
|
||||||
let file = OpenOptions::new().write(true).read(true).open(&fpath);
|
.read(true)
|
||||||
|
.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 pool = SqlitePool::connect("sqlite:db.sqlite").await?;
|
|
||||||
|
|
||||||
let stuff = sqlx::query!(
|
|
||||||
r#"
|
|
||||||
select * from kanban
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
.fetch_all(&pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for item in stuff {
|
|
||||||
println!("{} - {} - {}", item.id, item.name, item.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,8 +19,7 @@ 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
|
let items: Vec<ListItem<'_>> = column.tasks
|
||||||
.tasks
|
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(j, task)| {
|
.map(|(j, task)| {
|
||||||
@ -124,7 +123,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(),
|
||||||
)
|
)
|
||||||
@ -168,8 +167,7 @@ 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
|
task.title.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
.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());
|
||||||
@ -178,10 +176,8 @@ 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
|
task.description.set_style(Style::default().fg(Color::Yellow));
|
||||||
.set_style(Style::default().fg(Color::Yellow));
|
task.description.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
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