Auto merge of #17527 - Veykril:caps-config, r=Veykril

internal: Move capability querying out of the config module
This commit is contained in:
bors 2024-07-07 05:56:55 +00:00
commit 8c2adec32c
10 changed files with 525 additions and 515 deletions

View File

@ -0,0 +1,493 @@
//! Advertises the capabilities of the LSP Server.
use ide_db::{line_index::WideEncoding, FxHashSet};
use lsp_types::{
CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
CodeLensOptions, CompletionOptions, CompletionOptionsCompletionItem, DeclarationCapability,
DocumentOnTypeFormattingOptions, FileOperationFilter, FileOperationPattern,
FileOperationPatternKind, FileOperationRegistrationOptions, FoldingRangeProviderCapability,
HoverProviderCapability, ImplementationProviderCapability, InlayHintOptions,
InlayHintServerCapabilities, OneOf, PositionEncodingKind, RenameOptions, SaveOptions,
SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend,
SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability,
TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities,
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
};
use serde_json::json;
use crate::{
config::{Config, RustfmtConfig},
line_index::PositionEncoding,
lsp::{ext, semantic_tokens},
};
pub fn server_capabilities(config: &Config) -> ServerCapabilities {
ServerCapabilities {
position_encoding: match config.caps().negotiated_encoding() {
PositionEncoding::Utf8 => Some(PositionEncodingKind::UTF8),
PositionEncoding::Wide(wide) => match wide {
WideEncoding::Utf16 => Some(PositionEncodingKind::UTF16),
WideEncoding::Utf32 => Some(PositionEncodingKind::UTF32),
_ => None,
},
},
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::INCREMENTAL),
will_save: None,
will_save_wait_until: None,
save: Some(SaveOptions::default().into()),
})),
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
resolve_provider: config.caps().completions_resolve_provider(),
trigger_characters: Some(vec![
":".to_owned(),
".".to_owned(),
"'".to_owned(),
"(".to_owned(),
]),
all_commit_characters: None,
completion_item: config.caps().completion_item(),
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
}),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(vec!["(".to_owned(), ",".to_owned(), "<".to_owned()]),
retrigger_characters: None,
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
}),
declaration_provider: Some(DeclarationCapability::Simple(true)),
definition_provider: Some(OneOf::Left(true)),
type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
references_provider: Some(OneOf::Left(true)),
document_highlight_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
workspace_symbol_provider: Some(OneOf::Left(true)),
code_action_provider: Some(config.caps().code_action_capabilities()),
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: match config.rustfmt() {
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
_ => Some(OneOf::Left(false)),
},
document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
first_trigger_character: "=".to_owned(),
more_trigger_character: Some(more_trigger_character(config)),
}),
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
rename_provider: Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
})),
linked_editing_range_provider: None,
document_link_provider: None,
color_provider: None,
execute_command_provider: None,
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: Some(WorkspaceFileOperationsServerCapabilities {
did_create: None,
will_create: None,
did_rename: None,
will_rename: Some(FileOperationRegistrationOptions {
filters: vec![
FileOperationFilter {
scheme: Some(String::from("file")),
pattern: FileOperationPattern {
glob: String::from("**/*.rs"),
matches: Some(FileOperationPatternKind::File),
options: None,
},
},
FileOperationFilter {
scheme: Some(String::from("file")),
pattern: FileOperationPattern {
glob: String::from("**"),
matches: Some(FileOperationPatternKind::Folder),
options: None,
},
},
],
}),
did_delete: None,
will_delete: None,
}),
}),
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
semantic_tokens_provider: Some(
SemanticTokensOptions {
legend: SemanticTokensLegend {
token_types: semantic_tokens::SUPPORTED_TYPES.to_vec(),
token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(),
},
full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
range: Some(true),
work_done_progress_options: Default::default(),
}
.into(),
),
moniker_provider: None,
inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
InlayHintOptions {
work_done_progress_options: Default::default(),
resolve_provider: Some(true),
},
))),
inline_value_provider: None,
experimental: Some(json!({
"externalDocs": true,
"hoverRange": true,
"joinLines": true,
"matchingBrace": true,
"moveItem": true,
"onEnter": true,
"openCargoToml": true,
"parentModule": true,
"runnables": {
"kinds": [ "cargo" ],
},
"ssr": true,
"workspaceSymbolScopeKindFiltering": true,
})),
diagnostic_provider: None,
inline_completion_provider: None,
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct ClientCapabilities(lsp_types::ClientCapabilities);
impl ClientCapabilities {
pub fn new(caps: lsp_types::ClientCapabilities) -> Self {
Self(caps)
}
fn completions_resolve_provider(&self) -> Option<bool> {
self.completion_item_edit_resolve().then_some(true)
}
fn experimental_bool(&self, index: &'static str) -> bool {
|| -> _ { self.0.experimental.as_ref()?.get(index)?.as_bool() }().unwrap_or_default()
}
fn experimental<T: serde::de::DeserializeOwned>(&self, index: &'static str) -> Option<T> {
serde_json::from_value(self.0.experimental.as_ref()?.get(index)?.clone()).ok()
}
/// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
pub fn completion_item_edit_resolve(&self) -> bool {
(|| {
Some(
self.0
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.resolve_support
.as_ref()?
.properties
.iter()
.any(|cap_string| cap_string.as_str() == "additionalTextEdits"),
)
})() == Some(true)
}
pub fn completion_label_details_support(&self) -> bool {
(|| -> _ {
self.0
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.label_details_support
.as_ref()
})()
.is_some()
}
fn completion_item(&self) -> Option<CompletionOptionsCompletionItem> {
Some(CompletionOptionsCompletionItem {
label_details_support: Some(self.completion_label_details_support()),
})
}
fn code_action_capabilities(&self) -> CodeActionProviderCapability {
self.0
.text_document
.as_ref()
.and_then(|it| it.code_action.as_ref())
.and_then(|it| it.code_action_literal_support.as_ref())
.map_or(CodeActionProviderCapability::Simple(true), |_| {
CodeActionProviderCapability::Options(CodeActionOptions {
// Advertise support for all built-in CodeActionKinds.
// Ideally we would base this off of the client capabilities
// but the client is supposed to fall back gracefully for unknown values.
code_action_kinds: Some(vec![
CodeActionKind::EMPTY,
CodeActionKind::QUICKFIX,
CodeActionKind::REFACTOR,
CodeActionKind::REFACTOR_EXTRACT,
CodeActionKind::REFACTOR_INLINE,
CodeActionKind::REFACTOR_REWRITE,
]),
resolve_provider: Some(true),
work_done_progress_options: Default::default(),
})
})
}
pub fn negotiated_encoding(&self) -> PositionEncoding {
let client_encodings = match &self.0.general {
Some(general) => general.position_encodings.as_deref().unwrap_or_default(),
None => &[],
};
for enc in client_encodings {
if enc == &PositionEncodingKind::UTF8 {
return PositionEncoding::Utf8;
} else if enc == &PositionEncodingKind::UTF32 {
return PositionEncoding::Wide(WideEncoding::Utf32);
}
// NB: intentionally prefer just about anything else to utf-16.
}
PositionEncoding::Wide(WideEncoding::Utf16)
}
pub fn workspace_edit_resource_operations(
&self,
) -> Option<&[lsp_types::ResourceOperationKind]> {
self.0.workspace.as_ref()?.workspace_edit.as_ref()?.resource_operations.as_deref()
}
pub fn semantics_tokens_augments_syntax_tokens(&self) -> bool {
(|| -> _ {
self.0.text_document.as_ref()?.semantic_tokens.as_ref()?.augments_syntax_tokens
})()
.unwrap_or(false)
}
pub fn did_save_text_document_dynamic_registration(&self) -> bool {
let caps = (|| -> _ { self.0.text_document.as_ref()?.synchronization.clone() })()
.unwrap_or_default();
caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
}
pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
(|| -> _ {
self.0.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration
})()
.unwrap_or_default()
}
pub fn did_change_watched_files_relative_pattern_support(&self) -> bool {
(|| -> _ {
self.0.workspace.as_ref()?.did_change_watched_files.as_ref()?.relative_pattern_support
})()
.unwrap_or_default()
}
pub fn location_link(&self) -> bool {
(|| -> _ { self.0.text_document.as_ref()?.definition?.link_support })().unwrap_or_default()
}
pub fn line_folding_only(&self) -> bool {
(|| -> _ { self.0.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only })()
.unwrap_or_default()
}
pub fn hierarchical_symbols(&self) -> bool {
(|| -> _ {
self.0
.text_document
.as_ref()?
.document_symbol
.as_ref()?
.hierarchical_document_symbol_support
})()
.unwrap_or_default()
}
pub fn code_action_literals(&self) -> bool {
(|| -> _ {
self.0
.text_document
.as_ref()?
.code_action
.as_ref()?
.code_action_literal_support
.as_ref()
})()
.is_some()
}
pub fn work_done_progress(&self) -> bool {
(|| -> _ { self.0.window.as_ref()?.work_done_progress })().unwrap_or_default()
}
pub fn will_rename(&self) -> bool {
(|| -> _ { self.0.workspace.as_ref()?.file_operations.as_ref()?.will_rename })()
.unwrap_or_default()
}
pub fn change_annotation_support(&self) -> bool {
(|| -> _ {
self.0.workspace.as_ref()?.workspace_edit.as_ref()?.change_annotation_support.as_ref()
})()
.is_some()
}
pub fn code_action_resolve(&self) -> bool {
(|| -> _ {
Some(
self.0
.text_document
.as_ref()?
.code_action
.as_ref()?
.resolve_support
.as_ref()?
.properties
.as_slice(),
)
})()
.unwrap_or_default()
.iter()
.any(|it| it == "edit")
}
pub fn signature_help_label_offsets(&self) -> bool {
(|| -> _ {
self.0
.text_document
.as_ref()?
.signature_help
.as_ref()?
.signature_information
.as_ref()?
.parameter_information
.as_ref()?
.label_offset_support
})()
.unwrap_or_default()
}
pub fn code_action_group(&self) -> bool {
self.experimental_bool("codeActionGroup")
}
pub fn commands(&self) -> Option<ext::ClientCommandOptions> {
self.experimental("commands")
}
pub fn local_docs(&self) -> bool {
self.experimental_bool("localDocs")
}
pub fn open_server_logs(&self) -> bool {
self.experimental_bool("openServerLogs")
}
pub fn server_status_notification(&self) -> bool {
self.experimental_bool("serverStatusNotification")
}
pub fn snippet_text_edit(&self) -> bool {
self.experimental_bool("snippetTextEdit")
}
pub fn hover_actions(&self) -> bool {
self.experimental_bool("hoverActions")
}
/// Whether the client supports colored output for full diagnostics from `checkOnSave`.
pub fn color_diagnostic_output(&self) -> bool {
self.experimental_bool("colorDiagnosticOutput")
}
pub fn test_explorer(&self) -> bool {
self.experimental_bool("testExplorer")
}
pub fn completion_snippet(&self) -> bool {
(|| -> _ {
self.0
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.snippet_support
})()
.unwrap_or_default()
}
pub fn semantic_tokens_refresh(&self) -> bool {
(|| -> _ { self.0.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support })()
.unwrap_or_default()
}
pub fn code_lens_refresh(&self) -> bool {
(|| -> _ { self.0.workspace.as_ref()?.code_lens.as_ref()?.refresh_support })()
.unwrap_or_default()
}
pub fn inlay_hints_refresh(&self) -> bool {
(|| -> _ { self.0.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support })()
.unwrap_or_default()
}
pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<String> {
self.0
.text_document
.as_ref()
.and_then(|text| text.inlay_hint.as_ref())
.and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
.map(|inlay_resolve| inlay_resolve.properties.iter())
.into_iter()
.flatten()
.cloned()
.collect::<FxHashSet<_>>()
}
pub fn hover_markdown_support(&self) -> bool {
(|| -> _ {
Some(self.0.text_document.as_ref()?.hover.as_ref()?.content_format.as_ref()?.as_slice())
})()
.unwrap_or_default()
.contains(&lsp_types::MarkupKind::Markdown)
}
pub fn insert_replace_support(&self) -> bool {
(|| -> _ {
self.0
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.insert_replace_support
})()
.unwrap_or_default()
}
}
fn more_trigger_character(config: &Config) -> Vec<String> {
let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()];
if config.snippet_cap().is_some() {
res.push("<".to_owned());
}
res
}

