1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! Port of `_as_if` from `Completion/Base/Utility/_as_if`.
//!
//! Full upstream body (10 lines verbatim):
//! ```text
//! sh: 1 #autoload
//! sh: 2 local words=("$words[@]") CURRENT=$CURRENT
//! sh: 3 local _comp_command1 _comp_command2 _comp_command
//! sh: 4
//! sh: 5 words[1]=("$@")
//! sh: 6 (( CURRENT += $# - 1 ))
//! sh: 7
//! sh: 8 _set_command
//! sh: 9
//! sh:10 _dispatch "$_comp_command" "$_comp_command1" "$_comp_command2" -default-
//! ```
//!
//! `words[1]=("$@")` REPLACES element 1 with the entire argv (zsh
//! array-element-as-array semantics — it splats). `CURRENT` shifts
//! by `$# - 1` to track the new position.
use crate::ported::exec_hooks::dispatch_function_call;
use crate::ported::params::{getaparam, getsparam, setaparam, setsparam};
/// `_as_if` — re-dispatch completion AS IF the command line had
/// been replaced by `$@`. Used to simulate completion for a
/// different command (e.g. when `sudo` should defer to its first
/// non-option arg).
pub fn _as_if(args: &[String]) -> i32 {
// sh:2 snapshot words / CURRENT (we restore at end to mirror
// the shell's `local` scoping).
let saved_words = getaparam("words").unwrap_or_default();
let saved_current = getsparam("CURRENT").unwrap_or_default();
// sh:5 words[1]=("$@") — replace element 1 with full argv,
// splatted in place (zsh array-element-is-array semantic).
let mut new_words: Vec<String> = args.to_vec();
if saved_words.len() > 1 {
new_words.extend(saved_words.iter().skip(1).cloned());
}
setaparam("words", new_words);
// sh:6 (( CURRENT += $# - 1 ))
let cur: i64 = saved_current.parse().unwrap_or(0);
let new_current = cur + (args.len() as i64) - 1;
let _ = setsparam("CURRENT", &new_current.to_string());
// sh:8 _set_command — sibling shell fn that derives
// `$_comp_command{,1,2}` from $words.
let _ = dispatch_function_call("_set_command", &[]);
// sh:10
let cmd = getsparam("_comp_command").unwrap_or_default();
let cmd1 = getsparam("_comp_command1").unwrap_or_default();
let cmd2 = getsparam("_comp_command2").unwrap_or_default();
let r = dispatch_function_call(
"_dispatch",
&[cmd, cmd1, cmd2, "-default-".to_string()],
)
.unwrap_or(1);
// Restore shell-local scoping
setaparam("words", saved_words);
let _ = setsparam("CURRENT", &saved_current);
r
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_one_without_executor() {
let _g = crate::test_util::global_state_lock();
assert_eq!(_as_if(&["foo".to_string()]), 1);
}
#[test]
fn restores_words_and_current_after_call() {
// sh:2 `local` scoping — we manually restore in Rust port.
let _g = crate::test_util::global_state_lock();
setaparam("words", vec!["original".to_string()]);
let _ = setsparam("CURRENT", "5");
let _ = _as_if(&["replacement".to_string()]);
assert_eq!(getaparam("words").unwrap_or_default(), vec!["original"]);
assert_eq!(getsparam("CURRENT").as_deref(), Some("5"));
}
}