diff --git a/crates/wayfarer/src/main.rs b/crates/wayfarer/src/main.rs index 6ca253d..e30fb9e 100644 --- a/crates/wayfarer/src/main.rs +++ b/crates/wayfarer/src/main.rs @@ -1,19 +1,27 @@ mod edit; mod show; -mod state; mod tui; mod watcher; +use std::fs::create_dir_all; +use std::path::PathBuf; + use anyhow::Result; use clap::Parser as ArgParser; +use directories::ProjectDirs; use tracing::Level as TracingLevel; use tracing_appender::rolling; use tracing_subscriber::filter::Targets; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; -use crate::state::logs_dir; + +lazy_static::lazy_static! { + pub static ref DIRS: ProjectDirs = { + ProjectDirs::from("", "valeth", "wayfarer").unwrap() + }; +} #[derive(Debug, ArgParser)] @@ -80,3 +88,17 @@ fn tracing_setup() -> Result<()> { Ok(()) } + + +fn logs_dir() -> Result { + let log_root_path = DIRS + .state_dir() + .unwrap_or_else(|| DIRS.cache_dir()) + .join("logs"); + + if !log_root_path.exists() { + create_dir_all(&log_root_path)?; + } + + Ok(log_root_path) +} diff --git a/crates/wayfarer/src/state.rs b/crates/wayfarer/src/state.rs deleted file mode 100644 index 2b3792f..0000000 --- a/crates/wayfarer/src/state.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::fs::create_dir_all; -use std::path::PathBuf; -#[cfg(feature = "tui")] -use std::{ - fs::{self, read_to_string}, - io::Write, - os::unix::prelude::OsStrExt, - path::Path, -}; - -use anyhow::Result; -use directories::ProjectDirs; -#[cfg(feature = "tui")] -use jrny_save::Savefile; - - -lazy_static::lazy_static! { - static ref DIRS: ProjectDirs = { - ProjectDirs::from("", "valeth", "wayfarer").unwrap() - }; -} - - -pub fn logs_dir() -> Result { - let log_root_path = DIRS - .state_dir() - .unwrap_or_else(|| DIRS.cache_dir()) - .join("logs"); - - if !log_root_path.exists() { - create_dir_all(&log_root_path)?; - } - - Ok(log_root_path) -} - - -#[cfg(feature = "tui")] -#[derive(Debug, Default)] -pub struct PersistentState { - pub savefile: Option, -} - -#[cfg(feature = "tui")] -impl PersistentState { - pub fn load() -> Result { - let data_dir = DIRS.data_local_dir(); - - if !data_dir.exists() { - create_dir_all(&data_dir)?; - } - - let savefile = load_last_active_savefile()?; - - Ok(Self { savefile }) - } - - #[cfg(feature = "watch")] - pub fn reload_active_savefile(&mut self) -> Result<()> { - if let Some(cur_savefile) = &self.savefile { - let new_savefile = Savefile::from_path(&cur_savefile.path)?; - self.savefile = Some(new_savefile); - } - - Ok(()) - } - - pub fn set_active_savefile_path

