use {
super::CommandParts,
crate::{
app::*,
path::{
self,
PathAnchor,
},
syntactic::SYNTAX_THEMES,
verb::*,
},
lazy_regex::regex_captures,
std::{
io,
path::Path,
},
};
fn common_start<'l>(
a: &'l str,
b: &str,
) -> &'l str {
let l = a.len().min(b.len());
for i in 0..l {
if a.as_bytes()[i] != b.as_bytes()[i] {
return &a[..i];
}
}
&a[..l]
}
#[derive(Debug)]
pub enum Completions {
None,
Common(String),
List(Vec<String>),
}
impl Completions {
fn from_list(completions: Vec<String>) -> Self {
if completions.is_empty() {
return Self::None;
}
let mut iter = completions.iter();
let mut common: &str = match iter.next() {
Some(s) => s,
_ => {
return Self::None;
}
};
for c in iter {
common = common_start(common, c);
}
if common.is_empty() {
Self::List(completions)
} else {
Self::Common(common.to_string())
}
}
fn for_wholes(
start: &str,
wholes: &[&str],
) -> Self {
let completions = wholes
.iter()
.map(|w| {
if let Some(stripped) = w.strip_prefix(start) {
stripped
} else {
warn!("unexpected non completing whole: {w:?}");
*w
}
})
.map(ToString::to_string)
.collect();
Self::from_list(completions)
}
fn for_verb(
start: &str,
con: &AppContext,
sel_info: SelInfo<'_>,
panel_state_type: Option<PanelStateType>,
) -> Self {
match con
.verb_store
.search(start, Some(sel_info), false, panel_state_type)
{
PrefixSearchResult::NoMatch => Self::None,
PrefixSearchResult::Match(name, _) => {
if start.len() >= name.len() {
debug_assert!(name == start);
Self::None
} else {
Self::Common(name[start.len()..].to_string())
}
}
PrefixSearchResult::Matches(completions) => Self::for_wholes(start, &completions),
}
}
fn list_for_path(
verb_name: &str,
arg: &str,
path: &Path,
sel_info: SelInfo<'_>,
con: &AppContext,
panel_state_type: Option<PanelStateType>,
) -> io::Result<Vec<String>> {
let anchor = match con
.verb_store
.search(verb_name, Some(sel_info), false, panel_state_type)
{
PrefixSearchResult::Match(_, verb) => verb.get_unique_arg_anchor(),
_ => PathAnchor::Unspecified,
};
let (_, parent_part, child_part) = regex_captures!(r"^(.*?)([^/]*)$", arg).unwrap();
let parent = path::path_from(path, anchor, parent_part);
let mut children = Vec::new();
if parent.exists() {
for entry in parent.read_dir()? {
let entry = entry?;
let mut name = entry.file_name().to_string_lossy().to_string();
if !child_part.is_empty() {
if !name.starts_with(child_part) {
continue;
}
if name == child_part && entry.file_type()?.is_dir() {
name = "/".to_string();
} else {
name.drain(0..child_part.len());
}
}
children.push(name);
}
} else {
debug!(
"no path completion possible because {:?} doesn't exist",
&parent
);
}
Ok(children)
}
fn for_arg(
verb_name: &str,
arg: &str,
con: &AppContext,
sel_info: SelInfo<'_>,
panel_state_type: Option<PanelStateType>,
) -> Self {
if arg.contains(' ') {
return Self::None;
}
let is_theme = con
.verb_store
.search_sel_info_unique(verb_name, sel_info, panel_state_type)
.and_then(|verb| verb.invocation_parser.as_ref())
.and_then(InvocationParser::get_unique_arg_def)
.is_some_and(|arg_def| arg_def.has_flag(VerbArgFlag::Theme));
if is_theme {
Self::for_theme_arg(arg)
} else {
Self::for_path_arg(verb_name, arg, con, sel_info, panel_state_type)
}
}
fn for_theme_arg(arg: &str) -> Self {
let arg = arg.to_lowercase();
let completions: Vec<String> = SYNTAX_THEMES
.iter()
.map(|st| st.name().to_lowercase())
.filter_map(|name| name.strip_prefix(&arg).map(ToString::to_string))
.collect();
Self::from_list(completions)
}
fn for_path_arg(
verb_name: &str,
arg: &str,
con: &AppContext,
sel_info: SelInfo<'_>,
panel_state_type: Option<PanelStateType>,
) -> Self {
if arg.contains(' ') {
return Self::None;
}
match &sel_info {
SelInfo::None => Self::None,
SelInfo::One(sel) => {
match Self::list_for_path(verb_name, arg, sel.path, sel_info, con, panel_state_type)
{
Ok(list) => Self::from_list(list),
Err(e) => {
warn!("Error while trying to complete path: {e:?}");
Self::None
}
}
}
SelInfo::More(stage) => {
let mut lists = stage.paths().iter().filter_map(|path| {
Self::list_for_path(verb_name, arg, path, sel_info, con, panel_state_type).ok()
});
let Some(mut list) = lists.next() else {
return Self::None;
};
for ol in lists {
list.retain(|c| ol.contains(c));
if list.is_empty() {
break;
}
}
Self::from_list(list)
}
}
}
pub fn for_input(
parts: &CommandParts,
con: &AppContext,
sel_info: SelInfo<'_>,
panel_state_type: Option<PanelStateType>,
) -> Self {
match &parts.verb_invocation {
Some(invocation) if !invocation.is_empty() => {
match &invocation.args {
None => {
Self::for_verb(&invocation.name, con, sel_info, panel_state_type)
}
Some(args) if !args.is_empty() => {
Self::for_arg(&invocation.name, args, con, sel_info, panel_state_type)
}
_ => {
Self::None
}
}
}
_ => Self::None, }
}
}