Skip to content

Commit f28056f

Browse files
committed
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.
1 parent 96f289b commit f28056f

4 files changed

Lines changed: 44 additions & 10 deletions

File tree

src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,7 @@ struct ClientInfo {
10701070
version: Option<Version>,
10711071
}
10721072

1073+
/// The configuration of this rust-analyzer instance.
10731074
#[derive(Clone)]
10741075
pub struct Config {
10751076
/// Projects that have a Cargo.toml or a rust-project.json in a
@@ -1079,11 +1080,16 @@ pub struct Config {
10791080
/// Projects whose configuration was generated by a command
10801081
/// configured in discoverConfig.
10811082
discovered_projects_from_command: Vec<ProjectJsonFromCommand>,
1082-
/// The workspace roots as registered by the LSP client
1083+
/// The workspace roots as registered by the LSP client.
10831084
workspace_roots: Vec<AbsPathBuf>,
10841085
caps: ClientCapabilities,
1085-
/// The LSP root path, deprecated in favor of `workspace_roots`
1086+
1087+
/// The root of the first project encountered. This is deprecated
1088+
/// because rust-analyzer might be handling multiple projects.
1089+
///
1090+
/// Prefer `workspace_roots` and `workspace_root_for()`.
10861091
root_path: AbsPathBuf,
1092+
10871093
snippets: Vec<Snippet>,
10881094
client_info: Option<ClientInfo>,
10891095

@@ -1787,9 +1793,23 @@ impl Config {
17871793
s
17881794
}
17891795

1790-
pub fn root_path(&self) -> &AbsPathBuf {
1791-
// We should probably use `workspace_roots` here if set
1792-
&self.root_path
1796+
/// Find the workspace root that contains the given path, using the
1797+
/// longest prefix match.
1798+
pub fn workspace_root_for(&self, path: &AbsPath) -> &AbsPathBuf {
1799+
self.workspace_roots
1800+
.iter()
1801+
.filter(|root| path.starts_with(root.as_path()))
1802+
.max_by_key(|root| root.as_str().len())
1803+
.unwrap_or(self.default_root_path())
1804+
}
1805+
1806+
/// Best-effort root path for the current project.
1807+
///
1808+
/// Use `workspace_root_for` where possible, because
1809+
/// `default_root_path` may return the wrong path when a user has
1810+
/// multiple workspaces.
1811+
pub fn default_root_path(&self) -> &AbsPathBuf {
1812+
self.workspace_roots.first().unwrap_or(&self.root_path)
17931813
}
17941814

17951815
pub fn caps(&self) -> &ClientCapabilities {

src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2453,7 +2453,14 @@ fn run_rustfmt(
24532453
let cmd_path = if command.contains(std::path::MAIN_SEPARATOR)
24542454
|| (cfg!(windows) && command.contains('/'))
24552455
{
2456-
snap.config.root_path().join(cmd).into()
2456+
let project_root = Utf8PathBuf::from_path_buf(current_dir.clone())
2457+
.ok()
2458+
.and_then(|p| AbsPathBuf::try_from(p).ok());
2459+
let project_root = project_root
2460+
.as_ref()
2461+
.map(|dir| snap.config.workspace_root_for(dir))
2462+
.unwrap_or(snap.config.default_root_path());
2463+
project_root.join(cmd).into()
24572464
} else {
24582465
cmd
24592466
};

src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -830,12 +830,19 @@ impl GlobalState {
830830
let command = cfg.command.clone();
831831
let discover = DiscoverCommand::new(self.discover_sender.clone(), command);
832832

833+
let discover_path = match &arg {
834+
DiscoverProjectParam::Buildfile(it) => it,
835+
DiscoverProjectParam::Path(it) => it,
836+
};
837+
let current_dir =
838+
self.config.workspace_root_for(discover_path.as_path()).clone();
839+
833840
let arg = match arg {
834841
DiscoverProjectParam::Buildfile(it) => DiscoverArgument::Buildfile(it),
835842
DiscoverProjectParam::Path(it) => DiscoverArgument::Path(it),
836843
};
837844

838-
match discover.spawn(arg, self.config.root_path().as_ref()) {
845+
match discover.spawn(arg, current_dir.as_ref()) {
839846
Ok(handle) => {
840847
if self.discover_jobs_active == 0 {
841848
let title = &cfg.progress_label.clone();
@@ -953,7 +960,7 @@ impl GlobalState {
953960
if let Some(dir) = dir {
954961
message += &format!(
955962
": {}",
956-
match dir.strip_prefix(self.config.root_path()) {
963+
match dir.strip_prefix(self.config.workspace_root_for(&dir)) {
957964
Some(relative_path) => relative_path.as_utf8_path(),
958965
None => dir.as_ref(),
959966
}

src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ impl GlobalState {
390390
info!(%cause, "will fetch build data");
391391
let workspaces = Arc::clone(&self.workspaces);
392392
let config = self.config.cargo(None);
393-
let root_path = self.config.root_path().clone();
393+
let root_path = self.config.default_root_path().clone();
394394

395395
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
396396
sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
@@ -883,7 +883,7 @@ impl GlobalState {
883883
config,
884884
crate::flycheck::FlycheckConfigJson::default(),
885885
None,
886-
self.config.root_path().clone(),
886+
self.config.default_root_path().clone(),
887887
None,
888888
None,
889889
)]

0 commit comments

Comments
 (0)