use crate::compsys::ported::_next_label::_next_label;
use crate::compsys::ported::_tags::_tags;
use crate::ported::modules::zutil::lookupstyle;
use crate::ported::params::{getaparam, getiparam, getsparam, setaparam, unsetparam};
use crate::ported::zle::compcore::{get_compstate_str, set_compstate_str};
use crate::ported::zle::complete::bin_compadd;
use crate::ported::zle::computil::bin_compdescribe;
use crate::ported::zsh_h::{options, MAX_OPS};
fn make_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
fn style_is_true(vals: &[String]) -> bool {
matches!(
vals.first().map(String::as_str),
Some("true") | Some("yes") | Some("on") | Some("1")
)
}
fn zstyle_t_default_true(ctx: &str, style: &str) -> bool {
let v = lookupstyle(ctx, style);
if v.is_empty() {
true
} else {
style_is_true(&v)
}
}
fn zstyle_t_default_false(ctx: &str, style: &str) -> bool {
let v = lookupstyle(ctx, style);
!v.is_empty() && style_is_true(&v)
}
fn zstyle_s(ctx: &str, style: &str) -> Option<String> {
lookupstyle(ctx, style).into_iter().next()
}
fn extract_match_parts(arr: &[String]) -> Vec<String> {
arr.iter()
.map(|e| {
let b = e.as_bytes();
let mut out = Vec::<u8>::with_capacity(b.len());
let mut i = 0usize;
while i < b.len() {
if b[i] == b'\\' && i + 1 < b.len() {
out.push(b[i + 1]); i += 2;
} else if b[i] == b':' {
break;
} else {
out.push(b[i]);
i += 1;
}
}
String::from_utf8(out).unwrap_or_default()
})
.collect()
}
fn resolve_array_arg(arg: &str) -> Vec<String> {
if arg.starts_with('(') && arg.ends_with(')') && arg.len() >= 2 {
arg[1..arg.len() - 1]
.split_whitespace()
.map(str::to_string)
.collect()
} else {
getaparam(arg).unwrap_or_default()
}
}
pub fn _describe(args: &[String]) -> i32 {
let mut _type = "values".to_string();
let mut _noprefix = false;
let mut _ret: i32 = 1;
let mut _hide = String::new();
let mut _jvx12: Vec<String> = Vec::new();
let mut idx = 0usize;
while idx < args.len() {
match args[idx].as_str() {
"-o" => {
_type = "options".to_string(); idx += 1;
}
"-O" => {
_type = "options".to_string(); _noprefix = true; idx += 1;
}
"-t" if idx + 1 < args.len() => {
_type = args[idx + 1].clone(); idx += 2;
}
"-1" | "-2" | "-J" | "-V" | "-x" => {
_jvx12.push(args[idx].clone()); idx += 1;
}
_ => break,
}
}
let curcontext = getsparam("curcontext").unwrap_or_default();
if _type == "options" && !_noprefix {
let prefix = getsparam("PREFIX").unwrap_or_default();
let is_dashplus = prefix.starts_with('-') || prefix.starts_with('+');
if !is_dashplus
&& zstyle_t_default_true(&format!(":completion:{}:options", curcontext), "prefix-needed")
{
return 1;
}
}
let style_ctx = format!(":completion:{}:{}", curcontext, _type);
let _showd = zstyle_t_default_true(&style_ctx, "verbose");
let _sep = zstyle_s(&style_ctx, "list-separator").unwrap_or_else(|| "--".to_string());
let _mlen: String = zstyle_s(&style_ctx, "max-matches-width").unwrap_or_else(|| {
let cols = getiparam("COLUMNS");
(if cols > 0 { cols / 2 } else { 0 }).to_string()
});
if idx >= args.len() {
return 1;
}
let _descr = args[idx].clone();
idx += 1;
let positional: Vec<String> = args[idx..].to_vec();
let grouped = _showd && zstyle_t_default_true(&style_ctx, "list-grouped");
let _oargv: Vec<String> = if grouped {
positional.clone()
} else {
Vec::new()
};
if _type == "options"
&& zstyle_t_default_false(&format!(":completion:{}:options", curcontext), "prefix-hidden")
{
let prefix = getsparam("PREFIX").unwrap_or_default();
_hide = if prefix.starts_with("--") {
"--".to_string()
} else if prefix.starts_with('-') {
"-".to_string()
} else if prefix.starts_with('+') {
"+".to_string()
} else {
String::new()
};
}
let _ = _tags(&[_type.clone()]);
let csl = get_compstate_str("list").unwrap_or_default();
let mut _try = 0i32;
while _tags(&[]) == 0 {
loop {
let mut nl_args = _jvx12.clone();
nl_args.push(_type.clone());
nl_args.push("_expl".to_string());
nl_args.push(_descr.clone());
if _next_label(&nl_args) != 0 {
break;
}
let mut a_names: Vec<String> = Vec::new();
let cd_argv: Vec<String> = if grouped {
_try += 1; let _expl_vals = getaparam("_expl").unwrap_or_default();
let mut _argv: Vec<String> = Vec::with_capacity(_oargv.len());
let mut _i = 1i32; let mut p = 0usize;
while p < _oargv.len() {
let _strs = format!("_a_{}{}", _try, _i);
let vals = resolve_array_arg(&_oargv[p]);
setaparam(&_strs, vals.clone());
a_names.push(_strs.clone());
_argv.push(_strs.clone());
p += 1;
_i += 1;
let (mats_name, mats_vals): (Option<String>, Vec<String>) =
if p >= _oargv.len() || _oargv[p].is_empty() || _oargv[p].starts_with('-') {
(None, Vec::new())
} else {
let mn = format!("_a_{}{}", _try, _i);
let mv = resolve_array_arg(&_oargv[p]);
setaparam(&mn, mv.clone());
a_names.push(mn.clone());
_argv.push(mn.clone());
p += 1;
_i += 1;
(Some(mn), mv)
};
let mut _opts: Vec<String> = Vec::new();
while p < _oargv.len() && _oargv[p] != "--" {
_opts.push(_oargv[p].clone());
_argv.push(_oargv[p].clone());
p += 1;
_i += 1;
}
if p < _oargv.len() && _oargv[p] == "--" {
_argv.push("--".to_string()); p += 1;
_i += 1;
}
let mut cadd: Vec<String> = _opts;
cadd.push("-2".to_string());
cadd.push("-o".to_string());
cadd.push("nosort".to_string());
cadd.extend(_expl_vals.clone());
cadd.push("-D".to_string());
cadd.push(_strs.clone());
if let Some(mn) = &mats_name {
cadd.push("-O".to_string());
cadd.push(mn.clone());
}
cadd.push("-".to_string());
let words = if mats_name.is_some() {
extract_match_parts(&mats_vals)
} else {
extract_match_parts(&vals)
};
cadd.extend(words);
bin_compadd("compadd", &cadd, &make_ops(), 0);
}
_argv } else {
positional.clone()
};
if _showd {
let mut cd: Vec<String> = vec![
"-I".to_string(),
_hide.clone(),
_mlen.clone(),
format!("{} ", _sep),
"_expl".to_string(),
];
if grouped {
cd.push("-g".to_string());
}
cd.extend(cd_argv.clone());
bin_compdescribe("compdescribe", &cd, &make_ops(), 0);
} else {
let mut cd: Vec<String> =
vec!["-i".to_string(), _hide.clone(), _mlen.clone()];
cd.extend(cd_argv.clone());
bin_compdescribe("compdescribe", &cd, &make_ops(), 0);
}
set_compstate_str("list", &csl);
loop {
let g_argv = [
"-g".to_string(),
"csl2".to_string(),
"_args".to_string(),
"_tmpm".to_string(),
"_tmpd".to_string(),
];
if bin_compdescribe("compdescribe", &g_argv, &make_ops(), 0) != 0 {
break;
}
let csl2 = getsparam("csl2").unwrap_or_default();
let mut list_val = format!("{} {}", csl, csl2);
if !csl2.is_empty() {
list_val = list_val.replacen("rows", "", 1);
}
set_compstate_str("list", &list_val);
let mut cadd: Vec<String> = getaparam("_args").unwrap_or_default();
cadd.push("-d".to_string());
cadd.push("_tmpd".to_string());
cadd.push("-a".to_string());
cadd.push("_tmpm".to_string());
if bin_compadd("compadd", &cadd, &make_ops(), 0) == 0 {
_ret = 0;
}
}
for n in &a_names {
unsetparam(n);
}
}
if _ret == 0 {
return 0;
}
}
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_one_for_empty_args() {
let _g = crate::test_util::global_state_lock();
assert_eq!(_describe(&[]), 1);
}
#[test]
fn returns_one_with_no_tag_setup() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
_describe(&[
"-t".to_string(),
"mytag".to_string(),
"description".to_string(),
"myarr".to_string(),
]),
1
);
}
#[test]
fn extract_match_parts_splits_on_unescaped_colon_and_unescapes() {
assert_eq!(
extract_match_parts(&[
"foo:the foo".to_string(),
"a\\:b:desc".to_string(),
"plain".to_string(),
]),
vec!["foo".to_string(), "a:b".to_string(), "plain".to_string()]
);
}
#[test]
fn style_truthiness_matches_zstyle_semantics() {
assert!(style_is_true(&["yes".to_string()]));
assert!(style_is_true(&["1".to_string()]));
assert!(!style_is_true(&["no".to_string()]));
assert!(!style_is_true(&[]));
}
#[test]
fn resolve_array_arg_parses_inline_literal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
resolve_array_arg("(a b c)"),
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
}
}