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