use {
super::*,
crate::{
app::*,
errors::ConfError,
path::PathAnchor,
},
crokey::{
KeyCombination,
},
std::{
cmp::PartialEq,
path::PathBuf,
ptr,
},
};
#[derive(Debug)]
pub struct Verb {
pub id: VerbId,
pub names: Vec<String>,
pub keys: Vec<KeyCombination>,
pub invocation_parser: Option<InvocationParser>,
pub execution: VerbExecution,
pub description: VerbDescription,
pub selection_condition: FileTypeCondition,
pub file_extensions: Vec<String>,
pub needs_selection: bool,
pub needs_another_panel: bool,
pub auto_exec: bool,
pub show_in_doc: bool,
pub panels: Vec<PanelStateType>,
}
impl PartialEq for Verb {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
}
}
impl Verb {
pub fn new(
id: VerbId,
invocation_str: Option<&str>,
execution: VerbExecution,
description: VerbDescription,
) -> Result<Self, ConfError> {
let invocation_parser = invocation_str.map(InvocationParser::new).transpose()?;
let mut names = Vec::new();
if let Some(ref invocation_parser) = invocation_parser {
let name = invocation_parser.name().to_string();
check_verb_name(&name)?;
names.push(name);
}
let (
needs_selection,
needs_another_panel,
) = match &execution {
VerbExecution::Internal(ie) => (
ie.needs_selection(),
false,
),
VerbExecution::External(ee) => (
ee.exec_pattern.has_selection_group(),
ee.exec_pattern.has_other_panel_group(),
),
VerbExecution::Sequence(se) => (
se.sequence.has_selection_group(),
se.sequence.has_other_panel_group(),
)
};
Ok(Self {
id,
names,
keys: Vec::new(),
invocation_parser,
execution,
description,
selection_condition: FileTypeCondition::Any,
file_extensions: Vec::new(),
needs_selection,
needs_another_panel,
auto_exec: true,
show_in_doc: true,
panels: Vec::new(),
})
}
pub fn with_key(&mut self, key: KeyCombination) -> &mut Self {
self.keys.push(key);
self
}
pub fn add_keys(&mut self, keys: Vec<KeyCombination>) {
for key in keys {
self.keys.push(key);
}
}
pub fn no_doc(&mut self) -> &mut Self {
self.show_in_doc = false;
self
}
pub fn with_name(&mut self, name: &str) -> Result<&mut Self, ConfError> {
check_verb_name(name)?;
self.names.insert(0, name.to_string());
Ok(self)
}
pub fn with_description(&mut self, description: &str) -> &mut Self {
self.description = VerbDescription::from_text(description.to_string());
self
}
pub fn with_shortcut(&mut self, shortcut: &str) -> &mut Self {
self.names.push(shortcut.to_string());
self
}
pub fn with_condition(&mut self, selection_condition: FileTypeCondition) -> &mut Self {
self.selection_condition = selection_condition;
self
}
pub fn needing_another_panel(&mut self) -> &mut Self {
self.needs_another_panel = true;
self
}
pub fn with_auto_exec(&mut self, b: bool) -> &mut Self {
self.auto_exec = b;
self
}
pub fn has_name(&self, searched_name: &str) -> bool {
self.names.iter().any(|name| name == searched_name)
}
pub fn check_args(
&self,
sel_info: SelInfo<'_>,
invocation: &VerbInvocation,
other_path: &Option<PathBuf>,
) -> Option<String> {
match sel_info {
SelInfo::None => self.check_sel_args(None, invocation, other_path),
SelInfo::One(sel) => self.check_sel_args(Some(sel), invocation, other_path),
SelInfo::More(stage) => {
stage.paths().iter()
.filter_map(|path| {
let sel = Selection {
path,
line: 0,
stype: SelectionType::from(path),
is_exe: false,
};
self.check_sel_args(Some(sel), invocation, other_path)
})
.next()
}
}
}
fn check_sel_args(
&self,
sel: Option<Selection<'_>>,
invocation: &VerbInvocation,
other_path: &Option<PathBuf>,
) -> Option<String> {
if self.needs_selection && sel.is_none() {
Some("This verb needs a selection".to_string())
} else if self.needs_another_panel && other_path.is_none() {
Some("This verb needs exactly two panels".to_string())
} else if let Some(ref parser) = self.invocation_parser {
parser.check_args(invocation, other_path)
} else if invocation.args.is_some() {
Some("This verb doesn't take arguments".to_string())
} else {
None
}
}
pub fn get_status_markdown(
&self,
sel_info: SelInfo<'_>,
app_state: &AppState,
invocation: &VerbInvocation,
) -> String {
let name = self.names.first().unwrap_or(&invocation.name);
if let VerbExecution::Internal(internal_exec) = &self.execution {
if internal_exec.internal == Internal::focus {
return internal_focus::get_status_markdown(
self,
internal_exec,
sel_info,
invocation,
app_state,
);
}
}
let builder = || {
ExecutionStringBuilder::with_invocation(
self.invocation_parser.as_ref(),
sel_info,
app_state,
invocation.args.as_ref(),
)
};
if let VerbExecution::Sequence(seq_ex) = &self.execution {
let exec_desc = builder().shell_exec_string(
&ExecPattern::from_string(&seq_ex.sequence.raw)
);
format!("Hit *enter* to **{}**: `{}`", name, &exec_desc)
} else if let VerbExecution::External(external_exec) = &self.execution {
let exec_desc = builder().shell_exec_string(&external_exec.exec_pattern);
format!("Hit *enter* to **{}**: `{}`", name, &exec_desc)
} else if self.description.code {
format!("Hit *enter* to **{}**: `{}`", name, &self.description.content)
} else {
format!("Hit *enter* to **{}**: {}", name, &self.description.content)
}
}
pub fn get_unique_arg_anchor(&self) -> PathAnchor {
self.invocation_parser
.as_ref()
.map_or(PathAnchor::Unspecified, InvocationParser::get_unique_arg_anchor)
}
pub fn get_internal(&self) -> Option<Internal> {
match &self.execution {
VerbExecution::Internal(internal_exec) => Some(internal_exec.internal),
_ => None,
}
}
pub fn is_internal(&self, internal: Internal) -> bool {
self.get_internal() == Some(internal)
}
pub fn is_some_internal(v: Option<&Verb>, internal: Internal) -> bool {
v.map_or(false, |v| v.is_internal(internal))
}
pub fn is_sequence(&self) -> bool {
matches!(self.execution, VerbExecution::Sequence(_))
}
pub fn can_be_called_in_panel(&self, panel_state_type: PanelStateType) -> bool {
self.panels.is_empty() || self.panels.contains(&panel_state_type)
}
pub fn accepts_extension(&self, extension: Option<&str>) -> bool {
if self.file_extensions.is_empty() {
true
} else {
extension
.map_or(false, |ext| self.file_extensions.iter().any(|ve| ve == ext))
}
}
}
pub fn check_verb_name(name: &str) -> Result<(), ConfError> {
if regex_is_match!(r"^([@,#~&'%$\dù_-]+|[\w][\w_@,#~&'%$\dù_-]*)+$", name) {
Ok(())
} else {
Err(ConfError::InvalidVerbName{ name: name.to_string() })
}
}