Skip to main content

perl_lsp_capability_map/
lib.rs

1//! LSP capability/feature translation helpers.
2//!
3//! This microcrate owns one responsibility: map between
4//! [`lsp_types::ServerCapabilities`] and canonical Perl LSP feature IDs.
5
6use lsp_types::ServerCapabilities;
7use perl_lsp_feature_ids::*;
8
9/// Extract feature IDs from LSP `ServerCapabilities`.
10pub fn feature_ids_from_caps(c: &ServerCapabilities) -> Vec<&'static str> {
11    let mut v = Vec::new();
12
13    // Text Document Features
14    if c.completion_provider.is_some() {
15        v.push(LSP_COMPLETION);
16    }
17    if c.hover_provider.is_some() {
18        v.push(LSP_HOVER);
19    }
20    if c.signature_help_provider.is_some() {
21        v.push(LSP_SIGNATURE_HELP);
22    }
23    if c.definition_provider.is_some() {
24        v.push(LSP_DEFINITION);
25    }
26    if c.declaration_provider.is_some() {
27        v.push(LSP_DECLARATION);
28    }
29    if c.notebook_document_sync.is_some() {
30        v.push(LSP_NOTEBOOK_DOCUMENT_SYNC);
31    }
32    if c.type_definition_provider.is_some() {
33        v.push(LSP_TYPE_DEFINITION);
34    }
35    if c.implementation_provider.is_some() {
36        v.push(LSP_IMPLEMENTATION);
37    }
38    if c.references_provider.is_some() {
39        v.push(LSP_REFERENCES);
40    }
41    if c.document_highlight_provider.is_some() {
42        v.push(LSP_DOCUMENT_HIGHLIGHT);
43    }
44    if c.document_symbol_provider.is_some() {
45        v.push(LSP_DOCUMENT_SYMBOL);
46    }
47    if c.code_action_provider.is_some() {
48        v.push(LSP_CODE_ACTION);
49    }
50    if c.code_lens_provider.is_some() {
51        v.push(LSP_CODE_LENS);
52    }
53    if c.document_link_provider.is_some() {
54        v.push(LSP_DOCUMENT_LINK);
55    }
56    if c.color_provider.is_some() {
57        v.push(LSP_DOCUMENT_COLOR);
58    }
59    if c.document_formatting_provider.is_some() {
60        v.push(LSP_FORMATTING);
61    }
62    if c.document_range_formatting_provider.is_some() {
63        v.push(LSP_RANGE_FORMATTING);
64    }
65    if c.document_on_type_formatting_provider.is_some() {
66        v.push(LSP_ON_TYPE_FORMATTING);
67    }
68    if c.rename_provider.is_some() {
69        v.push(LSP_RENAME);
70    }
71    if c.folding_range_provider.is_some() {
72        v.push(LSP_FOLDING_RANGE);
73    }
74    if c.selection_range_provider.is_some() {
75        v.push(LSP_SELECTION_RANGE);
76    }
77    if c.linked_editing_range_provider.is_some() {
78        v.push(LSP_LINKED_EDITING_RANGE);
79    }
80    if c.call_hierarchy_provider.is_some() {
81        v.push(LSP_CALL_HIERARCHY);
82    }
83    if c.semantic_tokens_provider.is_some() {
84        v.push(LSP_SEMANTIC_TOKENS);
85    }
86    if c.moniker_provider.is_some() {
87        v.push(LSP_MONIKER);
88    }
89    // Note: type_hierarchy_provider doesn't exist in lsp-types 0.97
90    if c.inline_value_provider.is_some() {
91        v.push(LSP_INLINE_VALUE);
92    }
93    if c.inlay_hint_provider.is_some() {
94        v.push(LSP_INLAY_HINT);
95    }
96    if c.diagnostic_provider.is_some() {
97        v.push(LSP_PULL_DIAGNOSTICS);
98    }
99
100    // Workspace Features
101    if c.workspace_symbol_provider.is_some() {
102        v.push(LSP_WORKSPACE_SYMBOL);
103    }
104    if c.execute_command_provider.is_some() {
105        v.push(LSP_EXECUTE_COMMAND);
106    }
107
108    v.sort();
109    v.dedup();
110    v
111}
112
113/// Build LSP `ServerCapabilities` from feature IDs.
114pub fn caps_from_feature_ids(features: &[&str]) -> ServerCapabilities {
115    use lsp_types::*;
116
117    let mut caps = ServerCapabilities::default();
118
119    for &feature in features {
120        match feature {
121            LSP_COMPLETION => {
122                caps.completion_provider = Some(CompletionOptions {
123                    trigger_characters: Some(vec![
124                        "$".to_string(),
125                        "@".to_string(),
126                        "%".to_string(),
127                        ">".to_string(),
128                        ":".to_string(),
129                    ]),
130                    ..Default::default()
131                });
132            }
133            LSP_HOVER => {
134                caps.hover_provider = Some(HoverProviderCapability::Simple(true));
135            }
136            LSP_SIGNATURE_HELP => {
137                caps.signature_help_provider = Some(SignatureHelpOptions {
138                    trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
139                    ..Default::default()
140                });
141            }
142            LSP_DEFINITION => {
143                caps.definition_provider = Some(OneOf::Left(true));
144            }
145            LSP_DECLARATION => {
146                caps.declaration_provider = Some(DeclarationCapability::Simple(true));
147            }
148            LSP_NOTEBOOK_DOCUMENT_SYNC => {
149                caps.notebook_document_sync = Some(OneOf::Left(NotebookDocumentSyncOptions {
150                    notebook_selector: vec![NotebookSelector::ByNotebook {
151                        notebook: Notebook::String("jupyter-notebook".to_string()),
152                        cells: Some(vec![NotebookCellSelector { language: "perl".to_string() }]),
153                    }],
154                    save: Some(true),
155                }));
156            }
157            LSP_TYPE_DEFINITION => {
158                caps.type_definition_provider =
159                    Some(TypeDefinitionProviderCapability::Simple(true));
160            }
161            LSP_IMPLEMENTATION => {
162                caps.implementation_provider = Some(ImplementationProviderCapability::Simple(true));
163            }
164            LSP_REFERENCES => {
165                caps.references_provider = Some(OneOf::Left(true));
166            }
167            LSP_DOCUMENT_SYMBOL => {
168                caps.document_symbol_provider = Some(OneOf::Left(true));
169            }
170            LSP_CODE_ACTION => {
171                caps.code_action_provider = Some(CodeActionProviderCapability::Simple(true));
172            }
173            LSP_FORMATTING => {
174                caps.document_formatting_provider = Some(OneOf::Left(true));
175            }
176            LSP_RANGE_FORMATTING => {
177                caps.document_range_formatting_provider = Some(OneOf::Left(true));
178            }
179            LSP_RENAME => {
180                caps.rename_provider = Some(OneOf::Left(true));
181            }
182            LSP_FOLDING_RANGE => {
183                caps.folding_range_provider = Some(FoldingRangeProviderCapability::Simple(true));
184            }
185            LSP_SEMANTIC_TOKENS => {
186                caps.semantic_tokens_provider =
187                    Some(SemanticTokensServerCapabilities::SemanticTokensOptions(
188                        SemanticTokensOptions {
189                            legend: SemanticTokensLegend {
190                                token_types: vec![
191                                    SemanticTokenType::NAMESPACE,
192                                    SemanticTokenType::TYPE,
193                                    SemanticTokenType::CLASS,
194                                    SemanticTokenType::ENUM,
195                                    SemanticTokenType::INTERFACE,
196                                    SemanticTokenType::STRUCT,
197                                    SemanticTokenType::TYPE_PARAMETER,
198                                    SemanticTokenType::PARAMETER,
199                                    SemanticTokenType::VARIABLE,
200                                    SemanticTokenType::PROPERTY,
201                                    SemanticTokenType::ENUM_MEMBER,
202                                    SemanticTokenType::EVENT,
203                                    SemanticTokenType::FUNCTION,
204                                    SemanticTokenType::METHOD,
205                                    SemanticTokenType::MACRO,
206                                    SemanticTokenType::KEYWORD,
207                                    SemanticTokenType::MODIFIER,
208                                    SemanticTokenType::COMMENT,
209                                    SemanticTokenType::STRING,
210                                    SemanticTokenType::NUMBER,
211                                    SemanticTokenType::REGEXP,
212                                    SemanticTokenType::OPERATOR,
213                                ],
214                                token_modifiers: vec![
215                                    SemanticTokenModifier::DECLARATION,
216                                    SemanticTokenModifier::DEFINITION,
217                                    SemanticTokenModifier::READONLY,
218                                    SemanticTokenModifier::STATIC,
219                                    SemanticTokenModifier::DEPRECATED,
220                                    SemanticTokenModifier::ABSTRACT,
221                                    SemanticTokenModifier::ASYNC,
222                                    SemanticTokenModifier::MODIFICATION,
223                                    SemanticTokenModifier::DOCUMENTATION,
224                                    SemanticTokenModifier::DEFAULT_LIBRARY,
225                                ],
226                            },
227                            full: Some(SemanticTokensFullOptions::Bool(true)),
228                            range: Some(true),
229                            ..Default::default()
230                        },
231                    ));
232            }
233            LSP_DOCUMENT_HIGHLIGHT => {
234                caps.document_highlight_provider = Some(OneOf::Left(true));
235            }
236            LSP_CODE_LENS => {
237                caps.code_lens_provider = Some(CodeLensOptions { resolve_provider: Some(true) });
238            }
239            LSP_DOCUMENT_LINK => {
240                caps.document_link_provider = Some(DocumentLinkOptions {
241                    resolve_provider: Some(true),
242                    work_done_progress_options: WorkDoneProgressOptions::default(),
243                });
244            }
245            LSP_DOCUMENT_COLOR | LSP_COLOR => {
246                caps.color_provider = Some(ColorProviderCapability::Simple(true));
247            }
248            LSP_ON_TYPE_FORMATTING => {
249                caps.document_on_type_formatting_provider = Some(DocumentOnTypeFormattingOptions {
250                    first_trigger_character: ";".to_string(),
251                    more_trigger_character: Some(vec!["}".to_string()]),
252                });
253            }
254            LSP_SELECTION_RANGE => {
255                caps.selection_range_provider =
256                    Some(SelectionRangeProviderCapability::Simple(true));
257            }
258            LSP_LINKED_EDITING_RANGE => {
259                caps.linked_editing_range_provider =
260                    Some(LinkedEditingRangeServerCapabilities::Simple(true));
261            }
262            LSP_CALL_HIERARCHY => {
263                caps.call_hierarchy_provider = Some(CallHierarchyServerCapability::Simple(true));
264            }
265            LSP_MONIKER => {
266                caps.moniker_provider =
267                    Some(OneOf::Right(MonikerServerCapabilities::Options(MonikerOptions {
268                        work_done_progress_options: WorkDoneProgressOptions::default(),
269                    })));
270            }
271            LSP_INLINE_VALUE => {
272                caps.inline_value_provider = Some(OneOf::Right(
273                    InlineValueServerCapabilities::Options(InlineValueOptions::default()),
274                ));
275            }
276            LSP_INLAY_HINT => {
277                caps.inlay_hint_provider =
278                    Some(OneOf::Right(InlayHintServerCapabilities::Options(InlayHintOptions {
279                        resolve_provider: Some(true),
280                        ..Default::default()
281                    })));
282            }
283            LSP_PULL_DIAGNOSTICS => {
284                caps.diagnostic_provider =
285                    Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
286                        identifier: Some("perl-lsp".to_string()),
287                        inter_file_dependencies: true,
288                        workspace_diagnostics: true,
289                        ..Default::default()
290                    }));
291            }
292            LSP_WORKSPACE_SYMBOL => {
293                caps.workspace_symbol_provider = Some(OneOf::Left(true));
294            }
295            LSP_EXECUTE_COMMAND => {
296                caps.execute_command_provider = Some(ExecuteCommandOptions {
297                    commands: vec!["perl.runCritic".to_string()],
298                    ..Default::default()
299                });
300            }
301            _ => {
302                // Unknown feature - ignore.
303            }
304        }
305    }
306
307    caps
308}
309
310#[cfg(test)]
311mod tests {
312    use lsp_types::{ColorProviderCapability, ServerCapabilities};
313
314    use super::{LSP_COLOR, LSP_DOCUMENT_COLOR, caps_from_feature_ids, feature_ids_from_caps};
315
316    #[test]
317    fn feature_ids_from_caps_reports_catalog_color_id() {
318        let caps = ServerCapabilities {
319            color_provider: Some(ColorProviderCapability::Simple(true)),
320            ..Default::default()
321        };
322
323        assert_eq!(feature_ids_from_caps(&caps), vec![LSP_DOCUMENT_COLOR]);
324    }
325
326    #[test]
327    fn caps_from_feature_ids_accepts_legacy_color_alias() {
328        let caps = caps_from_feature_ids(&[LSP_COLOR]);
329        assert!(caps.color_provider.is_some());
330    }
331
332    #[test]
333    fn caps_from_feature_ids_accepts_canonical_color_id() {
334        let caps = caps_from_feature_ids(&[LSP_DOCUMENT_COLOR]);
335        assert!(caps.color_provider.is_some());
336    }
337}