(&mut self, path: P) -> Result<()> - where - P: AsRef, - { - let savefile = Savefile::from_path(&path)?; - - let state_path = DIRS.data_local_dir().join("active_savefile"); - let mut state_file = fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(state_path)?; - - let active_savefile = savefile.path.as_os_str().as_bytes(); - - state_file.write_all(active_savefile)?; - self.savefile = Some(savefile); - - Ok(()) - } -} - - -#[cfg(feature = "tui")] -fn load_last_active_savefile() -> Result> { - let state_path = DIRS.data_local_dir().join("active_savefile"); - - if !state_path.exists() { - return Ok(None); - } - - let path = read_to_string(&state_path).unwrap(); - - let savefile = Savefile::from_path(path.trim_end())?; - - Ok(Some(savefile)) -} diff --git a/crates/wayfarer/src/tui.rs b/crates/wayfarer/src/tui.rs index f9b7baf..5f5f689 100644 --- a/crates/wayfarer/src/tui.rs +++ b/crates/wayfarer/src/tui.rs @@ -1,6 +1,7 @@ #![cfg(feature = "tui")] mod events; +mod state; mod view; @@ -16,11 +17,8 @@ use crossterm::terminal::{ }; use ratatui::backend::CrosstermBackend; use tracing::{debug, error, info}; -use tui_input::Input; -use crate::state::PersistentState; -#[cfg(feature = "watch")] -use crate::watcher::FileWatcher; +use self::state::{Mode, State}; use crate::Args as AppArgs; @@ -31,15 +29,6 @@ type Terminal = ratatui::Terminal>; pub struct Args; -pub struct State { - persistent: PersistentState, - mode: Mode, - file_select: Input, - #[cfg(feature = "watch")] - file_watcher: Option, -} - - #[derive(Debug, Clone)] #[non_exhaustive] pub enum Message { @@ -57,30 +46,11 @@ pub enum Message { } -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub enum Mode { - #[default] - Normal, - - ShowError(String), - - SelectFile, -} - - pub(crate) fn execute(_app_args: &AppArgs, _args: &Args) -> Result<()> { - let persistent = PersistentState::load()?; + let state = State::load()?; let mut terminal = setup()?; - let state = State { - persistent, - mode: Mode::default(), - file_select: Input::default(), - #[cfg(feature = "watch")] - file_watcher: None, - }; - run(&mut terminal, state)?; reset(terminal)?; @@ -137,45 +107,39 @@ fn handle_message( Message::LoadFile => { let file_path = state.file_select.value(); - info!("Loading file {}", file_path); - state.persistent.set_active_savefile_path(file_path)?; + state.set_selected_as_active_savefile()?; #[cfg(feature = "watch")] - if state.file_watcher.is_some() { - state.file_watcher = None; + if state.is_watching_file() { + state.reset_file_watcher(); } msg_tx.send(Message::SetMode(Mode::Normal))?; } #[cfg(feature = "watch")] - Message::ToggleFileWatch if state.persistent.savefile.is_some() => { - let savefile = state.persistent.savefile.as_ref().unwrap(); + Message::ToggleFileWatch => { + if let Some(savefile) = state.savefile() { + if state.is_watching_file() { + let evq_tx = msg_tx.clone(); + let callback = move || { + evq_tx.send(Message::ReloadFile).unwrap(); + }; - if state.file_watcher.is_none() { - let evq_tx = msg_tx.clone(); - let callback = move || { - evq_tx.send(Message::ReloadFile).unwrap(); - }; - - info!("Starting file watcher on {}", savefile.path.display()); - - let file_watcher = FileWatcher::new(&savefile.path, callback); - state.file_watcher = Some(file_watcher); - } else { - info!("Stopped file watcher on {}", savefile.path.display()); - - state.file_watcher = None; + info!("Starting file watcher on {}", savefile.path.display()); + state.enable_file_watcher(callback); + } else { + info!("Stopped file watcher on {}", savefile.path.display()); + state.reset_file_watcher(); + } } } #[cfg(feature = "watch")] - Message::ReloadFile if state.persistent.savefile.is_some() => { - debug!("Reloading file"); - - state.persistent.reload_active_savefile()?; + Message::ReloadFile => { + state.reload_active_savefile()?; } _ => (), diff --git a/crates/wayfarer/src/tui/state.rs b/crates/wayfarer/src/tui/state.rs new file mode 100644 index 0000000..c7bda15 --- /dev/null +++ b/crates/wayfarer/src/tui/state.rs @@ -0,0 +1,122 @@ +use std::fs::{self, create_dir_all, read_to_string}; +use std::io::Write; +use std::os::unix::prelude::OsStrExt; + +use anyhow::Result; +use jrny_save::Savefile; +use tui_input::Input; + +#[cfg(feature = "watch")] +use crate::watcher::FileWatcher; +use crate::DIRS; + + +pub struct State { + savefile: Option, + pub mode: Mode, + pub file_select: Input, + #[cfg(feature = "watch")] + file_watcher: Option, +} + + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub enum Mode { + #[default] + Normal, + + ShowError(String), + + SelectFile, +} + +impl State { + pub fn load() -> Result { + let data_dir = DIRS.data_local_dir(); + + if !data_dir.exists() { + create_dir_all(&data_dir)?; + } + + let savefile = load_last_active_savefile()?; + + Ok(Self { + savefile, + mode: Mode::default(), + file_select: Input::default(), + #[cfg(feature = "watch")] + file_watcher: None, + }) + } + + pub fn savefile(&self) -> Option<&Savefile> { + self.savefile.as_ref() + } + + pub fn set_selected_as_active_savefile(&mut self) -> Result<()> { + let savefile = Savefile::from_path(&self.file_select.value())?; + + let state_path = DIRS.data_local_dir().join("active_savefile"); + let mut state_file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(state_path)?; + + let active_savefile = savefile.path.as_os_str().as_bytes(); + + state_file.write_all(active_savefile)?; + self.savefile = Some(savefile); + + Ok(()) + } + + #[cfg(feature = "watch")] + pub fn is_watching_file(&self) -> bool { + self.file_watcher.is_some() + } + + #[cfg(feature = "watch")] + pub fn enable_file_watcher(&mut self, callback: F) + where + F: Fn() + Send + 'static, + { + if let Some(savefile) = &self.savefile { + let file_watcher = FileWatcher::new(&savefile.path, callback); + self.file_watcher = Some(file_watcher); + } + } + + #[cfg(feature = "watch")] + pub fn reset_file_watcher(&mut self) { + self.file_watcher = None; + } + + #[cfg(feature = "watch")] + pub fn reload_active_savefile(&mut self) -> Result<()> { + use tracing::debug; + + if let Some(cur_savefile) = &self.savefile { + debug!("Reloading file"); + let new_savefile = Savefile::from_path(&cur_savefile.path)?; + self.savefile = Some(new_savefile); + } + + Ok(()) + } +} + + +fn load_last_active_savefile() -> Result> { + let state_path = DIRS.data_local_dir().join("active_savefile"); + + if !state_path.exists() { + return Ok(None); + } + + let path = read_to_string(&state_path)?; + + let savefile = Savefile::from_path(path.trim_end())?; + + Ok(Some(savefile)) +} diff --git a/crates/wayfarer/src/tui/view/info.rs b/crates/wayfarer/src/tui/view/info.rs index 31883dc..355d9d1 100644 --- a/crates/wayfarer/src/tui/view/info.rs +++ b/crates/wayfarer/src/tui/view/info.rs @@ -7,7 +7,7 @@ use crate::tui::State; pub fn render(state: &mut State, frame: &mut Frame, area: Rect) { - match &state.persistent.savefile { + match state.savefile() { Some(savefile) => render_info(savefile, frame, area), None => render_no_active_file(frame, area), } diff --git a/crates/wayfarer/src/tui/view/status_bar.rs b/crates/wayfarer/src/tui/view/status_bar.rs index d727571..db5f638 100644 --- a/crates/wayfarer/src/tui/view/status_bar.rs +++ b/crates/wayfarer/src/tui/view/status_bar.rs @@ -18,20 +18,20 @@ pub fn render(state: &State, mut frame: &mut Frame, area: Rect) { } #[cfg(feature = "watch")] - Mode::Normal if state.file_watcher.is_some() && state.persistent.savefile.is_some() => { - let savefile = state.persistent.savefile.as_ref().unwrap(); - let text = format!("Watching file: {}", savefile.path.display()); - let status = Paragraph::new(text).block(status_block); - frame.render_widget(status, area); + Mode::Normal if state.is_watching_file() => { + if let Some(savefile) = state.savefile() { + let text = format!("Watching file: {}", savefile.path.display()); + let status = Paragraph::new(text).block(status_block); + frame.render_widget(status, area); + } } - Mode::Normal if state.persistent.savefile.is_none() => (), - Mode::Normal => { - let savefile = state.persistent.savefile.as_ref().unwrap(); - let text = format!("Showing file: {}", savefile.path.display()); - let status = Paragraph::new(text).block(status_block); - frame.render_widget(status, area); + if let Some(savefile) = state.savefile() { + let text = format!("Showing file: {}", savefile.path.display()); + let status = Paragraph::new(text).block(status_block); + frame.render_widget(status, area); + } } Mode::SelectFile => render_file_select(&state, &mut frame, status_block, area),