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