theater_cli/commands/
dynamic_completion.rs1use clap::Parser;
2use std::net::SocketAddr;
3use tracing::debug;
4
5use crate::error::{CliError, CliResult};
6use crate::CommandContext;
7
8#[derive(Debug, Parser)]
9pub struct DynamicCompletionArgs {
10 #[arg(required = true)]
12 pub line: String,
13
14 #[arg(required = true)]
16 pub current: String,
17
18 #[arg(short, long, default_value = "127.0.0.1:9000")]
20 pub address: SocketAddr,
21}
22
23pub async fn execute_async(args: &DynamicCompletionArgs, ctx: &CommandContext) -> CliResult<()> {
25 debug!("Generating dynamic completion for: '{}'", args.line);
26 debug!("Current word: '{}'", args.current);
27
28 let completions = generate_dynamic_completions(args, ctx).await?;
29
30 for completion in completions {
32 println!("{}", completion);
33 }
34
35 Ok(())
36}
37
38async fn generate_dynamic_completions(
40 args: &DynamicCompletionArgs,
41 _ctx: &CommandContext,
42) -> CliResult<Vec<String>> {
43 let words: Vec<&str> = args.line.split_whitespace().collect();
44
45 match words.as_slice() {
47 ["theater"] => Ok(get_command_completions(&args.current)),
49
50 ["theater", "start"] => get_manifest_completions(&args.current).await,
52
53 ["theater", "stop"] => get_actor_id_completions(&args.current, args.address).await,
55
56 ["theater", "state"] => get_actor_id_completions(&args.current, args.address).await,
58
59 ["theater", "inspect"] => get_actor_id_completions(&args.current, args.address).await,
61
62 ["theater", "message"] => get_actor_id_completions(&args.current, args.address).await,
64
65 ["theater", "events"] => get_actor_id_completions(&args.current, args.address).await,
67
68 ["theater", "channel", "open"] => {
70 get_actor_id_completions(&args.current, args.address).await
71 }
72
73 ["theater", "create"] => Ok(get_template_completions(&args.current)),
75
76 ["theater", "completion"] => Ok(get_shell_completions(&args.current)),
78
79 _ => Ok(vec![]),
80 }
81}
82
83fn get_command_completions(current: &str) -> Vec<String> {
85 let commands = vec![
86 "build",
87 "channel",
88 "completion",
89 "create",
90 "events",
91 "inspect",
92 "list",
93 "list-stored",
94 "message",
95 "start",
96 "state",
97 "stop",
98 "subscribe",
99 ];
100
101 commands
102 .into_iter()
103 .filter(|cmd| cmd.starts_with(current))
104 .map(|s| s.to_string())
105 .collect()
106}
107
108fn get_template_completions(current: &str) -> Vec<String> {
110 let templates = vec!["basic", "http"];
111
112 templates
113 .into_iter()
114 .filter(|tmpl| tmpl.starts_with(current))
115 .map(|s| s.to_string())
116 .collect()
117}
118
119fn get_shell_completions(current: &str) -> Vec<String> {
121 let shells = vec!["bash", "zsh", "fish", "powershell", "elvish"];
122
123 shells
124 .into_iter()
125 .filter(|shell| shell.starts_with(current))
126 .map(|s| s.to_string())
127 .collect()
128}
129
130async fn get_manifest_completions(current: &str) -> CliResult<Vec<String>> {
132 let mut completions = Vec::new();
134
135 if let Ok(entries) = std::fs::read_dir(".") {
137 for entry in entries.flatten() {
138 if let Some(name) = entry.file_name().to_str() {
139 if name == "manifest.toml" || name.ends_with(".toml") {
140 if name.starts_with(current) {
141 completions.push(name.to_string());
142 }
143 }
144 }
145 }
146 }
147
148 if let Ok(stored_actors) = get_stored_actor_ids().await {
150 for actor_id in stored_actors {
151 if actor_id.starts_with(current) {
152 completions.push(actor_id);
153 }
154 }
155 }
156
157 Ok(completions)
158}
159
160async fn get_actor_id_completions(current: &str, address: SocketAddr) -> CliResult<Vec<String>> {
162 debug!("Getting actor completions from server at: {}", address);
163
164 match get_running_actor_ids(address).await {
165 Ok(actor_ids) => {
166 let completions = actor_ids
167 .into_iter()
168 .filter(|id| id.starts_with(current))
169 .collect();
170 Ok(completions)
171 }
172 Err(e) => {
173 debug!("Failed to get running actors: {}", e);
174 get_stored_actor_ids().await.map(|ids| {
176 ids.into_iter()
177 .filter(|id| id.starts_with(current))
178 .collect()
179 })
180 }
181 }
182}
183
184async fn get_running_actor_ids(address: SocketAddr) -> CliResult<Vec<String>> {
186 use bytes::Bytes;
187 use futures::{SinkExt, StreamExt};
188 use tokio::net::TcpStream;
189 use tokio_util::codec::{Framed, LengthDelimitedCodec};
190
191 let socket = TcpStream::connect(address)
192 .await
193 .map_err(|e| CliError::connection_failed(address, e))?;
194
195 let mut framed = Framed::new(socket, LengthDelimitedCodec::new());
196
197 let request = serde_json::json!({
199 "type": "list_actors"
200 });
201
202 let request_bytes = Bytes::from(serde_json::to_vec(&request).unwrap());
203 framed
204 .send(request_bytes)
205 .await
206 .map_err(|e| CliError::NetworkError {
207 operation: "send list request".to_string(),
208 source: Box::new(e),
209 })?;
210
211 if let Some(response_frame) = framed.next().await {
213 let response_bytes = response_frame.map_err(|e| CliError::NetworkError {
214 operation: "receive list response".to_string(),
215 source: Box::new(e),
216 })?;
217
218 let response: serde_json::Value =
219 serde_json::from_slice(&response_bytes).map_err(|e| CliError::InvalidResponse {
220 message: "Failed to parse actor list response".to_string(),
221 source: Some(Box::new(e)),
222 })?;
223
224 if let Some(actors) = response.get("actors").and_then(|a| a.as_array()) {
225 let actor_ids = actors
226 .iter()
227 .filter_map(|actor| {
228 actor
229 .get("id")
230 .and_then(|id| id.as_str().map(|s| s.to_string()))
231 })
232 .collect();
233
234 return Ok(actor_ids);
235 }
236 }
237
238 Ok(vec![])
239}
240
241async fn get_stored_actor_ids() -> CliResult<Vec<String>> {
243 Ok(vec![])
246}