Unnamed repository; edit this file 'description' to name the repository.
fix: Use the correct project root when there are multiple workspaces
Previously, Config::root_path() would always return the LSP rootUri of the first workspace folder. This can cause issues when the user has multiple workspaces open in their editor, especially if the first one in the list isn't a Rust project. This was noted as an issue in rust-lang/rust-analyzer#21483, and added comments suggesting that we should deprecate root_path(). This change splits root_path() into a `workspace_root_for()` function that handles the multiple workspace case correctly, and a `default_root_path()` fallback. This is particularly useful when the user has configured project-relative paths to e.g. their discover command or rustfmt, but it's the correct behaviour in general. AI disclosure: First draft was written with Claude Opus.
Wilfred Hughes 7 weeks ago
parent 63b3eff · commit 752da7d
-rw-r--r--crates/rust-analyzer/src/config.rs30
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs9
-rw-r--r--crates/rust-analyzer/src/main_loop.rs11
-rw-r--r--crates/rust-analyzer/src/reload.rs4
4 files changed, 44 insertions, 10 deletions
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 1bc164b157..90857a3073 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -1070,6 +1070,7 @@ struct ClientInfo {
version: Option<Version>,
}
+/// The configuration of this rust-analyzer instance.
#[derive(Clone)]
pub struct Config {
/// Projects that have a Cargo.toml or a rust-project.json in a
@@ -1079,11 +1080,16 @@ pub struct Config {
/// Projects whose configuration was generated by a command
/// configured in discoverConfig.
discovered_projects_from_command: Vec<ProjectJsonFromCommand>,
- /// The workspace roots as registered by the LSP client
+ /// The workspace roots as registered by the LSP client.
workspace_roots: Vec<AbsPathBuf>,
caps: ClientCapabilities,
- /// The LSP root path, deprecated in favor of `workspace_roots`
+
+ /// The root of the first project encountered. This is deprecated
+ /// because rust-analyzer might be handling multiple projects.
+ ///
+ /// Prefer `workspace_roots` and `workspace_root_for()`.
root_path: AbsPathBuf,
+
snippets: Vec<Snippet>,
client_info: Option<ClientInfo>,
@@ -1787,9 +1793,23 @@ impl Config {
s
}
- pub fn root_path(&self) -> &AbsPathBuf {
- // We should probably use `workspace_roots` here if set
- &self.root_path
+ /// Find the workspace root that contains the given path, using the
+ /// longest prefix match.
+ pub fn workspace_root_for(&self, path: &AbsPath) -> &AbsPathBuf {
+ self.workspace_roots
+ .iter()
+ .filter(|root| path.starts_with(root.as_path()))
+ .max_by_key(|root| root.as_str().len())
+ .unwrap_or(self.default_root_path())
+ }
+
+ /// Best-effort root path for the current project.
+ ///
+ /// Use `workspace_root_for` where possible, because
+ /// `default_root_path` may return the wrong path when a user has
+ /// multiple workspaces.
+ pub fn default_root_path(&self) -> &AbsPathBuf {
+ self.workspace_roots.first().unwrap_or(&self.root_path)
}
pub fn caps(&self) -> &ClientCapabilities {
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index c24591b7ab..9c2e0a5f32 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -2453,7 +2453,14 @@ fn run_rustfmt(
let cmd_path = if command.contains(std::path::MAIN_SEPARATOR)
|| (cfg!(windows) && command.contains('/'))
{
- snap.config.root_path().join(cmd).into()
+ let project_root = Utf8PathBuf::from_path_buf(current_dir.clone())
+ .ok()
+ .and_then(|p| AbsPathBuf::try_from(p).ok());
+ let project_root = project_root
+ .as_ref()
+ .map(|dir| snap.config.workspace_root_for(dir))
+ .unwrap_or(snap.config.default_root_path());
+ project_root.join(cmd).into()
} else {
cmd
};
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 7c494de6f7..a8c3d062d0 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -830,12 +830,19 @@ impl GlobalState {
let command = cfg.command.clone();
let discover = DiscoverCommand::new(self.discover_sender.clone(), command);
+ let discover_path = match &arg {
+ DiscoverProjectParam::Buildfile(it) => it,
+ DiscoverProjectParam::Path(it) => it,
+ };
+ let current_dir =
+ self.config.workspace_root_for(discover_path.as_path()).clone();
+
let arg = match arg {
DiscoverProjectParam::Buildfile(it) => DiscoverArgument::Buildfile(it),
DiscoverProjectParam::Path(it) => DiscoverArgument::Path(it),
};
- match discover.spawn(arg, self.config.root_path().as_ref()) {
+ match discover.spawn(arg, current_dir.as_ref()) {
Ok(handle) => {
if self.discover_jobs_active == 0 {
let title = &cfg.progress_label.clone();
@@ -953,7 +960,7 @@ impl GlobalState {
if let Some(dir) = dir {
message += &format!(
": {}",
- match dir.strip_prefix(self.config.root_path()) {
+ match dir.strip_prefix(self.config.workspace_root_for(&dir)) {
Some(relative_path) => relative_path.as_utf8_path(),
None => dir.as_ref(),
}
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 83f4a19b39..71accbed4e 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -390,7 +390,7 @@ impl GlobalState {
info!(%cause, "will fetch build data");
let workspaces = Arc::clone(&self.workspaces);
let config = self.config.cargo(None);
- let root_path = self.config.root_path().clone();
+ let root_path = self.config.default_root_path().clone();
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
@@ -883,7 +883,7 @@ impl GlobalState {
config,
crate::flycheck::FlycheckConfigJson::default(),
None,
- self.config.root_path().clone(),
+ self.config.default_root_path().clone(),
None,
None,
)]