//! utility functions to help handle the `:focus` internal use { super::*, crate::{ app::*, browser::BrowserState, command::TriggerType, display::Screen, path::{self, PathAnchor}, preview::PreviewState, task_sync::Dam, tree::TreeOptions, }, std::path::{Path, PathBuf}, }; pub fn on_path( path: PathBuf, screen: Screen, tree_options: TreeOptions, in_new_panel: bool, con: &AppContext, ) -> CmdResult { if in_new_panel { new_panel_on_path( path, screen, tree_options, PanelPurpose::None, con, HDir::Right, ) } else { new_state_on_path(path, screen, tree_options, con) } } pub fn new_state_on_path( path: PathBuf, screen: Screen, tree_options: TreeOptions, con: &AppContext, ) -> CmdResult { let path = path::closest_dir(&path); CmdResult::from_optional_state( BrowserState::new(path, tree_options, screen, con, &Dam::unlimited()), None, false, ) } pub fn new_panel_on_path( path: PathBuf, screen: Screen, tree_options: TreeOptions, purpose: PanelPurpose, con: &AppContext, direction: HDir, ) -> CmdResult { if purpose.is_preview() { let pattern = tree_options.pattern.tree_to_preview(); CmdResult::NewPanel { state: Box::new(PreviewState::new(path, pattern, None, tree_options, con)), purpose, direction, } } else { let path = path::closest_dir(&path); match BrowserState::new(path, tree_options, screen, con, &Dam::unlimited()) { Ok(os) => CmdResult::NewPanel { state: Box::new(os), purpose, direction, }, Err(e) => CmdResult::DisplayError(e.to_string()), } } } /// Compute the path to go to in case of the internal being triggered from /// the input. /// /// This path depends on the verb (which may hardcore the path or have a /// pattern), from the selection, fn path_from_input( verb: &Verb, internal_exec: &InternalExecution, base_path: &Path, // either the selected path or the root path input_arg: Option<&String>, app_state: &AppState, ) -> PathBuf { match (input_arg, internal_exec.arg.as_ref()) { (Some(input_arg), Some(verb_arg)) => { // The verb probably defines some patttern which uses the input. // For example: // { // invocation: "gotar {path}" // execution: ":focus {path}/target" // } // (or that input is useless) let path_builder = ExecutionStringBuilder::with_invocation( &verb.invocation_parser, SelInfo::from_path(base_path), app_state, Some(input_arg), ); path_builder.path(verb_arg) } (Some(input_arg), None) => { // the verb defines nothing // The :focus internal execution was triggered from the // input (which must be a kind of alias for :focus) // so we do exactly what the input asks for path::path_from(base_path, PathAnchor::Unspecified, input_arg) } (None, Some(verb_arg)) => { // the verb defines the path where to go.. // the internal_execution specifies the path to use // (it may come from a configured verb whose execution is // `:focus some/path`). // The given path may be relative hence the need for the // state's selection // (we assume a check before ensured it doesn't need an input) path::path_from(base_path, PathAnchor::Unspecified, verb_arg) } (None, None) => { // user only wants to open the selected path, either in the same panel or // in a new one base_path.to_path_buf() } } } pub fn get_status_markdown( verb: &Verb, internal_exec: &InternalExecution, sel_info: SelInfo<'_>, invocation: &VerbInvocation, app_state: &AppState, ) -> String { let base_path = sel_info .one_path() .unwrap_or(&app_state.root); let path = path_from_input( verb, internal_exec, base_path, invocation.args.as_ref(), app_state, ); format!("Hit *enter* to focus `{}`", path.to_string_lossy()) } /// general implementation for verbs based on the :focus internal with optionally /// a bang or an argument. pub fn on_internal( internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, selected_path: &Path, tree_options: TreeOptions, app_state: & AppState, cc: &CmdContext, ) -> CmdResult { let con = &cc.app.con; let screen = cc.app.screen; info!( "internal_focus.on_internal internal_exec={:?} input_invocation={:?} trygger_type={:?}", internal_exec, input_invocation, trigger_type, ); let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); let input_arg = input_invocation.as_ref() .and_then(|invocation| invocation.args.as_ref()); match trigger_type { TriggerType::Input(verb) => { let path = path_from_input( verb, internal_exec, selected_path, input_arg, app_state, ); on_path(path, screen, tree_options, bang, con) } _ => { // the :focus internal was triggered by a key if let Some(arg) = &internal_exec.arg { // the internal_execution specifies the path to use // (it may come from a configured verb whose execution is // `:focus some/path`). // The given path may be relative hence the need for the // state's selection let path = path::path_from(selected_path, PathAnchor::Unspecified, arg); let bang = input_invocation .map(|inv| inv.bang) .unwrap_or(internal_exec.bang); on_path(path, screen, tree_options, bang, con) } else if let Some(input_arg) = input_arg { // the :focus internal was triggered by a key, and without internal arg, // which means the user wants to explore the arg with purpose // of selecting a path let base_dir = selected_path.to_string_lossy(); let path = path::path_from(&*base_dir, PathAnchor::Unspecified, input_arg); let arg_type = SelectionType::Any; // We might do better later let purpose = PanelPurpose::ArgEdition { arg_type }; new_panel_on_path(path, screen, tree_options, purpose, con, HDir::Right) } else { // user only wants to open the selected path, either in the same panel or // in a new one on_path(selected_path.to_path_buf(), screen, tree_options, bang, con) } } } }