use crate::ported::exec::dispatch_function_call;
use crate::ported::params::{getaparam, getiparam, setaparam, setiparam};
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 _zcalc_line_escapes() -> i32 {
let base = [
"!:shell escape",
"q:quit",
"norm:normal output format",
"sci:scientific output format",
"fix:fixed point output format",
"eng:engineering (power of 1000) output format",
"raw:raw output format",
"local:make variables local",
"function:define math function (also \\:func or \\:f)",
];
let cmds: Vec<String> = base.iter().map(|c| format!("\\:{}", c)).collect();
setaparam("cmds", cmds);
dispatch_function_call(
"_describe",
&[
"-t".to_string(),
"command-escapes".to_string(),
"command escape".to_string(),
"cmds".to_string(),
"-Q".to_string(),
],
)
.unwrap_or(1)
}
fn user_math_functions() -> Vec<String> {
const PFX: &str = "zsh_math_func_";
let Ok(tab) = crate::ported::hashtable::shfunctab_lock().read() else {
return Vec::new();
};
tab.iter()
.filter_map(|(k, _)| k.strip_prefix(PFX).map(|s| s.to_string()))
.collect()
}
fn strip_leading_bang(w: &str) -> String {
if let Some(rest) = w.strip_prefix(":\\!") {
rest.to_string()
} else if let Some(rest) = w.strip_prefix(":!") {
rest.to_string()
} else {
w.to_string()
}
}
pub fn _zcalc_line() -> i32 {
let words = getaparam("words").unwrap_or_default();
let current = getiparam("CURRENT");
let word1 = words.first().cloned().unwrap_or_default();
let is_bang = word1.starts_with(":!") || word1.starts_with(":\\!");
if current == 1 && !is_bang {
let mut alts: Vec<String> = Vec::new();
if word1.is_empty() || word1.starts_with(':') {
alts.push("command-escapes:command escape:_zcalc_line_escapes".to_string());
}
if word1.is_empty() || !word1.starts_with(':') {
alts.push("math:math formula:_math".to_string());
}
return dispatch_function_call("_alternative", &alts).unwrap_or(1);
}
if is_bang {
if (word1 == ":!" || word1 == ":\\!") && current > 1 {
let mut w = words.clone();
if !w.is_empty() {
w.remove(0);
}
setaparam("words", w);
let _ = setiparam("CURRENT", current - 1);
} else {
let stripped = strip_leading_bang(&word1);
let mut w = words.clone();
if w.is_empty() {
w.push(stripped);
} else {
w[0] = stripped;
}
setaparam("words", w);
let _ = bin_compset(
"compset",
&["-P".to_string(), ":(\\\\|)!".to_string()],
&make_ops(),
0,
);
}
return dispatch_function_call("_normal", &[]).unwrap_or(1);
}
if word1 == ":function" {
if current == 2 {
let mut argv: Vec<String> = vec![
"math-functions".to_string(),
"expl".to_string(),
"math function".to_string(),
"compadd".to_string(),
"--".to_string(),
];
argv.extend(user_math_functions());
return dispatch_function_call("_wanted", &argv).unwrap_or(1);
} else {
return dispatch_function_call("_math", &[]).unwrap_or(1);
}
}
if word1 == ":local" {
return dispatch_function_call("_parameter", &[]).unwrap_or(1);
}
if word1 == ":fix" || word1 == ":sci" || word1 == ":eng" {
if current == 2 {
let _ = dispatch_function_call("_message", &["precision".to_string()]);
}
return dispatch_function_call("_message", &["no more arguments".to_string()])
.unwrap_or(1);
}
if word1.starts_with(':') {
return dispatch_function_call("_message", &["no more arguments".to_string()])
.unwrap_or(1);
}
dispatch_function_call("_math", &[]).unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::params::setsparam;
#[test]
fn command_position_returns_via_alternative() {
let _g = crate::test_util::global_state_lock();
setaparam("words", vec!["".to_string()]);
let _ = setsparam("CURRENT", "1");
assert_eq!(_zcalc_line(), 1);
}
#[test]
fn escapes_prefixed_with_single_colon_escape() {
let _g = crate::test_util::global_state_lock();
let _ = _zcalc_line_escapes();
let cmds = getaparam("cmds").unwrap_or_default();
assert_eq!(cmds.len(), 9);
assert_eq!(cmds[0], "\\:!:shell escape");
assert_eq!(
cmds[5],
"\\:eng:engineering (power of 1000) output format"
);
assert_eq!(
cmds[8],
"\\:function:define math function (also \\:func or \\:f)"
);
assert!(!cmds.iter().any(|c| c.starts_with("\\::")));
}
#[test]
fn strip_leading_bang_forms() {
assert_eq!(strip_leading_bang(":!ls"), "ls");
assert_eq!(strip_leading_bang(":\\!ls"), "ls");
assert_eq!(strip_leading_bang("plain"), "plain");
}
}