diff options
author | Jeff Zhao <jeff.no.zhao@gmail.com> | 2024-03-10 21:17:09 -0400 |
---|---|---|
committer | Jeff Zhao <jeff.no.zhao@gmail.com> | 2024-03-10 21:17:09 -0400 |
commit | 8af827604620f8135c98d6f4c8e4b647c06d1c32 (patch) | |
tree | 8641bdd607177d92df3c91e700493c65f120859d | |
parent | 4267cb55085b24770ee35c841c940f8ea7233445 (diff) |
move preview area into PreviewContext
- move a bunch of methods out into functions
-rw-r--r-- | src/commands/bulk_rename.rs | 5 | ||||
-rw-r--r-- | src/commands/custom_search.rs | 2 | ||||
-rw-r--r-- | src/commands/fzf.rs | 6 | ||||
-rw-r--r-- | src/commands/open_file.rs | 6 | ||||
-rw-r--r-- | src/commands/show_help.rs | 3 | ||||
-rw-r--r-- | src/commands/sub_process.rs | 2 | ||||
-rw-r--r-- | src/commands/zoxide.rs | 2 | ||||
-rw-r--r-- | src/context/app_context.rs | 97 | ||||
-rw-r--r-- | src/context/preview_context.rs | 105 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/run.rs | 13 | ||||
-rw-r--r-- | src/ui/backend.rs | 12 | ||||
-rw-r--r-- | src/ui/views/tui_folder_view.rs | 17 |
13 files changed, 151 insertions, 123 deletions
diff --git a/src/commands/bulk_rename.rs b/src/commands/bulk_rename.rs index 9cc7f26..816ff5e 100644 --- a/src/commands/bulk_rename.rs +++ b/src/commands/bulk_rename.rs @@ -6,6 +6,7 @@ use std::process; use rand::Rng; +use crate::context::remove_external_preview; use crate::context::AppContext; use crate::error::{AppError, AppErrorKind, AppResult}; use crate::ui::AppBackend; @@ -125,10 +126,10 @@ pub fn _bulk_rename(context: &mut AppContext) -> AppResult { } pub fn bulk_rename(context: &mut AppContext, backend: &mut AppBackend) -> AppResult { - context.remove_external_preview(); + remove_external_preview(context); backend.terminal_drop(); let res = _bulk_rename(context); - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; reload::soft_reload_curr_tab(context)?; res } diff --git a/src/commands/custom_search.rs b/src/commands/custom_search.rs index a9d4405..422143e 100644 --- a/src/commands/custom_search.rs +++ b/src/commands/custom_search.rs @@ -57,7 +57,7 @@ pub fn custom_search( .stdout(Stdio::piped()) .spawn()? .wait_with_output()?; - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; cmd_result } else { cmd.output()? diff --git a/src/commands/fzf.rs b/src/commands/fzf.rs index 9c60c54..bc4667a 100644 --- a/src/commands/fzf.rs +++ b/src/commands/fzf.rs @@ -51,7 +51,7 @@ pub fn fzf_multi( } fn fzf_impl( - context: &mut AppContext, + _context: &mut AppContext, backend: &mut AppBackend, items: Vec<String>, args: Vec<String>, @@ -68,7 +68,7 @@ fn fzf_impl( let mut fzf = match cmd.spawn() { Ok(child) => child, Err(e) => { - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; return Err(AppError::from(e)); } }; @@ -82,7 +82,7 @@ fn fzf_impl( } let fzf_output = fzf.wait_with_output(); - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; if let Ok(output) = fzf_output { if output.status.success() { diff --git a/src/commands/open_file.rs b/src/commands/open_file.rs index d206f35..0d248e9 100644 --- a/src/commands/open_file.rs +++ b/src/commands/open_file.rs @@ -60,7 +60,7 @@ where } else { backend.terminal_drop(); let res = execute_and_wait(option, files); - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; res?; } Ok(()) @@ -79,7 +79,7 @@ fn _open_with_xdg( backend.terminal_drop(); let handle = open::that_in_background(path); let result = handle.join(); - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; if let Ok(result) = result { result?; } @@ -136,7 +136,7 @@ where let mut option = ProgramEntry::new(String::from(cmd)); option.args(args_iter); let res = execute_and_wait(&option, files); - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; res? } } diff --git a/src/commands/show_help.rs b/src/commands/show_help.rs index d77dd63..6c98f79 100644 --- a/src/commands/show_help.rs +++ b/src/commands/show_help.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use termion::event::{Event, Key}; use crate::config::clean::keymap::AppKeyMapping; +use crate::context::remove_external_preview; use crate::context::AppContext; use crate::error::AppResult; use crate::event::process_event; @@ -30,7 +31,7 @@ pub fn help_loop( widgets::get_keymap_table(&keymap_t.default_view, &search_query[1..], sort_by) }; - context.remove_external_preview(); + remove_external_preview(context); backend.render(TuiHelp::new(&keymap, &mut offset, &search_query)); let event = match context.poll_event() { diff --git a/src/commands/sub_process.rs b/src/commands/sub_process.rs index e1a5262..2e5a1c8 100644 --- a/src/commands/sub_process.rs +++ b/src/commands/sub_process.rs @@ -126,7 +126,7 @@ pub fn sub_process( // shell command and restore it afterwards backend.terminal_drop(); execute_sub_process(context, words, mode)?; - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; let _ = reload::soft_reload_curr_tab(context); context .message_queue_mut() diff --git a/src/commands/zoxide.rs b/src/commands/zoxide.rs index a065117..222bb9d 100644 --- a/src/commands/zoxide.rs +++ b/src/commands/zoxide.rs @@ -49,7 +49,7 @@ pub fn zoxide_query_interactive(context: &mut AppContext, backend: &mut AppBacke .spawn()?; let zoxide_output = zoxide_process.wait_with_output()?; - backend.terminal_restore(context.config_ref().mouse_support)?; + backend.terminal_restore()?; if zoxide_output.status.success() { if let Ok(zoxide_str) = std::str::from_utf8(&zoxide_output.stdout) { diff --git a/src/context/app_context.rs b/src/context/app_context.rs index a524c6e..4b7f8b7 100644 --- a/src/context/app_context.rs +++ b/src/context/app_context.rs @@ -1,7 +1,5 @@ use std::collections::HashSet; -use std::process; use std::sync::mpsc; -use std::thread; use crate::commands::quit::QuitAction; use crate::config::clean::app::AppConfig; @@ -12,7 +10,7 @@ use crate::context::{ }; use crate::event::{AppEvent, Events}; use crate::preview::preview_file::PreviewFileState; -use crate::ui::{views, AppBackend, PreviewArea}; +use crate::ui::AppBackend; use crate::Args; use notify::{RecursiveMode, Watcher}; use ratatui_image::picker::Picker; @@ -37,7 +35,7 @@ pub struct AppContext { // context related to io workers worker_context: WorkerContext, // context related to previews - preview_context: PreviewContext, + pub preview_context: PreviewContext, // context related to command line commandline_context: CommandLineContext, // user interface context; data which is input to both, the UI rendering and the app state @@ -46,9 +44,6 @@ pub struct AppContext { watcher: notify::RecommendedWatcher, // list of watched paths; seems not to be possible to get them from a notify::Watcher watched_paths: HashSet<path::PathBuf>, - // the last preview area (or None if now preview shown) to check if a preview hook script needs - // to be called - preview_area: Option<PreviewArea>, // the stdout of the last `shell` command pub last_stdout: Option<String>, } @@ -97,6 +92,7 @@ impl AppContext { Self { quit: QuitAction::DoNot, events, + config, args, tab_context: TabContext::new(), local_state: None, @@ -106,99 +102,12 @@ impl AppContext { preview_context: PreviewContext::new(picker, preview_script, event_tx), ui_context: UiContext { layout: vec![] }, commandline_context, - config, watcher, watched_paths, - preview_area: None, last_stdout: None, } } - /// Calls the "preview shown hook script" if it's configured. - /// - /// This method takes the current preview area as argument to check for both, the path of the - /// currently previewed file and the geometry of the preview area. - fn call_preview_shown_hook(&self, preview_area: PreviewArea) { - let preview_options = self.config_ref().preview_options_ref(); - let preview_shown_hook_script = preview_options.preview_shown_hook_script.as_ref(); - if let Some(hook_script) = preview_shown_hook_script { - let hook_script = hook_script.to_path_buf(); - let _ = thread::spawn(move || { - let _ = process::Command::new(hook_script.as_path()) - .arg(preview_area.file_preview_path.as_path()) - .arg(preview_area.preview_area.x.to_string()) - .arg(preview_area.preview_area.y.to_string()) - .arg(preview_area.preview_area.width.to_string()) - .arg(preview_area.preview_area.height.to_string()) - .status(); - }); - } - } - - /// Calls the "preview removed hook script" if it's configured. - fn call_preview_removed_hook(&self) { - let preview_options = self.config_ref().preview_options_ref(); - let preview_removed_hook_script = preview_options.preview_removed_hook_script.as_ref(); - if let Some(hook_script) = preview_removed_hook_script { - let hook_script = hook_script.to_path_buf(); - let _ = thread::spawn(|| { - let _ = process::Command::new(hook_script).status(); - }); - } - } - - /// Updates the external preview to the current preview in Joshuto. - /// - /// The function checks if the current preview content is the same as the preview content which - /// has been last communicated to an external preview logic with the preview hook scripts. - /// If the preview content has changed, one of the hook scripts is called. Either the "preview - /// shown hook", if a preview is shown in Joshuto, or the "preview removed hook", if Joshuto has - /// changed from an entry with preview to an entry without a preview. - /// - /// This function shall be called each time a change of Joshuto's preview can be expected. - /// (As of now, it's called in each cycle of the main loop.) - pub fn update_external_preview(&mut self) { - let layout = &self.ui_context_ref().layout; - let new_preview_area = views::calculate_preview(self, layout[2]); - match new_preview_area.as_ref() { - Some(new) => { - let should_preview = if let Some(old) = &self.preview_area { - new.file_preview_path != old.file_preview_path - || new.preview_area != old.preview_area - } else { - true - }; - if should_preview { - self.call_preview_shown_hook(new.clone()) - } - } - None => { - if self.preview_area.is_some() { - self.call_preview_removed_hook() - } - } - } - self.preview_area = new_preview_area - } - - /// Remove the external preview, if any is present. - /// - /// If the last preview hook script called was the "preview shown hook", this function will - /// call the "preview removed hook" to remove any external preview. - /// Otherwise it won't do anything. - /// - /// To restore the external preview, `update_external_preview` is called which will detect the - /// difference and call the "preview shown hook" again for the current preview (if any). - /// - /// This function can be called if an external preview shall be temporarily removed, for example - /// when entering the help screen. - pub fn remove_external_preview(&mut self) { - if self.preview_area.is_some() { - self.call_preview_removed_hook(); - self.preview_area = None; - } - } - /// Updates the file system supervision with the currently shown directories. pub fn update_watcher(&mut self) { // collect the paths that shall be watched... diff --git a/src/context/preview_context.rs b/src/context/preview_context.rs index 57184a8..ad49cfc 100644 --- a/src/context/preview_context.rs +++ b/src/context/preview_context.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::error::Error; use std::path::{self, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::{self, Command, Stdio}; use std::sync::mpsc::{self, Sender}; use std::sync::Mutex; use std::{io, thread}; @@ -11,13 +11,16 @@ use ratatui_image::picker::Picker; use ratatui_image::protocol::Protocol; use ratatui_image::Resize; +use crate::config::clean::app::preview::PreviewOption; use crate::config::clean::app::AppConfig; use crate::event::{AppEvent, PreviewData}; use crate::lazy_static; use crate::preview::preview_file::{FilePreview, PreviewFileState}; -use crate::ui::{views, AppBackend}; +use crate::ui::{views, AppBackend, PreviewArea}; use crate::AppContext; +use super::{TabContext, UiContext}; + lazy_static! { static ref GUARD: Mutex<()> = Mutex::new(()); } @@ -25,10 +28,15 @@ lazy_static! { type FilePreviewMetadata = HashMap<path::PathBuf, PreviewFileState>; pub struct PreviewContext { + // the last preview area (or None if now preview shown) to check if a preview hook script needs + // to be called + pub preview_area: Option<PreviewArea>, + // hashmap of cached previews previews: FilePreviewMetadata, image_preview: Option<(PathBuf, Box<dyn Protocol>)>, sender_script: Sender<(PathBuf, Rect)>, sender_image: Option<Sender<(PathBuf, Rect)>>, + // for telling main thread when previews are ready event_ts: Sender<AppEvent>, } @@ -88,6 +96,7 @@ impl PreviewContext { }); PreviewContext { + preview_area: None, previews: HashMap::new(), image_preview: None, sender_script, @@ -190,6 +199,21 @@ impl PreviewContext { } } + pub fn update_external_preview(&mut self, preview_area: Option<PreviewArea>) { + self.preview_area = preview_area; + } + + /// Updates the external preview to the current preview in Joshuto. + /// + /// The function checks if the current preview content is the same as the preview content which + /// has been last communicated to an external preview logic with the preview hook scripts. + /// If the preview content has changed, one of the hook scripts is called. Either the "preview + /// shown hook", if a preview is shown in Joshuto, or the "preview removed hook", if Joshuto has + /// changed from an entry with preview to an entry without a preview. + /// + /// This function shall be called each time a change of Joshuto's preview can be expected. + /// (As of now, it's called in each cycle of the main loop.) + fn backend_rect(config: &AppConfig, backend: &AppBackend) -> io::Result<Rect> { let area = backend.terminal_ref().size()?; let area = Rect { @@ -213,3 +237,80 @@ impl PreviewContext { io::Error::new(io::ErrorKind::Other, format!("{err}")) } } + +/// Calls the "preview removed hook script" if it's configured. +pub fn call_preview_removed_hook(preview_options: &PreviewOption) { + let preview_removed_hook_script = preview_options.preview_removed_hook_script.as_ref(); + if let Some(hook_script) = preview_removed_hook_script { + let hook_script = hook_script.to_path_buf(); + let _ = thread::spawn(|| { + let _ = process::Command::new(hook_script).status(); + }); + } +} + +pub fn calculate_external_preview( + tab_context: &TabContext, + preview_context: &PreviewContext, + ui_context: &UiContext, + preview_options: &PreviewOption, +) -> Option<PreviewArea> { + let layout = &ui_context.layout; + let preview_area = views::calculate_preview(tab_context, preview_context, layout[2]); + match preview_area.as_ref() { + Some(new_preview_area) => { + let should_preview = if let Some(old) = &preview_context.preview_area { + new_preview_area.file_preview_path != old.file_preview_path + || new_preview_area.preview_area != old.preview_area + } else { + true + }; + if should_preview { + call_preview_shown_hook(new_preview_area.clone(), preview_options) + } + } + None => { + if preview_context.preview_area.is_some() { + call_preview_removed_hook(preview_options) + } + } + } + preview_area +} +/// Calls the "preview shown hook script" if it's configured. +/// +/// This method takes the current preview area as argument to check for both, the path of the +/// currently previewed file and the geometry of the preview area. +fn call_preview_shown_hook(preview_area: PreviewArea, preview_options: &PreviewOption) { + let preview_shown_hook_script = preview_options.preview_shown_hook_script.as_ref(); + if let Some(hook_script) = preview_shown_hook_script { + let hook_script = hook_script.to_path_buf(); + let _ = thread::spawn(move || { + let _ = process::Command::new(hook_script.as_path()) + .arg(preview_area.file_preview_path.as_path()) + .arg(preview_area.preview_area.x.to_string()) + .arg(preview_area.preview_area.y.to_string()) + .arg(preview_area.preview_area.width.to_string()) + .arg(preview_area.preview_area.height.to_string()) + .status(); + }); + } +} + +/// Remove the external preview, if any is present. +/// +/// If the last preview hook script called was the "preview shown hook", this function will +/// call the "preview removed hook" to remove any external preview. +/// Otherwise it won't do anything. +/// +/// To restore the external preview, `update_external_preview` is called which will detect the +/// difference and call the "preview shown hook" again for the current preview (if any). +/// +/// This function can be called if an external preview shall be temporarily removed, for example +/// when entering the help screen. +pub fn remove_external_preview(context: &mut AppContext) { + if context.preview_context_mut().preview_area.take().is_some() { + let preview_options = context.config_ref().preview_options_ref(); + call_preview_removed_hook(preview_options); + } +} diff --git a/src/main.rs b/src/main.rs index 02ea567..cae9c55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -157,6 +157,7 @@ fn run_main(args: Args) -> Result<i32, AppError> { // make sure all configs have been loaded before starting let config = AppConfig::get_config(); let keymap = AppKeyMapping::get_config(); + lazy_static::initialize(&THEME_T); lazy_static::initialize(&MIMETYPE_T); lazy_static::initialize(&PREVIEW_T); @@ -167,9 +168,10 @@ fn run_main(args: Args) -> Result<i32, AppError> { lazy_static::initialize(&USERNAME); lazy_static::initialize(&HOSTNAME); + let mouse_support = config.mouse_support; let mut context = AppContext::new(config, args.clone()); { - let mut backend: ui::AppBackend = ui::AppBackend::new(context.config_ref().mouse_support)?; + let mut backend: ui::AppBackend = ui::AppBackend::new(mouse_support)?; run::run_loop(&mut backend, &mut context, keymap)?; } run_quit(&args, &context)?; @@ -1,5 +1,6 @@ use crate::commands::quit::QuitAction; use crate::config::clean::keymap::AppKeyMapping; +use crate::context::calculate_external_preview; use crate::context::AppContext; use crate::event::process_event; use crate::event::AppEvent; @@ -53,7 +54,17 @@ pub fn run_loop( backend.render(TuiView::new(context)); // invoke preview hooks, if appropriate - context.update_external_preview(); + { + let new_preview_area = calculate_external_preview( + context.tab_context_ref(), + context.preview_context_ref(), + context.ui_context_ref(), + context.config_ref().preview_options_ref(), + ); + context + .preview_context_mut() + .update_external_preview(new_preview_area); + } } // wait for an event and pop it diff --git a/src/ui/backend.rs b/src/ui/backend.rs index 4b5b124..70c36ea 100644 --- a/src/ui/backend.rs +++ b/src/ui/backend.rs @@ -8,12 +8,6 @@ use termion::screen::IntoAlternateScreen; use termion::input::MouseTerminal; -trait New { - fn new() -> io::Result<Self> - where - Self: Sized; -} - pub enum Screen { WithMouse(MouseTerminal<AlternateScreen<RawTerminal<std::io::Stdout>>>), WithoutMouse(AlternateScreen<RawTerminal<std::io::Stdout>>), @@ -54,6 +48,7 @@ pub type TuiTerminal = ratatui::Terminal<TermionBackend<Screen>>; pub struct AppBackend { pub terminal: Option<TuiTerminal>, + pub mouse_support: bool, } impl AppBackend { @@ -66,6 +61,7 @@ impl AppBackend { let mut terminal = ratatui::Terminal::new(backend)?; terminal.hide_cursor()?; Ok(Self { + mouse_support, terminal: Some(terminal), }) } @@ -93,8 +89,8 @@ impl AppBackend { let _ = stdout().flush(); } - pub fn terminal_restore(&mut self, mouse_support: bool) -> io::Result<()> { - let mut new_backend = Self::new(mouse_support)?; + pub fn terminal_restore(&mut self) -> io::Result<()> { + let mut new_backend = Self::new(self.mouse_support)?; std::mem::swap(&mut self.terminal, &mut new_backend.terminal); Ok(()) } diff --git a/src/ui/views/tui_folder_view.rs b/src/ui/views/tui_folder_view.rs index 2d9538c..4f49ab1 100644 --- a/src/ui/views/tui_folder_view.rs +++ b/src/ui/views/tui_folder_view.rs @@ -6,7 +6,7 @@ use ratatui::text::Span; use ratatui::widgets::{Block, Borders, Paragraph, Widget, Wrap}; use ratatui_image::Image; -use crate::context::AppContext; +use crate::context::{AppContext, PreviewContext, TabContext}; use crate::preview::preview_dir::PreviewDirState; use crate::preview::preview_file::PreviewFileState; use crate::ui; @@ -208,7 +208,11 @@ impl<'a> Widget for TuiFolderView<'a> { if let Some(PreviewFileState::Success(data)) = preview_context.previews_ref().get(entry.file_path()) { - let preview_area = calculate_preview(self.context, layout_rect[2]); + let preview_area = calculate_preview( + self.context.tab_context_ref(), + self.context.preview_context_ref(), + layout_rect[2], + ); if let Some(preview_area) = preview_area { let area = Rect { x: preview_area.preview_area.x, @@ -324,9 +328,12 @@ pub fn calculate_layout_with_borders(area: Rect, constraints: &[Constraint; 3]) vec![inner1, layout_rect[1], inner3] } -pub fn calculate_preview(context: &AppContext, rect: Rect) -> Option<PreviewArea> { - let preview_context = context.preview_context_ref(); - let curr_tab = context.tab_context_ref().curr_tab_ref(); +pub fn calculate_preview( + tab_context: &TabContext, + preview_context: &PreviewContext, + rect: Rect, +) -> Option<PreviewArea> { + let curr_tab = tab_context.curr_tab_ref(); let child_list = curr_tab.child_list_ref(); |