use crate::compsys::ported::_arrays::_arrays;
use crate::compsys::ported::_delimiters::_delimiters;
use crate::compsys::ported::_describe::_describe;
use crate::compsys::ported::_globqual_delims::_globqual_delims;
use crate::compsys::ported::_history_modifiers::_history_modifiers;
use crate::compsys::ported::_message::_message;
use crate::compsys::ported::_parameters::call_parameters;
use crate::ported::params::{getsparam, setaparam, setsparam, unsetparam};
use crate::ported::zle::complete::bin_compset;
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 first_char() -> String {
getsparam("PREFIX")
.unwrap_or_default()
.chars()
.next()
.map(|c| c.to_string())
.unwrap_or_default()
}
fn describe_format(specs: &[&str]) -> i32 {
setaparam("flags", specs.iter().map(|x| s(x)).collect());
let dargv = vec![
s("-t"),
s("format"),
s("format option"),
s("flags"),
s("-Q"),
s("-S"),
s(""),
];
let r = _describe(&dargv);
unsetparam("flags");
r
}
pub fn _brace_parameter() -> i32 {
let _fn_scope = crate::compsys::ported::shared::FnScope::enter("_brace_parameter");
crate::compsys::ported::shared::declare_locals(
&["char", "delim", "found_percent", "found_m", "exp"], 0,
);
crate::compsys::ported::shared::declare_locals(
&["flags"], crate::compsys::ported::shared::PM_ARRAY,
);
crate::compsys::ported::shared::declare_locals(
&["q_last", "n_q"], crate::compsys::ported::shared::PM_INTEGER,
);
let prefix = getsparam("PREFIX").unwrap_or_default();
let in_flag_group = match prefix.rfind("${(") {
Some(idx) => !prefix[idx + 3..].contains(')'),
None => false,
};
if in_flag_group {
let _ = bin_compset("compset", &[s("-p"), s("3")], &make_ops(), 0);
let mut q_last: i64 = 0;
let mut n_q: i64 = 0;
let mut found_percent = false;
let mut found_m = false;
loop {
let pfx = getsparam("PREFIX").unwrap_or_default();
if pfx.is_empty() {
break;
}
let char = pfx.chars().next().unwrap();
let _ = bin_compset("compset", &[s("-p"), s("1")], &make_ops(), 0);
if char == 'q' {
q_last += 1;
n_q += 1;
continue;
} else {
q_last = 0;
}
match char {
'%' => {
found_percent = true;
}
'm' => {
found_m = true;
}
'g' | 'I' | 'j' | 's' | 'Z' | '_' => {
if getsparam("PREFIX").unwrap_or_default().is_empty() {
return _delimiters(&[format!("qualifier-{}", char)]);
} else if _globqual_delims() != 0 {
match char {
'g' => {
let _ = bin_compset("compset", &[s("-P"), s("*")], &make_ops(), 0);
return describe_format(&[
"o:octal escapes",
"c:expand ^X etc.",
"e:expand \\M-t etc.",
]);
}
'I' => {
return _message(&[s("integer expression")]);
}
'j' | 's' => {
return _message(&[s("separator")]);
}
'Z' => {
let _ = bin_compset("compset", &[s("-P"), s("*")], &make_ops(), 0);
return describe_format(&[
"c:parse comments as strings (else as ordinary words)",
"C:strip comments (else treat as ordinary words)",
"n:treat newlines as whitespace",
]);
}
'_' => {
return _message(&[s("no useful values")]);
}
_ => unreachable!(),
}
}
}
'l' | 'r' => {
if getsparam("PREFIX").unwrap_or_default().is_empty() {
return _delimiters(&[format!("qualifier-{}", char)]);
} else {
let _ = setsparam("delim", &first_char());
if _globqual_delims() != 0 {
return _message(&[s("padding width")]);
}
if getsparam("delim").unwrap_or_default() == first_char() {
if _globqual_delims() != 0 {
return _message(&[s("repeated padding")]);
}
if getsparam("delim").unwrap_or_default() == first_char() {
if _globqual_delims() != 0 {
return _message(&[s("one-off padding")]);
}
}
}
}
}
_ => {}
}
}
let mut flags: Vec<String> = Vec::new();
if !found_percent {
flags.push(s("%:expand prompt sequences"));
} else {
flags.push(s("%:expand prompts respecting options"));
}
match q_last {
0 => {
if n_q == 0 {
flags.push(s("q:quote with backslashes"));
}
}
1 => {
flags.push(s("q:quote with single quotes"));
flags.push(s("-:quote minimally for readability"));
flags.push(s("+:quote like q-, plus $'...' for unprintable characters"));
}
2 => {
flags.push(s("q:quote with double quotes"));
}
3 => {
flags.push(s("q:quote with $'...'"));
}
_ => {}
}
if n_q == 0 {
flags.push(s("Q:remove one level of quoting"));
}
if !found_m {
flags.push(s("m:count multibyte width in padding calculation"));
} else {
flags.push(s(
"m:count number of character code points in padding calculation",
));
}
for spec in [
"#:interpret numeric expression as character code",
"@:prevent double-quoted joining of arrays",
"*:enable extended globs for pattern",
"A:assign as an array parameter",
"a:sort in array index order (with O to reverse)",
"b:backslash quote pattern characters only",
"c:count characters in an array (with ${(c)#...})",
"C:capitalize words",
"D:perform directory name abbreviation",
"e:perform single-word shell expansions",
"f:split the result on newlines",
"F:join arrays with newlines",
"g:process echo array sequences (needs options)",
"i:sort case-insensitively",
"k:substitute keys of associative arrays",
"L:lower case all letters",
"n:sort positive decimal integers numerically",
"-:sort decimal integers numerically",
"o:sort in ascending order (lexically if no other sort option)",
"O:sort in descending order (lexically if no other sort option)",
"P:use parameter value as name of parameter for redirected lookup",
"t:substitute type of parameter",
"u:substitute first occurrence of each unique word",
"U:upper case all letters",
"v:substitute values of associative arrays (with (k))",
"V:visibility enhancements for special characters",
"w:count words in array or string (with ${(w)#...})",
"W:count words including empty words (with ${(W)#...})",
"X:report parsing errors and eXit substitution",
"z:split words as if zsh command line",
"0:split words on null bytes",
"p:handle print escapes or variables in parameter flag arguments",
"~:treat strings in parameter flag arguments as patterns",
"j:join arrays with specified string",
"l:left-pad resulting words",
"r:right-pad resulting words",
"s:split words on specified string",
"Z:split words as if zsh command line (with options)",
"S:match non-greedy in /, // or search substrings in % and # expressions",
"I:search <argument>th match in #, %, / expressions",
"B:include index of beginning of match in #, % expressions",
"E:include index of one past end of match in #, % expressions",
"M:include matched portion in #, % expressions",
"N:include length of match in #, % expressions",
"R:include rest (unmatched portion) in #, % expressions",
] {
flags.push(s(spec));
}
setaparam("flags", flags);
let dargv = vec![
s("-t"),
s("flags"),
s("parameter flag"),
s("flags"),
s("-Q"),
s("-S"),
s(""),
];
let r = _describe(&dargv);
unsetparam("flags");
return r;
}
if bin_compset(
"compset",
&[s("-P"), s("*:([\\|\\*\\^]|\\^\\^)")],
&make_ops(),
0,
) == 0
{
return _arrays(&[]);
}
if bin_compset("compset", &[s("-P"), s("*:")], &make_ops(), 0) == 0 {
setaparam(
"flags",
vec![
s("-:substitute alternate value if parameter is null"),
s("+:substitute alternate value if parameter is non-null"),
s("=:substitute and assign alternate value if parameter is null"),
s("\\:=:unconditionally assign value to parameter"),
s("?:print error if parameter is null"),
s("#:filter value matching pattern"),
s("/:replace whole word matching pattern"),
s("|:set difference"),
s("*:set intersection"),
s("^:zip arrays"),
s("^^:zip arrays reusing values from shorter array"),
],
);
let dargv = vec![
s("-t"),
s("flags"),
s("operator"),
s("flags"),
s("-Q"),
s("-S"),
s(""),
];
let _ = _describe(&dargv);
unsetparam("flags");
return _history_modifiers(&[s("p")]);
}
call_parameters(&[s("-e")], 214)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_name_falls_to_parameters() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::params::setsparam("PREFIX", "myvar");
assert_eq!(_brace_parameter(), 127);
}
#[test]
fn in_flag_group_detection() {
let mk = |p: &str| -> bool {
match p.rfind("${(") {
Some(idx) => !p[idx + 3..].contains(')'),
None => false,
}
};
assert!(mk("${("));
assert!(mk("${(f"));
assert!(mk("x${(oL"));
assert!(!mk("${(f)")); assert!(!mk("${(f)x")); assert!(!mk("myvar")); }
#[test]
fn first_char_reads_prefix_head() {
let _g = crate::test_util::global_state_lock();
let _ = crate::ported::params::setsparam("PREFIX", "abc");
assert_eq!(first_char(), "a");
let _ = crate::ported::params::setsparam("PREFIX", "");
assert_eq!(first_char(), "");
}
}