use crate::ported::exec_hooks::dispatch_function_call;
use crate::ported::modules::zutil::lookupstyle;
use crate::ported::params::{getsparam, setaparam};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::zle::complete::bin_compadd;
use crate::ported::zsh_h::{options, MAX_OPS};
fn make_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
pub fn _combination(args: &[String]) -> i32 {
let mut idx = 0usize;
let mut sep = ":".to_string();
if let Some(a) = args.first() {
if a == "-s" && args.len() >= 2 {
sep = args[1].clone();
idx = 2;
} else if let Some(rest) = a.strip_prefix("-s") {
sep = rest.to_string();
idx = 1;
}
}
if args.len() < idx + 2 {
return 1;
}
let tag = args[idx].clone();
let style = args[idx + 1].clone();
idx += 2;
let keys: Vec<&str> = style.split('-').collect();
let mut pats: Vec<String> = keys.iter().map(|_| "*".to_string()).collect();
let mut key_arg = String::new();
let mut key_num: usize = 1;
while idx < args.len() {
let a = &args[idx];
if a.contains('=') {
let eq = a.find('=').unwrap();
let tmp = &a[..eq];
let pat = &a[eq + 1..];
let (key_part, num_str) = if let Some(c) = tmp.find(':') {
(&tmp[..c], &tmp[c + 1..])
} else {
(tmp, "1")
};
let num: usize = num_str.parse().unwrap_or(1);
let mut found_count = 0usize;
for (i, k) in keys.iter().enumerate() {
if *k == key_part {
found_count += 1;
if found_count == num {
pats[i] = pat.to_string();
break;
}
}
}
idx += 1;
} else {
let (k_part, n_str) = if let Some(c) = a.find(':') {
(&a[..c], &a[c + 1..])
} else {
(a.as_str(), "1")
};
key_arg = k_part.to_string();
key_num = n_str.parse().unwrap_or(1);
idx += 1;
break;
}
}
let extras: &[String] = &args[idx..];
let curcontext = getsparam("curcontext").unwrap_or_default();
let style_ctx = format!(":completion:{}:{}", curcontext, tag);
let style_vals = lookupstyle(&style_ctx, &style);
if style_vals.is_empty() {
return dispatch_function_call(&format!("_{}", key_arg), extras).unwrap_or(1);
}
let combined_pat = pats.join(&sep);
let prog = patcompile(&combined_pat, 0, None);
let mut matches: Vec<String> = Vec::new();
for entry in &style_vals {
let head = entry.split(&sep).next().unwrap_or(entry);
let _ = head;
let matches_combined = match prog.as_ref() {
Some(p) => pattry(p, entry),
None => false,
};
if matches_combined {
let fields: Vec<&str> = entry.split(&sep).collect();
let mut occ = 0usize;
for (i, k) in keys.iter().enumerate() {
if *k == key_arg {
occ += 1;
if occ == key_num {
if let Some(f) = fields.get(i) {
matches.push(f.to_string());
}
break;
}
}
}
}
}
matches.sort();
matches.dedup();
if matches.is_empty() {
return dispatch_function_call(&format!("_{}", key_arg), extras).unwrap_or(1);
}
setaparam("tmp", matches);
let mut compadd_argv: Vec<String> = extras.to_vec();
compadd_argv.push("-a".to_string());
compadd_argv.push("tmp".to_string());
if bin_compadd("compadd", &compadd_argv, &make_ops(), 0) == 0 {
0
} else {
dispatch_function_call(&format!("_{}", key_arg), extras).unwrap_or(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_one_without_style() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
_combination(&[
"mytag".to_string(),
"users-hosts".to_string(),
"users".to_string(),
]),
1
);
}
}