Skip to main content

rust_analyzer_cli/
cli.rs

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    /// Port for the background daemon HTTP server
13    #[arg(short, long, env = "RUST_ANALYZER_CLI_PORT", default_value_t = 60094)]
14    pub port: u16,
15
16    /// Output results in JSON format
17    #[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    /// Start the background LSP daemon and HTTP server
27    Daemon {
28        /// Workspace root directory
29        #[arg(short, long, default_value = ".")]
30        workspace: PathBuf,
31    },
32    /// Query daemon and rust-analyzer status
33    Status,
34    /// Refresh / restart the rust-analyzer session
35    Refresh,
36    /// Search workspace symbols
37    Symbol {
38        /// Symbol name to search for
39        name: String,
40        /// Optional symbol filter: any, struct, enum, fn, trait, type, const, var
41        #[arg(default_value = "any")]
42        kind: String,
43        /// Match exact symbol name
44        #[arg(long)]
45        exact: bool,
46    },
47    /// Extract file document symbols outline
48    Outline {
49        /// Target file path
50        #[arg(short, long)]
51        file: Option<PathBuf>,
52        /// Comma-separated list of target file paths
53        #[arg(long)]
54        file_list: Option<String>,
55        /// Write outline output to a file
56        #[arg(short, long)]
57        output: Option<PathBuf>,
58    },
59    /// Go to definition at file line and column
60    Definition {
61        /// File path
62        #[arg(short, long)]
63        file: PathBuf,
64        /// Line number (1-based)
65        #[arg(short, long)]
66        line: u32,
67        /// Column number (1-based)
68        #[arg(short, long)]
69        col: u32,
70    },
71    /// Find references or call hierarchy at file line and column
72    Cursor {
73        /// File path
74        #[arg(short, long)]
75        file: PathBuf,
76        /// Line number (1-based)
77        #[arg(short, long)]
78        line: u32,
79        /// Column number (1-based)
80        #[arg(short, long)]
81        col: u32,
82        /// Mode: incoming, outgoing, or references
83        #[arg(short, long, default_value = "incoming")]
84        mode: String,
85        /// Traversal depth (default: 1)
86        #[arg(short, long, default_value_t = 1)]
87        depth: u32,
88    },
89    /// Query type hierarchy (supertypes / subtypes)
90    TypeHierarchy {
91        /// File path
92        #[arg(short, long)]
93        file: PathBuf,
94        /// Line number (1-based)
95        #[arg(short, long)]
96        line: u32,
97        /// Column number (1-based)
98        #[arg(short, long)]
99        col: u32,
100        /// Mode: supertypes or subtypes
101        #[arg(short, long, default_value = "supertypes")]
102        mode: String,
103        /// Traversal depth (default: 1)
104        #[arg(short, long, default_value_t = 1)]
105        depth: u32,
106    },
107    /// Run cargo check compilation verification
108    Check {
109        /// Optional cargo build target
110        #[arg(short, long)]
111        target: Option<String>,
112    },
113    /// Initialize/install the rust-codebase-navigation SKILL.md in a workspace
114    InitSkill {
115        /// Target workspace directory (default: .)
116        #[arg(short, long, default_value = ".")]
117        workspace: PathBuf,
118        /// Custom relative skill path (default: .agents/skills/rust-codebase-navigation/SKILL.md)
119        #[arg(short, long)]
120        dir: Option<PathBuf>,
121    },
122}
123
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_cli_symbol_parsing() {
131        let args = vec!["rust-analyzer-cli", "symbol", "LspClient", "struct", "--exact"];
132        let cli = Cli::try_parse_from(args).unwrap();
133        assert_eq!(cli.port, 60094);
134        assert!(!cli.json);
135        match cli.command {
136            Commands::Symbol { name, kind, exact } => {
137                assert_eq!(name, "LspClient");
138                assert_eq!(kind, "struct");
139                assert!(exact);
140            }
141            _ => panic!("Expected Symbol command"),
142        }
143    }
144
145    #[test]
146    fn test_cli_outline_parsing() {
147        let args = vec!["rust-analyzer-cli", "--json", "outline", "-f", "src/main.rs"];
148        let cli = Cli::try_parse_from(args).unwrap();
149        assert!(cli.json);
150        match cli.command {
151            Commands::Outline { file, file_list, output } => {
152                assert_eq!(file, Some(PathBuf::from("src/main.rs")));
153                assert!(file_list.is_none());
154                assert!(output.is_none());
155            }
156            _ => panic!("Expected Outline command"),
157        }
158    }
159
160    #[test]
161    fn test_cli_definition_parsing() {
162        let args = vec!["rust-analyzer-cli", "definition", "-f", "src/lib.rs", "-l", "10", "-c", "5"];
163        let cli = Cli::try_parse_from(args).unwrap();
164        match cli.command {
165            Commands::Definition { file, line, col } => {
166                assert_eq!(file, PathBuf::from("src/lib.rs"));
167                assert_eq!(line, 10);
168                assert_eq!(col, 5);
169            }
170            _ => panic!("Expected Definition command"),
171        }
172    }
173
174    #[test]
175    fn test_cli_init_skill_parsing() {
176        let args = vec!["rust-analyzer-cli", "init-skill", "-w", "/my/proj", "-d", "custom/SKILL.md"];
177        let cli = Cli::try_parse_from(args).unwrap();
178        match cli.command {
179            Commands::InitSkill { workspace, dir } => {
180                assert_eq!(workspace, PathBuf::from("/my/proj"));
181                assert_eq!(dir, Some(PathBuf::from("custom/SKILL.md")));
182            }
183            _ => panic!("Expected InitSkill command"),
184        }
185    }
186}
187
188
189