Skip to main content

fresh/services/completion/
service.rs

1//! Completion service: orchestrates multiple completion providers.
2//!
3//! The service owns a set of registered `CompletionProvider`s, builds a
4//! `CompletionContext` from the current editor state, fans out to each
5//! enabled provider, and merges the results into a single ranked list.
6//!
7//! ## Provider lifecycle
8//!
9//! 1. Built-in providers (dabbrev, buffer-words) are registered at startup.
10//! 2. The LSP provider is always registered but returns `Pending` — its
11//!    results arrive asynchronously and are fed in via `supply_async_results`.
12//! 3. TypeScript plugins register providers dynamically via the plugin API.
13//!
14//! ## Huge-file safety
15//!
16//! The service computes a safe scan window (`CompletionContext::scan_range`)
17//! before calling any provider. For normal files this is 512 KB around the
18//! cursor. For lazily-loaded files (>100 MB) it shrinks to 32 KB. Providers
19//! receive a pre-sliced `&[u8]` and never touch the `Buffer` directly.
20
21use super::buffer_words::BufferWordProvider;
22use super::dabbrev::DabbrevProvider;
23use super::provider::{
24    CompletionCandidate, CompletionContext, CompletionProvider, CompletionSourceId, ProviderResult,
25};
26
27/// The completion service.
28pub struct CompletionService {
29    providers: Vec<Box<dyn CompletionProvider>>,
30    /// Async results waiting to be merged (keyed by request id).
31    pending_async: Vec<(u64, CompletionSourceId)>,
32}
33
34impl CompletionService {
35    /// Create a new service with the built-in providers pre-registered.
36    pub fn new() -> Self {
37        let mut svc = Self {
38            providers: Vec::new(),
39            pending_async: Vec::new(),
40        };
41        svc.register(Box::new(BufferWordProvider::new()));
42        svc.register(Box::new(DabbrevProvider::new()));
43        svc
44    }
45
46    /// Register a completion provider.
47    pub fn register(&mut self, provider: Box<dyn CompletionProvider>) {
48        // Replace existing provider with the same id.
49        let id = provider.id();
50        self.providers.retain(|p| p.id() != id);
51        self.providers.push(provider);
52    }
53
54    /// Unregister a provider by id.
55    pub fn unregister(&mut self, id: &CompletionSourceId) {
56        self.providers.retain(|p| p.id() != *id);
57    }
58
59    /// Request completion from all enabled providers.
60    ///
61    /// `buffer_window` is the pre-sliced byte range corresponding to
62    /// `ctx.scan_range`. The caller (Editor) is responsible for extracting
63    /// this from the buffer, which keeps the service decoupled from the
64    /// buffer internals.
65    ///
66    /// Returns the synchronously-available candidates, already merged and
67    /// sorted. Any providers that return `Pending` are tracked internally;
68    /// their results should be supplied later via `supply_async_results`.
69    pub fn request(
70        &mut self,
71        ctx: &CompletionContext,
72        buffer_window: &[u8],
73    ) -> Vec<CompletionCandidate> {
74        self.pending_async.clear();
75        let mut all_candidates: Vec<CompletionCandidate> = Vec::new();
76
77        // Sort providers by priority so higher-priority providers run first.
78        let mut indices: Vec<usize> = (0..self.providers.len()).collect();
79        indices.sort_by_key(|&i| self.providers[i].priority());
80
81        for &i in &indices {
82            let provider = &self.providers[i];
83            if !provider.is_enabled(ctx) {
84                continue;
85            }
86            let source_id = provider.id();
87            match provider.provide(ctx, buffer_window) {
88                ProviderResult::Ready(mut candidates) => {
89                    for c in &mut candidates {
90                        c.source = Some(source_id.clone());
91                    }
92                    all_candidates.extend(candidates);
93                }
94                ProviderResult::Pending(request_id) => {
95                    self.pending_async.push((request_id, source_id));
96                }
97            }
98        }
99
100        Self::rank(&mut all_candidates);
101        all_candidates
102    }
103
104    /// Supply results from an async provider (e.g., LSP).
105    ///
106    /// Returns the merged, re-ranked candidate list including both the
107    /// new async results and any previously supplied candidates.
108    pub fn supply_async_results(
109        &mut self,
110        request_id: u64,
111        mut candidates: Vec<CompletionCandidate>,
112    ) -> Option<Vec<CompletionCandidate>> {
113        // Find and remove the pending entry.
114        let pos = self
115            .pending_async
116            .iter()
117            .position(|(id, _)| *id == request_id)?;
118        let (_rid, source_id) = self.pending_async.remove(pos);
119        for c in &mut candidates {
120            c.source = Some(source_id.clone());
121        }
122        Self::rank(&mut candidates);
123        Some(candidates)
124    }
125
126    /// Check if there are pending async provider requests.
127    pub fn has_pending(&self) -> bool {
128        !self.pending_async.is_empty()
129    }
130
131    /// Rank and deduplicate a candidate list.
132    ///
133    /// Primary sort: score descending. Tie-breaker: label ascending.
134    /// Deduplication keeps the highest-scoring entry for each label.
135    fn rank(candidates: &mut Vec<CompletionCandidate>) {
136        // Sort first so highest score comes first per label.
137        candidates.sort_by(|a, b| {
138            b.score
139                .cmp(&a.score)
140                .then_with(|| a.label.to_lowercase().cmp(&b.label.to_lowercase()))
141        });
142
143        // Deduplicate by (lowercase label, insert_text), keeping the first
144        // (highest-scored) occurrence.
145        let mut seen = std::collections::HashSet::new();
146        candidates.retain(|c| {
147            let key = (
148                c.label.to_lowercase(),
149                c.insert_text.clone().unwrap_or_default(),
150            );
151            seen.insert(key)
152        });
153    }
154
155    /// Access the registered providers (read-only).
156    pub fn providers(&self) -> &[Box<dyn CompletionProvider>] {
157        &self.providers
158    }
159}
160
161impl Default for CompletionService {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    /// A trivial test provider that returns static candidates.
172    struct StaticProvider {
173        id: &'static str,
174        candidates: Vec<CompletionCandidate>,
175        priority: u32,
176    }
177
178    impl CompletionProvider for StaticProvider {
179        fn id(&self) -> CompletionSourceId {
180            CompletionSourceId(self.id.into())
181        }
182        fn display_name(&self) -> &str {
183            self.id
184        }
185        fn is_enabled(&self, _ctx: &CompletionContext) -> bool {
186            true
187        }
188        fn provide(&self, _ctx: &CompletionContext, _buffer_window: &[u8]) -> ProviderResult {
189            ProviderResult::Ready(self.candidates.clone())
190        }
191        fn priority(&self) -> u32 {
192            self.priority
193        }
194    }
195
196    fn test_ctx() -> CompletionContext {
197        CompletionContext {
198            prefix: "te".into(),
199            cursor_byte: 10,
200            word_start_byte: 8,
201            buffer_len: 100,
202            is_large_file: false,
203            scan_range: 0..100,
204            viewport_top_byte: 0,
205            viewport_bottom_byte: 100,
206            language_id: None,
207            word_chars_extra: String::new(),
208            prefix_has_uppercase: false,
209            other_buffers: Vec::new(),
210        }
211    }
212
213    #[test]
214    fn merges_multiple_providers() {
215        let mut svc = CompletionService {
216            providers: Vec::new(),
217            pending_async: Vec::new(),
218        };
219        svc.register(Box::new(StaticProvider {
220            id: "a",
221            candidates: vec![CompletionCandidate::word("test_alpha".into(), 100)],
222            priority: 10,
223        }));
224        svc.register(Box::new(StaticProvider {
225            id: "b",
226            candidates: vec![CompletionCandidate::word("test_beta".into(), 200)],
227            priority: 20,
228        }));
229
230        let ctx = test_ctx();
231        let results = svc.request(&ctx, b"");
232        assert_eq!(results.len(), 2);
233        // Higher score first.
234        assert_eq!(results[0].label, "test_beta");
235        assert_eq!(results[1].label, "test_alpha");
236    }
237
238    #[test]
239    fn deduplicates_by_label() {
240        let mut svc = CompletionService {
241            providers: Vec::new(),
242            pending_async: Vec::new(),
243        };
244        svc.register(Box::new(StaticProvider {
245            id: "a",
246            candidates: vec![CompletionCandidate::word("test".into(), 50)],
247            priority: 10,
248        }));
249        svc.register(Box::new(StaticProvider {
250            id: "b",
251            candidates: vec![CompletionCandidate::word("test".into(), 100)],
252            priority: 20,
253        }));
254
255        let ctx = test_ctx();
256        let results = svc.request(&ctx, b"");
257        // Only one "test" survives, the one with higher score.
258        assert_eq!(results.len(), 1);
259        assert_eq!(results[0].score, 100);
260    }
261
262    #[test]
263    fn async_supply() {
264        let mut svc = CompletionService {
265            providers: Vec::new(),
266            pending_async: vec![(42, CompletionSourceId("lsp".into()))],
267        };
268        let candidates = vec![CompletionCandidate::word("testing".into(), 300)];
269        let merged = svc.supply_async_results(42, candidates);
270        assert!(merged.is_some());
271        let merged = merged.unwrap();
272        assert_eq!(merged.len(), 1);
273        assert_eq!(merged[0].source.as_ref().unwrap().0, "lsp");
274    }
275
276    #[test]
277    fn async_supply_unknown_id_returns_none() {
278        let mut svc = CompletionService {
279            providers: Vec::new(),
280            pending_async: vec![(42, CompletionSourceId("lsp".into()))],
281        };
282        assert!(svc.supply_async_results(99, vec![]).is_none());
283    }
284}