Skip to main content

rust_analyzer/lsp/
capabilities.rs

1//! Advertises the capabilities of the LSP Server.
2use ide::{CompletionFieldsToResolve, InlayFieldsToResolve};
3use ide_db::{FxHashSet, line_index::WideEncoding};
4use lsp_types::{
5    CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
6    CodeLensOptions, CompletionOptions, CompletionOptionsCompletionItem, DeclarationCapability,
7    DocumentOnTypeFormattingOptions, FileOperationFilter, FileOperationPattern,
8    FileOperationPatternKind, FileOperationRegistrationOptions, FoldingRangeProviderCapability,
9    HoverProviderCapability, ImplementationProviderCapability, InlayHintOptions,
10    InlayHintServerCapabilities, OneOf, PositionEncodingKind, RenameOptions, SaveOptions,
11    SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend,
12    SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability,
13    TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
14    WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities,
15    WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
16};
17use serde_json::json;
18
19use crate::{
20    config::{Config, RustfmtConfig},
21    line_index::PositionEncoding,
22    lsp::{ext, semantic_tokens},
23};
24
25pub fn server_capabilities(config: &Config) -> ServerCapabilities {
26    ServerCapabilities {
27        position_encoding: match config.caps().negotiated_encoding() {
28            PositionEncoding::Utf8 => Some(PositionEncodingKind::UTF8),
29            PositionEncoding::Wide(wide) => match wide {
30                WideEncoding::Utf16 => Some(PositionEncodingKind::UTF16),
31                WideEncoding::Utf32 => Some(PositionEncodingKind::UTF32),
32                _ => None,
33            },
34        },
35        text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
36            open_close: Some(true),
37            change: Some(TextDocumentSyncKind::INCREMENTAL),
38            will_save: None,
39            will_save_wait_until: None,
40            save: if config.caps().did_save_text_document_dynamic_registration() {
41                None
42            } else {
43                Some(SaveOptions::default().into())
44            },
45        })),
46        hover_provider: Some(HoverProviderCapability::Simple(true)),
47        completion_provider: Some(CompletionOptions {
48            resolve_provider: if config.client_is_neovim() {
49                config.has_completion_item_resolve_additionalTextEdits().then_some(true)
50            } else {
51                Some(config.caps().completions_resolve_provider())
52            },
53            trigger_characters: Some(vec![
54                ":".to_owned(),
55                ".".to_owned(),
56                "'".to_owned(),
57                "(".to_owned(),
58            ]),
59            all_commit_characters: None,
60            completion_item: config.caps().completion_item(),
61            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
62        }),
63        signature_help_provider: Some(SignatureHelpOptions {
64            trigger_characters: Some(vec!["(".to_owned(), ",".to_owned(), "<".to_owned()]),
65            retrigger_characters: None,
66            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
67        }),
68        declaration_provider: Some(DeclarationCapability::Simple(true)),
69        definition_provider: Some(OneOf::Left(true)),
70        type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
71        implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
72        references_provider: Some(OneOf::Left(true)),
73        document_highlight_provider: Some(OneOf::Left(true)),
74        document_symbol_provider: Some(OneOf::Left(true)),
75        workspace_symbol_provider: Some(OneOf::Left(true)),
76        code_action_provider: Some(config.caps().code_action_capabilities()),
77        code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
78        document_formatting_provider: Some(OneOf::Left(true)),
79        document_range_formatting_provider: match config.rustfmt(None) {
80            RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
81            _ => Some(OneOf::Left(false)),
82        },
83        document_on_type_formatting_provider: Some({
84            let mut chars = ide::Analysis::SUPPORTED_TRIGGER_CHARS.iter();
85            DocumentOnTypeFormattingOptions {
86                first_trigger_character: chars.next().unwrap().to_string(),
87                more_trigger_character: Some(chars.map(|c| c.to_string()).collect()),
88            }
89        }),
90        selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
91        folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
92        rename_provider: Some(OneOf::Right(RenameOptions {
93            prepare_provider: Some(true),
94            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
95        })),
96        linked_editing_range_provider: None,
97        document_link_provider: None,
98        color_provider: None,
99        execute_command_provider: None,
100        workspace: Some(WorkspaceServerCapabilities {
101            workspace_folders: Some(WorkspaceFoldersServerCapabilities {
102                supported: Some(true),
103                change_notifications: Some(OneOf::Left(true)),
104            }),
105            file_operations: Some(WorkspaceFileOperationsServerCapabilities {
106                did_create: None,
107                will_create: None,
108                did_rename: None,
109                will_rename: Some(FileOperationRegistrationOptions {
110                    filters: vec![
111                        FileOperationFilter {
112                            scheme: Some(String::from("file")),
113                            pattern: FileOperationPattern {
114                                glob: String::from("**/*.rs"),
115                                matches: Some(FileOperationPatternKind::File),
116                                options: None,
117                            },
118                        },
119                        FileOperationFilter {
120                            scheme: Some(String::from("file")),
121                            pattern: FileOperationPattern {
122                                glob: String::from("**"),
123                                matches: Some(FileOperationPatternKind::Folder),
124                                options: None,
125                            },
126                        },
127                    ],
128                }),
129                did_delete: None,
130                will_delete: None,
131            }),
132        }),
133        call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
134        semantic_tokens_provider: Some(
135            SemanticTokensOptions {
136                legend: SemanticTokensLegend {
137                    token_types: semantic_tokens::SUPPORTED_TYPES.to_vec(),
138                    token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(),
139                },
140
141                full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
142                range: Some(true),
143                work_done_progress_options: Default::default(),
144            }
145            .into(),
146        ),
147        moniker_provider: None,
148        inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
149            InlayHintOptions {
150                work_done_progress_options: Default::default(),
151                resolve_provider: Some(config.caps().inlay_hints_resolve_provider()),
152            },
153        ))),
154        inline_value_provider: None,
155        experimental: Some(json!({
156            "externalDocs": true,
157            "hoverRange": true,
158            "joinLines": true,
159            "matchingBrace": true,
160            "moveItem": true,
161            "onEnter": true,
162            "openCargoToml": true,
163            "parentModule": true,
164            "childModules": true,
165            "runnables": {
166                "kinds": [ "cargo" ],
167            },
168            "ssr": true,
169            "workspaceSymbolScopeKindFiltering": true,
170        })),
171        diagnostic_provider: Some(lsp_types::DiagnosticServerCapabilities::Options(
172            lsp_types::DiagnosticOptions {
173                identifier: Some("rust-analyzer".to_owned()),
174                inter_file_dependencies: true,
175                // FIXME
176                workspace_diagnostics: false,
177                work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
178            },
179        )),
180        inline_completion_provider: None,
181    }
182}
183
184#[derive(Debug, PartialEq, Clone, Default)]
185pub struct ClientCapabilities(lsp_types::ClientCapabilities);
186
187impl ClientCapabilities {
188    pub fn new(caps: lsp_types::ClientCapabilities) -> Self {
189        Self(caps)
190    }
191
192    fn completions_resolve_provider(&self) -> bool {
193        let client_capabilities = self.completion_resolve_support_properties();
194        let fields_to_resolve =
195            CompletionFieldsToResolve::from_client_capabilities(&client_capabilities);
196        fields_to_resolve != CompletionFieldsToResolve::empty()
197    }
198
199    fn inlay_hints_resolve_provider(&self) -> bool {
200        let client_capabilities = self.inlay_hint_resolve_support_properties();
201        let fields_to_resolve =
202            InlayFieldsToResolve::from_client_capabilities(&client_capabilities);
203        fields_to_resolve != InlayFieldsToResolve::empty()
204    }
205
206    fn experimental_bool(&self, index: &'static str) -> bool {
207        || -> _ { self.0.experimental.as_ref()?.get(index)?.as_bool() }().unwrap_or_default()
208    }
209
210    fn experimental<T: serde::de::DeserializeOwned>(&self, index: &'static str) -> Option<T> {
211        serde_json::from_value(self.0.experimental.as_ref()?.get(index)?.clone()).ok()
212    }
213
214    #[allow(non_snake_case)]
215    pub fn has_completion_item_resolve_additionalTextEdits(&self) -> bool {
216        (|| {
217            Some(
218                self.0
219                    .text_document
220                    .as_ref()?
221                    .completion
222                    .as_ref()?
223                    .completion_item
224                    .as_ref()?
225                    .resolve_support
226                    .as_ref()?
227                    .properties
228                    .iter()
229                    .any(|cap_string| cap_string.as_str() == "additionalTextEdits"),
230            )
231        })() == Some(true)
232    }
233
234    pub fn completion_label_details_support(&self) -> bool {
235        (|| -> _ {
236            self.0
237                .text_document
238                .as_ref()?
239                .completion
240                .as_ref()?
241                .completion_item
242                .as_ref()?
243                .label_details_support
244        })() == Some(true)
245    }
246
247    fn completion_item(&self) -> Option<CompletionOptionsCompletionItem> {
248        Some(CompletionOptionsCompletionItem {
249            label_details_support: Some(self.completion_label_details_support()),
250        })
251    }
252
253    fn code_action_capabilities(&self) -> CodeActionProviderCapability {
254        self.0
255            .text_document
256            .as_ref()
257            .and_then(|it| it.code_action.as_ref())
258            .and_then(|it| it.code_action_literal_support.as_ref())
259            .map_or(CodeActionProviderCapability::Simple(true), |_| {
260                CodeActionProviderCapability::Options(CodeActionOptions {
261                    // Advertise support for all built-in CodeActionKinds.
262                    // Ideally we would base this off of the client capabilities
263                    // but the client is supposed to fall back gracefully for unknown values.
264                    code_action_kinds: Some(vec![
265                        CodeActionKind::EMPTY,
266                        CodeActionKind::QUICKFIX,
267                        CodeActionKind::REFACTOR,
268                        CodeActionKind::REFACTOR_EXTRACT,
269                        CodeActionKind::REFACTOR_INLINE,
270                        CodeActionKind::REFACTOR_REWRITE,
271                    ]),
272                    resolve_provider: Some(true),
273                    work_done_progress_options: Default::default(),
274                })
275            })
276    }
277
278    pub fn negotiated_encoding(&self) -> PositionEncoding {
279        let client_encodings = match &self.0.general {
280            Some(general) => general.position_encodings.as_deref().unwrap_or_default(),
281            None => &[],
282        };
283
284        for enc in client_encodings {
285            if enc == &PositionEncodingKind::UTF8 {
286                return PositionEncoding::Utf8;
287            } else if enc == &PositionEncodingKind::UTF32 {
288                return PositionEncoding::Wide(WideEncoding::Utf32);
289            }
290            // NB: intentionally prefer just about anything else to utf-16.
291        }
292
293        PositionEncoding::Wide(WideEncoding::Utf16)
294    }
295
296    pub fn workspace_edit_resource_operations(
297        &self,
298    ) -> Option<&[lsp_types::ResourceOperationKind]> {
299        self.0.workspace.as_ref()?.workspace_edit.as_ref()?.resource_operations.as_deref()
300    }
301
302    pub fn semantics_tokens_augments_syntax_tokens(&self) -> bool {
303        (|| -> _ {
304            self.0.text_document.as_ref()?.semantic_tokens.as_ref()?.augments_syntax_tokens
305        })()
306        .unwrap_or(false)
307    }
308
309    pub fn did_save_text_document_dynamic_registration(&self) -> bool {
310        let caps = (|| -> _ { self.0.text_document.as_ref()?.synchronization.clone() })()
311            .unwrap_or_default();
312        caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
313    }
314
315    pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
316        (|| -> _ {
317            self.0.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration
318        })()
319        .unwrap_or_default()
320    }
321
322    pub fn did_change_watched_files_relative_pattern_support(&self) -> bool {
323        (|| -> _ {
324            self.0.workspace.as_ref()?.did_change_watched_files.as_ref()?.relative_pattern_support
325        })()
326        .unwrap_or_default()
327    }
328
329    pub fn location_link(&self) -> bool {
330        (|| -> _ { self.0.text_document.as_ref()?.definition?.link_support })().unwrap_or_default()
331    }
332
333    pub fn line_folding_only(&self) -> bool {
334        (|| -> _ { self.0.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only })()
335            .unwrap_or_default()
336    }
337
338    pub fn folding_range_collapsed_text(&self) -> bool {
339        (|| -> _ {
340            self.0
341                .text_document
342                .as_ref()?
343                .folding_range
344                .as_ref()?
345                .folding_range
346                .as_ref()?
347                .collapsed_text
348        })()
349        .unwrap_or_default()
350    }
351
352    pub fn hierarchical_symbols(&self) -> bool {
353        (|| -> _ {
354            self.0
355                .text_document
356                .as_ref()?
357                .document_symbol
358                .as_ref()?
359                .hierarchical_document_symbol_support
360        })()
361        .unwrap_or_default()
362    }
363
364    pub fn code_action_literals(&self) -> bool {
365        (|| -> _ {
366            self.0
367                .text_document
368                .as_ref()?
369                .code_action
370                .as_ref()?
371                .code_action_literal_support
372                .as_ref()
373        })()
374        .is_some()
375    }
376
377    pub fn work_done_progress(&self) -> bool {
378        (|| -> _ { self.0.window.as_ref()?.work_done_progress })().unwrap_or_default()
379    }
380
381    pub fn will_rename(&self) -> bool {
382        (|| -> _ { self.0.workspace.as_ref()?.file_operations.as_ref()?.will_rename })()
383            .unwrap_or_default()
384    }
385
386    pub fn change_annotation_support(&self) -> bool {
387        (|| -> _ {
388            self.0.workspace.as_ref()?.workspace_edit.as_ref()?.change_annotation_support.as_ref()
389        })()
390        .is_some()
391    }
392
393    pub fn code_action_resolve(&self) -> bool {
394        (|| -> _ {
395            Some(
396                self.0
397                    .text_document
398                    .as_ref()?
399                    .code_action
400                    .as_ref()?
401                    .resolve_support
402                    .as_ref()?
403                    .properties
404                    .as_slice(),
405            )
406        })()
407        .unwrap_or_default()
408        .iter()
409        .any(|it| it == "edit")
410    }
411
412    pub fn signature_help_label_offsets(&self) -> bool {
413        (|| -> _ {
414            self.0
415                .text_document
416                .as_ref()?
417                .signature_help
418                .as_ref()?
419                .signature_information
420                .as_ref()?
421                .parameter_information
422                .as_ref()?
423                .label_offset_support
424        })()
425        .unwrap_or_default()
426    }
427
428    pub fn text_document_diagnostic(&self) -> bool {
429        (|| -> _ { self.0.text_document.as_ref()?.diagnostic.as_ref() })().is_some()
430    }
431
432    pub fn text_document_diagnostic_related_document_support(&self) -> bool {
433        (|| -> _ { self.0.text_document.as_ref()?.diagnostic.as_ref()?.related_document_support })()
434            == Some(true)
435    }
436
437    pub fn code_action_group(&self) -> bool {
438        self.experimental_bool("codeActionGroup")
439    }
440
441    pub fn commands(&self) -> Option<ext::ClientCommandOptions> {
442        self.experimental("commands")
443    }
444
445    pub fn local_docs(&self) -> bool {
446        self.experimental_bool("localDocs")
447    }
448
449    pub fn open_server_logs(&self) -> bool {
450        self.experimental_bool("openServerLogs")
451    }
452
453    pub fn server_status_notification(&self) -> bool {
454        self.experimental_bool("serverStatusNotification")
455    }
456
457    pub fn snippet_text_edit(&self) -> bool {
458        self.experimental_bool("snippetTextEdit")
459    }
460
461    pub fn hover_actions(&self) -> bool {
462        self.experimental_bool("hoverActions")
463    }
464
465    /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
466    pub fn color_diagnostic_output(&self) -> bool {
467        self.experimental_bool("colorDiagnosticOutput")
468    }
469
470    pub fn test_explorer(&self) -> bool {
471        self.experimental_bool("testExplorer")
472    }
473
474    pub fn completion_snippet(&self) -> bool {
475        (|| -> _ {
476            self.0
477                .text_document
478                .as_ref()?
479                .completion
480                .as_ref()?
481                .completion_item
482                .as_ref()?
483                .snippet_support
484        })()
485        .unwrap_or_default()
486    }
487
488    pub fn semantic_tokens_refresh(&self) -> bool {
489        (|| -> _ { self.0.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support })()
490            .unwrap_or_default()
491    }
492
493    pub fn code_lens_refresh(&self) -> bool {
494        (|| -> _ { self.0.workspace.as_ref()?.code_lens.as_ref()?.refresh_support })()
495            .unwrap_or_default()
496    }
497
498    pub fn inlay_hints_refresh(&self) -> bool {
499        (|| -> _ { self.0.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support })()
500            .unwrap_or_default()
501    }
502
503    pub fn diagnostics_refresh(&self) -> bool {
504        (|| -> _ { self.0.workspace.as_ref()?.diagnostic.as_ref()?.refresh_support })()
505            .unwrap_or_default()
506    }
507
508    pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<&str> {
509        self.0
510            .text_document
511            .as_ref()
512            .and_then(|text| text.inlay_hint.as_ref())
513            .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
514            .map(|inlay_resolve| inlay_resolve.properties.iter())
515            .into_iter()
516            .flatten()
517            .map(|s| s.as_str())
518            .collect()
519    }
520
521    pub fn completion_resolve_support_properties(&self) -> FxHashSet<&str> {
522        self.0
523            .text_document
524            .as_ref()
525            .and_then(|text| text.completion.as_ref())
526            .and_then(|completion_caps| completion_caps.completion_item.as_ref())
527            .and_then(|completion_item_caps| completion_item_caps.resolve_support.as_ref())
528            .map(|resolve_support| resolve_support.properties.iter())
529            .into_iter()
530            .flatten()
531            .map(|s| s.as_str())
532            .collect()
533    }
534
535    pub fn hover_markdown_support(&self) -> bool {
536        (|| -> _ {
537            Some(self.0.text_document.as_ref()?.hover.as_ref()?.content_format.as_ref()?.as_slice())
538        })()
539        .unwrap_or_default()
540        .contains(&lsp_types::MarkupKind::Markdown)
541    }
542
543    pub fn insert_replace_support(&self) -> bool {
544        (|| -> _ {
545            self.0
546                .text_document
547                .as_ref()?
548                .completion
549                .as_ref()?
550                .completion_item
551                .as_ref()?
552                .insert_replace_support
553        })()
554        .unwrap_or_default()
555    }
556}