1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser, Debug)]
5#[command(
6 name = "rust-analyzer-cli",
7 author,
8 version,
9 about = "Compiler-accurate Rust codebase navigation powered by rust-analyzer LSP"
10)]
11pub struct Cli {
12 #[arg(short, long, env = "RUST_ANALYZER_CLI_PORT", default_value_t = 60094)]
14 pub port: u16,
15
16 #[arg(short, long, global = true)]
18 pub json: bool,
19
20 #[command(subcommand)]
21 pub command: Commands,
22}
23
24#[derive(Subcommand, Debug)]
25pub enum Commands {
26 Daemon {
28 #[arg(short, long, default_value = ".")]
30 workspace: PathBuf,
31 },
32 Status,
34 Refresh,
36 Symbol {
38 name: String,
40 #[arg(default_value = "any")]
42 kind: String,
43 #[arg(long)]
45 exact: bool,
46 #[arg(short, long)]
48 body: bool,
49 #[arg(long, default_value_t = 100)]
51 max_lines: usize,
52 },
53 Outline {
55 #[arg(short, long)]
57 file: Option<PathBuf>,
58 #[arg(long)]
60 file_list: Option<String>,
61 #[arg(short, long)]
63 output: Option<PathBuf>,
64 #[arg(short, long)]
66 body: bool,
67 #[arg(long, default_value_t = 100)]
69 max_lines: usize,
70 },
71 Definition {
73 #[arg(short, long)]
75 file: PathBuf,
76 #[arg(short, long)]
78 line: u32,
79 #[arg(short, long)]
81 col: u32,
82 #[arg(short, long)]
84 body: bool,
85 #[arg(long, default_value_t = 100)]
87 max_lines: usize,
88 #[arg(long)]
90 no_line_numbers: bool,
91 },
92 Body {
94 #[arg(short, long)]
96 file: PathBuf,
97 #[arg(short, long)]
99 line: u32,
100 #[arg(short, long)]
102 col: u32,
103 #[arg(long, default_value_t = 100)]
105 max_lines: usize,
106 #[arg(long)]
108 no_line_numbers: bool,
109 },
110 Cursor {
112 #[arg(short, long)]
114 file: PathBuf,
115 #[arg(short, long)]
117 line: u32,
118 #[arg(short, long)]
120 col: u32,
121 #[arg(short, long, default_value = "incoming")]
123 mode: String,
124 #[arg(short, long, default_value_t = 1)]
126 depth: u32,
127 },
128 TypeHierarchy {
130 #[arg(short, long)]
132 file: PathBuf,
133 #[arg(short, long)]
135 line: u32,
136 #[arg(short, long)]
138 col: u32,
139 #[arg(short, long, default_value = "supertypes")]
141 mode: String,
142 #[arg(short, long, default_value_t = 1)]
144 depth: u32,
145 },
146 Check {
148 #[arg(short, long)]
150 target: Option<String>,
151 },
152 InitSkill {
154 #[arg(short, long, default_value = ".")]
156 workspace: PathBuf,
157 #[arg(short, long)]
159 dir: Option<PathBuf>,
160 },
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_cli_symbol_parsing() {
169 let args = vec![
170 "rust-analyzer-cli",
171 "symbol",
172 "ExampleStruct",
173 "struct",
174 "--exact",
175 ];
176 let cli = Cli::try_parse_from(args).unwrap();
177 assert_eq!(cli.port, 60094);
178 assert!(!cli.json);
179 match cli.command {
180 Commands::Symbol {
181 name,
182 kind,
183 exact,
184 body,
185 max_lines,
186 } => {
187 assert_eq!(name, "ExampleStruct");
188 assert_eq!(kind, "struct");
189 assert!(exact);
190 assert!(!body);
191 assert_eq!(max_lines, 100);
192 }
193 _ => panic!("Expected Symbol command"),
194 }
195 }
196
197 #[test]
198 fn test_cli_outline_parsing() {
199 let args = vec![
200 "rust-analyzer-cli",
201 "--json",
202 "outline",
203 "-f",
204 "tests/code_example.rs",
205 ];
206 let cli = Cli::try_parse_from(args).unwrap();
207 assert!(cli.json);
208 match cli.command {
209 Commands::Outline {
210 file,
211 file_list,
212 output,
213 body,
214 max_lines,
215 } => {
216 assert_eq!(file, Some(PathBuf::from("tests/code_example.rs")));
217 assert!(file_list.is_none());
218 assert!(output.is_none());
219 assert!(!body);
220 assert_eq!(max_lines, 100);
221 }
222 _ => panic!("Expected Outline command"),
223 }
224 }
225
226 #[test]
227 fn test_cli_definition_parsing() {
228 let args = vec![
229 "rust-analyzer-cli",
230 "definition",
231 "-f",
232 "tests/code_example.rs",
233 "-l",
234 "10",
235 "-c",
236 "5",
237 "--body",
238 ];
239 let cli = Cli::try_parse_from(args).unwrap();
240 match cli.command {
241 Commands::Definition {
242 file,
243 line,
244 col,
245 body,
246 max_lines,
247 no_line_numbers,
248 } => {
249 assert_eq!(file, PathBuf::from("tests/code_example.rs"));
250 assert_eq!(line, 10);
251 assert_eq!(col, 5);
252 assert!(body);
253 assert_eq!(max_lines, 100);
254 assert!(!no_line_numbers);
255 }
256 _ => panic!("Expected Definition command"),
257 }
258 }
259
260 #[test]
261 fn test_cli_body_parsing() {
262 let args = vec![
263 "rust-analyzer-cli",
264 "body",
265 "-f",
266 "tests/code_example.rs",
267 "-l",
268 "10",
269 "-c",
270 "5",
271 "--max-lines",
272 "50",
273 "--no-line-numbers",
274 ];
275 let cli = Cli::try_parse_from(args).unwrap();
276 match cli.command {
277 Commands::Body {
278 file,
279 line,
280 col,
281 max_lines,
282 no_line_numbers,
283 } => {
284 assert_eq!(file, PathBuf::from("tests/code_example.rs"));
285 assert_eq!(line, 10);
286 assert_eq!(col, 5);
287 assert_eq!(max_lines, 50);
288 assert!(no_line_numbers);
289 }
290 _ => panic!("Expected Body command"),
291 }
292 }
293
294 #[test]
295 fn test_cli_init_skill_parsing() {
296 let args = vec![
297 "rust-analyzer-cli",
298 "init-skill",
299 "-w",
300 "/my/proj",
301 "-d",
302 "custom/SKILL.md",
303 ];
304 let cli = Cli::try_parse_from(args).unwrap();
305 match cli.command {
306 Commands::InitSkill { workspace, dir } => {
307 assert_eq!(workspace, PathBuf::from("/my/proj"));
308 assert_eq!(dir, Some(PathBuf::from("custom/SKILL.md")));
309 }
310 _ => panic!("Expected InitSkill command"),
311 }
312 }
313}