返回 CodeWhale
jobs.rs
根目录 / crates / tui / src / commands / groups / utility / jobs.rs
1 //! Shell job-center commands.
2
3 use crate::commands::traits::{CommandInfo, RegisterCommand};
4 use crate::localization::MessageId;
5 use crate::tui::app::{App, AppAction, ShellJobAction};
6
7 use crate::commands::CommandResult;
8
9 pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
10 name: "jobs",
11 aliases: &["job", "zuoye"],
12 usage: "/jobs [list|show <id>|poll <id>|wait <id>|stdin <id> <input>|cancel <id>]",
13 description_id: MessageId::CmdJobsDescription,
14 };
15
16 pub(in crate::commands) struct JobsCmd;
17
18 impl RegisterCommand for JobsCmd {
19 fn info() -> &'static CommandInfo {
20 &COMMAND_INFO
21 }
22
23 fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
24 jobs(app, arg)
25 }
26 }
27
28 fn jobs(_app: &mut App, args: Option<&str>) -> CommandResult {
29 let raw = args.unwrap_or("").trim();
30 if raw.is_empty() || raw.eq_ignore_ascii_case("list") {
31 return CommandResult::action(AppAction::ShellJob(ShellJobAction::List));
32 }
33
34 let mut parts = raw.splitn(3, char::is_whitespace);
35 let action = parts.next().unwrap_or("").to_ascii_lowercase();
36 let id = parts.next().map(str::trim).filter(|s| !s.is_empty());
37 let rest = parts.next().map(str::trim).unwrap_or("");
38
39 match action.as_str() {
40 "list" => CommandResult::action(AppAction::ShellJob(ShellJobAction::List)),
41 "show" | "inspect" => match id {
42 Some(id) => CommandResult::action(AppAction::ShellJob(ShellJobAction::Show {
43 id: id.to_string(),
44 })),
45 None => CommandResult::error("Usage: /jobs show <id>"),
46 },
47 "poll" | "wait" => match id {
48 Some(id) => CommandResult::action(AppAction::ShellJob(ShellJobAction::Poll {
49 id: id.to_string(),
50 wait: action == "wait",
51 })),
52 None => CommandResult::error("Usage: /jobs poll <id>"),
53 },
54 "stdin" | "send" => match id {
55 Some(id) if !rest.is_empty() => {
56 CommandResult::action(AppAction::ShellJob(ShellJobAction::SendStdin {
57 id: id.to_string(),
58 input: rest.to_string(),
59 close: false,
60 }))
61 }
62 _ => CommandResult::error("Usage: /jobs stdin <id> <input>"),
63 },
64 "close-stdin" | "eof" => match id {
65 Some(id) => CommandResult::action(AppAction::ShellJob(ShellJobAction::SendStdin {
66 id: id.to_string(),
67 input: String::new(),
68 close: true,
69 })),
70 None => CommandResult::error("Usage: /jobs close-stdin <id>"),
71 },
72 "cancel" | "kill" | "stop" => match id {
73 Some(id) => CommandResult::action(AppAction::ShellJob(ShellJobAction::Cancel {
74 id: id.to_string(),
75 })),
76 None => CommandResult::error("Usage: /jobs cancel <id>"),
77 },
78 "cancel-all" | "kill-all" | "stop-all" => {
79 CommandResult::action(AppAction::ShellJob(ShellJobAction::CancelAll))
80 }
81 _ => CommandResult::error(
82 "Usage: /jobs [list|show <id>|poll <id>|wait <id>|stdin <id> <input>|close-stdin <id>|cancel <id>|cancel-all]",
83 ),
84 }
85 }
86
87 #[cfg(test)]
88 mod tests {
89 use super::*;
90 use crate::config::Config;
91 use crate::tui::app::TuiOptions;
92 use std::path::PathBuf;
93
94 fn app() -> App {
95 App::new(
96 TuiOptions {
97 use_alt_screen: false,
98 max_subagents: 2,
99 ..crate::test_support::test_tui_options(PathBuf::from("."))
100 },
101 &Config::default(),
102 )
103 }
104
105 #[test]
106 fn parses_job_actions() {
107 let mut app = app();
108 let show = jobs(&mut app, Some("show shell_abcd"));
109 assert!(matches!(
110 show.action,
111 Some(AppAction::ShellJob(ShellJobAction::Show { id })) if id == "shell_abcd"
112 ));
113
114 let send = jobs(&mut app, Some("stdin shell_abcd y"));
115 assert!(matches!(
116 send.action,
117 Some(AppAction::ShellJob(ShellJobAction::SendStdin { id, input, close: false }))
118 if id == "shell_abcd" && input == "y"
119 ));
120
121 let cancel_all = jobs(&mut app, Some("cancel-all"));
122 assert!(matches!(
123 cancel_all.action,
124 Some(AppAction::ShellJob(ShellJobAction::CancelAll))
125 ));
126 }
127 }
128
128 lines RUST