about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/rust-analyzer/src/bin/main.rs35
-rw-r--r--crates/rust-analyzer/src/caps.rs8
-rw-r--r--crates/rust-analyzer/src/config.rs20
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs26
-rw-r--r--crates/rust-analyzer/tests/slow-tests/support.rs1
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 @@ mod rustc_wrapper;
 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::<Vec<_>>()
+        })
+        .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::<Vec<_>>()
-            })
-            .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 @@ use lsp_types::{
     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 @@ impl Default for ConfigData {
 #[derive(Debug, Clone)]
 pub struct Config {
     pub discovered_projects: Option<Vec<ProjectManifest>>,
+    pub workspace_roots: Vec<AbsPathBuf>,
     caps: lsp_types::ClientCapabilities,
     root_path: AbsPathBuf,
     data: ConfigData,
@@ -720,7 +721,11 @@ impl fmt::Display for ConfigUpdateError {
 }
 
 impl Config {
-    pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
+    pub fn new(
+        root_path: AbsPathBuf,
+        caps: ClientCapabilities,
+        workspace_roots: Vec<AbsPathBuf>,
+    ) -> Self {
         Config {
             caps,
             data: ConfigData::default(),
@@ -728,9 +733,19 @@ impl Config {
             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<LinkedProject> {
         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 @@ mod tests {
         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 ide_db::base_db::{SourceDatabaseExt, VfsPath};
 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 @@ impl GlobalState {
 
                 Ok(())
             })?
+            .on::<lsp_types::notification::DidChangeWorkspaceFolders>(|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::<lsp_types::notification::DidChangeWatchedFiles>(|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 @@ impl<'a> Project<'a> {
                 })),
                 ..Default::default()
             },
+            Vec::new(),
         );
         config.discovered_projects = Some(discovered_projects);
         config.update(self.config).expect("invalid config");