Skip to main content

cargo_brief/
cli.rs

1use clap::{Args, Parser, Subcommand};
2
3/// Cargo subcommand wrapper.
4#[derive(Parser, Debug)]
5#[command(name = "cargo", bin_name = "cargo", version)]
6pub struct Cargo {
7    #[command(subcommand)]
8    pub command: CargoCommand,
9}
10
11#[derive(Subcommand, Debug)]
12pub enum CargoCommand {
13    /// Extract and display Rust crate API as pseudo-Rust documentation.
14    Brief(BriefDirect),
15}
16
17/// Direct invocation wrapper (`cargo-brief <subcommand> ...`).
18#[derive(Parser, Debug)]
19#[command(
20    version,
21    about = "Visibility-aware Rust API extractor for AI agents",
22    after_help = "Run `cargo brief --help` for AI agent quick guide, or `cargo brief <cmd> --help` for details.",
23    after_long_help = "\
24QUICK GUIDE — which subcommand for which task:
25
26  \"What's the signature of X?\"        → search [--members]
27  \"Show a module's full API surface\"   → api [--depth N] [--compact]
28  \"Where is X defined? Show source\"    → code [--refs]
29  \"What modules exist in this crate?\"  → summary
30  \"What features does crate X have?\"   → features [-C <crate>]
31  \"How is X used in practice?\"         → examples [--tests]
32  \"Find structural patterns in AST\"    → ts '<s-expr>'
33  \"Who calls X? All references\"        → lsp references <symbol>
34  \"What breaks if I change X?\"         → lsp blast-radius <symbol> [--depth N]
35  \"What does X call / who calls X?\"    → lsp call-hierarchy <symbol> [--outgoing]
36
37TYPICAL WORKFLOW:
38  cargo brief summary self                    # 1. overview of modules
39  cargo brief api self::some_module           # 2. drill into a module
40  cargo brief search self SomeType --members  # 3. find a specific item
41  cargo brief code fn some_function --refs    # 4. read source + references
42  cargo brief lsp references some_function    # 5. cross-crate reference tracking
43
44REMOTE CRATES (-C):
45  cargo brief -C summary tokio@1              # explore an unfamiliar crate
46  cargo brief -C features serde@1             # inspect a crate's feature graph
47  cargo brief -C -F rt,net api tokio@1 net    # feature-gated APIs need -F
48  cargo brief -C search serde@1 Serialize     # search a crates.io dependency
49
50TIPS:
51  - Feature-gated items: run `features` to see what flags are available, then -F.
52  - api output annotates gated items (// requires feature \"...\") even when enabled.
53  - Smart-case: all-lowercase pattern = case-insensitive, any uppercase = exact.
54  - `code` searches ALL workspace members by default (not just current package).
55  - `lsp` resolves symbols by name. Workspace items are found instantly;
56    external deps (e.g., hecs::World) use usage-site fallback (slower).
57    Use unique names; common methods like \"new\" may still be ambiguous.
58  - `lsp` spawns a persistent rust-analyzer daemon; first query may be slow
59    while indexing. Use `lsp touch` to pre-warm.
60
61Run `cargo brief <subcommand> --help` for subcommand-specific options and examples."
62)]
63pub struct BriefDirect {
64    /// Interpret TARGET as a crates.io package spec (e.g., serde@1, tokio@1.0)
65    #[arg(short = 'C', long, global = true)]
66    pub crates: bool,
67
68    /// Comma-separated features to enable (requires -C)
69    #[arg(
70        short = 'F',
71        long,
72        value_name = "FEATURES",
73        global = true,
74        requires = "crates"
75    )]
76    pub features: Option<String>,
77
78    /// Disable default features (requires -C)
79    #[arg(long, global = true, requires = "crates")]
80    pub no_default_features: bool,
81
82    /// Skip cache and use a temporary workspace (requires -C)
83    #[arg(long, global = true, requires = "crates")]
84    pub no_cache: bool,
85
86    #[command(subcommand)]
87    pub command: BriefCommand,
88}
89
90impl BriefDirect {
91    /// Extract remote-mode options from the top-level flags.
92    pub fn remote_opts(&self) -> RemoteOpts {
93        RemoteOpts {
94            crates: self.crates,
95            features: self.features.clone(),
96            no_default_features: self.no_default_features,
97            no_cache: self.no_cache,
98        }
99    }
100}
101
102/// Remote crate mode options (extracted from BriefDirect global flags).
103#[derive(Debug, Clone, Default)]
104pub struct RemoteOpts {
105    pub crates: bool,
106    pub features: Option<String>,
107    pub no_default_features: bool,
108    pub no_cache: bool,
109}
110
111#[derive(Subcommand, Debug, Clone)]
112pub enum BriefCommand {
113    /// Extract and render crate API as pseudo-Rust documentation
114    #[command(after_help = "\
115EXAMPLES:
116  # Browse the current crate's API (run inside a Cargo project)
117  cargo brief api
118
119  # Browse a specific module in the current crate
120  cargo brief api self::net::tcp
121
122  # Inspect a crates.io dependency (cached after first run)
123  cargo brief -C api serde@1 --compact
124  cargo brief -C -F rt,net,io-util api tokio@1
125
126  # Browse a specific module of a remote crate
127  cargo brief -C api tokio@1::net
128  cargo brief -C -F net api tokio@1 net
129
130  # Reduce output verbosity for large crates
131  cargo brief -C api tokio@1 --compact
132  cargo brief -C api tokio@1 --doc-lines 1
133
134RESOLUTION RULES:
135  The <TARGET> argument is resolved as follows:
136    1. \"self\"           → current package (cwd-based detection)
137    2. \"self::mod\"      → current package, specific module
138    3. \"crate::mod\"     → named crate + module in one argument
139    4. \"src/foo.rs\"     → file path auto-converted to module path
140    5. \"crate_name\"     → workspace package (hyphen/underscore normalized)
141    6. \"unknown_name\"   → treated as package name (use \"self::mod\" for modules)
142
143  With -C, TARGET is the crate spec (e.g., serde@1, tokio@1.0).
144
145  The [MODULE_PATH] argument also accepts file paths (e.g., src/foo.rs).")]
146    Api(ApiArgs),
147
148    /// Search for items by name across a crate
149    #[command(after_help = "\
150EXAMPLES:
151  # Substring search (smart-case: all-lowercase = insensitive)
152  cargo brief -C search axum@0.8 Router route
153  cargo brief -C search bevy ShaderRef Material
154
155  # OR-match with comma, methods-of
156  cargo brief search self \"EventReader,EventWriter\"
157  cargo brief -C search bytes@1 --methods-of Bytes
158
159  # Glob, exact match, exclusion
160  cargo brief search bevy \"Shader*Ref\"               # * = 0+ chars, ? = 1 char
161  cargo brief search bevy \"=Router\"                   # final :: segment only
162  cargo brief search bevy -- spawn -test              # -- needed for -prefix args
163  cargo brief search bevy \"*Plugin*,*Resource* -test\"
164
165  # Signature type filters
166  cargo brief search self \"\" --in-params PathBuf
167  cargo brief search self parse --in-returns \"Result -Vec\"
168
169PATTERN SYNTAX:
170  Smart-case: all-lowercase → case-insensitive, any uppercase → case-sensitive.
171  Space = AND, comma = OR. Multiple args are joined with spaces.
172
173  Operators (per token):
174    word     substring — path contains \"word\"
175    w*ld     glob — * matches 0+ chars, ? matches 1 char (full-path anchored)
176    =Name    exact — final path segment (after last ::) equals \"Name\"
177    -term    exclude — remove matches (works with substring, glob, or -=exact)
178
179  Exclusions are global across all OR groups.
180  Type filters use the same syntax against rendered type strings. Quote multi-token
181  filter values, e.g. --in-params \"TokenStream -Option\".")]
182    Search(SearchArgs),
183
184    /// Grep example/test/bench source files from a crate
185    #[command(after_help = "\
186EXAMPLES:
187  # List example files with their module docs
188  cargo brief examples self
189  cargo brief -C examples tokio@1
190
191  # Grep for a pattern in example files
192  cargo brief examples self spawn
193  cargo brief -C examples hecs spawn_at --context 3
194
195  # Multiple patterns are AND-matched (no quotes needed)
196  cargo brief examples self spawn async
197
198  # Include tests and benches directories
199  cargo brief -C examples serde --tests --benches derive
200
201MATCHING:
202  Multiple pattern arguments are joined with spaces (AND-matched).
203  Smart-case: all-lowercase pattern = case-insensitive, any uppercase = case-sensitive.
204  Without a pattern, lists files with their //! doc comments.")]
205    Examples(ExamplesArgs),
206
207    /// Show a compact module-level summary with item counts
208    #[command(after_help = "\
209EXAMPLES:
210  # Summarize the current crate
211  cargo brief summary self
212
213  # Summarize a remote crate
214  cargo brief -C -F full summary tokio@1
215
216  # Summarize a specific module
217  cargo brief -C summary bevy bevy::ecs
218
219RESOLUTION RULES:
220  The <TARGET> argument is resolved as follows:
221    1. \"self\"           → current package (cwd-based detection)
222    2. \"self::mod\"      → current package, specific module
223    3. \"crate::mod\"     → named crate + module in one argument
224    4. \"src/foo.rs\"     → file path auto-converted to module path
225    5. \"crate_name\"     → workspace package (hyphen/underscore normalized)
226    6. \"unknown_name\"   → treated as package name
227
228  With -C, TARGET is the crate spec (e.g., serde@1, tokio@1.0).
229
230  The [MODULE_PATH] argument also accepts file paths (e.g., src/foo.rs).")]
231    Summary(SummaryArgs),
232
233    /// Run a tree-sitter structural query against crate source files
234    #[command(after_help = "\
235EXAMPLES:
236  # Find all function definitions
237  cargo brief ts self '(function_item)'
238
239  # Capture function names
240  cargo brief ts self '(function_item name: (identifier) @name)' --captures
241
242  # Find impl blocks for a specific trait
243  cargo brief ts self '(impl_item trait: (type_identifier) @t (#eq? @t \"MyTrait\"))'
244
245  # Find struct definitions with their fields
246  cargo brief ts self '(struct_item name: (type_identifier) @name body: (field_declaration_list) @fields)' --captures
247
248  # Find functions returning Result
249  cargo brief ts self '(function_item return_type: (generic_type type: (type_identifier) @r (#eq? @r \"Result\")))'
250
251  # Find let bindings with type annotations
252  cargo brief ts self '(let_declaration pattern: (_) @pat type: (_) @type)' --captures
253
254  # Find all call expressions to a specific function
255  cargo brief ts self '(call_expression function: (identifier) @fn (#eq? @fn \"spawn\"))'
256
257  # Location-only output for large result sets
258  cargo brief ts self '(function_item)' --quiet
259
260  # Limit results
261  cargo brief ts self '(function_item)' --limit 10
262
263  # Only search src/ (skip tests/examples/benches)
264  cargo brief ts self '(function_item)' --src-only
265
266  # Query a remote crate's source
267  cargo brief -C ts serde@1 '(struct_item)'
268
269QUERY SYNTAX:
270  Tree-sitter S-expression patterns match AST nodes by type and structure.
271  Explore the AST interactively: https://tree-sitter.github.io/tree-sitter/playground
272
273NODE TYPES (common Rust constructs):
274  Items:       function_item, struct_item, enum_item, impl_item, trait_item,
275               type_item, const_item, static_item, macro_definition,
276               use_declaration, mod_item
277  Expressions: call_expression, method_call_expression, field_expression,
278               match_expression, if_expression, closure_expression,
279               return_expression, await_expression
280  Statements:  let_declaration, expression_statement, assignment_expression
281  Types:       type_identifier, generic_type, reference_type, array_type,
282               function_type, tuple_type
283  Other:       attribute_item, line_comment, block_comment, string_literal,
284               identifier, field_identifier
285
286CAPTURES:
287  @name binds a node. In default mode, the outermost matched node is shown
288  (captures used only in predicates don't affect output). With --captures,
289  each @name: text pair is shown separately. Capture-less queries work too —
290  an internal @_match capture is auto-added.
291
292PREDICATES:
293  (#eq? @cap \"value\")     — exact string match on captured node text
294  (#match? @cap \"regex\")  — regex match on captured node text
295  (#not-eq? @cap \"value\") — negated exact match
296  (#any-of? @cap \"a\" \"b\") — match any of the listed strings
297
298TIPS:
299  - Rust async functions are function_item with modifiers, not a separate type
300  - Use (type_identifier) for type names, (identifier) for variable/fn names
301  - Nested captures: (struct_item (field_declaration_list
302      (field_declaration name: (field_identifier) @field)))
303  - Wildcards: (_) matches any single node type
304  - #[derive(...)] is: (attribute_item (attribute (identifier) @a (#eq? @a \"derive\")))")]
305    Ts(TsArgs),
306
307    /// Look up code definitions by kind and name using pre-crafted tree-sitter queries
308    #[command(after_help = "\
309EXAMPLES:
310  # Search current workspace (TARGET omitted — defaults to \"self\")
311  cargo brief code spawn
312  cargo brief code struct Commands
313
314  # Search a specific crate in the workspace
315  cargo brief code my-crate fn spawn
316  cargo brief code my-crate struct Commands
317
318  # Remote crate (TARGET required with -C)
319  cargo brief -C code serde@1 struct Serializer
320
321  # Smart-case: all-lowercase = case-insensitive
322  cargo brief code pubstruct              # finds PubStruct
323  cargo brief code PubStruct              # case-sensitive
324
325  # Show definitions + references
326  cargo brief code fn spawn --refs
327
328  # References only
329  cargo brief code spawn --refs-only
330
331  # Scope to items inside a type
332  cargo brief code --in Commands fn new
333  cargo brief code --in PubStruct field
334
335  # Quiet mode (location only, no source text)
336  cargo brief code fn spawn -q
337
338  # Limit results
339  cargo brief code fn spawn --limit 5
340
341POSITIONAL ARGS:
342  1 arg:   code NAME                  search all workspace members, all kinds
343           (error if NAME is a kind keyword — use 2-arg form instead)
344  2 args:  code KIND NAME             if first arg is a kind keyword (see below)
345           code TARGET NAME           otherwise: search named crate, all kinds
346  3 args:  code TARGET KIND NAME      search named crate, filter by kind
347
348  Disambiguation: if the first of two args matches a kind keyword, it is
349  treated as KIND (not TARGET). Use the 3-arg form to force a target that
350  happens to shadow a kind keyword.
351
352TARGET RESOLUTION:
353  \"self\" (default when TARGET omitted):
354    Searches ALL workspace members — not just the current package.
355    This differs from other subcommands where \"self\" = current package,
356    because code is a source-level lookup tool where project-wide search
357    is the common case.
358  Named crate:
359    Searches only that specific package (hyphen/underscore normalized).
360  With -C (remote):
361    TARGET is a crates.io spec (e.g., serde@1). Implicit \"self\" is not
362    allowed — you must provide an explicit TARGET.
363
364DEPENDENCY SEARCH:
365  Default:    workspace members + accessible dependencies (via rustdoc JSON
366              reachability analysis; requires nightly).
367  --no-deps:  workspace members (or named target) only.
368  --all-deps: workspace members + all direct dependencies (via cargo
369              metadata; no nightly needed, wider but noisier).
370
371ITEM KINDS:
372  fn, struct, enum, trait, field, type, impl, macro, const, use
373  Omit KIND to search all kinds (except use, to reduce noise).
374
375OUTPUT FORMAT:
376  Each match is printed as:
377    @<file>:<line>                          — source location
378      in <crate>::<module>[, <parent>]      — module path + parent context
379                                              (e.g., impl Type, trait Trait)
380    <source text>                           — full item definition
381
382  With --quiet (-q), only the location and module-path lines are shown.
383
384NAME MATCHING:
385  Smart-case: all-lowercase pattern = case-insensitive, any uppercase =
386  case-sensitive. The name must match the item's identifier exactly (not
387  a substring).
388
389REFERENCE SEARCH (--refs, --refs-only):
390  After definitions, grep for literal name occurrences across the same
391  source files. Output uses * markers on match lines with 2 lines of
392  surrounding context. --refs-only skips definitions entirely.
393  --limit applies to definitions (--refs) or grep matches (--refs-only).
394
395PARENT SCOPING (--in <TYPE>):
396  Filter definitions to items inside a specific type, impl block, or
397  trait. Matches the type identifier with smart-case rules.
398    --in Commands    matches impl Commands, impl T for Commands, etc.
399    --in commands    case-insensitive match
400  Top-level items (not inside any type) are excluded.")]
401    Code(CodeArgs),
402
403    /// Show the feature graph for a crate (features, defaults, optional deps)
404    #[command(after_help = "\
405EXAMPLES:
406  # Show feature graph for the current workspace crate
407  cargo brief features
408
409  # Show feature graph for a specific workspace member
410  cargo brief features my-crate
411
412  # Show feature graph for a crates.io crate (requires -C)
413  cargo brief -C features serde@1
414  cargo brief -C features tokio@1")]
415    Features(FeaturesArgs),
416
417    /// Clear cached remote crate workspaces
418    #[command(after_help = "\
419EXAMPLES:
420  # Clear all cached workspaces
421  cargo brief clean
422
423  # Clear caches for a specific crate
424  cargo brief clean serde")]
425    Clean(CleanArgs),
426
427    /// Manage LSP daemon (persistent rust-analyzer)
428    #[command(after_help = "\
429EXAMPLES:
430  # Find all references to a symbol across the workspace
431  cargo brief lsp references resolve_symbol
432  cargo brief lsp references Foo::bar -q        # quiet: locations only
433
434  # Blast radius: direct + transitive callers (\"what breaks if I change X?\")
435  cargo brief lsp blast-radius handle_request
436  cargo brief lsp blast-radius handle_request --depth 3
437
438  # Call hierarchy: who calls X (incoming) / what does X call (outgoing)
439  cargo brief lsp call-hierarchy spawn
440  cargo brief lsp call-hierarchy spawn --outgoing -q
441
442  # Daemon lifecycle (daemon auto-starts on first query)
443  cargo brief lsp touch                         # pre-warm rust-analyzer
444  cargo brief lsp status                        # check if running
445  cargo brief lsp stop                          # shut down daemon
446
447SYMBOL RESOLUTION:
448  Symbols are resolved in two stages:
449    1. workspace/symbol search (fast, finds workspace-defined items)
450    2. Fallback: grep workspace source for usage sites, then resolve via
451       textDocument/definition (slower, finds external deps like hecs::World)
452
453  Tips:
454    - Qualified names work: \"hecs::World\", \"App::new\", \"MyStruct::method\"
455    - Common names like \"new\" or \"get\" may return Ambiguous (many matches).
456      Use a qualified form to narrow down.
457    - call-hierarchy and blast-radius work on functions/methods, not types.
458      Use `references` for tracking struct/enum/trait usage.
459    - If resolution still fails, try `code --refs <name>` as a last resort.
460
461NOTE:
462  The LSP daemon spawns automatically on first query. Initial indexing may
463  take time; subsequent queries are fast. Use `lsp touch` to pre-warm.")]
464    Lsp(LspArgs),
465}
466
467// === Shared Args Groups ===
468
469/// Target resolution arguments (crate + module).
470#[derive(Args, Debug, Clone)]
471pub struct TargetArgs {
472    /// Target to inspect: crate name, "self", crate::module, or file path
473    #[arg(value_name = "TARGET", default_value = "self")]
474    pub crate_name: String,
475
476    /// Module path or file path within the crate (e.g., "my_mod::submod" or "src/foo.rs")
477    pub module_path: Option<String>,
478
479    /// Caller's package name (for visibility resolution)
480    #[arg(long, help_heading = "Local Workspace")]
481    pub at_package: Option<String>,
482
483    /// Caller's module path (determines what is visible)
484    #[arg(long, help_heading = "Local Workspace")]
485    pub at_mod: Option<String>,
486
487    /// Path to Cargo.toml
488    #[arg(long, help_heading = "Local Workspace")]
489    pub manifest_path: Option<String>,
490}
491
492/// Output filtering and density flags.
493#[derive(Args, Debug, Clone)]
494pub struct FilterArgs {
495    /// Exclude structs
496    #[arg(long, help_heading = "Filtering")]
497    pub no_structs: bool,
498
499    /// Exclude enums
500    #[arg(long, help_heading = "Filtering")]
501    pub no_enums: bool,
502
503    /// Exclude traits
504    #[arg(long, help_heading = "Filtering")]
505    pub no_traits: bool,
506
507    /// Exclude free functions
508    #[arg(long, help_heading = "Filtering")]
509    pub no_functions: bool,
510
511    /// Exclude type aliases
512    #[arg(long, help_heading = "Filtering")]
513    pub no_aliases: bool,
514
515    /// Exclude constants and statics
516    #[arg(long, help_heading = "Filtering")]
517    pub no_constants: bool,
518
519    /// Exclude unions
520    #[arg(long, help_heading = "Filtering")]
521    pub no_unions: bool,
522
523    /// Exclude macros and proc-macros (bang, attribute, derive)
524    #[arg(long, help_heading = "Filtering")]
525    pub no_macros: bool,
526
527    /// Suppress doc comments from output
528    #[arg(long, help_heading = "Filtering")]
529    pub no_docs: bool,
530
531    /// Suppress crate-level //! documentation
532    #[arg(long, help_heading = "Filtering")]
533    pub no_crate_docs: bool,
534
535    /// Limit doc comments to first N lines (0 = suppress all)
536    #[arg(long, value_name = "N", help_heading = "Filtering")]
537    pub doc_lines: Option<usize>,
538
539    /// Compact output: suppress doc comments, collapse struct fields, enum variants, and trait items
540    #[arg(long, help_heading = "Filtering")]
541    pub compact: bool,
542
543    /// Show all attributes (#[must_use], #[repr(...)], etc.)
544    #[arg(long, help_heading = "Filtering")]
545    pub verbose_metadata: bool,
546
547    /// Show all item kinds including blanket/auto-trait impls
548    #[arg(long)]
549    pub all: bool,
550
551    /// Suppress feature-gate annotations (// requires feature "...") from output
552    #[arg(long, help_heading = "Filtering")]
553    pub no_feature_gates: bool,
554}
555
556/// Global options shared across all subcommands.
557#[derive(Args, Debug, Clone)]
558pub struct GlobalArgs {
559    /// Nightly toolchain name
560    #[arg(long, default_value = "nightly", help_heading = "Advanced")]
561    pub toolchain: String,
562
563    /// Show progress messages on stderr during pipeline execution
564    #[arg(short, long)]
565    pub verbose: bool,
566}
567
568// === Subcommand Args ===
569
570/// Arguments for the `api` subcommand.
571#[derive(Args, Debug, Clone)]
572pub struct ApiArgs {
573    #[command(flatten)]
574    pub target: TargetArgs,
575
576    #[command(flatten)]
577    pub filter: FilterArgs,
578
579    #[command(flatten)]
580    pub global: GlobalArgs,
581
582    /// How many submodule levels to recurse into
583    #[arg(long, default_value = "1")]
584    pub depth: u32,
585
586    /// Recurse into all submodules (no depth limit)
587    #[arg(long)]
588    pub recursive: bool,
589
590    /// Suppress glob re-export expansion (show pub use lines instead of inlined definitions)
591    #[arg(long)]
592    pub no_expand_glob: bool,
593}
594
595/// Arguments for the `search` subcommand.
596#[derive(Args, Debug, Clone)]
597pub struct SearchArgs {
598    /// Target crate to search: crate name, "self", or crate::module
599    #[arg(value_name = "TARGET", default_value = "self")]
600    pub crate_name: String,
601
602    /// Search patterns — multiple args are AND-matched (use -- for patterns starting with -)
603    #[arg(value_name = "PATTERN", num_args = 0..)]
604    pub patterns: Vec<String>,
605
606    #[command(flatten)]
607    pub filter: FilterArgs,
608
609    #[command(flatten)]
610    pub global: GlobalArgs,
611
612    /// Caller's package name (for visibility resolution)
613    #[arg(long, help_heading = "Local Workspace")]
614    pub at_package: Option<String>,
615
616    /// Caller's module path (determines what is visible)
617    #[arg(long, help_heading = "Local Workspace")]
618    pub at_mod: Option<String>,
619
620    /// Path to Cargo.toml
621    #[arg(long, help_heading = "Local Workspace")]
622    pub manifest_path: Option<String>,
623
624    /// Limit search results: N (first N) or OFFSET:N (skip OFFSET, show N)
625    #[arg(long, value_name = "[OFFSET:]N")]
626    pub limit: Option<String>,
627
628    /// Show methods/fields of a type (shorthand for pattern + exclusion flags)
629    #[arg(long, value_name = "TYPE")]
630    pub methods_of: Option<String>,
631
632    /// Filter results by item kind (comma-separated: fn, struct, enum, trait, union, field, variant, const, static, type, macro, use)
633    #[arg(long, value_name = "KINDS", help_heading = "Filtering")]
634    pub search_kind: Option<String>,
635
636    /// Show all members (fields, variants, methods) of matched types
637    #[arg(long)]
638    pub members: bool,
639
640    /// Filter functions by parameter type (quote multi-token patterns)
641    #[arg(long, value_name = "PATTERN", help_heading = "Filtering")]
642    pub in_params: Option<String>,
643
644    /// Filter functions by return type (quote multi-token patterns)
645    #[arg(long, value_name = "PATTERN", help_heading = "Filtering")]
646    pub in_returns: Option<String>,
647}
648
649impl SearchArgs {
650    /// Join pattern args with space (AND semantics). Empty string if no patterns.
651    pub fn pattern(&self) -> String {
652        self.patterns.join(" ")
653    }
654}
655
656/// Arguments for the `examples` subcommand.
657#[derive(Args, Debug, Clone)]
658pub struct ExamplesArgs {
659    /// Target crate: crate name, "self", or crates.io spec (with -C)
660    #[arg(value_name = "TARGET", default_value = "self")]
661    pub crate_name: String,
662
663    /// Grep patterns — multiple args are AND-matched (omit for list mode)
664    #[arg(value_name = "PATTERN", num_args = 0..)]
665    pub patterns: Vec<String>,
666
667    #[command(flatten)]
668    pub global: GlobalArgs,
669
670    /// Path to Cargo.toml
671    #[arg(long, help_heading = "Local Workspace")]
672    pub manifest_path: Option<String>,
673
674    /// Lines of context around matches: N or BEFORE:AFTER
675    #[arg(long, default_value = "2")]
676    pub context: String,
677
678    /// Include tests/ directory [default depth: unlimited]
679    #[arg(long, num_args(0..=1), default_missing_value = "999", value_name = "DEPTH")]
680    pub tests: Option<u32>,
681
682    /// Include benches/ directory [default depth: unlimited]
683    #[arg(long, num_args(0..=1), default_missing_value = "999", value_name = "DEPTH")]
684    pub benches: Option<u32>,
685}
686
687/// Arguments for the `ts` (tree-sitter) subcommand.
688#[derive(Args, Debug, Clone)]
689pub struct TsArgs {
690    /// Target crate (use 'self' for current crate)
691    #[arg(value_name = "TARGET")]
692    pub crate_name: String,
693
694    /// Tree-sitter S-expression query
695    #[arg(value_name = "QUERY")]
696    pub query: String,
697
698    #[command(flatten)]
699    pub global: GlobalArgs,
700
701    /// Path to Cargo.toml
702    #[arg(long, help_heading = "Local Workspace")]
703    pub manifest_path: Option<String>,
704
705    /// Show capture names with matched text (for multi-capture queries)
706    #[arg(long)]
707    pub captures: bool,
708
709    /// Lines of context around matched nodes: N or BEFORE:AFTER
710    #[arg(long, default_value = "0")]
711    pub context: String,
712
713    /// Only search src/ directory (skip examples, tests, benches)
714    #[arg(long)]
715    pub src_only: bool,
716
717    /// Limit matches: N (first N) or OFFSET:N (skip OFFSET, show N)
718    #[arg(long, value_name = "[OFFSET:]N")]
719    pub limit: Option<String>,
720
721    /// Output only file:line locations, no source text
722    #[arg(short = 'q', long)]
723    pub quiet: bool,
724}
725
726/// Arguments for the `code` subcommand.
727#[derive(Args, Debug, Clone)]
728pub struct CodeArgs {
729    /// Positional arguments: [TARGET] [KIND] NAME
730    #[arg(value_name = "ARGS", num_args = 1..=3)]
731    pub args: Vec<String>,
732
733    #[command(flatten)]
734    pub global: GlobalArgs,
735
736    /// Path to Cargo.toml
737    #[arg(long, help_heading = "Local Workspace")]
738    pub manifest_path: Option<String>,
739
740    /// Only search src/ directory (skip examples, tests, benches)
741    #[arg(long)]
742    pub src_only: bool,
743
744    /// Don't search dependencies (target crate only)
745    #[arg(long)]
746    pub no_deps: bool,
747
748    /// Search all direct dependencies (no nightly needed; skips accessible-path filtering)
749    #[arg(long, conflicts_with = "no_deps")]
750    pub all_deps: bool,
751
752    /// Limit matches: N (first N) or OFFSET:N (skip OFFSET, show N)
753    #[arg(long, value_name = "[OFFSET:]N")]
754    pub limit: Option<String>,
755
756    /// Output only file:line locations and module context, no source text
757    #[arg(short = 'q', long)]
758    pub quiet: bool,
759
760    /// Also show grep-based references after definitions
761    #[arg(long)]
762    pub refs: bool,
763
764    /// Only show grep references, skip definitions
765    #[arg(long, conflicts_with = "refs")]
766    pub refs_only: bool,
767
768    /// Scope to items inside a specific type/impl block
769    #[arg(long, value_name = "TYPE")]
770    pub in_type: Option<String>,
771}
772
773/// Arguments for the `summary` subcommand.
774#[derive(Args, Debug, Clone)]
775pub struct SummaryArgs {
776    #[command(flatten)]
777    pub target: TargetArgs,
778
779    #[command(flatten)]
780    pub global: GlobalArgs,
781}
782
783/// Arguments for the `features` subcommand.
784#[derive(Args, Debug, Clone)]
785pub struct FeaturesArgs {
786    /// Target crate name (defaults to current workspace package)
787    #[arg(value_name = "CRATE", default_value = "self")]
788    pub crate_name: String,
789
790    #[command(flatten)]
791    pub global: GlobalArgs,
792
793    /// Path to Cargo.toml
794    #[arg(long, help_heading = "Local Workspace")]
795    pub manifest_path: Option<String>,
796}
797
798/// Arguments for the `clean` subcommand.
799#[derive(Args, Debug, Clone)]
800pub struct CleanArgs {
801    /// Crate spec to clean (omit to clean all)
802    #[arg(value_name = "SPEC")]
803    pub spec: Option<String>,
804}
805
806/// Arguments for the `lsp` subcommand.
807#[derive(Args, Debug, Clone)]
808pub struct LspArgs {
809    #[command(subcommand)]
810    pub command: LspCommand,
811
812    #[command(flatten)]
813    pub global: GlobalArgs,
814
815    /// Path to Cargo.toml
816    #[arg(long, help_heading = "Local Workspace")]
817    pub manifest_path: Option<String>,
818}
819
820#[derive(Subcommand, Debug, Clone)]
821pub enum LspCommand {
822    /// Ensure LSP daemon is running (pre-warm rust-analyzer)
823    Touch {
824        /// Return immediately after ensuring daemon is running (skip indexing wait)
825        #[arg(long)]
826        no_wait: bool,
827    },
828    /// Stop the LSP daemon
829    Stop,
830    /// Show LSP daemon status
831    Status,
832    /// Find all references to a symbol via rust-analyzer
833    References {
834        /// Symbol to find references for (e.g., "Foo::bar", "CrateModel")
835        symbol: String,
836        /// Location-only output format
837        #[arg(long, short)]
838        quiet: bool,
839    },
840    /// Show direct and transitive callers of a symbol (blast radius)
841    BlastRadius {
842        /// Symbol to analyze (e.g., "resolve_symbol", "Foo::bar")
843        symbol: String,
844        /// Depth of transitive caller search (1 = direct only, max 10)
845        #[arg(long, default_value = "1")]
846        depth: u32,
847        /// Location-only output format
848        #[arg(long, short)]
849        quiet: bool,
850    },
851    /// Show incoming or outgoing call hierarchy for a symbol
852    CallHierarchy {
853        /// Symbol to analyze (e.g., "resolve_symbol", "Foo::bar")
854        symbol: String,
855        /// Show outgoing calls instead of incoming
856        #[arg(long)]
857        outgoing: bool,
858        /// Location-only output format
859        #[arg(long, short)]
860        quiet: bool,
861    },
862}
863
864impl ExamplesArgs {
865    /// Join pattern args with space. None if no patterns (list mode).
866    pub fn pattern(&self) -> Option<String> {
867        if self.patterns.is_empty() {
868            None
869        } else {
870            Some(self.patterns.join(" "))
871        }
872    }
873}