mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-24 23:46:41 +02:00
WIP
This commit is contained in:
parent
18a54f7f03
commit
732c5b6f84
6 changed files with 311 additions and 119 deletions
12
src/app.rs
12
src/app.rs
|
@ -330,7 +330,8 @@ impl TaskwarriorTui {
|
|||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
let mut tui = tui::Tui::new(self.tick_rate as usize)?;
|
||||
let mut tui = tui::Tui::new()?;
|
||||
tui.tick_rate((self.tick_rate as usize, self.tick_rate as usize));
|
||||
tui.enter()?;
|
||||
|
||||
let mut events: Vec<KeyEvent> = Vec::new();
|
||||
|
@ -342,9 +343,7 @@ impl TaskwarriorTui {
|
|||
tui.resize(s)?;
|
||||
self.requires_redraw = false;
|
||||
}
|
||||
tui.draw(|f| self.draw(f))?;
|
||||
if let Some(event) = tui.next().await {
|
||||
trace_dbg!(event);
|
||||
let mut maybe_action = match event {
|
||||
Event::Quit => Some(Action::Quit),
|
||||
Event::Error => Some(Action::Error("Received event error".into())),
|
||||
|
@ -359,6 +358,13 @@ impl TaskwarriorTui {
|
|||
}
|
||||
Event::Mouse(_) => None,
|
||||
Event::Resize(x, y) => None,
|
||||
Event::Render => {
|
||||
tui.draw(|f| self.draw(f))?;
|
||||
None
|
||||
}
|
||||
Event::FocusGained => None,
|
||||
Event::FocusLost => None,
|
||||
Event::Paste(s) => None,
|
||||
};
|
||||
while let Some(action) = maybe_action {
|
||||
maybe_action = self.update(action)?;
|
||||
|
|
|
@ -92,7 +92,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
initialize_logging(data_dir)?;
|
||||
initialize_logging()?;
|
||||
initialize_panic_handler()?;
|
||||
|
||||
log::info!("getting matches from clap...");
|
||||
|
|
78
src/tui.rs
78
src/tui.rs
|
@ -1,4 +1,8 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::{
|
||||
|
@ -15,14 +19,18 @@ use tokio::{
|
|||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub type CrosstermFrame<'a> = ratatui::Frame<'a, Backend<std::io::Stderr>>;
|
||||
pub type Frame<'a> = ratatui::Frame<'a, Backend<std::io::Stderr>>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Event {
|
||||
Quit,
|
||||
Error,
|
||||
Closed,
|
||||
Tick,
|
||||
Render,
|
||||
FocusGained,
|
||||
FocusLost,
|
||||
Paste(String),
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Resize(u16, u16),
|
||||
|
@ -34,36 +42,37 @@ pub struct Tui {
|
|||
pub cancellation_token: CancellationToken,
|
||||
pub event_rx: UnboundedReceiver<Event>,
|
||||
pub event_tx: UnboundedSender<Event>,
|
||||
pub tick_rate: usize,
|
||||
pub tick_rate: (usize, usize),
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
pub fn new(tick_rate: usize) -> Result<Self> {
|
||||
pub fn new() -> Result<Self> {
|
||||
let tick_rate = (1000, 100);
|
||||
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let task = tokio::spawn(async {});
|
||||
Ok(Self {
|
||||
terminal,
|
||||
task,
|
||||
cancellation_token,
|
||||
event_rx,
|
||||
event_tx,
|
||||
tick_rate,
|
||||
})
|
||||
Ok(Self { terminal, task, cancellation_token, event_rx, event_tx, tick_rate })
|
||||
}
|
||||
|
||||
pub fn tick_rate(&mut self, tick_rate: (usize, usize)) {
|
||||
self.tick_rate = tick_rate;
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
let tick_rate = std::time::Duration::from_millis(self.tick_rate as u64);
|
||||
self.cancellation_token.cancel();
|
||||
let tick_rate = std::time::Duration::from_millis(self.tick_rate.0 as u64);
|
||||
let render_tick_rate = std::time::Duration::from_millis(self.tick_rate.1 as u64);
|
||||
self.cancel();
|
||||
self.cancellation_token = CancellationToken::new();
|
||||
let _cancellation_token = self.cancellation_token.clone();
|
||||
let _event_tx = self.event_tx.clone();
|
||||
self.task = tokio::spawn(async move {
|
||||
let mut reader = crossterm::event::EventStream::new();
|
||||
let mut interval = tokio::time::interval(tick_rate);
|
||||
let mut render_interval = tokio::time::interval(render_tick_rate);
|
||||
loop {
|
||||
let delay = interval.tick();
|
||||
let render_delay = render_interval.tick();
|
||||
let crossterm_event = reader.next().fuse();
|
||||
tokio::select! {
|
||||
_ = _cancellation_token.cancelled() => {
|
||||
|
@ -78,10 +87,21 @@ impl Tui {
|
|||
_event_tx.send(Event::Key(key)).unwrap();
|
||||
}
|
||||
},
|
||||
CrosstermEvent::Mouse(mouse) => {
|
||||
_event_tx.send(Event::Mouse(mouse)).unwrap();
|
||||
},
|
||||
CrosstermEvent::Resize(x, y) => {
|
||||
_event_tx.send(Event::Resize(x, y)).unwrap();
|
||||
},
|
||||
_ => {},
|
||||
CrosstermEvent::FocusLost => {
|
||||
_event_tx.send(Event::FocusLost).unwrap();
|
||||
},
|
||||
CrosstermEvent::FocusGained => {
|
||||
_event_tx.send(Event::FocusGained).unwrap();
|
||||
},
|
||||
CrosstermEvent::Paste(s) => {
|
||||
_event_tx.send(Event::Paste(s)).unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
|
@ -93,11 +113,31 @@ impl Tui {
|
|||
_ = delay => {
|
||||
_event_tx.send(Event::Tick).unwrap();
|
||||
},
|
||||
_ = render_delay => {
|
||||
_event_tx.send(Event::Render).unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
self.cancel();
|
||||
let mut counter = 0;
|
||||
while !self.task.is_finished() {
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
counter += 1;
|
||||
if counter > 50 {
|
||||
self.task.abort();
|
||||
}
|
||||
if counter > 100 {
|
||||
log::error!("Failed to abort task in 100 milliseconds for unknown reason");
|
||||
return Err(color_eyre::eyre::eyre!("Unable to abort task"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enter(&mut self) -> Result<()> {
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
||||
|
@ -106,12 +146,16 @@ impl Tui {
|
|||
}
|
||||
|
||||
pub fn exit(&self) -> Result<()> {
|
||||
self.stop()?;
|
||||
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?;
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
self.cancellation_token.cancel();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
self.cancellation_token.cancel();
|
||||
}
|
||||
|
||||
pub fn suspend(&self) -> Result<()> {
|
||||
self.exit()?;
|
||||
#[cfg(not(windows))]
|
||||
|
|
76
src/utils.rs
76
src/utils.rs
|
@ -19,7 +19,7 @@ impl ChangeListener for Changeset {
|
|||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::{anyhow, Context, Result};
|
||||
use color_eyre::eyre::Result;
|
||||
use directories::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
use tracing::error;
|
||||
|
@ -31,33 +31,48 @@ use tracing_subscriber::{
|
|||
use crate::tui::Tui;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CRATE_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
|
||||
pub static ref DATA_FOLDER: Option<PathBuf> = std::env::var(format!("{}_DATA", CRATE_NAME.clone()))
|
||||
pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
|
||||
pub static ref DATA_FOLDER: Option<PathBuf> = std::env::var(format!("{}_DATA", PROJECT_NAME.clone()))
|
||||
.ok()
|
||||
.map(PathBuf::from);
|
||||
pub static ref CONFIG_FOLDER: Option<PathBuf> = std::env::var(format!("{}_CONFIG", CRATE_NAME.clone()))
|
||||
pub static ref CONFIG_FOLDER: Option<PathBuf> = std::env::var(format!("{}_CONFIG", PROJECT_NAME.clone()))
|
||||
.ok()
|
||||
.map(PathBuf::from);
|
||||
pub static ref GIT_COMMIT_HASH: String =
|
||||
std::env::var(format!("{}_GIT_INFO", CRATE_NAME.clone())).unwrap_or_else(|_| String::from("Unknown"));
|
||||
pub static ref LOG_FILE: String = format!("{}.log", CRATE_NAME.to_lowercase());
|
||||
std::env::var(format!("{}_GIT_INFO", PROJECT_NAME.clone())).unwrap_or_else(|_| String::from("Unknown"));
|
||||
pub static ref LOG_LEVEL: String = std::env::var(format!("{}_LOG_LEVEL", PROJECT_NAME.clone())).unwrap_or_default();
|
||||
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME").to_lowercase());
|
||||
}
|
||||
|
||||
fn project_directory() -> Option<ProjectDirs> {
|
||||
ProjectDirs::from("com", "kdheepak", CRATE_NAME.clone().to_lowercase().as_str())
|
||||
ProjectDirs::from("com", "kdheepak", PROJECT_NAME.clone().to_lowercase().as_str())
|
||||
}
|
||||
|
||||
pub fn initialize_panic_handler() -> Result<()> {
|
||||
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks();
|
||||
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default()
|
||||
.panic_section(format!(
|
||||
"This is a bug. Consider reporting it at {}",
|
||||
env!("CARGO_PKG_REPOSITORY")
|
||||
))
|
||||
.display_location_section(true)
|
||||
.display_env_section(true)
|
||||
.issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new"))
|
||||
.add_issue_metadata("version", env!("CARGO_PKG_VERSION"))
|
||||
.add_issue_metadata("os", std::env::consts::OS)
|
||||
.add_issue_metadata("arch", std::env::consts::ARCH)
|
||||
.into_hooks();
|
||||
eyre_hook.install()?;
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
if let Ok(t) = Tui::new(0) {
|
||||
if let Ok(t) = Tui::new() {
|
||||
if let Err(r) = t.exit() {
|
||||
error!("Unable to exit Terminal: {:?}", r);
|
||||
}
|
||||
}
|
||||
|
||||
let msg = format!("{}", panic_hook.panic_report(panic_info));
|
||||
tracing::error!("{}", strip_ansi_escapes::strip_str(&msg));
|
||||
eprintln!("{}", msg);
|
||||
log::error!("Error: {}", strip_ansi_escapes::strip_str(msg));
|
||||
|
||||
use human_panic::{handle_dump, print_msg, Metadata};
|
||||
let meta = Metadata {
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
|
@ -65,9 +80,20 @@ pub fn initialize_panic_handler() -> Result<()> {
|
|||
authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(),
|
||||
homepage: env!("CARGO_PKG_HOMEPAGE").into(),
|
||||
};
|
||||
|
||||
let file_path = handle_dump(&meta, panic_info);
|
||||
print_msg(file_path, &meta).expect("human-panic: printing error message to console failed");
|
||||
eprintln!("{}", msg);
|
||||
|
||||
// Better Panic. Only enabled *when* debugging.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
better_panic::Settings::auto()
|
||||
.most_recent_first(false)
|
||||
.lineno_suffix(true)
|
||||
.verbosity(better_panic::Verbosity::Full)
|
||||
.create_panic_handler()(panic_info);
|
||||
}
|
||||
|
||||
std::process::exit(libc::EXIT_FAILURE);
|
||||
}));
|
||||
Ok(())
|
||||
|
@ -95,7 +121,8 @@ pub fn get_config_dir() -> PathBuf {
|
|||
directory
|
||||
}
|
||||
|
||||
pub fn initialize_logging(directory: PathBuf) -> Result<()> {
|
||||
pub fn initialize_logging() -> Result<()> {
|
||||
let directory = get_data_dir();
|
||||
std::fs::create_dir_all(directory.clone())?;
|
||||
let log_path = directory.join(LOG_FILE.clone());
|
||||
let log_file = std::fs::File::create(log_path)?;
|
||||
|
@ -106,23 +133,22 @@ pub fn initialize_logging(directory: PathBuf) -> Result<()> {
|
|||
.with_target(false)
|
||||
.with_ansi(false)
|
||||
.with_filter(EnvFilter::from_default_env());
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(file_subscriber)
|
||||
.with(tui_logger::tracing_subscriber_layer())
|
||||
// .with(tui_logger::tracing_subscriber_layer())
|
||||
.with(ErrorLayer::default())
|
||||
.init();
|
||||
let default_level =
|
||||
std::env::var("RUST_LOG").map_or(log::LevelFilter::Info, |val| match val.to_lowercase().as_str() {
|
||||
"off" => log::LevelFilter::Off,
|
||||
"error" => log::LevelFilter::Error,
|
||||
"warn" => log::LevelFilter::Warn,
|
||||
"info" => log::LevelFilter::Info,
|
||||
"debug" => log::LevelFilter::Debug,
|
||||
"trace" => log::LevelFilter::Trace,
|
||||
_ => log::LevelFilter::Info,
|
||||
});
|
||||
tui_logger::set_default_level(default_level);
|
||||
|
||||
// let default_level = match LOG_LEVEL.clone().to_lowercase().as_str() {
|
||||
// "off" => log::LevelFilter::Off,
|
||||
// "error" => log::LevelFilter::Error,
|
||||
// "warn" => log::LevelFilter::Warn,
|
||||
// "info" => log::LevelFilter::Info,
|
||||
// "debug" => log::LevelFilter::Debug,
|
||||
// "trace" => log::LevelFilter::Trace,
|
||||
// _ => log::LevelFilter::Info,
|
||||
// };
|
||||
// tui_logger::set_default_level(default_level);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue