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
//! Port of `_sub_commands` from `Completion/Base/Utility/_sub_commands`.
//!
//! Full upstream body (9 lines verbatim):
//! ```text
//! sh:1 #autoload
//! sh:2
//! sh:3 local expl
//! sh:4
//! sh:5 if [[ CURRENT -eq 2 ]]; then
//! sh:6 _wanted commands expl command compadd "$@"
//! sh:7 else
//! sh:8 _message 'no more arguments'
//! sh:9 fi
//! ```
use crate::compsys::ported::_message::_message;
use crate::compsys::ported::_wanted::_wanted;
use crate::ported::params::getsparam;
/// `_sub_commands` — complete the FIRST sub-command argument only
/// (CURRENT == 2 in 1-based shell terms); emit a "no more arguments"
/// message thereafter.
pub fn _sub_commands(args: &[String]) -> i32 {
let _fn_scope = crate::compsys::ported::shared::FnScope::enter("_sub_commands");
// sh:3 — `local expl`.
//
// This port does not assign `expl` itself; it hands the NAME to
// `_wanted`/`_description`, and `_description` fills it through
// `setaparam` — `createparam(name, PM_SCALAR)` with no PM_LOCAL
// (shared.rs:16-30). The array was therefore born at level 0 and
// `endparamscope` had nothing to unwind, so one TAB left `expl`
// in the user's shell. Measured through a pty, `${(t)expl}` and
// the value read back at the next prompt after a single TAB on a
// `_sub_commands` wrapper:
//
// zsh : [][]
// zshrs: [array][-J|-default-]
//
// kind 0, as sh:3 spells a bare `local`.
crate::compsys::ported::shared::declare_locals(&["expl"], 0);
// sh:5
let current: i64 = getsparam("CURRENT")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
if current == 2 {
// sh:6
let mut wanted_argv: Vec<String> = vec![
"commands".to_string(),
"expl".to_string(),
"command".to_string(),
"compadd".to_string(),
];
wanted_argv.extend(args.iter().cloned());
_wanted(&wanted_argv)
} else {
// sh:8
_message(&["no more arguments".to_string()])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::params::setsparam;
use crate::ported::zle::complete::INCOMPFUNC;
use std::sync::atomic::Ordering;
#[test]
fn current_two_takes_wanted_path() {
let _g = crate::test_util::global_state_lock();
INCOMPFUNC.store(1, Ordering::Relaxed);
let _ = setsparam("CURRENT", "2");
let r = _sub_commands(&[]);
INCOMPFUNC.store(0, Ordering::Relaxed);
assert_eq!(r, 1); // no tags → _wanted returns 1
}
#[test]
fn current_other_emits_message() {
let _g = crate::test_util::global_state_lock();
INCOMPFUNC.store(1, Ordering::Relaxed);
let _ = setsparam("CURRENT", "5");
let _r = _sub_commands(&[]);
INCOMPFUNC.store(0, Ordering::Relaxed);
}
}