Skip to main content

oak_lsp/
service.rs

1use crate::types::{CodeAction, CompletionItem, Diagnostic, DocumentHighlight, FoldingRange, Hover, InitializeParams, InlayHint, LocationRange, SelectionRange, SemanticTokens, SignatureHelp, StructureItem, TextEdit, WorkspaceEdit, WorkspaceSymbol};
2use core::range::Range;
3use oak_core::{
4    language::{ElementRole, ElementType, Language},
5    source::Source,
6    tree::RedNode,
7};
8use oak_resolver::ModuleResolver;
9use oak_vfs::{Vfs, WritableVfs};
10use std::future::Future;
11
12pub trait LanguageService: Send + Sync {
13    type Lang: Language;
14    type Vfs: WritableVfs;
15
16    /// Get the underlying VFS.
17    fn vfs(&self) -> &Self::Vfs;
18
19    /// Get the workspace manager.
20    fn workspace(&self) -> &crate::workspace::WorkspaceManager;
21
22    /// Helper to get source from VFS.
23    fn get_source(&self, uri: &str) -> Option<<Self::Vfs as Vfs>::Source> {
24        self.vfs().get_source(uri)
25    }
26
27    /// Helper to get the root red node of a file.
28    /// Implementations should override this to provide actual parsing/caching.
29    fn get_root(&self, _uri: &str) -> impl Future<Output = Option<RedNode<'_, Self::Lang>>> + Send + '_ {
30        async { None }
31    }
32
33    /// Helper to run logic with the root node.
34    fn with_root<'a, R, F>(&'a self, uri: &'a str, f: F) -> impl Future<Output = Option<R>> + Send + 'a
35    where
36        R: Send,
37        F: FnOnce(RedNode<'a, Self::Lang>) -> R + Send + 'a,
38    {
39        async move {
40            let root = self.get_root(uri).await?;
41            Some(f(root))
42        }
43    }
44
45    /// Helper to run logic with multiple root nodes in parallel.
46    fn with_roots<'a, R, F>(&'a self, uris: Vec<String>, f: F) -> impl Future<Output = Vec<R>> + Send + 'a
47    where
48        R: Send + 'static,
49        F: Fn(RedNode<'a, Self::Lang>) -> R + Send + Sync + 'a,
50    {
51        let mut futures = Vec::new();
52        let f = std::sync::Arc::new(f);
53
54        for uri in uris {
55            let f = f.clone();
56            futures.push(async move { if let Some(root) = self.get_root(&uri).await { Some(f(root)) } else { None } });
57        }
58
59        async move { futures::future::join_all(futures).await.into_iter().flatten().collect() }
60    }
61
62    /// Provide hover information. Defaults to None.
63    fn hover(&self, _uri: &str, _range: Range<usize>) -> impl Future<Output = Option<Hover>> + Send + '_ {
64        async { None }
65    }
66
67    /// Provide folding ranges. Defaults to empty.
68    fn folding_ranges(&self, _uri: &str) -> impl Future<Output = Vec<FoldingRange>> + Send + '_ {
69        async { vec![] }
70    }
71
72    /// Provide document symbols. Defaults to empty.
73    fn document_symbols<'a>(&'a self, uri: &'a str) -> impl Future<Output = Vec<StructureItem>> + Send + 'a {
74        let uri = uri.to_string();
75        async move {
76            let _source = match self.get_source(&uri) {
77                Some(s) => s,
78                None => return vec![],
79            };
80            let _root = match self.get_root(&uri).await {
81                Some(r) => r,
82                None => return vec![],
83            };
84            let symbols = self.workspace().symbols.query_file(&uri);
85            if !symbols.is_empty() {
86                return symbols.into_iter().map(StructureItem::from).collect();
87            }
88            vec![]
89        }
90    }
91
92    /// Provide workspace symbols.
93    fn workspace_symbols<'a>(&'a self, query: String) -> impl Future<Output = Vec<WorkspaceSymbol>> + Send + 'a {
94        async move { self.workspace().symbols.query(&query).into_iter().map(|s| WorkspaceSymbol::from(s)).collect() }
95    }
96
97    /// Helper to list all files recursively.
98    fn list_all_files(&self, root_uri: &str) -> impl Future<Output = Vec<String>> + Send + '_ {
99        let root_uri = root_uri.to_string();
100        async move {
101            let mut files = Vec::new();
102            let mut stack = vec![root_uri];
103
104            while let Some(uri) = stack.pop() {
105                if self.vfs().is_file(&uri) {
106                    files.push(uri);
107                }
108                else if self.vfs().is_dir(&uri) {
109                    if let Some(entries) = self.vfs().read_dir(&uri) {
110                        for entry in entries {
111                            stack.push(entry.to_string());
112                        }
113                    }
114                }
115            }
116            files
117        }
118    }
119
120    /// Find definition. Defaults to empty.
121    fn definition<'a>(&'a self, uri: &'a str, range: Range<usize>) -> impl Future<Output = Vec<LocationRange>> + Send + 'a {
122        let uri = uri.to_string();
123        async move {
124            let root = match self.get_root(&uri).await {
125                Some(r) => r,
126                None => return vec![],
127            };
128            let source = match self.get_source(&uri) {
129                Some(s) => s,
130                None => return vec![],
131            };
132
133            // 1. Identify token at range
134            use oak_core::tree::RedTree;
135            let node = match root.child_at_offset(range.start) {
136                Some(RedTree::Node(n)) => n,
137                Some(RedTree::Leaf(l)) => return vec![LocationRange { uri: uri.clone().into(), range: l.span }],
138                None => root,
139            };
140
141            // 2. If it's a reference, try to resolve it
142            let role = node.green.kind.role();
143            if role.universal() == oak_core::language::UniversalElementRole::Reference {
144                let name = &source.get_text_in(node.span());
145
146                // Try local symbols first (not implemented here, should be done by lang-specific logic)
147
148                // Try global symbols
149                if let Some(sym) = self.workspace().symbols.lookup(name) {
150                    return vec![LocationRange { uri: sym.uri, range: sym.range }];
151                }
152
153                // Try as a module import
154                if let Some(resolved_uri) = self.workspace().resolver.resolve(&uri, name) {
155                    return vec![LocationRange { uri: resolved_uri.into(), range: (0..0).into() }];
156                }
157            }
158
159            vec![]
160        }
161    }
162
163    /// Find references. Defaults to empty.
164    fn references<'a>(&'a self, _uri: &'a str, _range: Range<usize>) -> impl Future<Output = Vec<LocationRange>> + Send + 'a {
165        async { vec![] }
166    }
167
168    /// Find type definition. Defaults to empty.
169    fn type_definition<'a>(&'a self, _uri: &'a str, _range: Range<usize>) -> impl Future<Output = Vec<LocationRange>> + Send + 'a {
170        async { vec![] }
171    }
172
173    /// Find implementation. Defaults to empty.
174    fn implementation<'a>(&'a self, _uri: &'a str, _range: Range<usize>) -> impl Future<Output = Vec<LocationRange>> + Send + 'a {
175        async { vec![] }
176    }
177
178    /// Provide document highlights. Defaults to empty.
179    fn document_highlights<'a>(&'a self, _uri: &'a str, _range: Range<usize>) -> impl Future<Output = Vec<DocumentHighlight>> + Send + 'a {
180        async { vec![] }
181    }
182
183    /// Rename a symbol.
184    fn rename<'a>(&'a self, _uri: &'a str, _range: Range<usize>, _new_name: String) -> impl Future<Output = Option<WorkspaceEdit>> + Send + 'a {
185        async { None }
186    }
187
188    /// Provide completion items. Defaults to empty.
189    fn completion<'a>(&'a self, _uri: &'a str, _position: usize) -> impl Future<Output = Vec<CompletionItem>> + Send + 'a {
190        async { vec![] }
191    }
192
193    /// Provide diagnostics for a file. Defaults to empty.
194    fn diagnostics<'a>(&'a self, _uri: &'a str) -> impl Future<Output = Vec<Diagnostic>> + Send + 'a {
195        async { vec![] }
196    }
197
198    /// Provide semantic tokens for a file. Defaults to None.
199    fn semantic_tokens<'a>(&'a self, _uri: &'a str) -> impl Future<Output = Option<SemanticTokens>> + Send + 'a {
200        async { None }
201    }
202
203    /// Provide semantic tokens for a range. Defaults to None.
204    fn semantic_tokens_range<'a>(&'a self, _uri: &'a str, _range: Range<usize>) -> impl Future<Output = Option<SemanticTokens>> + Send + 'a {
205        async { None }
206    }
207
208    /// Provide selection ranges for a file. Defaults to empty.
209    fn selection_ranges<'a>(&'a self, _uri: &'a str, _ranges: Vec<usize>) -> impl Future<Output = Vec<SelectionRange>> + Send + 'a {
210        async { vec![] }
211    }
212
213    /// Provide signature help at a position. Defaults to None.
214    fn signature_help<'a>(&'a self, _uri: &'a str, _position: usize) -> impl Future<Output = Option<SignatureHelp>> + Send + 'a {
215        async { None }
216    }
217
218    /// Provide inlay hints for a file. Defaults to empty.
219    fn inlay_hints<'a>(&'a self, _uri: &'a str) -> impl Future<Output = Vec<InlayHint>> + Send + 'a {
220        async { vec![] }
221    }
222
223    /// Provide document formatting. Defaults to empty.
224    fn formatting<'a>(&'a self, _uri: &'a str) -> impl Future<Output = Vec<TextEdit>> + Send + 'a {
225        async { vec![] }
226    }
227
228    /// Provide code actions for a file. Defaults to empty.
229    fn code_actions<'a>(&'a self, _uri: &'a str, _range: Range<usize>) -> impl Future<Output = Vec<CodeAction>> + Send + 'a {
230        async { vec![] }
231    }
232
233    /// Called when the language server is initialized.
234    fn initialize<'a>(&'a self, _params: InitializeParams) -> impl Future<Output = ()> + Send + 'a {
235        async {}
236    }
237
238    /// Called when the language server is initialized (notification).
239    fn initialized<'a>(&'a self) -> impl Future<Output = ()> + Send + 'a {
240        async {}
241    }
242
243    /// Called when the language server is shut down.
244    fn shutdown<'a>(&'a self) -> impl Future<Output = ()> + Send + 'a {
245        async {}
246    }
247
248    /// Called when a file is saved.
249    fn did_save<'a>(&'a self, _uri: &'a str) -> impl Future<Output = ()> + Send + 'a {
250        async {}
251    }
252
253    /// Called when a file is closed.
254    fn did_close<'a>(&'a self, _uri: &'a str) -> impl Future<Output = ()> + Send + 'a {
255        async {}
256    }
257}