View File

@ -1,230 +0,0 @@
//! Advertises the capabilities of the LSP Server.
use ide_db::line_index::WideEncoding;
use lsp_types::{
CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions,
CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
CompletionOptionsCompletionItem, DeclarationCapability, DocumentOnTypeFormattingOptions,
FileOperationFilter, FileOperationPattern, FileOperationPatternKind,
FileOperationRegistrationOptions, FoldingRangeProviderCapability, HoverProviderCapability,
ImplementationProviderCapability, InlayHintOptions, InlayHintServerCapabilities, OneOf,
PositionEncodingKind, RenameOptions, SaveOptions, SelectionRangeProviderCapability,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities,
SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind,
TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions,
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
};
use serde_json::json;
use crate::{
config::{Config, RustfmtConfig},
line_index::PositionEncoding,
lsp::semantic_tokens,
lsp_ext::negotiated_encoding,
};
pub fn server_capabilities(config: &Config) -> ServerCapabilities {
ServerCapabilities {
position_encoding: match negotiated_encoding(config.caps()) {
PositionEncoding::Utf8 => Some(PositionEncodingKind::UTF8),
PositionEncoding::Wide(wide) => match wide {
WideEncoding::Utf16 => Some(PositionEncodingKind::UTF16),
WideEncoding::Utf32 => Some(PositionEncodingKind::UTF32),
_ => None,
},
},
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::INCREMENTAL),
will_save: None,
will_save_wait_until: None,
save: Some(SaveOptions::default().into()),
})),
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
resolve_provider: completions_resolve_provider(config.caps()),
trigger_characters: Some(vec![
":".to_owned(),
".".to_owned(),
"'".to_owned(),
"(".to_owned(),
]),
all_commit_characters: None,
completion_item: completion_item(config),
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
}),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(vec!["(".to_owned(), ",".to_owned(), "<".to_owned()]),
retrigger_characters: None,
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
}),
declaration_provider: Some(DeclarationCapability::Simple(true)),
definition_provider: Some(OneOf::Left(true)),
type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
references_provider: Some(OneOf::Left(true)),
document_highlight_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
workspace_symbol_provider: Some(OneOf::Left(true)),
code_action_provider: Some(code_action_capabilities(config.caps())),
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: match config.rustfmt() {
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
_ => Some(OneOf::Left(false)),
},
document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
first_trigger_character: "=".to_owned(),
more_trigger_character: Some(more_trigger_character(config)),
}),
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
rename_provider: Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
})),
linked_editing_range_provider: None,
document_link_provider: None,
color_provider: None,
execute_command_provider: None,
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: Some(WorkspaceFileOperationsServerCapabilities {
did_create: None,
will_create: None,
did_rename: None,
will_rename: Some(FileOperationRegistrationOptions {
filters: vec![
FileOperationFilter {
scheme: Some(String::from("file")),
pattern: FileOperationPattern {
glob: String::from("**/*.rs"),
matches: Some(FileOperationPatternKind::File),
options: None,
},
},
FileOperationFilter {
scheme: Some(String::from("file")),
pattern: FileOperationPattern {
glob: String::from("**"),
matches: Some(FileOperationPatternKind::Folder),
options: None,
},
},
],
}),
did_delete: None,
will_delete: None,
}),
}),
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
semantic_tokens_provider: Some(
SemanticTokensOptions {
legend: SemanticTokensLegend {
token_types: semantic_tokens::SUPPORTED_TYPES.to_vec(),
token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(),
},
full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
range: Some(true),
work_done_progress_options: Default::default(),
}
.into(),
),
moniker_provider: None,
inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
InlayHintOptions {
work_done_progress_options: Default::default(),
resolve_provider: Some(true),
},
))),
inline_value_provider: None,
experimental: Some(json!({
"externalDocs": true,
"hoverRange": true,
"joinLines": true,
"matchingBrace": true,
"moveItem": true,
"onEnter": true,
"openCargoToml": true,
"parentModule": true,
"runnables": {
"kinds": [ "cargo" ],
},
"ssr": true,
"workspaceSymbolScopeKindFiltering": true,
})),
diagnostic_provider: None,
inline_completion_provider: None,
}
}
fn completions_resolve_provider(client_caps: &ClientCapabilities) -> Option<bool> {
if completion_item_edit_resolve(client_caps) {
Some(true)
} else {
tracing::info!("No `additionalTextEdits` completion resolve capability was found in the client capabilities, autoimport completion is disabled");
None
}
}
/// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
pub(crate) fn completion_item_edit_resolve(caps: &ClientCapabilities) -> bool {
(|| {
Some(
caps.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.resolve_support
.as_ref()?
.properties
.iter()
.any(|cap_string| cap_string.as_str() == "additionalTextEdits"),
)
})() == Some(true)
}
fn completion_item(config: &Config) -> Option<CompletionOptionsCompletionItem> {
Some(CompletionOptionsCompletionItem {
label_details_support: Some(config.completion_label_details_support()),
})
}
fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability {
client_caps
.text_document
.as_ref()
.and_then(|it| it.code_action.as_ref())
.and_then(|it| it.code_action_literal_support.as_ref())
.map_or(CodeActionProviderCapability::Simple(true), |_| {
CodeActionProviderCapability::Options(CodeActionOptions {
// Advertise support for all built-in CodeActionKinds.
// Ideally we would base this off of the client capabilities
// but the client is supposed to fall back gracefully for unknown values.
code_action_kinds: Some(vec![
CodeActionKind::EMPTY,
CodeActionKind::QUICKFIX,
CodeActionKind::REFACTOR,
CodeActionKind::REFACTOR_EXTRACT,
CodeActionKind::REFACTOR_INLINE,
CodeActionKind::REFACTOR_REWRITE,
]),
resolve_provider: Some(true),
work_done_progress_options: Default::default(),
})
})
}
fn more_trigger_character(config: &Config) -> Vec<String> {
let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()];
if config.snippet_cap().is_some() {
res.push("<".to_owned());
}
res
}

