reovim_driver_command/query.rs
1//! Command query service for command discovery and completion.
2//!
3//! Provides a query interface for modules to discover commands without
4//! depending on runner internals. Used for cmdline tab-completion,
5//! help systems, and command palettes.
6//!
7//! # Architecture
8//!
9//! - **Driver layer**: Defines trait contract (`CommandQueryService`)
10//! - **Runner layer**: Implements trait (`CommandQuerySnapshot`)
11//! - **Modules**: Access via `ServiceRegistry`
12
13use {
14 crate::{ArgSpec, Command},
15 reovim_kernel::api::v1::{CommandId, Service},
16};
17
18/// Command metadata for queries (no execution capability).
19///
20/// This is a snapshot of command information that can be serialized,
21/// cloned, and passed to modules without exposing the handler.
22///
23/// # Fields
24///
25/// - `id`: Unique command identifier (module:name)
26/// - `names`: User command aliases (e.g., `["w", "write"]`)
27/// - `description`: Human-readable description
28/// - `args`: Argument specifications
29#[derive(Debug, Clone)]
30pub struct CommandInfo {
31 /// Unique command identifier.
32 pub id: CommandId,
33 /// User command aliases (e.g., `["w", "write"]`).
34 pub names: Vec<String>,
35 /// Human-readable description.
36 pub description: String,
37 /// Argument specifications.
38 pub args: Vec<ArgSpec>,
39}
40
41impl CommandInfo {
42 /// Create from a `Command` trait implementor.
43 ///
44 /// Extracts all metadata into owned types for safe passing across
45 /// module boundaries.
46 pub fn from_command<C: Command + ?Sized>(cmd: &C) -> Self {
47 Self {
48 id: cmd.id(),
49 names: cmd.names().iter().map(|s| (*s).to_string()).collect(),
50 description: cmd.description().to_string(),
51 args: cmd.args(),
52 }
53 }
54
55 /// Check if this command has any user-facing names.
56 ///
57 /// Commands with names can be invoked from the command line (`:w`).
58 /// Commands without names are internal-only (keybinding commands).
59 #[must_use]
60 pub const fn has_user_names(&self) -> bool {
61 !self.names.is_empty()
62 }
63}
64
65/// Query service for command discovery and completion.
66///
67/// Modules access this via `ServiceRegistry` to implement features
68/// like tab-completion without depending on runner internals.
69///
70/// # Thread Safety
71///
72/// Implementations must be thread-safe (`Send + Sync`).
73///
74/// # Example
75///
76/// ```ignore
77/// // In cmdline module for tab completion:
78/// let query = services.get::<dyn CommandQueryService>()?;
79/// let matches = query.search_by_prefix("wri");
80/// // matches contains CommandInfo for "write", etc.
81/// ```
82pub trait CommandQueryService: Service + Send + Sync {
83 /// Search commands by name prefix (for tab completion).
84 ///
85 /// Returns commands where any alias starts with `prefix`.
86 /// The search is case-sensitive.
87 /// An empty prefix returns all commands (every string starts with `""`).
88 ///
89 /// # Example
90 ///
91 /// ```ignore
92 /// let matches = query.search_by_prefix("wri");
93 /// // Returns CommandInfo for commands with aliases like "write"
94 /// ```
95 fn search_by_prefix(&self, prefix: &str) -> Vec<CommandInfo>;
96
97 /// Find command by exact name or alias.
98 ///
99 /// # Example
100 ///
101 /// ```ignore
102 /// let info = query.find_by_name("w");
103 /// // Returns Some(CommandInfo) for the :write command
104 /// ```
105 fn find_by_name(&self, name: &str) -> Option<CommandInfo>;
106
107 /// List all commands that have user-facing names.
108 ///
109 /// Returns only commands where `names()` is non-empty.
110 /// Use this for cmdline completion UI.
111 fn list_user_commands(&self) -> Vec<CommandInfo>;
112
113 /// List all registered commands.
114 ///
115 /// Includes commands without ex-names (internal commands).
116 fn list_all(&self) -> Vec<CommandInfo>;
117
118 /// Get total command count.
119 fn count(&self) -> usize;
120}
121
122// ============================================================================
123// Command query provider for module access (#522)
124// ============================================================================
125
126/// Concrete service for module-level command discovery.
127///
128/// Stores a snapshot of command metadata so modules can list commands
129/// without depending on the server crate's `CommandQuerySnapshot`.
130/// Registered by the runner during bootstrap.
131pub struct CommandQueryProvider {
132 commands: Vec<CommandInfo>,
133}
134
135impl Service for CommandQueryProvider {}
136
137impl CommandQueryProvider {
138 /// Create from a list of command metadata.
139 #[must_use]
140 pub const fn new(commands: Vec<CommandInfo>) -> Self {
141 Self { commands }
142 }
143
144 /// List all registered commands.
145 #[must_use]
146 pub fn list_all(&self) -> &[CommandInfo] {
147 &self.commands
148 }
149
150 /// Get total command count.
151 #[must_use]
152 pub const fn count(&self) -> usize {
153 self.commands.len()
154 }
155}
156
157#[cfg(test)]
158#[path = "query_tests.rs"]
159mod tests;