use crate::ported::exec::dispatch_function_call;
use crate::ported::glob::remnulargs;
use crate::ported::lex::{parse_subst_string, untokenize};
use crate::ported::modules::zutil::bin_zparseopts;
use crate::ported::params::{getaparam, getsparam, setaparam, setsparam};
use crate::ported::utils::quotestring;
use crate::ported::zle::compcore::{get_compstate_str, set_compstate_str};
use crate::ported::zle::complete::bin_compset;
use crate::ported::zle::computil::bin_compquote;
use crate::ported::zsh_h::{options, MAX_OPS, QT_BACKSLASH};
fn make_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
fn compquote(argv: &[String]) {
let mut ops = make_ops();
let mut names: Vec<String> = Vec::with_capacity(argv.len());
let mut opts_done = false;
for a in argv {
if !opts_done && a.len() > 1 && a.starts_with('-') {
for ch in &a.as_bytes()[1..] {
ops.ind[*ch as usize] = 1;
}
} else {
opts_done = true;
names.push(a.clone());
}
}
bin_compquote("compquote", &names, &ops, 0);
}
fn q(s: &str) -> String {
quotestring(s, QT_BACKSLASH)
}
fn remove_chars(end: &str, qsep: &str) -> String {
let qsep1 = q(&qsep.chars().next().map(String::from).unwrap_or_default());
format!("{}{} \\t\\n\\-", end, qsep1)
}
fn dequote_q(s: &str) -> String {
match parse_subst_string(s) {
Ok(mut r) => {
remnulargs(&mut r);
untokenize(&r)
}
Err(_) => s.to_string(),
}
}
fn run_zparseopts_sequence(
args: &[String],
) -> (
Vec<String>,
Vec<String>,
Vec<String>,
Vec<String>,
Vec<String>,
Vec<String>,
Vec<String>,
) {
let src = "__compsys_argv";
crate::compsys::ported::shared::set_bridge_argv(src, args);
setaparam("opts", Vec::new());
setaparam("sep", Vec::new());
setaparam("num", Vec::new());
setaparam("pref", Vec::new());
setaparam("suf", Vec::new());
setaparam("cont", Vec::new());
setaparam("uniq", Vec::new());
let _ = bin_zparseopts(
"zparseopts",
&[
"-D".to_string(),
"-v".to_string(),
src.to_string(),
"-a".to_string(),
"opts".to_string(),
"s:=sep".to_string(),
"n:=num".to_string(),
"p:=pref".to_string(),
"i:=pref".to_string(),
"P:=pref".to_string(),
"I:=suf".to_string(),
"S:=suf".to_string(),
"q=suf".to_string(),
"r:=suf".to_string(),
"R:=suf".to_string(),
"C:=cont".to_string(),
"F:=cont".to_string(), "d=uniq".to_string(),
"M+:".to_string(),
"J+:".to_string(),
"V+:".to_string(),
"1".to_string(),
"2".to_string(),
"o+:".to_string(),
"X+:".to_string(),
"x+:".to_string(),
],
&make_ops(),
0,
);
let remaining = getaparam(src).unwrap_or_default();
crate::ported::params::unsetparam(src);
(
remaining,
getaparam("opts").unwrap_or_default(),
getaparam("sep").unwrap_or_default(),
getaparam("num").unwrap_or_default(),
getaparam("pref").unwrap_or_default(),
getaparam("suf").unwrap_or_default(),
getaparam("uniq").unwrap_or_default(),
)
}
pub fn _sequence(args: &[String]) -> i32 {
let _fn_scope = crate::compsys::ported::shared::FnScope::enter("_sequence");
crate::compsys::ported::shared::declare_locals(
&[
"opts", "sep", "num", "pref", "suf", "cont", "uniq", "dedup",
],
crate::compsys::ported::shared::PM_ARRAY,
);
crate::compsys::ported::shared::declare_locals(&["qsep"], 0);
let (mut argv, opts, sep, num, mut pref, mut suf, uniq) = run_zparseopts_sequence(args);
let sep_char = sep.get(1).cloned().unwrap_or_else(|| ",".to_string());
let mut nosep = false;
let mut end = String::new();
if let Some(s_pos) = suf.iter().position(|s| s == "-S") {
end = q(&suf.get(s_pos + 1).cloned().unwrap_or_default());
if !end.is_empty()
&& bin_compset(
"compset",
&["-S".to_string(), format!("{}*", end)],
&make_ops(),
0,
) == 0
{
suf.clear();
nosep = true;
}
}
setsparam("qsep", &sep_char);
compquote(&["-p".to_string(), "qsep".to_string()]);
let qsep = getsparam("qsep").unwrap_or_default();
let qqsep = q(&qsep);
let dedup: Vec<String> = if uniq.is_empty() {
let mut pre = String::new();
if let Some(p_pos) = pref.iter().position(|s| s == "-P") {
if let Some(v) = pref.get(p_pos + 1) {
pre = q(v);
}
}
let prefix = getsparam("PREFIX").unwrap_or_default();
let suffix = getsparam("SUFFIX").unwrap_or_default();
let trimmed_prefix = prefix.strip_prefix(&pre as &str).unwrap_or(&prefix);
let mut dd: Vec<String> = trimmed_prefix.split(&qsep).map(|s| s.to_string()).collect();
if dd.len() > 1 {
dd.pop(); } else {
dd.clear();
}
for tail in suffix.split(&qsep).skip(1) {
dd.push(tail.to_string());
}
if get_compstate_str("quoting")
.unwrap_or_default()
.is_empty()
{
dd = dd.iter().map(|e| dequote_q(e)).collect();
}
dd
} else {
Vec::new()
};
setaparam("dedup", dedup);
let num_val: i64 = num.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
if !num.is_empty()
&& bin_compset(
"compset",
&[
"-P".to_string(),
(num_val - 1).to_string(),
format!("*{}", qqsep),
],
&make_ops(),
0,
) == 0
{
pref.clear();
} else {
if !nosep && (num.is_empty() || num_val > 1) {
suf = vec![
"-S".to_string(),
qsep.clone(),
"-r".to_string(),
remove_chars(&end, &qsep),
];
}
if bin_compset(
"compset",
&["-S".to_string(), format!("{}*", qqsep)],
&make_ops(),
0,
) == 0
{
suf.clear();
}
if bin_compset(
"compset",
&["-P".to_string(), format!("*{}", qqsep)],
&make_ops(),
0,
) == 0
{
pref.clear();
}
}
tracing::debug!(
target: "compsys::_sequence",
qsep = %qsep,
qqsep = %qqsep,
quote = %get_compstate_str("quote").unwrap_or_default(),
suf = ?suf,
"sh:23-36 separator text for this quoting context",
);
set_compstate_str("ignored", "");
let minus = argv
.iter()
.skip(1)
.position(|s| s == "-")
.map(|i| i + 1)
.unwrap_or(argv.len());
let cmd_chunk = &argv[..minus];
let extras: Vec<String> = if minus < argv.len() {
argv[minus + 1..].to_vec()
} else {
Vec::new()
};
if cmd_chunk.is_empty() {
return 1;
}
let cmd = cmd_chunk[0].clone();
let mut call_argv: Vec<String> = cmd_chunk[1..].to_vec();
call_argv.extend(opts);
call_argv.push("-F".to_string());
call_argv.push("dedup".to_string());
call_argv.extend(pref);
call_argv.extend(suf);
call_argv.extend(extras);
if cmd == "compadd" {
return crate::ported::zle::complete::bin_compadd(
"compadd",
&call_argv,
&make_ops(),
0,
);
}
dispatch_function_call(&cmd, &call_argv).unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn remove_chars_matches_zsh() {
let _g = crate::test_util::global_state_lock();
crate::ported::utils::inittyptab();
assert_eq!(remove_chars("", "\\|"), "\\\\ \\t\\n\\-");
assert_eq!(remove_chars("", "|"), "\\| \\t\\n\\-");
assert_eq!(remove_chars("", ","), ", \\t\\n\\-");
assert_eq!(remove_chars("\\]", ","), "\\], \\t\\n\\-");
}
#[test]
fn returns_one_for_empty_command() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::params::setsparam("PREFIX", "");
let _ = crate::ported::params::setsparam("SUFFIX", "");
let r = _sequence(&["-".to_string()]);
assert_eq!(r, 1);
}
}