View File

@ -19,7 +19,6 @@ use ide_db::{
SnippetCap,
};
use itertools::Itertools;
use lsp_types::{ClientCapabilities, MarkupKind};
use paths::{Utf8Path, Utf8PathBuf};
use project_model::{
CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource,
@ -35,10 +34,9 @@ use triomphe::Arc;
use vfs::{AbsPath, AbsPathBuf, VfsPath};
use crate::{
caps::completion_item_edit_resolve,
capabilities::ClientCapabilities,
diagnostics::DiagnosticsMapConfig,
line_index::PositionEncoding,
lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
lsp_ext::{WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
};
mod patch_old_style;
@ -659,7 +657,7 @@ pub struct Config {
discovered_projects: Vec<ProjectManifest>,
/// The workspace roots as registered by the LSP client
workspace_roots: Vec<AbsPathBuf>,
caps: lsp_types::ClientCapabilities,
caps: ClientCapabilities,
root_path: AbsPathBuf,
snippets: Vec<Snippet>,
visual_studio_code_version: Option<Version>,
@ -698,6 +696,15 @@ pub struct Config {
detached_files: Vec<AbsPathBuf>,
}
// Delegate capability fetching methods
impl std::ops::Deref for Config {
type Target = ClientCapabilities;
fn deref(&self) -> &Self::Target {
&self.caps
}
}
impl Config {
pub fn user_config_path(&self) -> &VfsPath {
&self.user_config_path
@ -954,23 +961,6 @@ impl ConfigChange {
}
}
macro_rules! try_ {
($expr:expr) => {
|| -> _ { Some($expr) }()
};
}
macro_rules! try_or {
($expr:expr, $or:expr) => {
try_!($expr).unwrap_or($or)
};
}
macro_rules! try_or_def {
($expr:expr) => {
try_!($expr).unwrap_or_default()
};
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum LinkedProject {
ProjectManifest(ProjectManifest),
@ -1177,7 +1167,7 @@ impl std::error::Error for ConfigErrors {}
impl Config {
pub fn new(
root_path: AbsPathBuf,
caps: ClientCapabilities,
caps: lsp_types::ClientCapabilities,
workspace_roots: Vec<AbsPathBuf>,
visual_studio_code_version: Option<Version>,
user_config_path: Option<Utf8PathBuf>,
@ -1205,7 +1195,7 @@ impl Config {
};
Config {
caps,
caps: ClientCapabilities::new(caps),
discovered_projects: Vec::new(),
root_path,
snippets: Default::default(),
@ -1266,7 +1256,7 @@ impl Config {
&self.root_ratoml_path
}
pub fn caps(&self) -> &lsp_types::ClientCapabilities {
pub fn caps(&self) -> &ClientCapabilities {
&self.caps
}
}
@ -1289,7 +1279,7 @@ impl Config {
CompletionConfig {
enable_postfix_completions: self.completion_postfix_enable().to_owned(),
enable_imports_on_the_fly: self.completion_autoimport_enable().to_owned()
&& completion_item_edit_resolve(&self.caps),
&& self.caps.completion_item_edit_resolve(),
enable_self_on_the_fly: self.completion_autoself_enable().to_owned(),
enable_private_editable: self.completion_privateEditable_enable().to_owned(),
full_function_signatures: self.completion_fullFunctionSignatures_enable().to_owned(),
@ -1298,16 +1288,7 @@ impl Config {
CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
CallableCompletionDef::None => None,
},
snippet_cap: SnippetCap::new(try_or_def!(
self.caps
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.snippet_support?
)),
snippet_cap: SnippetCap::new(self.completion_snippet()),
insert_use: self.insert_use_config(source_root),
prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
@ -1359,7 +1340,7 @@ impl Config {
}
pub fn hover_actions(&self) -> HoverActionsConfig {
let enable = self.experimental("hoverActions") && self.hover_actions_enable().to_owned();
let enable = self.caps.hover_actions() && self.hover_actions_enable().to_owned();
HoverActionsConfig {
implementations: enable && self.hover_actions_implementations_enable().to_owned(),
references: enable && self.hover_actions_references_enable().to_owned(),
@ -1385,17 +1366,7 @@ impl Config {
}),
documentation: self.hover_documentation_enable().to_owned(),
format: {
let is_markdown = try_or_def!(self
.caps
.text_document
.as_ref()?
.hover
.as_ref()?
.content_format
.as_ref()?
.as_slice())
.contains(&MarkupKind::Markdown);
if is_markdown {
if self.caps.hover_markdown_support() {
HoverDocFormat::Markdown
} else {
HoverDocFormat::PlainText
@ -1409,17 +1380,7 @@ impl Config {
}
pub fn inlay_hints(&self) -> InlayHintsConfig {
let client_capability_fields = self
.caps
.text_document
.as_ref()
.and_then(|text| text.inlay_hint.as_ref())
.and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
.map(|inlay_resolve| inlay_resolve.properties.iter())
.into_iter()
.flatten()
.cloned()
.collect::<FxHashSet<_>>();
let client_capability_fields = self.inlay_hint_resolve_support_properties();
InlayHintsConfig {
render_colons: self.inlayHints_renderColons().to_owned(),
@ -1590,165 +1551,10 @@ impl Config {
}
}
pub fn did_save_text_document_dynamic_registration(&self) -> bool {
let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
}
pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
try_or_def!(
self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
)
}
pub fn did_change_watched_files_relative_pattern_support(&self) -> bool {
try_or_def!(
self.caps
.workspace
.as_ref()?
.did_change_watched_files
.as_ref()?
.relative_pattern_support?
)
}
pub fn prefill_caches(&self) -> bool {
self.cachePriming_enable().to_owned()
}
pub fn location_link(&self) -> bool {
try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
}
pub fn line_folding_only(&self) -> bool {
try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
}
pub fn hierarchical_symbols(&self) -> bool {
try_or_def!(
self.caps
.text_document
.as_ref()?
.document_symbol
.as_ref()?
.hierarchical_document_symbol_support?
)
}
pub fn code_action_literals(&self) -> bool {
try_!(self
.caps
.text_document
.as_ref()?
.code_action
.as_ref()?
.code_action_literal_support
.as_ref()?)
.is_some()
}
pub fn work_done_progress(&self) -> bool {
try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
}
pub fn will_rename(&self) -> bool {
try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
}
pub fn change_annotation_support(&self) -> bool {
try_!(self
.caps
.workspace
.as_ref()?
.workspace_edit
.as_ref()?
.change_annotation_support
.as_ref()?)
.is_some()
}
pub fn code_action_resolve(&self) -> bool {
try_or_def!(self
.caps
.text_document
.as_ref()?
.code_action
.as_ref()?
.resolve_support
.as_ref()?
.properties
.as_slice())
.iter()
.any(|it| it == "edit")
}
pub fn signature_help_label_offsets(&self) -> bool {
try_or_def!(
self.caps
.text_document
.as_ref()?
.signature_help
.as_ref()?
.signature_information
.as_ref()?
.parameter_information
.as_ref()?
.label_offset_support?
)
}
pub fn completion_label_details_support(&self) -> bool {
try_!(self
.caps
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.label_details_support
.as_ref()?)
.is_some()
}
pub fn semantics_tokens_augments_syntax_tokens(&self) -> bool {
try_!(self.caps.text_document.as_ref()?.semantic_tokens.as_ref()?.augments_syntax_tokens?)
.unwrap_or(false)
}
pub fn position_encoding(&self) -> PositionEncoding {
negotiated_encoding(&self.caps)
}
fn experimental(&self, index: &'static str) -> bool {
try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
}
pub fn code_action_group(&self) -> bool {
self.experimental("codeActionGroup")
}
pub fn local_docs(&self) -> bool {
self.experimental("localDocs")
}
pub fn open_server_logs(&self) -> bool {
self.experimental("openServerLogs")
}
pub fn server_status_notification(&self) -> bool {
self.experimental("serverStatusNotification")
}
/// Whether the client supports colored output for full diagnostics from `checkOnSave`.
pub fn color_diagnostic_output(&self) -> bool {
self.experimental("colorDiagnosticOutput")
}
pub fn test_explorer(&self) -> bool {
self.experimental("testExplorer")
}
pub fn publish_diagnostics(&self) -> bool {
self.diagnostics_enable().to_owned()
}
@ -2026,7 +1832,7 @@ impl Config {
pub fn snippet_cap(&self) -> Option<SnippetCap> {
// FIXME: Also detect the proposed lsp version at caps.workspace.workspaceEdit.snippetEditSupport
// once lsp-types has it.
SnippetCap::new(self.experimental("snippetTextEdit"))
SnippetCap::new(self.snippet_text_edit())
}
pub fn call_info(&self) -> CallInfoConfig {
@ -2066,36 +1872,8 @@ impl Config {
}
}
pub fn semantic_tokens_refresh(&self) -> bool {
try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
}
pub fn code_lens_refresh(&self) -> bool {
try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
}
pub fn inlay_hints_refresh(&self) -> bool {
try_or_def!(self.caps.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support?)
}
pub fn insert_replace_support(&self) -> bool {
try_or_def!(
self.caps
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.insert_replace_support?
)
}
pub fn client_commands(&self) -> ClientCommandsConfig {
let commands =
try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
let commands: Option<lsp_ext::ClientCommandOptions> =
serde_json::from_value(commands.clone()).ok();
let commands = self.commands();
let force = commands.is_none() && *self.lens_forceCustomCommands();
let commands = commands.map(|it| it.commands).unwrap_or_default();

View File

@ -66,7 +66,7 @@ fn location(
let uri = url_from_abs_path(&file_name);
let range = {
let position_encoding = snap.config.position_encoding();
let position_encoding = snap.config.negotiated_encoding();
lsp_types::Range::new(
position(
&position_encoding,

View File

@ -529,7 +529,7 @@ impl GlobalStateSnapshot {
pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> {
let endings = self.vfs.read().1[&file_id];
let index = self.analysis.file_line_index(file_id)?;
let res = LineIndex { index, endings, encoding: self.config.position_encoding() };
let res = LineIndex { index, endings, encoding: self.config.caps().negotiated_encoding() };
Ok(res)
}

View File

@ -100,7 +100,7 @@ pub(crate) fn handle_did_change_text_document(
*version = params.text_document.version;
let new_contents = apply_document_changes(
state.config.position_encoding(),
state.config.negotiated_encoding(),
std::str::from_utf8(data).unwrap(),
params.content_changes,
)

View File

@ -2294,19 +2294,8 @@ fn to_url(path: VfsPath) -> Option<Url> {
}
fn resource_ops_supported(config: &Config, kind: ResourceOperationKind) -> anyhow::Result<()> {
#[rustfmt::skip]
let resops = (|| {
config
.caps()
.workspace
.as_ref()?
.workspace_edit
.as_ref()?
.resource_operations
.as_ref()
})();
if !matches!(resops, Some(resops) if resops.contains(&kind)) {
if !matches!(config.workspace_edit_resource_operations(), Some(resops) if resops.contains(&kind))
{
return Err(LspError::new(
ErrorCode::RequestFailed as i32,
format!(

View File

@ -11,7 +11,7 @@
pub mod cli;
mod caps;
mod capabilities;
mod diagnostics;
mod diff;
mod dispatch;
@ -47,7 +47,8 @@ mod integrated_benchmarks;
use serde::de::DeserializeOwned;
pub use crate::{
caps::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph, version::version,
capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph,
version::version,
};
pub fn from_json<T: DeserializeOwned>(

View File

@ -4,19 +4,16 @@
use std::ops;
use ide_db::line_index::WideEncoding;
use lsp_types::request::Request;
use lsp_types::Url;
use lsp_types::{
notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams,
PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
};
use lsp_types::{PositionEncodingKind, Url};
use paths::Utf8PathBuf;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use crate::line_index::PositionEncoding;
pub enum InternalTestingFetchConfig {}
impl Request for InternalTestingFetchConfig {
@ -737,24 +734,6 @@ pub enum CodeLensResolveDataKind {
References(lsp_types::TextDocumentPositionParams),
}
pub fn negotiated_encoding(caps: &lsp_types::ClientCapabilities) -> PositionEncoding {
let client_encodings = match &caps.general {
Some(general) => general.position_encodings.as_deref().unwrap_or_default(),
None => &[],
};
for enc in client_encodings {
if enc == &PositionEncodingKind::UTF8 {
return PositionEncoding::Utf8;
} else if enc == &PositionEncodingKind::UTF32 {
return PositionEncoding::Wide(WideEncoding::Utf32);
}
// NB: intentionally prefer just about anything else to utf-16.
}
PositionEncoding::Wide(WideEncoding::Utf16)
}
pub enum MoveItem {}
impl Request for MoveItem {

View File

@ -1,5 +1,5 @@
<!---
lsp/ext.rs hash: a0867710490bf8da
lsp/ext.rs hash: 39b47906286ad9c
If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: