reovim_plugin_completion/
lib.rs1mod cache;
17mod commands;
18mod events;
19mod registry;
20mod saturator;
21mod source;
22mod state;
23mod window;
24
25use std::{any::TypeId, sync::Arc};
26
27pub use {
29 cache::{CompletionCache, CompletionSnapshot},
30 commands::{
31 CompletionConfirm, CompletionDismiss, CompletionSelectNext, CompletionSelectPrev,
32 CompletionTrigger, CompletionTriggered,
33 },
34 events::{CompletionDismissed, CompletionReady, RegisterSource},
35 registry::{SourceRegistry, SourceSupport},
36 saturator::{CompletionRequest, CompletionSaturatorHandle, spawn_completion_saturator},
37 source::BufferWordsSource,
38 state::SharedCompletionManager,
39 window::CompletionPluginWindow,
40};
41
42pub use reovim_core::completion::{CompletionContext, CompletionItem, CompletionKind};
44
45use reovim_core::{
46 bind::CommandRef,
47 command::{CommandContext, id::CommandId},
48 event::CommandEvent,
49 event_bus::{
50 EventBus, EventResult,
51 core_events::{BufferModification, BufferModified, RequestInsertText},
52 },
53 keys,
54 plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
55};
56
57pub mod command_id {
59 use super::CommandId;
60
61 pub const COMPLETION_TRIGGER: CommandId = CommandId::new("completion_trigger");
62 pub const COMPLETION_NEXT: CommandId = CommandId::new("completion_next");
63 pub const COMPLETION_PREV: CommandId = CommandId::new("completion_prev");
64 pub const COMPLETION_CONFIRM: CommandId = CommandId::new("completion_confirm");
65 pub const COMPLETION_DISMISS: CommandId = CommandId::new("completion_dismiss");
66}
67
68pub struct CompletionPlugin {
75 manager: Arc<SharedCompletionManager>,
76}
77
78impl CompletionPlugin {
79 #[must_use]
81 pub fn new() -> Self {
82 Self {
83 manager: Arc::new(SharedCompletionManager::new()),
84 }
85 }
86}
87
88impl Default for CompletionPlugin {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl Plugin for CompletionPlugin {
95 fn id(&self) -> PluginId {
96 PluginId::new("reovim:completion")
97 }
98
99 fn name(&self) -> &'static str {
100 "Completion"
101 }
102
103 fn description(&self) -> &'static str {
104 "Auto-completion with background processing"
105 }
106
107 fn dependencies(&self) -> Vec<TypeId> {
108 vec![]
109 }
110
111 fn build(&self, ctx: &mut PluginContext) {
112 let _ = ctx.register_command(CompletionTrigger::new(0));
114 let _ = ctx.register_command(CompletionSelectNext);
115 let _ = ctx.register_command(CompletionSelectPrev);
116 let _ = ctx.register_command(CompletionConfirm);
117 let _ = ctx.register_command(CompletionDismiss);
118
119 use reovim_core::bind::KeymapScope;
121 let insert_mode = KeymapScope::editor_insert();
122
123 ctx.bind_key_scoped(
125 insert_mode.clone(),
126 keys![(Alt Space)],
127 CommandRef::Registered(command_id::COMPLETION_TRIGGER),
128 );
129
130 ctx.bind_key_scoped(
132 insert_mode.clone(),
133 keys![(Ctrl 'n')],
134 CommandRef::Registered(command_id::COMPLETION_NEXT),
135 );
136
137 ctx.bind_key_scoped(
138 insert_mode.clone(),
139 keys![(Ctrl 'p')],
140 CommandRef::Registered(command_id::COMPLETION_PREV),
141 );
142
143 ctx.bind_key_scoped(
145 insert_mode.clone(),
146 keys![(Ctrl 'y')],
147 CommandRef::Registered(command_id::COMPLETION_CONFIRM),
148 );
149
150 }
156
157 fn init_state(&self, registry: &PluginStateRegistry) {
158 registry.register(Arc::clone(&self.manager));
160
161 registry.register_plugin_window(Arc::new(CompletionPluginWindow::new(Arc::clone(
163 &self.manager,
164 ))));
165 }
166
167 fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
168 let rt_handle = tokio::runtime::Handle::current();
171
172 let manager = Arc::clone(&self.manager);
174 bus.subscribe::<CompletionTriggered, _>(100, move |event, ctx| {
175 if manager.is_active() {
177 let snapshot = manager.snapshot();
178 let req = &event.request;
179
180 if req.cursor_row != snapshot.cursor_row {
182 tracing::debug!("Completion dismissed: line changed");
183 manager.dismiss();
184 ctx.request_render();
185 return EventResult::Handled;
186 }
187
188 if req.cursor_col < snapshot.word_start_col {
190 tracing::debug!("Completion dismissed: cursor before word start");
191 manager.dismiss();
192 ctx.request_render();
193 return EventResult::Handled;
194 }
195
196 if req.prefix.is_empty() {
198 tracing::debug!("Completion dismissed: empty prefix");
199 manager.dismiss();
200 ctx.request_render();
201 return EventResult::Handled;
202 }
203 }
204
205 tracing::info!("CompletionTriggered event received, prefix={}", event.request.prefix);
206 manager.request_completion(event.request.clone());
207 ctx.request_render();
208 EventResult::Handled
209 });
210
211 let manager = Arc::clone(&self.manager);
213 bus.subscribe::<RegisterSource, _>(100, move |event, _ctx| {
214 manager.register_source(Arc::clone(&event.source));
215 EventResult::Handled
216 });
217
218 let manager = Arc::clone(&self.manager);
220 bus.subscribe::<CompletionSelectNext, _>(100, move |_event, ctx| {
221 if manager.is_active() {
222 manager.select_next();
223 ctx.request_render();
224 EventResult::Handled
225 } else {
226 EventResult::NotHandled
227 }
228 });
229
230 let manager = Arc::clone(&self.manager);
232 bus.subscribe::<CompletionSelectPrev, _>(100, move |_event, ctx| {
233 if manager.is_active() {
234 manager.select_prev();
235 ctx.request_render();
236 EventResult::Handled
237 } else {
238 EventResult::NotHandled
239 }
240 });
241
242 let manager = Arc::clone(&self.manager);
244 bus.subscribe::<CompletionDismiss, _>(100, move |_event, ctx| {
245 if manager.is_active() {
246 manager.dismiss();
247 ctx.request_render();
248 EventResult::Handled
249 } else {
250 EventResult::NotHandled
251 }
252 });
253
254 let manager = Arc::clone(&self.manager);
256 bus.subscribe::<CompletionConfirm, _>(100, move |_event, ctx| {
257 if !manager.is_active() {
258 return EventResult::NotHandled;
259 }
260
261 let snapshot = manager.snapshot();
262 let Some(item) = snapshot.selected_item() else {
263 return EventResult::NotHandled;
264 };
265
266 let prefix_len = snapshot.prefix.len();
269 let insert_text = item.insert_text.clone();
270
271 if !insert_text.is_empty() || prefix_len > 0 {
272 ctx.emit(RequestInsertText {
273 text: insert_text,
274 move_cursor_left: false,
275 delete_prefix_len: prefix_len,
276 });
277 }
278
279 manager.dismiss();
280 ctx.request_render();
281 EventResult::Handled
282 });
283
284 let manager = Arc::clone(&self.manager);
286 bus.subscribe::<reovim_core::event_bus::core_events::ModeChanged, _>(
287 100,
288 move |event, ctx| {
289 if !event.to.contains("Insert") && manager.is_active() {
291 manager.dismiss();
292 ctx.request_render();
293 }
294 EventResult::Handled
295 },
296 );
297
298 let manager = Arc::clone(&self.manager);
303 let rt_handle = rt_handle.clone();
304 bus.subscribe::<BufferModified, _>(100, move |event, _ctx| {
305 let is_active = manager.is_active();
306
307 match &event.modification {
308 BufferModification::Insert { text, .. } => {
309 if text.chars().all(|c| c.is_whitespace()) {
311 if is_active {
312 manager.dismiss();
314 }
315 return EventResult::NotHandled;
316 }
317 }
318 BufferModification::Delete { .. } => {
319 if !is_active {
321 return EventResult::NotHandled;
322 }
323 }
324 BufferModification::Replace { .. } | BufferModification::FullReplace => {
325 if is_active {
327 manager.dismiss();
328 }
329 return EventResult::NotHandled;
330 }
331 }
332
333 let generation = manager.next_debounce_generation();
334 let manager = Arc::clone(&manager);
335 let buffer_id = event.buffer_id;
336
337 rt_handle.spawn(async move {
340 if !is_active {
341 tokio::time::sleep(std::time::Duration::from_millis(
343 crate::state::AUTO_POPUP_DELAY_MS,
344 ))
345 .await;
346
347 if manager.current_debounce_generation() != generation {
349 return;
350 }
351 }
352 manager.send_command_event(CommandEvent {
356 command: CommandRef::Registered(command_id::COMPLETION_TRIGGER),
357 context: CommandContext {
358 buffer_id,
359 window_id: 0,
360 count: None,
361 },
362 });
363 });
364
365 EventResult::NotHandled
366 });
367
368 let _ = state; }
370
371 fn boot(
372 &self,
373 _bus: &EventBus,
374 state: Arc<PluginStateRegistry>,
375 event_tx: Option<tokio::sync::mpsc::Sender<reovim_core::event::RuntimeEvent>>,
376 ) {
377 let Some(event_tx) = event_tx.or_else(|| state.inner_event_tx()) else {
379 tracing::warn!("Completion plugin boot: event_tx not available");
380 return;
381 };
382
383 self.manager.set_inner_event_tx(event_tx.clone());
385
386 let sources = self.manager.sources();
388 let cache = Arc::clone(&self.manager.cache);
389
390 let handle = spawn_completion_saturator(sources, cache, event_tx, 50);
391
392 self.manager.set_saturator(handle);
393
394 tracing::info!("Completion plugin booted with saturator");
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_completion_plugin_new() {
404 let plugin = CompletionPlugin::new();
405 assert_eq!(plugin.id().as_str(), "reovim:completion");
406 assert_eq!(plugin.name(), "Completion");
407 }
408
409 #[test]
410 fn test_completion_plugin_default() {
411 let plugin = CompletionPlugin::default();
412 assert_eq!(plugin.name(), "Completion");
413 }
414
415 #[test]
416 fn test_completion_plugin_dependencies() {
417 let plugin = CompletionPlugin::new();
418 let deps = plugin.dependencies();
419 assert!(deps.is_empty());
420 }
421}