mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-03 17:35:28 +03:00
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.
This commit is contained in:
@@ -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 @@ fn sort_objects_by_field(json: &mut serde_json::Value) {
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -830,12 +830,19 @@ fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>,
|
||||
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 @@ fn handle_vfs_msg(
|
||||
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(),
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ pub(crate) fn fetch_build_data(&mut self, cause: Cause) {
|
||||
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 @@ fn reload_flycheck(&mut self) {
|
||||
config,
|
||||
crate::flycheck::FlycheckConfigJson::default(),
|
||||
None,
|
||||
self.config.root_path().clone(),
|
||||
self.config.default_root_path().clone(),
|
||||
None,
|
||||
None,
|
||||
)]
|
||||
|
||||
Reference in New Issue
Block a user