reovim_plugin_cmdline_completion/
lib.rs1mod cache;
14mod command;
15mod source;
16mod window;
17
18use std::{any::TypeId, sync::Arc};
19
20pub use {
21 cache::{
22 CmdlineCompletionCache, CmdlineCompletionItem, CmdlineCompletionKind,
23 CmdlineCompletionSnapshot,
24 },
25 command::{
26 CmdlineComplete, CmdlineCompleteConfirm, CmdlineCompleteDismiss, CmdlineCompleteNext,
27 CmdlineCompletePrev,
28 },
29 source::{complete_commands, complete_paths},
30 window::CmdlineCompletionWindow,
31};
32
33use reovim_core::{
34 command_line::CmdlineCompletionContext,
35 event_bus::{EventBus, EventResult, core_events},
36 plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
37};
38
39pub struct CmdlineCompletionState {
41 pub cache: Arc<CmdlineCompletionCache>,
43}
44
45impl CmdlineCompletionState {
46 #[must_use]
48 pub fn new() -> Self {
49 Self {
50 cache: Arc::new(CmdlineCompletionCache::new()),
51 }
52 }
53}
54
55impl Default for CmdlineCompletionState {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61pub struct CmdlineCompletionPlugin {
63 state: Arc<CmdlineCompletionState>,
64}
65
66impl CmdlineCompletionPlugin {
67 #[must_use]
69 pub fn new() -> Self {
70 Self {
71 state: Arc::new(CmdlineCompletionState::new()),
72 }
73 }
74}
75
76impl Default for CmdlineCompletionPlugin {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl Plugin for CmdlineCompletionPlugin {
83 fn id(&self) -> PluginId {
84 PluginId::new("reovim:cmdline-completion")
85 }
86
87 fn name(&self) -> &'static str {
88 "Command-line Completion"
89 }
90
91 fn description(&self) -> &'static str {
92 "TAB completion for command-line mode"
93 }
94
95 fn dependencies(&self) -> Vec<TypeId> {
96 vec![]
97 }
98
99 fn build(&self, ctx: &mut PluginContext) {
100 let _ = ctx.register_command(CmdlineComplete);
102 let _ = ctx.register_command(CmdlineCompleteNext);
103 let _ = ctx.register_command(CmdlineCompletePrev);
104 let _ = ctx.register_command(CmdlineCompleteConfirm);
105 let _ = ctx.register_command(CmdlineCompleteDismiss);
106
107 }
109
110 fn init_state(&self, registry: &PluginStateRegistry) {
111 registry.register(Arc::clone(&self.state));
113
114 registry.register_plugin_window(Arc::new(CmdlineCompletionWindow::new(Arc::clone(
116 &self.state.cache,
117 ))));
118 }
119
120 fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
121 let cache = Arc::clone(&self.state.cache);
123 let bus_sender = bus.sender();
124 bus.subscribe::<core_events::CmdlineCompletionTriggered, _>(100, move |event, ctx| {
125 if cache.is_active() {
127 cache.select_next();
128 ctx.request_render();
129 return EventResult::Handled;
130 }
131
132 let items = match &event.context {
134 CmdlineCompletionContext::Command { prefix } => {
135 complete_commands(prefix, &event.registry_commands)
136 }
137 CmdlineCompletionContext::Argument {
138 command,
139 prefix,
140 arg_start,
141 } => {
142 let path_commands = [
144 "e", "edit", "w", "write", "sp", "split", "vs", "vsplit", "tabnew",
145 ];
146 if path_commands.contains(&command.as_str()) {
147 complete_paths(prefix, *arg_start)
148 } else {
149 Vec::new()
150 }
151 }
152 };
153
154 if items.is_empty() {
155 return EventResult::Handled; }
157
158 let replace_start = match &event.context {
160 CmdlineCompletionContext::Command { .. } => 0,
161 CmdlineCompletionContext::Argument { arg_start, .. } => *arg_start,
162 };
163
164 let prefix = match &event.context {
166 CmdlineCompletionContext::Command { prefix } => prefix.clone(),
167 CmdlineCompletionContext::Argument { prefix, .. } => prefix.clone(),
168 };
169
170 cache.store(CmdlineCompletionSnapshot {
172 items,
173 selected_index: 0,
174 active: true,
175 prefix,
176 replace_start,
177 });
178
179 ctx.request_render();
180 EventResult::Handled
181 });
182
183 let cache = Arc::clone(&self.state.cache);
185 bus.subscribe::<CmdlineCompleteNext, _>(100, move |_event, ctx| {
186 if cache.is_active() {
187 cache.select_next();
188 ctx.request_render();
189 EventResult::Handled
190 } else {
191 EventResult::NotHandled
192 }
193 });
194
195 let cache = Arc::clone(&self.state.cache);
197 bus.subscribe::<core_events::CmdlineCompletionPrevRequested, _>(100, move |_event, ctx| {
198 if cache.is_active() {
199 cache.select_prev();
200 ctx.request_render();
201 EventResult::Handled
202 } else {
203 EventResult::NotHandled
204 }
205 });
206
207 let cache = Arc::clone(&self.state.cache);
209 bus.subscribe::<CmdlineCompletePrev, _>(100, move |_event, ctx| {
210 if cache.is_active() {
211 cache.select_prev();
212 ctx.request_render();
213 EventResult::Handled
214 } else {
215 EventResult::NotHandled
216 }
217 });
218
219 let cache = Arc::clone(&self.state.cache);
221 let bus_sender_confirm = bus.sender();
222 bus.subscribe::<CmdlineCompleteConfirm, _>(100, move |_event, ctx| {
223 if !cache.is_active() {
224 return EventResult::NotHandled;
225 }
226
227 let snapshot = cache.load();
228 let Some(item) = snapshot.items.get(snapshot.selected_index) else {
229 cache.dismiss();
230 return EventResult::NotHandled;
231 };
232
233 bus_sender_confirm.try_send(core_events::RequestApplyCmdlineCompletion {
235 text: item.insert_text.clone(),
236 replace_start: snapshot.replace_start,
237 });
238
239 cache.dismiss();
240 ctx.request_render();
241 EventResult::Handled
242 });
243
244 let cache = Arc::clone(&self.state.cache);
246 bus.subscribe::<CmdlineCompleteDismiss, _>(100, move |_event, ctx| {
247 if cache.is_active() {
248 cache.dismiss();
249 ctx.request_render();
250 EventResult::Handled
251 } else {
252 EventResult::NotHandled
253 }
254 });
255
256 let cache = Arc::clone(&self.state.cache);
258 bus.subscribe::<core_events::ModeChanged, _>(100, move |event, ctx| {
259 if !event.to.contains("Command") && cache.is_active() {
261 cache.dismiss();
262 ctx.request_render();
263 }
264 EventResult::NotHandled });
266
267 let _ = state; let _ = bus_sender; }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_plugin_new() {
281 let plugin = CmdlineCompletionPlugin::new();
282 assert_eq!(plugin.id().as_str(), "reovim:cmdline-completion");
283 assert_eq!(plugin.name(), "Command-line Completion");
284 }
285
286 #[test]
287 fn test_plugin_default() {
288 let plugin = CmdlineCompletionPlugin::default();
289 assert_eq!(plugin.name(), "Command-line Completion");
290 }
291
292 #[test]
293 fn test_plugin_dependencies() {
294 let plugin = CmdlineCompletionPlugin::new();
295 assert!(plugin.dependencies().is_empty());
296 }
297}