From c7010eda1ba4e619952be9fe87487c19ec924eaa Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Tue, 7 Feb 2023 22:36:44 +0100 Subject: [PATCH] Support DidChangeWorkspaceFolders capability --- crates/rust-analyzer/src/bin/main.rs | 35 ++++++++----------- crates/rust-analyzer/src/caps.rs | 8 +++-- crates/rust-analyzer/src/config.rs | 20 ++++++++++- .../rust-analyzer/src/diagnostics/to_proto.rs | 2 +- crates/rust-analyzer/src/main_loop.rs | 26 +++++++++++++- .../rust-analyzer/tests/slow-tests/support.rs | 1 + 6 files changed, 66 insertions(+), 26 deletions(-) diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index a4902d63c68..4de022b6ed6 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -10,7 +10,6 @@ use std::{env, fs, path::Path, process}; use lsp_server::Connection; -use project_model::ProjectManifest; use rust_analyzer::{cli::flags, config::Config, from_json, Result}; use vfs::AbsPathBuf; @@ -168,7 +167,18 @@ fn run_server() -> Result<()> { } }; - let mut config = Config::new(root_path, initialize_params.capabilities); + let workspace_roots = initialize_params + .workspace_folders + .map(|workspaces| { + workspaces + .into_iter() + .filter_map(|it| it.uri.to_file_path().ok()) + .filter_map(|it| AbsPathBuf::try_from(it).ok()) + .collect::>() + }) + .filter(|workspaces| !workspaces.is_empty()) + .unwrap_or_else(|| vec![root_path.clone()]); + let mut config = Config::new(root_path, initialize_params.capabilities, workspace_roots); if let Some(json) = initialize_params.initialization_options { if let Err(e) = config.update(json) { use lsp_types::{ @@ -202,25 +212,8 @@ fn run_server() -> Result<()> { tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); } - if config.linked_projects().is_empty() && config.detached_files().is_empty() { - let workspace_roots = initialize_params - .workspace_folders - .map(|workspaces| { - workspaces - .into_iter() - .filter_map(|it| it.uri.to_file_path().ok()) - .filter_map(|it| AbsPathBuf::try_from(it).ok()) - .collect::>() - }) - .filter(|workspaces| !workspaces.is_empty()) - .unwrap_or_else(|| vec![config.root_path().clone()]); - - let discovered = ProjectManifest::discover_all(&workspace_roots); - tracing::info!("discovered projects: {:?}", discovered); - if discovered.is_empty() { - tracing::error!("failed to find any projects in {:?}", workspace_roots); - } - config.discovered_projects = Some(discovered); + if !config.has_linked_projects() && config.detached_files().is_empty() { + config.rediscover_workspaces(); } rust_analyzer::main_loop(config, connection)?; diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 122d2e6ff1b..841861635c6 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -10,7 +10,8 @@ SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions, - WorkspaceFileOperationsServerCapabilities, WorkspaceServerCapabilities, + WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, + WorkspaceServerCapabilities, }; use serde_json::json; @@ -80,7 +81,10 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { color_provider: None, execute_command_provider: None, workspace: Some(WorkspaceServerCapabilities { - workspace_folders: None, + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), file_operations: Some(WorkspaceFileOperationsServerCapabilities { did_create: None, will_create: None, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c8075aefbbe..67091dc7f22 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -524,6 +524,7 @@ fn default() -> Self { #[derive(Debug, Clone)] pub struct Config { pub discovered_projects: Option>, + pub workspace_roots: Vec, caps: lsp_types::ClientCapabilities, root_path: AbsPathBuf, data: ConfigData, @@ -720,7 +721,11 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } impl Config { - pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { + pub fn new( + root_path: AbsPathBuf, + caps: ClientCapabilities, + workspace_roots: Vec, + ) -> Self { Config { caps, data: ConfigData::default(), @@ -728,9 +733,19 @@ pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { discovered_projects: None, root_path, snippets: Default::default(), + workspace_roots, } } + pub fn rediscover_workspaces(&mut self) { + let discovered = ProjectManifest::discover_all(&self.workspace_roots); + tracing::info!("discovered projects: {:?}", discovered); + if discovered.is_empty() { + tracing::error!("failed to find any projects in {:?}", &self.workspace_roots); + } + self.discovered_projects = Some(discovered); + } + pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> { tracing::info!("updating config from JSON: {:#}", json); if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) { @@ -827,6 +842,9 @@ macro_rules! try_or_def { } impl Config { + pub fn has_linked_projects(&self) -> bool { + !self.data.linkedProjects.is_empty() + } pub fn linked_projects(&self) -> Vec { match self.data.linkedProjects.as_slice() { [] => match self.discovered_projects.as_ref() { diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index acb416a0689..55b89019b47 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -534,7 +534,7 @@ fn check_with_config(config: DiagnosticsMapConfig, diagnostics_json: &str, expec let (sender, _) = crossbeam_channel::unbounded(); let state = GlobalState::new( sender, - Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()), + Config::new(workspace_root.to_path_buf(), ClientCapabilities::default(), Vec::new()), ); let snap = state.snapshot(); let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap); diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 4290b776068..346a74e270f 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -14,7 +14,7 @@ use itertools::Itertools; use lsp_server::{Connection, Notification, Request}; use lsp_types::notification::Notification as _; -use vfs::{ChangeKind, FileId}; +use vfs::{AbsPathBuf, ChangeKind, FileId}; use crate::{ config::Config, @@ -933,6 +933,30 @@ fn run_flycheck(this: &mut GlobalState, vfs_path: VfsPath) -> bool { Ok(()) })? + .on::(|this, params| { + let config = Arc::make_mut(&mut this.config); + + for workspace in params.event.removed { + let Ok(path) = workspace.uri.to_file_path() else { continue }; + let Ok(path) = AbsPathBuf::try_from(path) else { continue }; + let Some(position) = config.workspace_roots.iter().position(|it| it == &path) else { continue }; + config.workspace_roots.remove(position); + } + + let added = params + .event + .added + .into_iter() + .filter_map(|it| it.uri.to_file_path().ok()) + .filter_map(|it| AbsPathBuf::try_from(it).ok()); + config.workspace_roots.extend(added); + if !config.has_linked_projects() && config.detached_files().is_empty() { + config.rediscover_workspaces(); + this.fetch_workspaces_queue.request_op("client workspaces changed".to_string()) + } + + Ok(()) + })? .on::(|this, params| { for change in params.changes { if let Ok(path) = from_proto::abs_path(&change.uri) { diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index 269212ebb99..b7275df0f40 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -137,6 +137,7 @@ pub(crate) fn server(self) -> Server { })), ..Default::default() }, + Vec::new(), ); config.discovered_projects = Some(discovered_projects); config.update(self.config).expect("invalid config");