use crate::compsys::ported::_all_labels::_all_labels;
use crate::compsys::ported::_dynamic_directory_name::_dynamic_directory_name;
use crate::compsys::ported::_message::_message;
use crate::compsys::ported::_parameters::_parameters;
use crate::compsys::ported::_requested::_requested;
use crate::compsys::ported::_tags::_tags;
use crate::compsys::ported::_values::_values;
use crate::compsys::ported::_wanted::_wanted;
use crate::ported::exec::dispatch_function_call;
use crate::ported::modules::zutil::{bin_zformat, bin_zstyle};
use crate::ported::params::{getaparam, getsparam, setaparam, setsparam, unsetparam};
use crate::ported::zle::compcore::get_compstate_str;
use crate::ported::zle::complete::bin_compset;
use crate::ported::zle::computil::bin_compquote;
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 s(v: &str) -> String {
v.to_string()
}
fn param_type(name: &str) -> String {
if name.is_empty() {
return String::new();
}
let is_assoc = crate::ported::params::paramtab_hashed_storage()
.lock()
.map(|t| t.contains_key(name))
.unwrap_or(false);
if is_assoc {
s("association")
} else if getaparam(name).is_some() {
s("array")
} else if getsparam(name).is_some() {
s("scalar")
} else {
String::new()
}
}
fn index_matches(i: &str, prefix: &str, suffix: &str) -> bool {
i.len() >= prefix.len() + suffix.len() && i.starts_with(prefix) && i.ends_with(suffix)
}
fn dir_abbrev(val: &str) -> String {
if let Some(home) = getsparam("HOME") {
if !home.is_empty() {
if val == home {
return s("~");
}
let with_slash = format!("{}/", home);
if let Some(rest) = val.strip_prefix(&with_slash) {
return format!("~/{}", rest);
}
}
}
val.to_string()
}
pub fn _subscript(args: &[String]) -> i32 {
let isuffix = getsparam("ISUFFIX").unwrap_or_default();
let mut osuf: String = if isuffix.contains(']') {
String::new()
} else {
s("]")
};
if args.first().map(|a| a == "-q").unwrap_or(false) {
let _ = setsparam("osuf", &osuf);
let _ = bin_compquote("compquote", &[s("osuf")], &make_ops(), 0);
osuf = getsparam("osuf").unwrap_or_default();
unsetparam("osuf");
osuf.push(' ');
}
let _ = bin_compset(
"compset",
&[s("-P"), s("\\(([^\\(\\)]|\\(*\\))##\\)")],
&make_ops(),
0,
);
let prefix = getsparam("PREFIX").unwrap_or_default();
let buffer = getsparam("BUFFER").unwrap_or_default();
let cursor: i64 = getsparam("CURSOR")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
{
let bytes = buffer.as_bytes();
let mut pos: i64 = cursor + 1;
while pos > 1 && bytes.get((pos - 2) as usize).copied() != Some(b'[') {
pos -= 1;
}
if pos >= 2 {
let end = (pos - 1) as usize; if end <= buffer.len() {
let segment = &buffer[..end];
if let Some(before_bracket) = segment.strip_suffix('[') {
if let Some(before_tilde) = before_bracket.strip_suffix('~') {
let ok = before_tilde.is_empty()
|| before_tilde
.chars()
.last()
.map(|c| c.is_whitespace() || c == ':' || c == '=')
.unwrap_or(false);
if ok {
return _dynamic_directory_name();
}
}
}
}
}
}
if prefix.starts_with(':') {
return _wanted(&[
s("characters"),
s("expl"),
s("character class"),
s("compadd"),
s("-p:"),
s("-S"),
s(":]"),
s("alnum"),
s("alpha"),
s("ascii"),
s("blank"),
s("cntrl"),
s("digit"),
s("graph"),
s("lower"),
s("print"),
s("punct"),
s("space"),
s("upper"),
s("xdigit"),
s("IFS"),
s("IDENT"),
s("IFSSPACE"),
s("WORD"),
]);
}
let param = get_compstate_str("parameter").unwrap_or_default();
if bin_compset("compset", &[s("-P"), s("\\(")], &make_ops(), 0) == 0 {
let _ = bin_compset("compset", &[s("-S"), s("\\)*")], &make_ops(), 0);
let prefix2 = getsparam("PREFIX").unwrap_or_default();
let pchars: Vec<char> = prefix2.chars().collect();
if let Some(i) = pchars
.iter()
.rposition(|&c| c == 'b' || c == 'n' || c == 's')
{
let f = pchars[i];
let d: String = pchars.get(i + 1).map(|c| c.to_string()).unwrap_or_default();
let mut e: String = d.clone();
let v: String = if i + 2 <= pchars.len() {
pchars[i + 2..].iter().collect()
} else {
String::new()
};
if f == 's' && !param_type(¶m).starts_with("scalar") {
return 1;
}
if d.is_empty() {
return _message(&[s("-e"), s("delimiters"), s("delimiter")]);
} else {
match d.as_str() {
"(" => e = s(")"),
"[" => e = s("]"),
"{" => e = s("}"),
_ => {}
}
if !v.contains(&e) {
match f {
's' => {
let _ = _message(&[s("separator string")]);
}
'b' | 'n' => {
if v.chars().all(|c| c.is_ascii_digit()) {
let _ = _message(&[s("number")]);
} else {
return 1;
}
}
_ => {}
}
let suffix = getsparam("SUFFIX").unwrap_or_default();
let isuffix2 = getsparam("ISUFFIX").unwrap_or_default();
let combined = format!("{}{}", suffix, isuffix2);
if !v.is_empty() && !combined.contains(&e) {
let _ = _message(&[s("delimiter")]);
}
return 0;
}
}
}
let ptype = param_type(¶m);
let assoc_specs: [&str; 7] = [
"(R k K i I)r[any one value matched by subscript as pattern]",
"(r k K i I)R[all values matched by subscript as pattern]",
"(r R K i I)k[any one value where subscript matched by key as pattern]",
"(r R k i I)K[all values where subscript matched by key as pattern]",
"(r R k K I)i[any one key matched by subscript as pattern]",
"(r R k K i)I[all keys matched by subscript as pattern]",
"e[interpret * or @ as a single key]",
];
let scalar_specs: [&str; 4] = [
"f[make subscripting work on lines of scalar]",
"w[make subscripting work on words of scalar]",
"s[specify word separator]",
"p[recognise escape sequences in subsequent s flag]",
];
let array_specs: [&str; 7] = [
"e[interpret * or @ as a single key and use plain string matching]",
"n[Nth lowest/highest index with i/I/r/R flag]",
"b[begin with specified element]",
"(r R k K i)I[highest index of value matched by subscript]",
"(r R k K I)i[lowest index of value matched by subscript]",
"(r k K i I)R[value matched by subscript at highest index]",
"(R k K i I)r[value matched by subscript at lowest index]",
];
let mut flags: Vec<String> = Vec::new();
if ptype.starts_with("assoc") {
flags.extend(assoc_specs.iter().map(|x| s(x)));
} else if ptype.is_empty() || ptype.starts_with("scalar") {
flags.extend(scalar_specs.iter().map(|x| s(x)));
flags.extend(array_specs.iter().map(|x| s(x)));
} else if ptype.starts_with("array") {
flags.extend(array_specs.iter().map(|x| s(x)));
}
let mut vargs: Vec<String> = vec![s("-s"), s(""), s("subscript flag")];
vargs.extend(flags);
return _values(&vargs);
}
if param_type(¶m).starts_with("assoc") {
let mut keys: Vec<String> = Vec::new();
if let Ok(tab) = crate::ported::params::paramtab_hashed_storage().lock() {
if let Some(h) = tab.get(¶m) {
keys.extend(h.keys().cloned());
}
}
for k in keys.iter_mut() {
let mut esc = String::with_capacity(k.len());
for c in k.chars() {
if matches!(c, '$' | '\\' | '[' | ']' | '(' | ')' | '{' | '}') {
esc.push('\\');
}
esc.push(c);
}
*k = esc;
}
for k in keys.iter_mut() {
if k == "*" || k == "@" {
*k = format!("(e){}", k);
}
}
let rbuffer = getsparam("RBUFFER").unwrap_or_default();
let starts_bracket = rbuffer.starts_with(']') || rbuffer.starts_with("\\]");
let suf: String = if !starts_bracket {
osuf.clone()
} else {
String::new()
};
setaparam("keys", keys);
let r = _wanted(&[
s("association-keys"),
s("expl"),
s("association key"),
s("compadd"),
s("-Q"),
s("-S"),
suf,
s("-a"),
s("keys"),
]);
unsetparam("keys");
return r;
}
if param_type(¶m).starts_with("array") {
let mut ret: i32 = 1;
let _ = _tags(&[s("indexes"), s("parameters")]);
loop {
if _tags(&[]) != 0 {
break;
}
if _requested(&[s("indexes")]) == 0 {
let arr = getaparam(¶m).unwrap_or_default();
let n = arr.len();
let ind: Vec<String> = (1..=n).map(|i| i.to_string()).collect();
setaparam("ind", ind.clone());
let curcontext = getsparam("curcontext").unwrap_or_default();
let ctx = format!(":completion:{}:indexes", curcontext);
let verbose = bin_zstyle(
"zstyle",
&[s("-T"), ctx.clone(), s("verbose")],
&make_ops(),
0,
) == 0;
let disp: Vec<String>;
if verbose {
let pfx = getsparam("PREFIX").unwrap_or_default();
let sfx = getsparam("SUFFIX").unwrap_or_default();
let mut list: Vec<String> = Vec::new();
for (idx0, iv) in ind.iter().enumerate() {
if index_matches(iv, &pfx, &sfx) {
let val = arr.get(idx0).cloned().unwrap_or_default();
list.push(format!("{}:{}", iv, dir_abbrev(&val)));
} else {
list.push(String::new());
}
}
unsetparam("sep");
let got = bin_zstyle(
"zstyle",
&[s("-s"), ctx.clone(), s("list-separator"), s("sep")],
&make_ops(),
0,
);
let sep = if got == 0 {
getsparam("sep").unwrap_or_else(|| s("--"))
} else {
s("--")
};
let mut zf: Vec<String> = vec![s("-a"), s("list"), format!(" {} ", sep)];
zf.extend(list);
let _ = bin_zformat("zformat", &zf, &make_ops(), 0);
disp = vec![s("-d"), s("list")];
} else {
disp = Vec::new();
}
let rbuffer = getsparam("RBUFFER").unwrap_or_default();
let starts_bracket = rbuffer.starts_with(']') || rbuffer.starts_with("\\]");
let mut la: Vec<String> = vec![
s("-V"),
s("indexes"),
s("expl"),
s("array index"),
s("compadd"),
s("-S"),
];
if starts_bracket {
la.push(s(""));
} else {
la.push(osuf.clone());
}
la.extend(disp); la.push(s("-a"));
la.push(s("ind"));
if _all_labels(&la) == 0 {
ret = 0;
}
unsetparam("list");
unsetparam("sep");
}
if _requested(&[s("parameters")]) == 0 && _parameters(&[]) == 0 {
ret = 0;
}
if ret == 0 {
unsetparam("ind");
return 0;
}
}
unsetparam("ind");
return 1;
}
dispatch_function_call("_dispatch", &[s("-math-"), s("-math-")]).unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_no_executor_falls_to_dispatch() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::params::setsparam("PREFIX", "");
let _ = crate::ported::params::setsparam("ISUFFIX", "");
let _ = crate::ported::params::setsparam("BUFFER", "");
let _ = crate::ported::params::setsparam("CURSOR", "0");
crate::ported::zle::compcore::set_compstate_str("parameter", "");
assert_eq!(_subscript(&[]), 1);
}
#[test]
fn osuf_empty_when_isuffix_has_bracket() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::params::setsparam("ISUFFIX", "]rest");
let _ = crate::ported::params::setsparam("PREFIX", ":");
let _ = crate::ported::params::setsparam("BUFFER", "");
let _ = crate::ported::params::setsparam("CURSOR", "0");
let _ = _subscript(&[]);
}
#[test]
fn param_type_classifies_scalar_array_assoc_unset() {
let _g = crate::test_util::global_state_lock();
crate::ported::params::unsetparam("st_scalar");
crate::ported::params::unsetparam("st_array");
let _ = crate::ported::params::setsparam("st_scalar", "x");
assert_eq!(param_type("st_scalar"), "scalar");
setaparam("st_array", vec![s("a"), s("b")]);
assert_eq!(param_type("st_array"), "array");
assert_eq!(param_type("never_ever_set_qwerty"), "");
assert_eq!(param_type(""), "");
}
#[test]
fn index_matches_prefix_suffix_literal() {
assert!(index_matches("123", "1", "3"));
assert!(index_matches("12", "1", "2"));
assert!(!index_matches("12", "3", ""));
assert!(index_matches("5", "", ""));
assert!(!index_matches("1", "1", "1"));
}
#[test]
fn dir_abbrev_replaces_home_prefix() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::params::setsparam("HOME", "/home/u");
assert_eq!(dir_abbrev("/home/u"), "~");
assert_eq!(dir_abbrev("/home/u/x/y"), "~/x/y");
assert_eq!(dir_abbrev("/etc/passwd"), "/etc/passwd");
}
}