summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Zhao <jeff.no.zhao@gmail.com>2024-03-10 21:17:09 -0400
committerJeff Zhao <jeff.no.zhao@gmail.com>2024-03-10 21:17:09 -0400
commit8af827604620f8135c98d6f4c8e4b647c06d1c32 (patch)
tree8641bdd607177d92df3c91e700493c65f120859d
parent4267cb55085b24770ee35c841c940f8ea7233445 (diff)
move preview area into PreviewContext
- move a bunch of methods out into functions
-rw-r--r--src/commands/bulk_rename.rs5
-rw-r--r--src/commands/custom_search.rs2
-rw-r--r--src/commands/fzf.rs6
-rw-r--r--src/commands/open_file.rs6
-rw-r--r--src/commands/show_help.rs3
-rw-r--r--src/commands/sub_process.rs2
-rw-r--r--src/commands/zoxide.rs2
-rw-r--r--src/context/app_context.rs97
-rw-r--r--src/context/preview_context.rs105
-rw-r--r--src/main.rs4
-rw-r--r--src/run.rs13
-rw-r--r--src/ui/backend.rs12
-rw-r--r--src/ui/views/tui_folder_view.rs17
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)?;
diff --git a/src/run.rs b/src/run.rs
index 958fe80..884564c 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -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();