summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qu3nt1n@gmail.com>2023-10-31 17:17:43 +0100
committerqkzk <qu3nt1n@gmail.com>2023-10-31 17:17:43 +0100
commit50d448c93b13fcc47ba8ff197951694345b840e8 (patch)
treea86b1f499fd6674c86b419b6fb5640171f2a70f6
parenta859239199a5bfacca351f339009739bc51bbda5 (diff)
move app and refresher to separate files
-rw-r--r--src/app.rs135
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs201
-rw-r--r--src/refresher.rs66
4 files changed, 205 insertions, 199 deletions
diff --git a/src/app.rs b/src/app.rs
new file mode 100644
index 0000000..3e31b4c
--- /dev/null
+++ b/src/app.rs
@@ -0,0 +1,135 @@
+use std::sync::Arc;
+
+use anyhow::Result;
+use log::info;
+use tuikit::prelude::Event;
+
+use crate::config::load_config;
+use crate::constant_strings_paths::{CONFIG_PATH, OPENER_PATH};
+use crate::event_dispatch::EventDispatcher;
+use crate::help::Help;
+use crate::log::set_loggers;
+use crate::opener::{load_opener, Opener};
+use crate::refresher::Refresher;
+use crate::status::Status;
+use crate::term_manager::{Display, EventReader};
+use crate::utils::{clear_tmp_file, init_term, print_on_quit};
+
+/// Holds everything about the application itself.
+/// Most attributes holds an `Arc<tuiki::Term::term>`.
+/// Dropping the instance of FM allows to write again to stdout.
+pub struct FM {
+ /// Poll the event sent to the terminal by the user or the OS
+ event_reader: EventReader,
+ /// Associate the event to a method, modifing the status.
+ event_dispatcher: EventDispatcher,
+ /// Current status of the application. Mostly the filetrees
+ status: Status,
+ /// Responsible for the display on screen.
+ display: Display,
+ /// Refresher is used to force a refresh when a file has been modified externally.
+ /// It send `Event::Key(Key::AltPageUp)` every 10 seconds.
+ /// It also has a `mpsc::Sender` to send a quit message and reset the cursor.
+ refresher: Refresher,
+}
+
+impl FM {
+ /// Setup everything the application needs in its main loop :
+ /// an `EventReader`,
+ /// an `EventDispatcher`,
+ /// a `Status`,
+ /// a `Display`,
+ /// a `Refresher`.
+ /// It reads and drops the configuration from the config file.
+ /// If the config can't be parsed, it exits with error code 1.
+ pub fn start() -> Result<Self> {
+ set_loggers()?;
+ let Ok(config) = load_config(CONFIG_PATH) else {
+ exit_wrong_config()
+ };
+ let term = Arc::new(init_term()?);
+ let event_reader = EventReader::new(Arc::clone(&term));
+ let event_dispatcher = EventDispatcher::new(config.binds.clone());
+ let opener = load_opener(OPENER_PATH, &config.terminal).unwrap_or_else(|_| {
+ eprintln!("Couldn't read the opener config file at {OPENER_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/opener.yaml for an example. Using default.");
+ info!("Couldn't read opener file at {OPENER_PATH}. Using default.");
+ Opener::new(&config.terminal)
+ });
+ let help = Help::from_keybindings(&config.binds, &opener)?.help;
+ let display = Display::new(Arc::clone(&term));
+ let status = Status::new(
+ display.height()?,
+ Arc::clone(&term),
+ help,
+ opener,
+ &config.settings,
+ )?;
+ let refresher = Refresher::new(term);
+ drop(config);
+ Ok(Self {
+ event_reader,
+ event_dispatcher,
+ status,
+ display,
+ refresher,
+ })
+ }
+
+ /// Return the last event received by the terminal
+ pub fn poll_event(&self) -> Result<Event> {
+ self.event_reader.poll_event()
+ }
+
+ /// Force clear the display if the status requires it, then reset it in status.
+ pub fn force_clear_if_needed(&mut self) -> Result<()> {
+ if self.status.force_clear {
+ self.display.force_clear()?;
+ self.status.force_clear = false;
+ }
+ Ok(())
+ }
+
+ /// Update itself, changing its status.
+ pub fn update(&mut self, event: Event) -> Result<()> {
+ self.event_dispatcher.dispatch(
+ &mut self.status,
+ event,
+ // &self.colors,
+ self.event_reader.term_height()?,
+ )?;
+ self.status.refresh_disks();
+ Ok(())
+ }
+
+ /// Display itself using its `display` attribute.
+ pub fn display(&mut self) -> Result<()> {
+ self.force_clear_if_needed()?;
+ self.display.display_all(&self.status)
+ }
+
+ /// True iff the application must quit.
+ pub fn must_quit(&self) -> bool {
+ self.status.must_quit()
+ }
+
+ /// Display the cursor,
+ /// drop itself, which allow us to print normally afterward
+ /// print the final path
+ pub fn quit(self) -> Result<()> {
+ clear_tmp_file();
+ self.display.show_cursor()?;
+ let final_path = self.status.selected_path_str().to_owned();
+ self.refresher.quit()?;
+ print_on_quit(&final_path);
+ info!("fm is shutting down");
+ Ok(())
+ }
+}
+
+/// Exit the application and log a message.
+/// Used when the config can't be read.
+fn exit_wrong_config() -> ! {
+ eprintln!("Couldn't load the config file at {CONFIG_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/config.yaml for an example.");
+ info!("Couldn't read the config file {CONFIG_PATH}");
+ std::process::exit(1)
+}
diff --git a/src/lib.rs b/src/lib.rs
index 47dab28..900c090 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
pub mod action_map;
+pub mod app;
pub mod args;
pub mod bulkrename;
pub mod cli_info;
@@ -31,6 +32,7 @@ pub mod nvim;
pub mod opener;
pub mod password;
pub mod preview;
+pub mod refresher;
pub mod removable_devices;
pub mod selectable_content;
pub mod shell_menu;
diff --git a/src/main.rs b/src/main.rs
index 18b04cc..c9a0381 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,207 +1,10 @@
-use std::sync::mpsc::{self, TryRecvError};
-use std::sync::Arc;
-use std::thread;
-use std::time::Duration;
-
-use anyhow::Result;
-use log::info;
-
-use fm::config::load_config;
-use fm::constant_strings_paths::{CONFIG_PATH, OPENER_PATH};
-use fm::event_dispatch::EventDispatcher;
-use fm::help::Help;
-use fm::log::set_loggers;
-use fm::opener::{load_opener, Opener};
-use fm::status::Status;
-use fm::term_manager::{Display, EventReader};
-use fm::utils::{clear_tmp_file, init_term, print_on_quit};
-
-/// Holds everything about the application itself.
-/// Most attributes holds an `Arc<tuiki::Term::term>`.
-/// Dropping the instance of FM allows to write again to stdout.
-struct FM {
- /// Poll the event sent to the terminal by the user or the OS
- event_reader: EventReader,
- /// Associate the event to a method, modifing the status.
- event_dispatcher: EventDispatcher,
- /// Current status of the application. Mostly the filetrees
- status: Status,
- /// Responsible for the display on screen.
- display: Display,
- /// Refresher is used to force a refresh when a file has been modified externally.
- /// It send `Event::Key(Key::AltPageUp)` every 10 seconds.
- /// It also has a `mpsc::Sender` to send a quit message and reset the cursor.
- refresher: Refresher,
-}
-
-impl FM {
- /// Setup everything the application needs in its main loop :
- /// an `EventReader`,
- /// an `EventDispatcher`,
- /// a `Status`,
- /// a `Display`,
- /// a `Refresher`.
- /// It reads and drops the configuration from the config file.
- /// If the config can't be parsed, it exits with error code 1.
- fn start() -> Result<Self> {
- let Ok(config) = load_config(CONFIG_PATH) else {
- exit_wrong_config()
- };
- let term = Arc::new(init_term()?);
- let event_reader = EventReader::new(Arc::clone(&term));
- let event_dispatcher = EventDispatcher::new(config.binds.clone());
- let opener = load_opener(OPENER_PATH, &config.terminal).unwrap_or_else(|_| {
- eprintln!("Couldn't read the opener config file at {OPENER_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/opener.yaml for an example. Using default.");
- info!("Couldn't read opener file at {OPENER_PATH}. Using default.");
- Opener::new(&config.terminal)
- });
- let help = Help::from_keybindings(&config.binds, &opener)?.help;
- let display = Display::new(Arc::clone(&term));
- let status = Status::new(
- display.height()?,
- Arc::clone(&term),
- help,
- opener,
- &config.settings,
- )?;
- let refresher = Refresher::spawn(term);
- drop(config);
- Ok(Self {
- event_reader,
- event_dispatcher,
- status,
- display,
- refresher,
- })
- }
-
- /// Return the last event received by the terminal
- fn poll_event(&self) -> Result<tuikit::prelude::Event> {
- self.event_reader.poll_event()
- }
-
- /// Force clear the display if the status requires it, then reset it in status.
- fn force_clear_if_needed(&mut self) -> Result<()> {
- if self.status.force_clear {
- self.display.force_clear()?;
- self.status.force_clear = false;
- }
- Ok(())
- }
-
- /// Update itself, changing its status.
- fn update(&mut self, event: tuikit::prelude::Event) -> Result<()> {
- self.event_dispatcher.dispatch(
- &mut self.status,
- event,
- // &self.colors,
- self.event_reader.term_height()?,
- )?;
- self.status.refresh_disks();
- Ok(())
- }
-
- /// Display itself using its `display` attribute.
- fn display(&mut self) -> Result<()> {
- self.force_clear_if_needed()?;
- self.display.display_all(&self.status)
- }
-
- /// True iff the application must quit.
- fn must_quit(&self) -> bool {
- self.status.must_quit()
- }
-
- /// Display the cursor,
- /// drop itself, which allow us to print normally afterward
- /// print the final path
- fn quit(self) -> Result<()> {
- clear_tmp_file();
- self.display.show_cursor()?;
- let final_path = self.status.selected_path_str().to_owned();
- self.refresher.quit()?;
- print_on_quit(&final_path);
- info!("fm is shutting down");
- Ok(())
- }
-}
-
-/// Allows refresh if the current path has been modified externally.
-struct Refresher {
- /// Sender of messages, used to terminate the thread properly
- tx: mpsc::Sender<()>,
- /// Handle to the `term::Event` sender thread.
- handle: thread::JoinHandle<()>,
-}
-
-impl Refresher {
- /// Between 2 refreshed
- const TEN_SECONDS_IN_DECISECONDS: u8 = 10 * 10;
-
- /// Event sent to Fm event poller which is interpreted
- /// as a request for refresh.
- /// This key can't be bound to anything (who would use that ?).
- const REFRESH_EVENT: tuikit::prelude::Event =
- tuikit::prelude::Event::Key(tuikit::prelude::Key::AltPageUp);
-
- /// Spawn a thread which sends events to the terminal.
- /// Those events are interpreted as refresh requests.
- /// It also listen to a receiver for quit messages.
- ///
- /// This will send periodically an `Key::AltPageUp` event to the terminal which requires a refresh.
- /// This keybind is reserved and can't be bound to anything.
- ///
- /// Using Event::User(()) conflicts with skim internal which interpret this
- /// event as a signal(1) and hangs the terminal.
- fn spawn(term: Arc<tuikit::term::Term>) -> Self {
- let (tx, rx) = mpsc::channel();
- let mut counter: u8 = 0;
- let handle = thread::spawn(move || loop {
- match rx.try_recv() {
- Ok(_) | Err(TryRecvError::Disconnected) => {
- log::info!("terminating refresher");
- let _ = term.show_cursor(true);
- return;
- }
- Err(TryRecvError::Empty) => {}
- }
- counter += 1;
- thread::sleep(Duration::from_millis(100));
- if counter >= Self::TEN_SECONDS_IN_DECISECONDS {
- counter = 0;
- if term.send_event(Self::REFRESH_EVENT).is_err() {
- break;
- }
- }
- });
- Self { tx, handle }
- }
-
- /// Send a quit message to the receiver, signaling it to quit.
- /// Join the refreshing thread which should be terminated.
- fn quit(self) -> Result<()> {
- self.tx.send(())?;
- let _ = self.handle.join();
- Ok(())
- }
-}
-
-/// Exit the application and log a message.
-/// Used when the config can't be read.
-fn exit_wrong_config() -> ! {
- eprintln!("Couldn't load the config file at {CONFIG_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/config.yaml for an example.");
- info!("Couldn't read the config file {CONFIG_PATH}");
- std::process::exit(1)
-}
-
/// Main function
/// Init the status and display and listen to events (keyboard, mouse, resize, custom...).
/// The application is redrawn after every event.
/// When the user issues a quit event, the main loop is broken
/// Then we reset the cursor, drop everything holding a terminal and print the last path.
-fn main() -> Result<()> {
- set_loggers()?;
- let mut fm = FM::start()?;
+fn main() -> anyhow::Result<()> {
+ let mut fm = fm::app::FM::start()?;
while let Ok(event) = fm.poll_event() {
fm.update(event)?;
diff --git a/src/refresher.rs b/src/refresher.rs
new file mode 100644
index 0000000..ce26ffb
--- /dev/null
+++ b/src/refresher.rs
@@ -0,0 +1,66 @@
+use std::sync::mpsc::{self, TryRecvError};
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+use anyhow::Result;
+use tuikit::prelude::{Event, Key};
+
+/// Allows refresh if the current path has been modified externally.
+pub struct Refresher {
+ /// Sender of messages, used to terminate the thread properly
+ tx: mpsc::Sender<()>,
+ /// Handle to the `term::Event` sender thread.
+ handle: thread::JoinHandle<()>,
+}
+
+impl Refresher {
+ /// Between 2 refreshed
+ const TEN_SECONDS_IN_DECISECONDS: u8 = 10 * 10;
+
+ /// Event sent to Fm event poller which is interpreted
+ /// as a request for refresh.
+ /// This key can't be bound to anything (who would use that ?).
+ const REFRESH_EVENT: Event = Event::Key(Key::AltPageUp);
+
+ /// Spawn a thread which sends events to the terminal.
+ /// Those events are interpreted as refresh requests.
+ /// It also listen to a receiver for quit messages.
+ ///
+ /// This will send periodically an `Key::AltPageUp` event to the terminal which requires a refresh.
+ /// This keybind is reserved and can't be bound to anything.
+ ///
+ /// Using Event::User(()) conflicts with skim internal which interpret this
+ /// event as a signal(1) and hangs the terminal.
+ pub fn new(term: Arc<tuikit::term::Term>) -> Self {
+ let (tx, rx) = mpsc::channel();
+ let mut counter: u8 = 0;
+ let handle = thread::spawn(move || loop {
+ match rx.try_recv() {
+ Ok(_) | Err(TryRecvError::Disconnected) => {
+ log::info!("terminating refresher");
+ let _ = term.show_cursor(true);
+ return;
+ }
+ Err(TryRecvError::Empty) => {}
+ }
+ counter += 1;
+ thread::sleep(Duration::from_millis(100));
+ if counter >= Self::TEN_SECONDS_IN_DECISECONDS {
+ counter = 0;
+ if term.send_event(Self::REFRESH_EVENT).is_err() {
+ break;
+ }
+ }
+ });
+ Self { tx, handle }
+ }
+
+ /// Send a quit message to the receiver, signaling it to quit.
+ /// Join the refreshing thread which should be terminated.
+ pub fn quit(self) -> Result<()> {
+ self.tx.send(())?;
+ let _ = self.handle.join();
+ Ok(())
+ }
+}