From e219ac64c06fd119c536701bb082b8611f120d72 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 10 Aug 2024 16:46:53 +0200 Subject: Move some stuff --- .../crates/rust-analyzer/src/capabilities.rs | 493 --------------------- .../crates/rust-analyzer/src/config.rs | 2 +- .../rust-analyzer/crates/rust-analyzer/src/diff.rs | 53 --- .../crates/rust-analyzer/src/dispatch.rs | 334 -------------- .../crates/rust-analyzer/src/handlers/dispatch.rs | 334 ++++++++++++++ .../crates/rust-analyzer/src/handlers/request.rs | 45 +- .../rust-analyzer/crates/rust-analyzer/src/lib.rs | 6 +- .../rust-analyzer/crates/rust-analyzer/src/lsp.rs | 2 + .../crates/rust-analyzer/src/lsp/capabilities.rs | 493 +++++++++++++++++++++ .../crates/rust-analyzer/src/main_loop.rs | 2 +- 10 files changed, 877 insertions(+), 887 deletions(-) delete mode 100644 src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs delete mode 100644 src/tools/rust-analyzer/crates/rust-analyzer/src/diff.rs delete mode 100644 src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs create mode 100644 src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs create mode 100644 src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs deleted file mode 100644 index 9610808c27e..00000000000 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs +++ /dev/null @@ -1,493 +0,0 @@ -//! 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(None) { - 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 { - 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(&self, index: &'static str) -> Option { - 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 { - 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 { - 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 { - 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::>() - } - - 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 { - let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()]; - if config.snippet_cap().is_some() { - res.push("<".to_owned()); - } - res -} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 486046c47c7..02f5d75136e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -34,9 +34,9 @@ use triomphe::Arc; use vfs::{AbsPath, AbsPathBuf, VfsPath}; use crate::{ - capabilities::ClientCapabilities, diagnostics::DiagnosticsMapConfig, flycheck::{CargoOptions, FlycheckConfig}, + lsp::capabilities::ClientCapabilities, lsp_ext::{WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope}, }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diff.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diff.rs deleted file mode 100644 index 3fcfb4a1b08..00000000000 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diff.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Generate minimal `TextEdit`s from different text versions -use dissimilar::Chunk; -use ide::{TextEdit, TextRange, TextSize}; - -pub(crate) fn diff(left: &str, right: &str) -> TextEdit { - let chunks = dissimilar::diff(left, right); - textedit_from_chunks(chunks) -} - -fn textedit_from_chunks(chunks: Vec>) -> TextEdit { - let mut builder = TextEdit::builder(); - let mut pos = TextSize::default(); - - let mut chunks = chunks.into_iter().peekable(); - while let Some(chunk) = chunks.next() { - if let (Chunk::Delete(deleted), Some(&Chunk::Insert(inserted))) = (chunk, chunks.peek()) { - chunks.next().unwrap(); - let deleted_len = TextSize::of(deleted); - builder.replace(TextRange::at(pos, deleted_len), inserted.into()); - pos += deleted_len; - continue; - } - - match chunk { - Chunk::Equal(text) => { - pos += TextSize::of(text); - } - Chunk::Delete(deleted) => { - let deleted_len = TextSize::of(deleted); - builder.delete(TextRange::at(pos, deleted_len)); - pos += deleted_len; - } - Chunk::Insert(inserted) => { - builder.insert(pos, inserted.into()); - } - } - } - builder.finish() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn diff_applies() { - let mut original = String::from("fn foo(a:u32){\n}"); - let result = "fn foo(a: u32) {}"; - let edit = diff(&original, result); - edit.apply(&mut original); - assert_eq!(original, result); - } -} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs deleted file mode 100644 index ebdc196a658..00000000000 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! See [RequestDispatcher]. -use std::{ - fmt::{self, Debug}, - panic, thread, -}; - -use ide::Cancelled; -use lsp_server::ExtractError; -use serde::{de::DeserializeOwned, Serialize}; -use stdx::thread::ThreadIntent; - -use crate::{ - global_state::{GlobalState, GlobalStateSnapshot}, - lsp::LspError, - main_loop::Task, - version::version, -}; - -/// A visitor for routing a raw JSON request to an appropriate handler function. -/// -/// Most requests are read-only and async and are handled on the threadpool -/// (`on` method). -/// -/// Some read-only requests are latency sensitive, and are immediately handled -/// on the main loop thread (`on_sync`). These are typically typing-related -/// requests. -/// -/// Some requests modify the state, and are run on the main thread to get -/// `&mut` (`on_sync_mut`). -/// -/// Read-only requests are wrapped into `catch_unwind` -- they don't modify the -/// state, so it's OK to recover from their failures. -pub(crate) struct RequestDispatcher<'a> { - pub(crate) req: Option, - pub(crate) global_state: &'a mut GlobalState, -} - -impl RequestDispatcher<'_> { - /// Dispatches the request onto the current thread, given full access to - /// mutable global state. Unlike all other methods here, this one isn't - /// guarded by `catch_unwind`, so, please, don't make bugs :-) - pub(crate) fn on_sync_mut( - &mut self, - f: fn(&mut GlobalState, R::Params) -> anyhow::Result, - ) -> &mut Self - where - R: lsp_types::request::Request, - R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug, - R::Result: Serialize, - { - let (req, params, panic_context) = match self.parse::() { - Some(it) => it, - None => return self, - }; - let _guard = - tracing::info_span!("request", method = ?req.method, "request_id" = ?req.id).entered(); - tracing::debug!(?params); - let result = { - let _pctx = stdx::panic_context::enter(panic_context); - f(self.global_state, params) - }; - if let Ok(response) = result_to_response::(req.id, result) { - self.global_state.respond(response); - } - - self - } - - /// Dispatches the request onto the current thread. - pub(crate) fn on_sync( - &mut self, - f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, - ) -> &mut Self - where - R: lsp_types::request::Request, - R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug, - R::Result: Serialize, - { - let (req, params, panic_context) = match self.parse::() { - Some(it) => it, - None => return self, - }; - let _guard = - tracing::info_span!("request", method = ?req.method, "request_id" = ?req.id).entered(); - tracing::debug!(?params); - let global_state_snapshot = self.global_state.snapshot(); - - let result = panic::catch_unwind(move || { - let _pctx = stdx::panic_context::enter(panic_context); - f(global_state_snapshot, params) - }); - - if let Ok(response) = thread_result_to_response::(req.id, result) { - self.global_state.respond(response); - } - - self - } - - /// Dispatches a non-latency-sensitive request onto the thread pool. - pub(crate) fn on( - &mut self, - f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, - ) -> &mut Self - where - R: lsp_types::request::Request + 'static, - R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, - R::Result: Serialize, - { - self.on_with_thread_intent::(ThreadIntent::Worker, f) - } - - /// Dispatches a latency-sensitive request onto the thread pool. - pub(crate) fn on_latency_sensitive( - &mut self, - f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, - ) -> &mut Self - where - R: lsp_types::request::Request + 'static, - R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, - R::Result: Serialize, - { - self.on_with_thread_intent::(ThreadIntent::LatencySensitive, f) - } - - /// Formatting requests should never block on waiting a for task thread to open up, editors will wait - /// on the response and a late formatting update might mess with the document and user. - /// We can't run this on the main thread though as we invoke rustfmt which may take arbitrary time to complete! - pub(crate) fn on_fmt_thread( - &mut self, - f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, - ) -> &mut Self - where - R: lsp_types::request::Request + 'static, - R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, - R::Result: Serialize, - { - self.on_with_thread_intent::(ThreadIntent::LatencySensitive, f) - } - - pub(crate) fn finish(&mut self) { - if let Some(req) = self.req.take() { - tracing::error!("unknown request: {:?}", req); - let response = lsp_server::Response::new_err( - req.id, - lsp_server::ErrorCode::MethodNotFound as i32, - "unknown request".to_owned(), - ); - self.global_state.respond(response); - } - } - - fn on_with_thread_intent( - &mut self, - intent: ThreadIntent, - f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, - ) -> &mut Self - where - R: lsp_types::request::Request + 'static, - R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, - R::Result: Serialize, - { - let (req, params, panic_context) = match self.parse::() { - Some(it) => it, - None => return self, - }; - let _guard = - tracing::info_span!("request", method = ?req.method, "request_id" = ?req.id).entered(); - tracing::debug!(?params); - - let world = self.global_state.snapshot(); - if MAIN_POOL { - &mut self.global_state.task_pool.handle - } else { - &mut self.global_state.fmt_pool.handle - } - .spawn(intent, move || { - let result = panic::catch_unwind(move || { - let _pctx = stdx::panic_context::enter(panic_context); - f(world, params) - }); - match thread_result_to_response::(req.id.clone(), result) { - Ok(response) => Task::Response(response), - Err(_cancelled) if ALLOW_RETRYING => Task::Retry(req), - Err(_cancelled) => Task::Response(lsp_server::Response::new_err( - req.id, - lsp_server::ErrorCode::ContentModified as i32, - "content modified".to_owned(), - )), - } - }); - - self - } - - fn parse(&mut self) -> Option<(lsp_server::Request, R::Params, String)> - where - R: lsp_types::request::Request, - R::Params: DeserializeOwned + fmt::Debug, - { - let req = match &self.req { - Some(req) if req.method == R::METHOD => self.req.take()?, - _ => return None, - }; - - let res = crate::from_json(R::METHOD, &req.params); - match res { - Ok(params) => { - let panic_context = - format!("\nversion: {}\nrequest: {} {params:#?}", version(), R::METHOD); - Some((req, params, panic_context)) - } - Err(err) => { - let response = lsp_server::Response::new_err( - req.id, - lsp_server::ErrorCode::InvalidParams as i32, - err.to_string(), - ); - self.global_state.respond(response); - None - } - } - } -} - -fn thread_result_to_response( - id: lsp_server::RequestId, - result: thread::Result>, -) -> Result -where - R: lsp_types::request::Request, - R::Params: DeserializeOwned, - R::Result: Serialize, -{ - match result { - Ok(result) => result_to_response::(id, result), - Err(panic) => { - let panic_message = panic - .downcast_ref::() - .map(String::as_str) - .or_else(|| panic.downcast_ref::<&str>().copied()); - - let mut message = "request handler panicked".to_owned(); - if let Some(panic_message) = panic_message { - message.push_str(": "); - message.push_str(panic_message) - }; - - Ok(lsp_server::Response::new_err( - id, - lsp_server::ErrorCode::InternalError as i32, - message, - )) - } - } -} - -fn result_to_response( - id: lsp_server::RequestId, - result: anyhow::Result, -) -> Result -where - R: lsp_types::request::Request, - R::Params: DeserializeOwned, - R::Result: Serialize, -{ - let res = match result { - Ok(resp) => lsp_server::Response::new_ok(id, &resp), - Err(e) => match e.downcast::() { - Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message), - Err(e) => match e.downcast::() { - Ok(cancelled) => return Err(cancelled), - Err(e) => lsp_server::Response::new_err( - id, - lsp_server::ErrorCode::InternalError as i32, - e.to_string(), - ), - }, - }, - }; - Ok(res) -} - -pub(crate) struct NotificationDispatcher<'a> { - pub(crate) not: Option, - pub(crate) global_state: &'a mut GlobalState, -} - -impl NotificationDispatcher<'_> { - pub(crate) fn on_sync_mut( - &mut self, - f: fn(&mut GlobalState, N::Params) -> anyhow::Result<()>, - ) -> anyhow::Result<&mut Self> - where - N: lsp_types::notification::Notification, - N::Params: DeserializeOwned + Send + Debug, - { - let not = match self.not.take() { - Some(it) => it, - None => return Ok(self), - }; - - let _guard = tracing::info_span!("notification", method = ?not.method).entered(); - - let params = match not.extract::(N::METHOD) { - Ok(it) => it, - Err(ExtractError::JsonError { method, error }) => { - panic!("Invalid request\nMethod: {method}\n error: {error}",) - } - Err(ExtractError::MethodMismatch(not)) => { - self.not = Some(not); - return Ok(self); - } - }; - - tracing::debug!(?params); - - let _pctx = stdx::panic_context::enter(format!( - "\nversion: {}\nnotification: {}", - version(), - N::METHOD - )); - f(self.global_state, params)?; - Ok(self) - } - - pub(crate) fn finish(&mut self) { - if let Some(not) = &self.not { - if !not.method.starts_with("$/") { - tracing::error!("unhandled notification: {:?}", not); - } - } - } -} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs new file mode 100644 index 00000000000..ebdc196a658 --- /dev/null +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs @@ -0,0 +1,334 @@ +//! See [RequestDispatcher]. +use std::{ + fmt::{self, Debug}, + panic, thread, +}; + +use ide::Cancelled; +use lsp_server::ExtractError; +use serde::{de::DeserializeOwned, Serialize}; +use stdx::thread::ThreadIntent; + +use crate::{ + global_state::{GlobalState, GlobalStateSnapshot}, + lsp::LspError, + main_loop::Task, + version::version, +}; + +/// A visitor for routing a raw JSON request to an appropriate handler function. +/// +/// Most requests are read-only and async and are handled on the threadpool +/// (`on` method). +/// +/// Some read-only requests are latency sensitive, and are immediately handled +/// on the main loop thread (`on_sync`). These are typically typing-related +/// requests. +/// +/// Some requests modify the state, and are run on the main thread to get +/// `&mut` (`on_sync_mut`). +/// +/// Read-only requests are wrapped into `catch_unwind` -- they don't modify the +/// state, so it's OK to recover from their failures. +pub(crate) struct RequestDispatcher<'a> { + pub(crate) req: Option, + pub(crate) global_state: &'a mut GlobalState, +} + +impl RequestDispatcher<'_> { + /// Dispatches the request onto the current thread, given full access to + /// mutable global state. Unlike all other methods here, this one isn't + /// guarded by `catch_unwind`, so, please, don't make bugs :-) + pub(crate) fn on_sync_mut( + &mut self, + f: fn(&mut GlobalState, R::Params) -> anyhow::Result, + ) -> &mut Self + where + R: lsp_types::request::Request, + R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug, + R::Result: Serialize, + { + let (req, params, panic_context) = match self.parse::() { + Some(it) => it, + None => return self, + }; + let _guard = + tracing::info_span!("request", method = ?req.method, "request_id" = ?req.id).entered(); + tracing::debug!(?params); + let result = { + let _pctx = stdx::panic_context::enter(panic_context); + f(self.global_state, params) + }; + if let Ok(response) = result_to_response::(req.id, result) { + self.global_state.respond(response); + } + + self + } + + /// Dispatches the request onto the current thread. + pub(crate) fn on_sync( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, + ) -> &mut Self + where + R: lsp_types::request::Request, + R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug, + R::Result: Serialize, + { + let (req, params, panic_context) = match self.parse::() { + Some(it) => it, + None => return self, + }; + let _guard = + tracing::info_span!("request", method = ?req.method, "request_id" = ?req.id).entered(); + tracing::debug!(?params); + let global_state_snapshot = self.global_state.snapshot(); + + let result = panic::catch_unwind(move || { + let _pctx = stdx::panic_context::enter(panic_context); + f(global_state_snapshot, params) + }); + + if let Ok(response) = thread_result_to_response::(req.id, result) { + self.global_state.respond(response); + } + + self + } + + /// Dispatches a non-latency-sensitive request onto the thread pool. + pub(crate) fn on( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, + ) -> &mut Self + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + R::Result: Serialize, + { + self.on_with_thread_intent::(ThreadIntent::Worker, f) + } + + /// Dispatches a latency-sensitive request onto the thread pool. + pub(crate) fn on_latency_sensitive( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, + ) -> &mut Self + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + R::Result: Serialize, + { + self.on_with_thread_intent::(ThreadIntent::LatencySensitive, f) + } + + /// Formatting requests should never block on waiting a for task thread to open up, editors will wait + /// on the response and a late formatting update might mess with the document and user. + /// We can't run this on the main thread though as we invoke rustfmt which may take arbitrary time to complete! + pub(crate) fn on_fmt_thread( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, + ) -> &mut Self + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + R::Result: Serialize, + { + self.on_with_thread_intent::(ThreadIntent::LatencySensitive, f) + } + + pub(crate) fn finish(&mut self) { + if let Some(req) = self.req.take() { + tracing::error!("unknown request: {:?}", req); + let response = lsp_server::Response::new_err( + req.id, + lsp_server::ErrorCode::MethodNotFound as i32, + "unknown request".to_owned(), + ); + self.global_state.respond(response); + } + } + + fn on_with_thread_intent( + &mut self, + intent: ThreadIntent, + f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, + ) -> &mut Self + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + R::Result: Serialize, + { + let (req, params, panic_context) = match self.parse::() { + Some(it) => it, + None => return self, + }; + let _guard = + tracing::info_span!("request", method = ?req.method, "request_id" = ?req.id).entered(); + tracing::debug!(?params); + + let world = self.global_state.snapshot(); + if MAIN_POOL { + &mut self.global_state.task_pool.handle + } else { + &mut self.global_state.fmt_pool.handle + } + .spawn(intent, move || { + let result = panic::catch_unwind(move || { + let _pctx = stdx::panic_context::enter(panic_context); + f(world, params) + }); + match thread_result_to_response::(req.id.clone(), result) { + Ok(response) => Task::Response(response), + Err(_cancelled) if ALLOW_RETRYING => Task::Retry(req), + Err(_cancelled) => Task::Response(lsp_server::Response::new_err( + req.id, + lsp_server::ErrorCode::ContentModified as i32, + "content modified".to_owned(), + )), + } + }); + + self + } + + fn parse(&mut self) -> Option<(lsp_server::Request, R::Params, String)> + where + R: lsp_types::request::Request, + R::Params: DeserializeOwned + fmt::Debug, + { + let req = match &self.req { + Some(req) if req.method == R::METHOD => self.req.take()?, + _ => return None, + }; + + let res = crate::from_json(R::METHOD, &req.params); + match res { + Ok(params) => { + let panic_context = + format!("\nversion: {}\nrequest: {} {params:#?}", version(), R::METHOD); + Some((req, params, panic_context)) + } + Err(err) => { + let response = lsp_server::Response::new_err( + req.id, + lsp_server::ErrorCode::InvalidParams as i32, + err.to_string(), + ); + self.global_state.respond(response); + None + } + } + } +} + +fn thread_result_to_response( + id: lsp_server::RequestId, + result: thread::Result>, +) -> Result +where + R: lsp_types::request::Request, + R::Params: DeserializeOwned, + R::Result: Serialize, +{ + match result { + Ok(result) => result_to_response::(id, result), + Err(panic) => { + let panic_message = panic + .downcast_ref::() + .map(String::as_str) + .or_else(|| panic.downcast_ref::<&str>().copied()); + + let mut message = "request handler panicked".to_owned(); + if let Some(panic_message) = panic_message { + message.push_str(": "); + message.push_str(panic_message) + }; + + Ok(lsp_server::Response::new_err( + id, + lsp_server::ErrorCode::InternalError as i32, + message, + )) + } + } +} + +fn result_to_response( + id: lsp_server::RequestId, + result: anyhow::Result, +) -> Result +where + R: lsp_types::request::Request, + R::Params: DeserializeOwned, + R::Result: Serialize, +{ + let res = match result { + Ok(resp) => lsp_server::Response::new_ok(id, &resp), + Err(e) => match e.downcast::() { + Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message), + Err(e) => match e.downcast::() { + Ok(cancelled) => return Err(cancelled), + Err(e) => lsp_server::Response::new_err( + id, + lsp_server::ErrorCode::InternalError as i32, + e.to_string(), + ), + }, + }, + }; + Ok(res) +} + +pub(crate) struct NotificationDispatcher<'a> { + pub(crate) not: Option, + pub(crate) global_state: &'a mut GlobalState, +} + +impl NotificationDispatcher<'_> { + pub(crate) fn on_sync_mut( + &mut self, + f: fn(&mut GlobalState, N::Params) -> anyhow::Result<()>, + ) -> anyhow::Result<&mut Self> + where + N: lsp_types::notification::Notification, + N::Params: DeserializeOwned + Send + Debug, + { + let not = match self.not.take() { + Some(it) => it, + None => return Ok(self), + }; + + let _guard = tracing::info_span!("notification", method = ?not.method).entered(); + + let params = match not.extract::(N::METHOD) { + Ok(it) => it, + Err(ExtractError::JsonError { method, error }) => { + panic!("Invalid request\nMethod: {method}\n error: {error}",) + } + Err(ExtractError::MethodMismatch(not)) => { + self.not = Some(not); + return Ok(self); + } + }; + + tracing::debug!(?params); + + let _pctx = stdx::panic_context::enter(format!( + "\nversion: {}\nnotification: {}", + version(), + N::METHOD + )); + f(self.global_state, params)?; + Ok(self) + } + + pub(crate) fn finish(&mut self) { + if let Some(not) = &self.not { + if !not.method.starts_with("$/") { + tracing::error!("unhandled notification: {:?}", not); + } + } + } +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index a77d31167a7..34325ac7a93 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -36,7 +36,6 @@ use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ config::{Config, RustfmtConfig, WorkspaceSymbolConfig}, - diff::diff, global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot}, hack_recover_crate_name, line_index::LineEndings, @@ -2370,3 +2369,47 @@ fn resolve_resource_op(op: &ResourceOp) -> ResourceOperationKind { ResourceOp::Delete(_) => ResourceOperationKind::Delete, } } + +pub(crate) fn diff(left: &str, right: &str) -> TextEdit { + use dissimilar::Chunk; + + let chunks = dissimilar::diff(left, right); + + let mut builder = TextEdit::builder(); + let mut pos = TextSize::default(); + + let mut chunks = chunks.into_iter().peekable(); + while let Some(chunk) = chunks.next() { + if let (Chunk::Delete(deleted), Some(&Chunk::Insert(inserted))) = (chunk, chunks.peek()) { + chunks.next().unwrap(); + let deleted_len = TextSize::of(deleted); + builder.replace(TextRange::at(pos, deleted_len), inserted.into()); + pos += deleted_len; + continue; + } + + match chunk { + Chunk::Equal(text) => { + pos += TextSize::of(text); + } + Chunk::Delete(deleted) => { + let deleted_len = TextSize::of(deleted); + builder.delete(TextRange::at(pos, deleted_len)); + pos += deleted_len; + } + Chunk::Insert(inserted) => { + builder.insert(pos, inserted.into()); + } + } + } + builder.finish() +} + +#[test] +fn diff_smoke_test() { + let mut original = String::from("fn foo(a:u32){\n}"); + let result = "fn foo(a: u32) {}"; + let edit = diff(&original, result); + edit.apply(&mut original); + assert_eq!(original, result); +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs index 56eb420770e..714991e8116 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs @@ -11,12 +11,9 @@ pub mod cli; -mod capabilities; mod command; mod diagnostics; -mod diff; mod discover; -mod dispatch; mod flycheck; mod hack_recover_crate_name; mod line_index; @@ -30,6 +27,7 @@ mod test_runner; mod version; mod handlers { + pub(crate) mod dispatch; pub(crate) mod notification; pub(crate) mod request; } @@ -51,7 +49,7 @@ mod integrated_benchmarks; use serde::de::DeserializeOwned; pub use crate::{ - capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph, + lsp::capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph, version::version, }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs index 9e0d42faed4..122ad20d65e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs @@ -3,6 +3,8 @@ use core::fmt; pub mod ext; + +pub(crate) mod capabilities; pub(crate) mod from_proto; pub(crate) mod semantic_tokens; pub(crate) mod to_proto; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs new file mode 100644 index 00000000000..9610808c27e --- /dev/null +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs @@ -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(None) { + 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 { + 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(&self, index: &'static str) -> Option { + 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 { + 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 { + 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 { + 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::>() + } + + 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 { + let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()]; + if config.snippet_cap().is_some() { + res.push("<".to_owned()); + } + res +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 85e7d81fce3..8035b7867cc 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -20,10 +20,10 @@ use crate::{ config::Config, diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration, NativeDiagnosticsFetchKind}, discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage}, - dispatch::{NotificationDispatcher, RequestDispatcher}, flycheck::{self, FlycheckMessage}, global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState}, hack_recover_crate_name, + handlers::dispatch::{NotificationDispatcher, RequestDispatcher}, lsp::{ from_proto, to_proto, utils::{notification_is, Progress}, -- cgit 1.4.1-3-g733a5 From 01262d972a03aa0eee275bea07876d44402fbb4c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 10 Aug 2024 17:04:38 +0200 Subject: Add comments regarding workspace structure change querying --- src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs | 5 +++++ .../rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs | 2 ++ src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs | 1 + 3 files changed, 8 insertions(+) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index df809c07235..d1f107a62a4 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -460,6 +460,11 @@ impl GlobalState { } } + // FIXME: `workspace_structure_change` is computed from `should_refresh_for_change` which is + // path syntax based. That is not sufficient for all cases so we should lift that check out + // into a `QueuedTask`, see `handle_did_save_text_document`. + // Or maybe instead of replacing that check, kick off a semantic one if the syntactic one + // didn't find anything (to make up for the lack of precision). { if !matches!(&workspace_structure_change, Some((.., true))) { _ = self diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs index a2f9229047e..de5d1f23136 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs @@ -158,6 +158,8 @@ pub(crate) fn handle_did_save_text_document( .map(|cfg| cfg.files_to_watch.iter().map(String::as_str).collect::>()) .unwrap_or_default(); + // FIXME: We should move this check into a QueuedTask and do semantic resolution of + // the files. There is only so much we can tell syntactically from the path. if reload::should_refresh_for_change(path, ChangeKind::Modify, additional_files) { state.fetch_workspaces_queue.request_op( format!("workspace vfs file change saved {path}"), diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 8035b7867cc..e303765aab6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -105,6 +105,7 @@ pub(crate) enum Task { FetchWorkspace(ProjectWorkspaceProgress), FetchBuildData(BuildDataProgress), LoadProcMacros(ProcMacroProgress), + // FIXME: Remove this in favor of a more general QueuedTask, see `handle_did_save_text_document` BuildDepsHaveChanged, } -- cgit 1.4.1-3-g733a5