Skip to main content

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;