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 hierarchical_symbols(&self) -> bool {
339        (|| -> _ {
340            self.0
341                .text_document
342                .as_ref()?
343                .document_symbol
344                .as_ref()?
345                .hierarchical_document_symbol_support
346        })()
347        .unwrap_or_default()
348    }
349
350    pub fn code_action_literals(&self) -> bool {
351        (|| -> _ {
352            self.0
353                .text_document
354                .as_ref()?
355                .code_action
356                .as_ref()?
357                .code_action_literal_support
358                .as_ref()
359        })()
360        .is_some()
361    }
362
363    pub fn work_done_progress(&self) -> bool {
364        (|| -> _ { self.0.window.as_ref()?.work_done_progress })().unwrap_or_default()
365    }
366
367    pub fn will_rename(&self) -> bool {
368        (|| -> _ { self.0.workspace.as_ref()?.file_operations.as_ref()?.will_rename })()
369            .unwrap_or_default()
370    }
371
372    pub fn change_annotation_support(&self) -> bool {
373        (|| -> _ {
374            self.0.workspace.as_ref()?.workspace_edit.as_ref()?.change_annotation_support.as_ref()
375        })()
376        .is_some()
377    }
378
379    pub fn code_action_resolve(&self) -> bool {
380        (|| -> _ {
381            Some(
382                self.0
383                    .text_document
384                    .as_ref()?
385                    .code_action
386                    .as_ref()?
387                    .resolve_support
388                    .as_ref()?
389                    .properties
390                    .as_slice(),
391            )
392        })()
393        .unwrap_or_default()
394        .iter()
395        .any(|it| it == "edit")
396    }
397
398    pub fn signature_help_label_offsets(&self) -> bool {
399        (|| -> _ {
400            self.0
401                .text_document
402                .as_ref()?
403                .signature_help
404                .as_ref()?
405                .signature_information
406                .as_ref()?
407                .parameter_information
408                .as_ref()?
409                .label_offset_support
410        })()
411        .unwrap_or_default()
412    }
413
414    pub fn text_document_diagnostic(&self) -> bool {
415        (|| -> _ { self.0.text_document.as_ref()?.diagnostic.as_ref() })().is_some()
416    }
417
418    pub fn text_document_diagnostic_related_document_support(&self) -> bool {
419        (|| -> _ { self.0.text_document.as_ref()?.diagnostic.as_ref()?.related_document_support })()
420            == Some(true)
421    }
422
423    pub fn code_action_group(&self) -> bool {
424        self.experimental_bool("codeActionGroup")
425    }
426
427    pub fn commands(&self) -> Option<ext::ClientCommandOptions> {
428        self.experimental("commands")
429    }
430
431    pub fn local_docs(&self) -> bool {
432        self.experimental_bool("localDocs")
433    }
434
435    pub fn open_server_logs(&self) -> bool {
436        self.experimental_bool("openServerLogs")
437    }
438
439    pub fn server_status_notification(&self) -> bool {
440        self.experimental_bool("serverStatusNotification")
441    }
442
443    pub fn snippet_text_edit(&self) -> bool {
444        self.experimental_bool("snippetTextEdit")
445    }
446
447    pub fn hover_actions(&self) -> bool {
448        self.experimental_bool("hoverActions")
449    }
450
451    /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
452    pub fn color_diagnostic_output(&self) -> bool {
453        self.experimental_bool("colorDiagnosticOutput")
454    }
455
456    pub fn test_explorer(&self) -> bool {
457        self.experimental_bool("testExplorer")
458    }
459
460    pub fn completion_snippet(&self) -> bool {
461        (|| -> _ {
462            self.0
463                .text_document
464                .as_ref()?
465                .completion
466                .as_ref()?
467                .completion_item
468                .as_ref()?
469                .snippet_support
470        })()
471        .unwrap_or_default()
472    }
473
474    pub fn semantic_tokens_refresh(&self) -> bool {
475        (|| -> _ { self.0.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support })()
476            .unwrap_or_default()
477    }
478
479    pub fn code_lens_refresh(&self) -> bool {
480        (|| -> _ { self.0.workspace.as_ref()?.code_lens.as_ref()?.refresh_support })()
481            .unwrap_or_default()
482    }
483
484    pub fn inlay_hints_refresh(&self) -> bool {
485        (|| -> _ { self.0.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support })()
486            .unwrap_or_default()
487    }
488
489    pub fn diagnostics_refresh(&self) -> bool {
490        (|| -> _ { self.0.workspace.as_ref()?.diagnostic.as_ref()?.refresh_support })()
491            .unwrap_or_default()
492    }
493
494    pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<&str> {
495        self.0
496            .text_document
497            .as_ref()
498            .and_then(|text| text.inlay_hint.as_ref())
499            .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
500            .map(|inlay_resolve| inlay_resolve.properties.iter())
501            .into_iter()
502            .flatten()
503            .map(|s| s.as_str())
504            .collect()
505    }
506
507    pub fn completion_resolve_support_properties(&self) -> FxHashSet<&str> {
508        self.0
509            .text_document
510            .as_ref()
511            .and_then(|text| text.completion.as_ref())
512            .and_then(|completion_caps| completion_caps.completion_item.as_ref())
513            .and_then(|completion_item_caps| completion_item_caps.resolve_support.as_ref())
514            .map(|resolve_support| resolve_support.properties.iter())
515            .into_iter()
516            .flatten()
517            .map(|s| s.as_str())
518            .collect()
519    }
520
521    pub fn hover_markdown_support(&self) -> bool {
522        (|| -> _ {
523            Some(self.0.text_document.as_ref()?.hover.as_ref()?.content_format.as_ref()?.as_slice())
524        })()
525        .unwrap_or_default()
526        .contains(&lsp_types::MarkupKind::Markdown)
527    }
528
529    pub fn insert_replace_support(&self) -> bool {
530        (|| -> _ {
531            self.0
532                .text_document
533                .as_ref()?
534                .completion
535                .as_ref()?
536                .completion_item
537                .as_ref()?
538                .insert_replace_support
539        })()
540        .unwrap_or_default()
541    }
542}