From ff7265ca6437b858d9ed8e4472f4b7268a06e949 Mon Sep 17 00:00:00 2001 From: Canop Date: Sat, 24 Sep 2022 18:19:07 +0200 Subject: add the :select internal This internal selects a visible tree line by its path (either absolute or relative) --- CHANGELOG.md | 3 +- src/browser/browser_state.rs | 8 +++ src/verb/builtin.rs | 2 + src/verb/internal.rs | 2 + src/verb/internal_focus.rs | 2 +- src/verb/internal_select.rs | 145 +++++++++++++++++++++++++++++++++++++++++++ src/verb/mod.rs | 1 + website/docs/conf_verbs.md | 1 + 8 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/verb/internal_select.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ae2c42..c7cf2d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ - with `show_matching_characters_on_path_searches: false`, it's possible to show only file names even when searching paths - Fix #490 - `--sort-by-type-dirs-first` and `--sort-by-type-dirs-last` - Fix #602 - modal: in intput mode, uppercase letters don't trigger verbs anymore - Fix #604 -- fix :line_down_no_cycle which was cycling +- fix :line_down_no_cycle which was cycling - Fix #603 - selecting lines up or down with the mouse wheel now wraps in both direction (ie going up when your on top brings you to the bottom, and vice-versa) +- :select internal, which can be used to select a visible file when given a path as argument. Experimental ### v1.14.3 - 2022-09-12 diff --git a/src/browser/browser_state.rs b/src/browser/browser_state.rs index 744bae8..7c222c6 100644 --- a/src/browser/browser_state.rs +++ b/src/browser/browser_state.rs @@ -339,6 +339,14 @@ impl PanelState for BrowserState { app_state, cc, ), + Internal::select => internal_select::on_internal( + internal_exec, + input_invocation, + trigger_type, + self.displayed_tree_mut(), + app_state, + cc, + ), Internal::up_tree => match self.displayed_tree().root().parent() { Some(path) => internal_focus::on_path( path.to_path_buf(), diff --git a/src/verb/builtin.rs b/src/verb/builtin.rs index d4869b6..5d7f642 100644 --- a/src/verb/builtin.rs +++ b/src/verb/builtin.rs @@ -249,6 +249,7 @@ pub fn builtin_verbs() -> Vec { .with_key(key!(ctrl-down)), internal(select_first), internal(select_last), + internal(select), internal(clear_stage).with_shortcut("cls"), internal(stage) .with_key(key!('+')), @@ -292,5 +293,6 @@ pub fn builtin_verbs() -> Vec { internal(toggle_trim_root), internal(total_search).with_key(key!(ctrl-s)), internal(up_tree).with_shortcut("up"), + ] } diff --git a/src/verb/internal.rs b/src/verb/internal.rs index 13559c3..20bf8d8 100644 --- a/src/verb/internal.rs +++ b/src/verb/internal.rs @@ -113,6 +113,7 @@ Internals! { //restore_pattern: "restore a pattern which was just removed" false, select_first: "select the first item" false, select_last: "select the last item" false, + select: "select a file by path" true, set_syntax_theme: "set the theme of code preview" false, sort_by_count: "sort by count" false, sort_by_date: "sort by date" false, @@ -149,6 +150,7 @@ impl Internal { pub fn invocation_pattern(self) -> &'static str { match self { Internal::focus => r"focus (?P.*)?", + Internal::select => r"select (?P.*)?", Internal::line_down => r"line_down (?P\d*)?", Internal::line_up => r"line_up (?P\d*)?", Internal::line_down_no_cycle => r"line_down_no_cycle (?P\d*)?", diff --git a/src/verb/internal_focus.rs b/src/verb/internal_focus.rs index 2399a79..7a13673 100644 --- a/src/verb/internal_focus.rs +++ b/src/verb/internal_focus.rs @@ -80,7 +80,7 @@ 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 +/// This path depends on the verb (which may hardcore the path or have a /// pattern), from the selection, fn path_from_input( verb: &Verb, diff --git a/src/verb/internal_select.rs b/src/verb/internal_select.rs new file mode 100644 index 0000000..ae1b2f7 --- /dev/null +++ b/src/verb/internal_select.rs @@ -0,0 +1,145 @@ +//! utility functions to help handle the `:select` internal + +use { + super::*, + crate::{ + app::*, + browser::BrowserState, + command::TriggerType, + display::Screen, + path::{self, PathAnchor}, + tree::Tree, + }, + std::path::{Path, PathBuf}, +}; + + +/// general implementation for verbs based on the :select internal with optionally +/// a bang or an argument. +pub fn on_internal( + internal_exec: &InternalExecution, + input_invocation: Option<&VerbInvocation>, + trigger_type: TriggerType, + tree: &mut Tree, + app_state: & AppState, + cc: &CmdContext, +) -> CmdResult { + let screen = cc.app.screen; + info!( + "internal_select.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, + &tree.selected_line().path, + input_arg, + app_state, + ); + on_path(path, tree, screen, bang) + } + _ => { + // the :select 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 + // `:select some/path`). + // The given path may be relative hence the need for the + // state's selection + let path = path::path_from( + &tree.selected_line().path, + PathAnchor::Unspecified, + arg, + ); + let bang = input_invocation + .map(|inv| inv.bang) + .unwrap_or(internal_exec.bang); + on_path(path, tree, screen, bang) + } else { + // there's nothing really to do here + CmdResult::Keep + } + } + } +} + + +/// 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: ":select {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 :select internal execution was triggered from the + // input (which must be a kind of alias for :select) + // 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 + // `:select 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) => { + // This doesn't really make sense: we're selecting the currently + // selected path + base_path.to_path_buf() + } + } + +} + +pub fn on_path( + path: PathBuf, + tree: &mut Tree, + screen: Screen, + in_new_panel: bool, +) -> CmdResult { + info!("executing :select on path {:?}", &path); + if in_new_panel { + warn!("bang in :select isn't supported yet"); + } + if tree.try_select_path(&path) { + tree.make_selection_visible(BrowserState::page_height(screen) as usize); + } + CmdResult::Keep +} diff --git a/src/verb/mod.rs b/src/verb/mod.rs index 3a835de..7ea25ea 100644 --- a/src/verb/mod.rs +++ b/src/verb/mod.rs @@ -7,6 +7,7 @@ mod external_execution_mode; mod internal; mod internal_execution; pub mod internal_focus; +pub mod internal_select; mod invocation_parser; mod sequence_execution; mod verb; diff --git a/website/docs/conf_verbs.md b/website/docs/conf_verbs.md index 149313b..92954fa 100644 --- a/website/docs/conf_verbs.md +++ b/website/docs/conf_verbs.md @@ -386,6 +386,7 @@ invocation | default key | default shortcut | behavior / details :rm | - | - | remove the selected file or directory. To stay safe, don't define a keyboard key for this action :select_first | - | - | select the first line :select_last | - | - | select the last line +:select | - | - | select a path given as argument, if it's in the visible tree :sort_by_count | - | sc | sort by count (only one level of the tree is displayed) :sort_by_date | - | sd | sort by date :sort_by_size | - | ss | sort by size -- cgit v1.2.3