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, SnippetCap,
}; };
use itertools::Itertools; use itertools::Itertools;
use lsp_types::{ClientCapabilities, MarkupKind};
use paths::{Utf8Path, Utf8PathBuf}; use paths::{Utf8Path, Utf8PathBuf};
use project_model::{ use project_model::{
CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource, CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource,
@ -35,10 +34,9 @@ use triomphe::Arc;
use vfs::{AbsPath, AbsPathBuf, VfsPath}; use vfs::{AbsPath, AbsPathBuf, VfsPath};
use crate::{ use crate::{
caps::completion_item_edit_resolve, capabilities::ClientCapabilities,
diagnostics::DiagnosticsMapConfig, diagnostics::DiagnosticsMapConfig,
line_index::PositionEncoding, lsp_ext::{WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
}; };
mod patch_old_style; mod patch_old_style;
@ -659,7 +657,7 @@ pub struct Config {
discovered_projects: Vec<ProjectManifest>, discovered_projects: Vec<ProjectManifest>,
/// The workspace roots as registered by the LSP client /// The workspace roots as registered by the LSP client
workspace_roots: Vec<AbsPathBuf>, workspace_roots: Vec<AbsPathBuf>,
caps: lsp_types::ClientCapabilities, caps: ClientCapabilities,
root_path: AbsPathBuf, root_path: AbsPathBuf,
snippets: Vec<Snippet>, snippets: Vec<Snippet>,
visual_studio_code_version: Option<Version>, visual_studio_code_version: Option<Version>,
@ -698,6 +696,15 @@ pub struct Config {
detached_files: Vec<AbsPathBuf>, 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 { impl Config {
pub fn user_config_path(&self) -> &VfsPath { pub fn user_config_path(&self) -> &VfsPath {
&self.user_config_path &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)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum LinkedProject { pub enum LinkedProject {
ProjectManifest(ProjectManifest), ProjectManifest(ProjectManifest),
@ -1177,7 +1167,7 @@ impl std::error::Error for ConfigErrors {}
impl Config { impl Config {
pub fn new( pub fn new(
root_path: AbsPathBuf, root_path: AbsPathBuf,
caps: ClientCapabilities, caps: lsp_types::ClientCapabilities,
workspace_roots: Vec<AbsPathBuf>, workspace_roots: Vec<AbsPathBuf>,
visual_studio_code_version: Option<Version>, visual_studio_code_version: Option<Version>,
user_config_path: Option<Utf8PathBuf>, user_config_path: Option<Utf8PathBuf>,
@ -1205,7 +1195,7 @@ impl Config {
}; };
Config { Config {
caps, caps: ClientCapabilities::new(caps),
discovered_projects: Vec::new(), discovered_projects: Vec::new(),
root_path, root_path,
snippets: Default::default(), snippets: Default::default(),
@ -1266,7 +1256,7 @@ impl Config {
&self.root_ratoml_path &self.root_ratoml_path
} }
pub fn caps(&self) -> &lsp_types::ClientCapabilities { pub fn caps(&self) -> &ClientCapabilities {
&self.caps &self.caps
} }
} }
@ -1289,7 +1279,7 @@ impl Config {
CompletionConfig { CompletionConfig {
enable_postfix_completions: self.completion_postfix_enable().to_owned(), enable_postfix_completions: self.completion_postfix_enable().to_owned(),
enable_imports_on_the_fly: self.completion_autoimport_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_self_on_the_fly: self.completion_autoself_enable().to_owned(),
enable_private_editable: self.completion_privateEditable_enable().to_owned(), enable_private_editable: self.completion_privateEditable_enable().to_owned(),
full_function_signatures: self.completion_fullFunctionSignatures_enable().to_owned(), full_function_signatures: self.completion_fullFunctionSignatures_enable().to_owned(),
@ -1298,16 +1288,7 @@ impl Config {
CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses), CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
CallableCompletionDef::None => None, CallableCompletionDef::None => None,
}, },
snippet_cap: SnippetCap::new(try_or_def!( snippet_cap: SnippetCap::new(self.completion_snippet()),
self.caps
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.snippet_support?
)),
insert_use: self.insert_use_config(source_root), insert_use: self.insert_use_config(source_root),
prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
prefer_prelude: self.imports_preferPrelude(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 { 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 { HoverActionsConfig {
implementations: enable && self.hover_actions_implementations_enable().to_owned(), implementations: enable && self.hover_actions_implementations_enable().to_owned(),
references: enable && self.hover_actions_references_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(), documentation: self.hover_documentation_enable().to_owned(),
format: { format: {
let is_markdown = try_or_def!(self if self.caps.hover_markdown_support() {
.caps
.text_document
.as_ref()?
.hover
.as_ref()?
.content_format
.as_ref()?
.as_slice())
.contains(&MarkupKind::Markdown);
if is_markdown {
HoverDocFormat::Markdown HoverDocFormat::Markdown
} else { } else {
HoverDocFormat::PlainText HoverDocFormat::PlainText
@ -1409,17 +1380,7 @@ impl Config {
} }
pub fn inlay_hints(&self) -> InlayHintsConfig { pub fn inlay_hints(&self) -> InlayHintsConfig {
let client_capability_fields = self let client_capability_fields = self.inlay_hint_resolve_support_properties();
.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<_>>();
InlayHintsConfig { InlayHintsConfig {
render_colons: self.inlayHints_renderColons().to_owned(), 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 { pub fn prefill_caches(&self) -> bool {
self.cachePriming_enable().to_owned() 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 { pub fn publish_diagnostics(&self) -> bool {
self.diagnostics_enable().to_owned() self.diagnostics_enable().to_owned()
} }
@ -2026,7 +1832,7 @@ impl Config {
pub fn snippet_cap(&self) -> Option<SnippetCap> { pub fn snippet_cap(&self) -> Option<SnippetCap> {
// FIXME: Also detect the proposed lsp version at caps.workspace.workspaceEdit.snippetEditSupport // FIXME: Also detect the proposed lsp version at caps.workspace.workspaceEdit.snippetEditSupport
// once lsp-types has it. // once lsp-types has it.
SnippetCap::new(self.experimental("snippetTextEdit")) SnippetCap::new(self.snippet_text_edit())
} }
pub fn call_info(&self) -> CallInfoConfig { 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 { pub fn client_commands(&self) -> ClientCommandsConfig {
let commands = let commands = self.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 force = commands.is_none() && *self.lens_forceCustomCommands(); let force = commands.is_none() && *self.lens_forceCustomCommands();
let commands = commands.map(|it| it.commands).unwrap_or_default(); 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 uri = url_from_abs_path(&file_name);
let range = { let range = {
let position_encoding = snap.config.position_encoding(); let position_encoding = snap.config.negotiated_encoding();
lsp_types::Range::new( lsp_types::Range::new(
position( position(
&position_encoding, &position_encoding,

View file

@ -529,7 +529,7 @@ impl GlobalStateSnapshot {
pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> { pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> {
let endings = self.vfs.read().1[&file_id]; let endings = self.vfs.read().1[&file_id];
let index = self.analysis.file_line_index(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) Ok(res)
} }

View file

@ -100,7 +100,7 @@ pub(crate) fn handle_did_change_text_document(
*version = params.text_document.version; *version = params.text_document.version;
let new_contents = apply_document_changes( let new_contents = apply_document_changes(
state.config.position_encoding(), state.config.negotiated_encoding(),
std::str::from_utf8(data).unwrap(), std::str::from_utf8(data).unwrap(),
params.content_changes, 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<()> { fn resource_ops_supported(config: &Config, kind: ResourceOperationKind) -> anyhow::Result<()> {
#[rustfmt::skip] if !matches!(config.workspace_edit_resource_operations(), Some(resops) if resops.contains(&kind))
let resops = (|| { {
config
.caps()
.workspace
.as_ref()?
.workspace_edit
.as_ref()?
.resource_operations
.as_ref()
})();
if !matches!(resops, Some(resops) if resops.contains(&kind)) {
return Err(LspError::new( return Err(LspError::new(
ErrorCode::RequestFailed as i32, ErrorCode::RequestFailed as i32,
format!( format!(

View file

@ -11,7 +11,7 @@
pub mod cli; pub mod cli;
mod caps; mod capabilities;
mod diagnostics; mod diagnostics;
mod diff; mod diff;
mod dispatch; mod dispatch;
@ -47,7 +47,8 @@ mod integrated_benchmarks;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
pub use crate::{ 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>( pub fn from_json<T: DeserializeOwned>(

View file

@ -4,19 +4,16 @@
use std::ops; use std::ops;
use ide_db::line_index::WideEncoding;
use lsp_types::request::Request; use lsp_types::request::Request;
use lsp_types::Url;
use lsp_types::{ use lsp_types::{
notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams, notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams,
PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams, PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
}; };
use lsp_types::{PositionEncodingKind, Url};
use paths::Utf8PathBuf; use paths::Utf8PathBuf;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::line_index::PositionEncoding;
pub enum InternalTestingFetchConfig {} pub enum InternalTestingFetchConfig {}
impl Request for InternalTestingFetchConfig { impl Request for InternalTestingFetchConfig {
@ -737,24 +734,6 @@ pub enum CodeLensResolveDataKind {
References(lsp_types::TextDocumentPositionParams), 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 {} pub enum MoveItem {}
impl Request for 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 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: need to adjust this doc as well and ping this issue: