1use 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 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 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 }
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 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}