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