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
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_cli_symbol_parsing() {
171 let args = vec!["rust-analyzer-cli", "symbol", "LspClient", "struct", "--exact"];
172 let cli = Cli::try_parse_from(args).unwrap();
173 assert_eq!(cli.port, 60094);
174 assert!(!cli.json);
175 match cli.command {
176 Commands::Symbol { name, kind, exact, body, max_lines } => {
177 assert_eq!(name, "LspClient");
178 assert_eq!(kind, "struct");
179 assert!(exact);
180 assert!(!body);
181 assert_eq!(max_lines, 100);
182 }
183 _ => panic!("Expected Symbol command"),
184 }
185 }
186
187 #[test]
188 fn test_cli_outline_parsing() {
189 let args = vec!["rust-analyzer-cli", "--json", "outline", "-f", "src/main.rs"];
190 let cli = Cli::try_parse_from(args).unwrap();
191 assert!(cli.json);
192 match cli.command {
193 Commands::Outline { file, file_list, output, body, max_lines } => {
194 assert_eq!(file, Some(PathBuf::from("src/main.rs")));
195 assert!(file_list.is_none());
196 assert!(output.is_none());
197 assert!(!body);
198 assert_eq!(max_lines, 100);
199 }
200 _ => panic!("Expected Outline command"),
201 }
202 }
203
204 #[test]
205 fn test_cli_definition_parsing() {
206 let args = vec!["rust-analyzer-cli", "definition", "-f", "src/lib.rs", "-l", "10", "-c", "5", "--body"];
207 let cli = Cli::try_parse_from(args).unwrap();
208 match cli.command {
209 Commands::Definition { file, line, col, body, max_lines, no_line_numbers } => {
210 assert_eq!(file, PathBuf::from("src/lib.rs"));
211 assert_eq!(line, 10);
212 assert_eq!(col, 5);
213 assert!(body);
214 assert_eq!(max_lines, 100);
215 assert!(!no_line_numbers);
216 }
217 _ => panic!("Expected Definition command"),
218 }
219 }
220
221 #[test]
222 fn test_cli_body_parsing() {
223 let args = vec!["rust-analyzer-cli", "body", "-f", "src/lib.rs", "-l", "10", "-c", "5", "--max-lines", "50", "--no-line-numbers"];
224 let cli = Cli::try_parse_from(args).unwrap();
225 match cli.command {
226 Commands::Body { file, line, col, max_lines, no_line_numbers } => {
227 assert_eq!(file, PathBuf::from("src/lib.rs"));
228 assert_eq!(line, 10);
229 assert_eq!(col, 5);
230 assert_eq!(max_lines, 50);
231 assert!(no_line_numbers);
232 }
233 _ => panic!("Expected Body command"),
234 }
235 }
236
237 #[test]
238 fn test_cli_init_skill_parsing() {
239 let args = vec!["rust-analyzer-cli", "init-skill", "-w", "/my/proj", "-d", "custom/SKILL.md"];
240 let cli = Cli::try_parse_from(args).unwrap();
241 match cli.command {
242 Commands::InitSkill { workspace, dir } => {
243 assert_eq!(workspace, PathBuf::from("/my/proj"));
244 assert_eq!(dir, Some(PathBuf::from("custom/SKILL.md")));
245 }
246 _ => panic!("Expected InitSkill command"),
247 }
248 }
249}
250
251
252
253