summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-09-22 18:56:47 +0200
committerCanop <cano.petrole@gmail.com>2020-09-22 18:56:47 +0200
commit60b742fceda175bd1b357280535b27ba5e915d94 (patch)
tree30c48e578a42db9428b82effaead61be4ce87af1 /src
parent5edab5fd73ac38df12ff4ce7db21091f6bd8acbb (diff)
refactor: move the invocation pattern from execution to verb
Diffstat (limited to 'src')
-rw-r--r--src/app/selection.rs63
-rw-r--r--src/app/state.rs104
-rw-r--r--src/browser/browser_state.rs62
-rw-r--r--src/cli.rs1
-rw-r--r--src/command/sequence.rs2
-rw-r--r--src/conf/default_conf.rs7
-rw-r--r--src/help/help_state.rs1
-rw-r--r--src/image/image_view.rs1
-rw-r--r--src/preview/preview_state.rs1
-rw-r--r--src/tree/tree_line.rs1
-rw-r--r--src/verb/builtin.rs185
-rw-r--r--src/verb/cd.rs10
-rw-r--r--src/verb/execution_builder.rs158
-rw-r--r--src/verb/external_execution.rs258
-rw-r--r--src/verb/internal.rs21
-rw-r--r--src/verb/internal_execution.rs17
-rw-r--r--src/verb/internal_focus.rs4
-rw-r--r--src/verb/invocation_parser.rs126
-rw-r--r--src/verb/mod.rs16
-rw-r--r--src/verb/verb.rs177
-rw-r--r--src/verb/verb_conf.rs36
21 files changed, 682 insertions, 569 deletions
diff --git a/src/app/selection.rs b/src/app/selection.rs
index 9cbb3f6..4a106f0 100644
--- a/src/app/selection.rs
+++ b/src/app/selection.rs
@@ -1,21 +1,24 @@
use {
- std::path::Path,
+ super::{
+ AppContext,
+ AppStateCmdResult,
+ },
+ crate::{
+ errors::ProgramError,
+ launchable::Launchable,
+ },
+ std::{
+ fs::OpenOptions,
+ io::Write,
+ path::Path,
+ },
};
/// the id of a line, starting at 1
/// (0 if not specified)
pub type LineNumber = usize;
-/// light information about the currently selected
-/// file and maybe line number
-#[derive(Debug, Clone, Copy)]
-pub struct Selection<'s> {
- pub path: &'s Path,
- pub line: LineNumber, // the line number in the file (0 if none selected)
- pub stype: SelectionType,
-}
-
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SelectionType {
File,
@@ -28,3 +31,43 @@ impl SelectionType {
constraint == Self::Any || self == constraint
}
}
+
+/// light information about the currently selected
+/// file and maybe line number
+#[derive(Debug, Clone, Copy)]
+pub struct Selection<'s> {
+ pub path: &'s Path,
+ pub line: LineNumber, // the line number in the file (0 if none selected)
+ pub stype: SelectionType,
+ pub is_exe: bool,
+}
+
+impl Selection<'_> {
+
+ /// build a AppStateCmdResult with a launchable which will be used to
+ /// 1/ quit broot
+ /// 2/ open the relevant file the best possible way
+ pub fn to_opener(
+ self,
+ con: &AppContext,
+ ) -> Result<AppStateCmdResult, ProgramError> {
+ Ok(if self.is_exe {
+ let path = self.path.to_string_lossy().to_string();
+ if let Some(export_path) = &con.launch_args.cmd_export_path {
+ // broot was launched as br, we can launch the executable from the shell
+ let f = OpenOptions::new().append(true).open(export_path)?;
+ writeln!(&f, "{}", path)?;
+ AppStateCmdResult::Quit
+ } else {
+ AppStateCmdResult::from(Launchable::program(
+ vec![path],
+ None, // we don't set the working directory
+ )?)
+ }
+ } else {
+ AppStateCmdResult::from(Launchable::opener(self.path.to_path_buf()))
+ })
+ }
+
+}
+
diff --git a/src/app/state.rs b/src/app/state.rs
index e1b30d7..686f8c3 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -13,7 +13,9 @@ use {
task_sync::Dam,
verb::*,
},
- std::path::{Path, PathBuf},
+ std::{
+ path::{Path, PathBuf},
+ },
termimad::Area,
};
@@ -63,7 +65,7 @@ pub trait AppState {
input_invocation: Option<&VerbInvocation>,
trigger_type: TriggerType,
cc: &CmdContext,
- screen: &mut Screen,
+ screen: &mut Screen, // TODO remove (seeems to be used only for page_height)
) -> Result<AppStateCmdResult, ProgramError>;
/// a generic implementation of on_internal which may be
@@ -121,6 +123,7 @@ pub trait AppState {
AppStateCmdResult::NewState(Box::new(HelpState::new(screen, con)))
}
}
+ Internal::open_leave => self.selection().to_opener(con)?,
Internal::open_preview => self.open_preview(None, false, cc),
Internal::preview_image => self.open_preview(Some(PreviewMode::Image), false, cc),
Internal::preview_text => self.open_preview(Some(PreviewMode::Text), false, cc),
@@ -164,6 +167,44 @@ pub trait AppState {
})
}
+ fn execute_verb(
+ &mut self,
+ w: &mut W, // needed because we may want to switch from alternate in some externals
+ verb: &Verb,
+ invocation: Option<&VerbInvocation>,
+ trigger_type: TriggerType,
+ cc: &CmdContext,
+ screen: &mut Screen,
+ ) -> Result<AppStateCmdResult, ProgramError> {
+ match &verb.execution {
+ VerbExecution::Internal(internal_exec) => self.on_internal(
+ w,
+ internal_exec,
+ invocation,
+ trigger_type,
+ cc,
+ screen,
+ ),
+ VerbExecution::External(external) => {
+ let exec_builder = ExecutionStringBuilder::from_invocation(
+ &verb.invocation_parser,
+ self.selection(),
+ &cc.other_path,
+ if let Some(inv) = invocation {
+ &inv.args
+ } else {
+ &None
+ },
+ );
+ external.to_cmd_result(
+ w,
+ exec_builder,
+ &cc.con,
+ )
+ }
+ }
+ }
+
/// change the state, does no rendering
fn on_command(
&mut self,
@@ -185,30 +226,14 @@ pub trait AppState {
Command::VerbTrigger {
index,
input_invocation,
- } => {
- let verb = &con.verb_store.verbs[*index];
- match &verb.execution {
- VerbExecution::Internal(internal_exec) => self.on_internal(
- w,
- internal_exec,
- input_invocation.as_ref(),
- TriggerType::Other,
- cc,
- screen,
- ),
- VerbExecution::External(external) => external.to_cmd_result(
- w,
- self.selection(),
- &cc.other_path,
- if let Some(inv) = &input_invocation {
- &inv.args
- } else {
- &None
- },
- con,
- ),
- }
- }
+ } => self.execute_verb(
+ w,
+ &con.verb_store.verbs[*index],
+ input_invocation.as_ref(),
+ TriggerType::Other,
+ cc,
+ screen,
+ ),
Command::Internal {
internal,
input_invocation,
@@ -225,25 +250,14 @@ pub trait AppState {
if let Some(err) = verb.check_args(invocation, &cc.other_path) {
Ok(AppStateCmdResult::DisplayError(err))
} else {
- match &verb.execution {
- VerbExecution::Internal(internal_exec) => self.on_internal(
- w,
- internal_exec,
- Some(invocation),
- TriggerType::Input,
- cc,
- screen,
- ),
- VerbExecution::External(external) => {
- external.to_cmd_result(
- w,
- self.selection(),
- &cc.other_path,
- &invocation.args,
- con,
- )
- }
- }
+ self.execute_verb(
+ w,
+ verb,
+ Some(invocation),
+ TriggerType::Input,
+ cc,
+ screen,
+ )
}
}
_ => Ok(AppStateCmdResult::verb_not_found(&invocation.name)),
diff --git a/src/browser/browser_state.rs b/src/browser/browser_state.rs
index 9cd833b..591d2d6 100644
--- a/src/browser/browser_state.rs
+++ b/src/browser/browser_state.rs
@@ -6,7 +6,6 @@ use {
errors::{ProgramError, TreeBuildError},
flag::Flag,
git,
- launchable::Launchable,
pattern::*,
path,
path_anchor::PathAnchor,
@@ -19,8 +18,6 @@ use {
},
open,
std::{
- fs::OpenOptions,
- io::Write,
path::{Path, PathBuf},
},
termimad::Area,
@@ -155,37 +152,6 @@ impl BrowserState {
}
}
- pub fn open_selection_quit_broot(
- &mut self,
- w: &mut W,
- con: &AppContext,
- ) -> Result<AppStateCmdResult, ProgramError> {
- let tree = self.displayed_tree();
- let line = tree.selected_line();
- match &line.line_type {
- TreeLineType::File => make_opener(line.path.clone(), line.is_exe(), con),
- TreeLineType::Dir | TreeLineType::SymLinkToDir(_) => {
- Ok(if con.launch_args.cmd_export_path.is_some() {
- CD.to_cmd_result(w, line.as_selection(), &None, &None, con)?
- } else {
- AppStateCmdResult::DisplayError(
- "This feature needs broot to be launched with the `br` script".to_owned(),
- )
- })
- }
- TreeLineType::SymLinkToFile(target) => {
- make_opener(
- PathBuf::from(target),
- line.is_exe(), // today this always return false
- con,
- )
- }
- _ => {
- unreachable!();
- }
- }
- }
-
pub fn go_to_parent(
&mut self,
screen: &mut Screen,
@@ -209,32 +175,6 @@ impl BrowserState {
}
-/// build a AppStateCmdResult with a launchable which will be used to
-/// 1/ quit broot
-/// 2/ open the relevant file the best possible way
-fn make_opener(
- path: PathBuf,
- is_exe: bool,
- con: &AppContext,
-) -> Result<AppStateCmdResult, ProgramError> {
- Ok(if is_exe {
- let path = path.to_string_lossy().to_string();
- if let Some(export_path) = &con.launch_args.cmd_export_path {
- // broot was launched as br, we can launch the executable from the shell
- let f = OpenOptions::new().append(true).open(export_path)?;
- writeln!(&f, "{}", path)?;
- AppStateCmdResult::Quit
- } else {
- AppStateCmdResult::from(Launchable::program(
- vec![path],
- None, // we don't set the working directory
- )?)
- }
- } else {
- AppStateCmdResult::from(Launchable::opener(path))
- })
-}
-
impl AppState for BrowserState {
fn get_pending_task(&self) -> Option<&'static str> {
@@ -249,7 +189,6 @@ impl AppState for BrowserState {
}
}
-
fn selected_path(&self) -> &Path {
&self.displayed_tree().selected_line().path
}
@@ -352,7 +291,6 @@ impl AppState for BrowserState {
},
Internal::open_stay => self.open_selection_stay_in_broot(screen, con, bang, false)?,
Internal::open_stay_filter => self.open_selection_stay_in_broot(screen, con, bang, true)?,
- Internal::open_leave => self.open_selection_quit_broot(w, con)?,
Internal::line_down => {
self.displayed_tree_mut().move_selection(1, page_height);
AppStateCmdResult::Keep
diff --git a/src/cli.rs b/src/cli.rs
index f88493e..4461eb7 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -27,7 +27,6 @@ use {
},
};
-
/// launch arguments related to installation
/// (not used by the application after the first step)
struct InstallLaunchArgs {
diff --git a/src/command/sequence.rs b/src/command/sequence.rs
index a8d8fe0..7856abb 100644
--- a/src/command/sequence.rs
+++ b/src/command/sequence.rs
@@ -75,11 +75,9 @@ fn add_commands(
let raw_parts = CommandParts::from(input.to_string());
let (pattern, verb_invocation) = raw_parts.split();
if let Some(pattern) = pattern {
- debug!("adding pattern: {:?}", pattern);
commands.push((input.to_string(), Command::from_parts(pattern, false)));
}
if let Some(verb_invocation) = verb_invocation {
- debug!("adding verb_invocation: {:?}", verb_invocation);
let command = Command::from_parts(verb_invocation, true);
if let Command::VerbInvocate(invocation) = &command {
// we check that the verb exists to avoid running a sequence
diff --git a/src/conf/default_conf.rs b/src/conf/default_conf.rs
index 1350b83..6375e83 100644
--- a/src/conf/default_conf.rs
+++ b/src/conf/default_conf.rs
@@ -133,11 +133,18 @@ leave_broot = false
# uncomment if you want to launch a terminal on ctrl-T
# (on exit you'll be back in broot)
# [[verbs]]
+# invocation = "terminal"
# key = "ctrl-t"
# execution = "$SHELL"
# set_working_dir = true
# leave_broot = false
+# Here's an example of a shorctut bringing you to your home directory
+# [[verbs]]
+# invocation = "home"
+# key = "ctrl-home"
+# execution = ":focus ~"
+
# A popular set of shorctuts for going up and down:
#
# [[verbs]]
diff --git a/src/help/help_state.rs b/src/help/help_state.rs
index 1a58ec6..aa19572 100644
--- a/src/help/help_state.rs
+++ b/src/help/help_state.rs
@@ -42,6 +42,7 @@ impl AppState for HelpState {
Selection {
path: Conf::default_location(),
stype: SelectionType::File,
+ is_exe: false,
line: 0,
}
}
diff --git a/src/image/image_view.rs b/src/image/image_view.rs
index 30b7c3d..74053f4 100644
--- a/src/image/image_view.rs
+++ b/src/image/image_view.rs
@@ -68,7 +68,6 @@ impl ImageView {
let bg = styles.preview.get_bg()
.or_else(|| styles.default.get_bg())
.unwrap_or(Color::AnsiValue(238));
- debug!("true colors: {:?}", con.true_colors);
let mut double_line = DoubleLine::new(width as usize, con.true_colors);
let mut y = area.top;
let margin = area.width as usize - width as usize;
diff --git a/src/preview/preview_state.rs b/src/preview/preview_state.rs
index dfa3544..86a1468 100644
--- a/src/preview/preview_state.rs
+++ b/src/preview/preview_state.rs
@@ -155,6 +155,7 @@ impl AppState for PreviewState {
Selection {
path: &self.path,
stype: SelectionType::File,
+ is_exe: false, // not always true. It means :open_leave won't execute it
line: self.preview.get_selected_line_number().unwrap_or(0),
}
}
diff --git a/src/tree/tree_line.rs b/src/tree/tree_line.rs
index ab1c76c..5e0ae19 100644
--- a/src/tree/tree_line.rs
+++ b/src/tree/tree_line.rs
@@ -85,6 +85,7 @@ impl TreeLine {
Selection {
path: &self.path,
stype: self.selection_type(),
+ is_exe: self.is_exe(),
line: 0,
}
}
diff --git a/src/verb/builtin.rs b/src/verb/builtin.rs
index 90375ed..0c7092f 100644
--- a/src/verb/builtin.rs
+++ b/src/verb/builtin.rs
@@ -1,132 +1,185 @@
use {
- super::Verb,
- crate::keys::*,
+ super::*,
+ crate::{
+ app::SelectionType,
+ keys::*,
+ },
crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
};
+fn build_internal(
+ internal: Internal,
+ bang: bool,
+) -> Verb {
+ let invocation = internal.invocation_pattern();
+ let execution = VerbExecution::Internal(
+ InternalExecution::from_internal_bang(internal, bang)
+ );
+ let description = VerbDescription::from_text(internal.description().to_string());
+ Verb::new(Some(invocation), execution, description).unwrap()
+}
+
+fn internal(
+ internal: Internal,
+) -> Verb {
+ build_internal(internal, false)
+}
+
+fn internal_bang(
+ internal: Internal,
+) -> Verb {
+ build_internal(internal, true)
+}
+
+fn external(
+ invocation_str: &str,
+ execution_str: &str,
+ exec_mode: ExternalExecutionMode,
+) -> Verb {
+ let execution = VerbExecution::External(ExternalExecution::new(
+ execution_str,
+ exec_mode,
+ ));
+ Verb::new(
+ Some(invocation_str),
+ execution,
+ VerbDescription::from_code(execution_str.to_string()),
+ ).unwrap()
+}
+
/// declare the built_in verbs, the ones which are available
/// in standard (they still may be overriden by configuration)
pub fn builtin_verbs() -> Vec<Verb> {
use super::{ExternalExecutionMode::*, Internal::*};
vec![
- Verb::internal(back),
- Verb::from(super::cd::CD.clone())
- .with_description("change directory and quit (mapped to *alt*-*enter*)"),
+ internal(back),
+
+ // those two operations are mapped on ALT-ENTER, one
+ // for directories and the other one for the other files
+ external(
+ "cd",
+ "cd {directory}",
+ FromParentShell,
+ )
+ .with_stype(SelectionType::Directory)
+ .with_key(ALT_ENTER)
+ .with_description("change directory and quit"),
+ internal(open_leave) // calls the system open
+ .with_key(ALT_ENTER)
+ .with_shortcut("ol"),
+
#[cfg(unix)]
- Verb::external(
+ external(
"chmod {args}",
"chmod {args} {file}",
StayInBroot,
- ).unwrap(),
- Verb::internal(open_preview),
- Verb::internal(close_preview),
- Verb::internal(toggle_preview),
- Verb::internal(preview_image),
- Verb::internal(preview_text),
- Verb::internal(preview_binary),
- Verb::internal(close_panel_ok),
- Verb::internal(close_panel_cancel)
+ ),
+ internal(open_preview),
+ internal(close_preview),
+ internal(toggle_preview),
+ internal(preview_image),
+ internal(preview_text),
+ internal(preview_binary),
+ internal(close_panel_ok),
+ internal(close_panel_cancel)
.with_key(BACK_TAB)
.with_control_key('w'),
- Verb::external(
+ external(
"copy {newpath:path-from-parent}",
"/bin/cp -r {file} {newpath:path-from-parent}",
StayInBroot,
- ).unwrap()
+ )
.with_shortcut("cp"),
#[cfg(feature="clipboard")]
- Verb::internal(copy_path)
+ internal(copy_path)
.with_alt_key('c'),
- Verb::external(
+ external(
"copy_to_panel",
"/bin/cp -r {file} {other-panel-directory}",
StayInBroot,
- ).unwrap()
+ )
.with_shortcut("cpp"),
// :focus is also hardcoded on Enter on directories
// but ctrl-f is useful for focusing on a file's parent
// (and keep the filter)
- Verb::internal(focus)
+ internal(focus)
.with_control_key('f'),
- Verb::internal(help).with_key(F1).with_shortcut("?"),
+ internal(help).with_key(F1).with_shortcut("?"),
#[cfg(feature="clipboard")]
- Verb::internal(input_paste)
+ internal(input_paste)
.with_control_key('v'),
- Verb::internal(line_down).with_key(DOWN),
- Verb::internal(line_up).with_key(UP),
- Verb::external(
+ internal(line_down).with_key(DOWN),
+ internal(line_up).with_key(UP),
+ external(
"mkdir {subpath}",
"/bin/mkdir -p {subpath:path-from-directory}",
StayInBroot,
- ).unwrap()
+ )
.with_shortcut("md"),
- Verb::external(
+ external(
"move {newpath:path-from-parent}",
"/bin/mv {file} {newpath:path-from-parent}",
StayInBroot,
- ).unwrap()
+ )
.with_shortcut("mv"),
- Verb::external(
+ external(
"move_to_panel",
"/bin/mv {file} {other-panel-directory}",
StayInBroot,
- ).unwrap()
+ )
.with_shortcut("mvp"),
- Verb::internal_bang(start_end_panel)
+ internal_bang(start_end_panel)
.with_control_key('p'),
- Verb::internal(next_match)
+ internal(next_match)
.with_key(TAB),
- Verb::internal(no_sort)
+ internal(no_sort)
.with_shortcut("ns"),
- Verb::internal(open_stay)
+ internal(open_stay)
.with_key(ENTER)
.with_shortcut("os"),
- Verb::internal(open_stay_filter)
+ internal(open_stay_filter)
.with_shortcut("osf"),
- Verb::internal(open_leave)
- .with_key(ALT_ENTER)
- .with_shortcut("ol"),
- Verb::internal(parent).with_shortcut("p"),
- Verb::internal(page_down).with_key(PAGE_DOWN),
- Verb::internal(page_up).with_key(PAGE_UP),
- Verb::internal(panel_left)
+ internal(parent).with_shortcut("p"),
+ internal(page_down).with_key(PAGE_DOWN),
+ internal(page_up).with_key(PAGE_UP),
+ internal(panel_left)
.with_key(KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::CONTROL,
}),
- Verb::internal(panel_right)
+ internal(panel_right)
.with_key(KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::CONTROL,
}),
- Verb::internal(print_path).with_shortcut("pp"),
- Verb::internal(print_relative_path).with_shortcut("prp"),
- Verb::internal(print_tree).with_shortcut("pt"),
- Verb::internal(quit)
+ internal(print_path).with_shortcut("pp"),
+ internal(print_relative_path).with_shortcut("prp"),
+ internal(print_tree).with_shortcut("pt"),
+ internal(quit)
.with_control_key('c')
.with_control_key('q')
.with_shortcut("q"),
- Verb::internal(refresh).with_key(F5),
- Verb::internal(sort_by_count).with_shortcut("sc"),
- Verb::internal(sort_by_date).with_shortcut("sd"),
- Verb::internal(sort_by_size).with_shortcut("ss"),
- Verb::external(
+ internal(refresh).with_key(F5),
+ internal(sort_by_count).with_shortcut("sc"),
+ internal(sort_by_date).with_shortcut("sd"),
+ internal(sort_by_size).with_shortcut("ss"),
+ external(
"rm",
"/bin/rm -rf {file}",
StayInBroot,
- ).unwrap(),
- Verb::internal(toggle_counts).with_shortcut("counts"),
- Verb::internal(toggle_dates).with_shortcut("dates"),
- Verb::internal(toggle_files).with_shortcut("files"),
- Verb::internal(toggle_git_ignore).with_shortcut("gi"),
- Verb::internal(toggle_git_file_info).with_shortcut("gf"),
- Verb::internal(toggle_git_status).with_shortcut("gs"),
- Verb::internal(toggle_hidden).with_shortcut("h"),
+ ),
+ internal(toggle_counts).with_shortcut("counts"),
+ internal(toggle_dates).with_shortcut("dates"),
+ internal(toggle_files).with_shortcut("files"),
+ internal(toggle_git_ignore).with_shortcut("gi"),
+ internal(toggle_git_file_info).with_shortcut("gf"),
+ internal(toggle_git_status).with_shortcut("gs"),
+ internal(toggle_hidden).with_shortcut("h"),
#[cfg(unix)]
- Verb::internal(toggle_perm).with_shortcut("perm"),
- Verb::internal(toggle_sizes).with_shortcut("sizes"),
- Verb::internal(toggle_trim_root),
- Verb::internal(total_search).with_control_key('s'),
- Verb::internal(up_tree).with_shortcut("up"),
+ internal(toggle_perm).with_shortcut("perm"),
+ internal(toggle_sizes).with_shortcut("sizes"),
+ internal(toggle_trim_root),
+ internal(total_search).with_control_key('s'),
+ internal(up_tree).with_shortcut("up"),
]
}
diff --git a/src/verb/cd.rs b/src/verb/cd.rs
deleted file mode 100644
index ab08dbd..0000000
--- a/src/verb/cd.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use super::{ExternalExecution, ExternalExecutionMode};
-
-lazy_static! {
- pub static ref CD: ExternalExecution = ExternalExecution::new(
- "cd",
- "cd {directory}",
- ExternalExecutionMode::FromParentShell,
- )
- .unwrap();
-}
diff --git a/src/verb/execution_builder.rs b/src/verb/execution_builder.rs
new file mode 100644
index 0000000..c1882bd
--- /dev/null
+++ b/src/verb/execution_builder.rs
@@ -0,0 +1,158 @@
+
+use {
+ super::{
+ GROUP,
+ InvocationParser,
+ },
+ crate::{
+ app::{Selection},
+ path,
+ },
+ regex::{Captures},
+ std::{
+ collections::HashMap,
+ path::{Path, PathBuf},
+ },
+};
+
+/// a temporary structure gathering selection and invocation
+/// parameters and able to generate an executable string from
+/// a verb's execution pattern
+pub struct ExecutionStringBuilder<'b> {
+ /// the current file selection
+ pub sel: Selection<'b>,
+
+ /// the selection in the other panel, when there exactly two
+ other_file: Option<&'b PathBuf>,
+
+ /// parsed arguments
+ invocation_values: Option<HashMap<String, String>>,
+}
+
+impl<'b> ExecutionStringBuilder<'b> {
+ pub fn from_selection(
+ sel: Selection<'b>,
+ ) -> Self {
+ Self {
+ sel,
+ other_file: None,
+ invocation_values: None,
+ }
+ }
+ pub fn from_invocation(
+ invocation_parser: &Option<InvocationParser>,
+ sel: Selection<'b>,
+ other_file: &'b Option<PathBuf>,
+ invocation_args: & Option<String>,
+ ) -> Self {
+ let invocation_values = invocation_parser.as_ref().zip(invocation_args.as_ref())
+ .and_then(|(parser, args)| parser.parse(args));
+ Self {
+ sel,
+ other_file: other_file.as_ref(),
+ invocation_values,
+ }
+ }
+ fn get_file(&self) -> &Path {
+ &self.sel.path
+ }
+ fn get_directory(&self) -> PathBuf {
+ path::closest_dir(self.sel.path)
+ }
+ fn get_parent(&self) -> &Path {
+ let file = &self.sel.path;
+ file.parent().unwrap_or(file)
+ }
+ fn path_to_string(&self, path: &Path, escape: bool) -> String {
+ if escape {
+ path::escape_for_shell(path)
+ } else {
+ path.to_string_lossy().to_string()
+ }
+ }
+ fn get_raw_capture_replacement(&self, ec: &Captures<'_>, escape: bool) -> Option<String> {
+ let name = ec.get(1).unwrap().as_str();
+ match name {
+ "line" => Some(self.sel.line.to_string()),
+ "file" => Some(self.path_to_string(self.get_file(), escape)),
+ "directory" => Some(self.path_to_string(&self.get_directory(), escape)),
+ "parent" => Some(self.path_to_string(self.get_parent(), escape)),
+ "other-panel-file" => self.other_file.map(|p| self.path_to_string(p, escape)),
+ "other-panel-directory" => {
+ self.other_file
+ .map(|p| path::closest_dir(p))
+ .as_ref()
+ .map(|p| self.path_to_string(p, escape))
+ }
+ "other-panel-parent" => {
+ self.other_file
+ .and_then(|p| p.parent())
+ .map(|p| self.path_to_string(p, escape))
+ }
+ _ => {
+ // it's not one of the standard group names, so we'll look
+ // into the ones provided by the invocation pattern
+ self.invocation_values.as_ref()
+ .and_then(|map| map.get(name)
+ .map(|value| {
+ if let Some(fmt) = ec.get(2) {
+ match fmt.as_str() {
+ "path-from-directory" => path::path_str_from(self.get_directory(), value),
+ "path-from-parent" => path::path_str_from(self.get_parent(), value),
+ _ => format!("invalid format: {:?}", fmt.as_str()),
+ }
+ } else {
+ value.to_string()
+ }
+ })
+ )
+ }
+ }
+ }
+ fn get_capture_replacement(&self, ec: &Captures<'_>, escape: bool) -> String {
+ self.get_raw_capture_replacement(ec, escape)
+ .unwrap_or_else(|| ec[0].to_string())
+ }
+ /// build a shell compatible command, with escapings
+ pub fn shell_exec_string(
+ &self,
+ exec_pattern: &str,
+ ) -> String {
+ GROUP
+ .replace_all(
+