From 94ef5f6ed4376df0d1b96b2ba38e8f5b2ca70ed9 Mon Sep 17 00:00:00 2001 From: Canop Date: Sun, 10 Jul 2022 22:28:15 +0200 Subject: allow :focus based verbs to take a pattern This allows verb definitions such as the following ones: ```Hjson { invocation: "gos" execution: ":focus" } { invocation: "go {path}" execution: ":focus {path}" } { invocation: "goroot" execution: ":focus /" } { invocation: "gotar {path}" execution: ":focus {path}/target" } ``` Fix #389 --- src/verb/execution_builder.rs | 16 +++- src/verb/internal_focus.rs | 165 ++++++++++++++++++++++++++++++++---------- src/verb/verb.rs | 39 ++++++---- 3 files changed, 160 insertions(+), 60 deletions(-) (limited to 'src/verb') diff --git a/src/verb/execution_builder.rs b/src/verb/execution_builder.rs index 2d56e9a..bfd4c7d 100644 --- a/src/verb/execution_builder.rs +++ b/src/verb/execution_builder.rs @@ -45,7 +45,7 @@ impl<'b> ExecutionStringBuilder<'b> { invocation_parser: &Option, sel_info: SelInfo<'b>, app_state: &'b AppState, - invocation_args: &Option, + invocation_args: Option<&String>, ) -> Self { let invocation_values = invocation_parser .as_ref() @@ -223,16 +223,24 @@ impl<'b> ExecutionStringBuilder<'b> { } } + fn base_dir(&self) -> &Path { + self.sel_info + .one_sel() + .map_or(self.root, |sel| sel.path) + } + /// build a path pub fn path( &self, pattern: &str, ) -> PathBuf { - PathBuf::from( - GROUP.replace_all( + path::path_from( + self.base_dir(), + path::PathAnchor::Unspecified, + &GROUP.replace_all( pattern, |ec: &Captures<'_>| self.get_capture_replacement(ec), - ).to_string() + ) ) } /// build a shell compatible command, with escapings diff --git a/src/verb/internal_focus.rs b/src/verb/internal_focus.rs index c1e2028..2399a79 100644 --- a/src/verb/internal_focus.rs +++ b/src/verb/internal_focus.rs @@ -77,6 +77,81 @@ pub fn new_panel_on_path( } } +/// 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 of 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( @@ -84,51 +159,61 @@ pub fn on_internal( input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, selected_path: &Path, - screen: Screen, - con: &AppContext, tree_options: TreeOptions, + app_state: & AppState, + cc: &CmdContext, ) -> CmdResult { - 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 + 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); - return on_path(path, screen, tree_options, bang, con); - } - if let Some(input_invocation) = &input_invocation { - if let Some(input_arg) = &input_invocation.args { - // user typed a path in the input - match trigger_type { - TriggerType::Input => { - // 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 - let path = path::path_from(selected_path, PathAnchor::Unspecified, input_arg); - let bang = input_invocation.bang || internal_exec.bang; - return on_path(path, screen, tree_options, bang, con); - } - _ => { - // 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 }; - return new_panel_on_path(path, screen, tree_options, purpose, con, HDir::Right); - } + 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) } } } - // user only wants to open the selected path, either in the same panel or - // in a new one - let bang = input_invocation - .map(|inv| inv.bang) - .unwrap_or(internal_exec.bang); - on_path(selected_path.to_path_buf(), screen, tree_options, bang, con) } diff --git a/src/verb/verb.rs b/src/verb/verb.rs index d315975..e6c6f8b 100644 --- a/src/verb/verb.rs +++ b/src/verb/verb.rs @@ -4,10 +4,14 @@ use { app::*, errors::ConfError, keys::KEY_FORMAT, - path::{self, PathAnchor}, + path::PathAnchor, }, crossterm::event::KeyEvent, - std::path::PathBuf, + std::{ + cmp::PartialEq, + path::PathBuf, + ptr, + }, }; /// what makes a verb. @@ -20,6 +24,9 @@ use { /// - internal behaviors (focusing a path, going back, showing the help, etc.) /// Some verbs are builtins, some other ones are created by configuration. /// Both builtins and configured vers can be internal or external based. +/// +/// Verbs can't be cloned. Two verbs are equal if they have the same address +/// in memory. #[derive(Debug)] pub struct Verb { /// names (like "cd", "focus", "focus_tab", "c") by which @@ -67,6 +74,12 @@ pub struct Verb { pub show_in_doc: bool } +impl PartialEq for Verb { + fn eq(&self, other: &Self) -> bool { + ptr::eq(self, other) + } +} + impl Verb { pub fn new( @@ -221,19 +234,13 @@ impl Verb { // thus I hardcode the test here. if let VerbExecution::Internal(internal_exec) = &self.execution { if internal_exec.internal == Internal::focus { - if let Some(sel) = sel_info.one_sel() { - let arg = invocation.args.as_ref().or(internal_exec.arg.as_ref()); - let pb; - let arg_path = if let Some(arg) = arg { - pb = path::path_from(sel.path, PathAnchor::Unspecified, arg); - &pb - } else { - sel.path - }; - return format!("Hit *enter* to {} `{}`", name, arg_path.to_string_lossy()); - } else { - return "You can't focus without selection".to_string(); - } + return internal_focus::get_status_markdown( + self, + internal_exec, + sel_info, + invocation, + app_state, + ); } } @@ -242,7 +249,7 @@ impl Verb { &self.invocation_parser, sel_info, app_state, - &invocation.args, + invocation.args.as_ref(), ) }; if let VerbExecution::Sequence(seq_ex) = &self.execution { -- cgit v1.2.3