From 18ca22a98e6a6a1a42717fe95c2d7535dc73c70e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 26 Apr 2024 11:06:52 +0200 Subject: [PATCH] Show workspace info in the status bar --- .git-blame-ignore-revs | 7 ++ crates/hir/src/semantics.rs | 12 ++- crates/load-cargo/src/lib.rs | 4 +- crates/proc-macro-api/src/lib.rs | 14 ++- crates/proc-macro-api/src/process.rs | 10 +- crates/project-model/src/project_json.rs | 14 ++- crates/project-model/src/sysroot.rs | 18 ++++ crates/project-model/src/tests.rs | 2 +- crates/project-model/src/workspace.rs | 5 +- crates/rust-analyzer/src/config.rs | 2 +- crates/rust-analyzer/src/lsp/ext.rs | 13 +++ crates/rust-analyzer/src/reload.rs | 129 +++++++++++++---------- docs/dev/lsp-extensions.md | 2 +- editors/code/src/ctx.ts | 16 +-- editors/code/src/lsp_ext.ts | 1 + 15 files changed, 168 insertions(+), 81 deletions(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index a302e23781..d5951a9420 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -6,3 +6,10 @@ # prettier format f247090558c9ba3c551566eae5882b7ca865225f + +# subtree syncs +932d85b52946d917deab2c23ead552f7f713b828 +3e358a6827d83e8d6473913a5e304734aadfed04 +9d2cb42a413e51deb50b36794a2e1605381878fc +f532576ac53ddcc666bc8d59e0b6437065e2f599 +c48062fe2ab9a2d913d1985a6b0aec4bf936bfc1 diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index e792e159ac..b99cde6db4 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -131,7 +131,7 @@ pub struct SemanticsImpl<'db> { pub db: &'db dyn HirDatabase, s2d_cache: RefCell, /// Rootnode to HirFileId cache - cache: RefCell>, + root_to_file_cache: RefCell>, // These 2 caches are mainly useful for semantic highlighting as nothing else descends a lot of tokens // So we might wanna move them out into something specific for semantic highlighting expansion_info_cache: RefCell>, @@ -294,7 +294,7 @@ impl<'db> SemanticsImpl<'db> { SemanticsImpl { db, s2d_cache: Default::default(), - cache: Default::default(), + root_to_file_cache: Default::default(), expansion_info_cache: Default::default(), macro_call_cache: Default::default(), } @@ -690,6 +690,7 @@ impl<'db> SemanticsImpl<'db> { exp_info }); + // FIXME: uncached parse // Create the source analyzer for the macro call scope let Some(sa) = self.analyze_no_infer(&self.parse_or_expand(expansion_info.call_file())) else { @@ -1025,6 +1026,7 @@ impl<'db> SemanticsImpl<'db> { None => { let call_node = file_id.macro_file()?.call_node(db); // cache the node + // FIXME: uncached parse self.parse_or_expand(call_node.file_id); Some(call_node) } @@ -1397,7 +1399,7 @@ impl<'db> SemanticsImpl<'db> { fn cache(&self, root_node: SyntaxNode, file_id: HirFileId) { assert!(root_node.parent().is_none()); - let mut cache = self.cache.borrow_mut(); + let mut cache = self.root_to_file_cache.borrow_mut(); let prev = cache.insert(root_node, file_id); assert!(prev.is_none() || prev == Some(file_id)) } @@ -1407,7 +1409,7 @@ impl<'db> SemanticsImpl<'db> { } fn lookup(&self, root_node: &SyntaxNode) -> Option { - let cache = self.cache.borrow(); + let cache = self.root_to_file_cache.borrow(); cache.get(root_node).copied() } @@ -1427,7 +1429,7 @@ impl<'db> SemanticsImpl<'db> { known nodes: {}\n\n", node, root_node, - self.cache + self.root_to_file_cache .borrow() .keys() .map(|it| format!("{it:?}")) diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index 3ac8fd9856..c859941abd 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -68,9 +68,9 @@ pub fn load_workspace( let proc_macro_server = match &load_config.with_proc_macro_server { ProcMacroServerChoice::Sysroot => ws .find_sysroot_proc_macro_srv() - .and_then(|it| ProcMacroServer::spawn(it, extra_env).map_err(Into::into)), + .and_then(|it| ProcMacroServer::spawn(&it, extra_env).map_err(Into::into)), ProcMacroServerChoice::Explicit(path) => { - ProcMacroServer::spawn(path.clone(), extra_env).map_err(Into::into) + ProcMacroServer::spawn(path, extra_env).map_err(Into::into) } ProcMacroServerChoice::None => Err(anyhow::format_err!("proc macro server disabled")), }; diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs index 0ab16c38c8..c0b27397cb 100644 --- a/crates/proc-macro-api/src/lib.rs +++ b/crates/proc-macro-api/src/lib.rs @@ -13,7 +13,7 @@ mod version; use base_db::Env; use indexmap::IndexSet; -use paths::AbsPathBuf; +use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; use span::Span; use std::{ @@ -54,6 +54,7 @@ pub struct ProcMacroServer { /// /// Therefore, we just wrap the `ProcMacroProcessSrv` in a mutex here. process: Arc>, + path: AbsPathBuf, } pub struct MacroDylib { @@ -113,11 +114,18 @@ pub struct MacroPanic { impl ProcMacroServer { /// Spawns an external process as the proc macro server and returns a client connected to it. pub fn spawn( - process_path: AbsPathBuf, + process_path: &AbsPath, env: &FxHashMap, ) -> io::Result { let process = ProcMacroProcessSrv::run(process_path, env)?; - Ok(ProcMacroServer { process: Arc::new(Mutex::new(process)) }) + Ok(ProcMacroServer { + process: Arc::new(Mutex::new(process)), + path: process_path.to_owned(), + }) + } + + pub fn path(&self) -> &AbsPath { + &self.path } pub fn load_dylib(&self, dylib: MacroDylib) -> Result, ServerError> { diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs index 35d48a1554..dce086d429 100644 --- a/crates/proc-macro-api/src/process.rs +++ b/crates/proc-macro-api/src/process.rs @@ -6,7 +6,7 @@ use std::{ sync::Arc, }; -use paths::{AbsPath, AbsPathBuf}; +use paths::AbsPath; use rustc_hash::FxHashMap; use stdx::JodChild; @@ -28,11 +28,11 @@ pub(crate) struct ProcMacroProcessSrv { impl ProcMacroProcessSrv { pub(crate) fn run( - process_path: AbsPathBuf, + process_path: &AbsPath, env: &FxHashMap, ) -> io::Result { let create_srv = |null_stderr| { - let mut process = Process::run(process_path.clone(), env, null_stderr)?; + let mut process = Process::run(process_path, env, null_stderr)?; let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); io::Result::Ok(ProcMacroProcessSrv { @@ -153,11 +153,11 @@ struct Process { impl Process { fn run( - path: AbsPathBuf, + path: &AbsPath, env: &FxHashMap, null_stderr: bool, ) -> io::Result { - let child = JodChild(mk_child(&path, env, null_stderr)?); + let child = JodChild(mk_child(path, env, null_stderr)?); Ok(Process { child }) } diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index fac6eb8ad3..5bee446f61 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -56,6 +56,7 @@ use serde::{de, Deserialize, Serialize}; use span::Edition; use crate::cfg::CfgFlag; +use crate::ManifestPath; /// Roots and crates that compose this Rust project. #[derive(Clone, Debug, Eq, PartialEq)] @@ -65,6 +66,7 @@ pub struct ProjectJson { /// e.g. `path/to/sysroot/lib/rustlib/src/rust` pub(crate) sysroot_src: Option, project_root: AbsPathBuf, + manifest: Option, crates: Vec, } @@ -96,12 +98,17 @@ impl ProjectJson { /// * `base` - The path to the workspace root (i.e. the folder containing `rust-project.json`) /// * `data` - The parsed contents of `rust-project.json`, or project json that's passed via /// configuration. - pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { + pub fn new( + manifest: Option, + base: &AbsPath, + data: ProjectJsonData, + ) -> ProjectJson { let absolutize_on_base = |p| base.absolutize(p); ProjectJson { sysroot: data.sysroot.map(absolutize_on_base), sysroot_src: data.sysroot_src.map(absolutize_on_base), project_root: base.to_path_buf(), + manifest, crates: data .crates .into_iter() @@ -159,6 +166,11 @@ impl ProjectJson { pub fn path(&self) -> &AbsPath { &self.project_root } + + /// Returns the path to the project's manifest or root folder, if no manifest exists. + pub fn manifest_or_root(&self) -> &AbsPath { + self.manifest.as_ref().map_or(&self.project_root, |manifest| manifest.as_ref()) + } } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs index 00dd18b5b1..e35316bbed 100644 --- a/crates/project-model/src/sysroot.rs +++ b/crates/project-model/src/sysroot.rs @@ -133,6 +133,24 @@ impl Sysroot { } } + pub fn check_has_core(&self) -> Result<(), String> { + let Some(Ok(src_root)) = &self.src_root else { return Ok(()) }; + let has_core = match &self.mode { + SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"), + SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(), + }; + if !has_core { + let var_note = if env::var_os("RUST_SRC_PATH").is_some() { + " (`RUST_SRC_PATH` might be incorrect, try unsetting it)" + } else { + " try running `rustup component add rust-src` to possible fix this" + }; + Err(format!("could not find libcore in loaded sysroot at `{}`{var_note}", src_root,)) + } else { + Ok(()) + } + } + pub fn num_packages(&self) -> usize { match &self.mode { SysrootMode::Workspace(ws) => ws.packages().count(), diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index 1308015d13..3d5a934fc9 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -152,7 +152,7 @@ fn rooted_project_json(data: ProjectJsonData) -> ProjectJson { replace_root(&mut root, true); let path = Utf8Path::new(&root); let base = AbsPath::assert(path); - ProjectJson::new(base, data) + ProjectJson::new(None, base, data) } fn to_crate_graph(project_workspace: ProjectWorkspace) -> (CrateGraph, ProcMacroPaths) { diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index a4e184502d..62dc94f99c 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -199,7 +199,8 @@ impl ProjectWorkspace { let data = serde_json::from_str(&file) .with_context(|| format!("Failed to deserialize json file {project_json}"))?; let project_location = project_json.parent().to_path_buf(); - let project_json: ProjectJson = ProjectJson::new(&project_location, data); + let project_json: ProjectJson = + ProjectJson::new(Some(project_json.clone()), &project_location, data); ProjectWorkspace::load_inline( project_json, config.target.as_deref(), @@ -555,7 +556,7 @@ impl ProjectWorkspace { pub fn manifest_or_root(&self) -> &AbsPath { match &self.kind { ProjectWorkspaceKind::Cargo { cargo, .. } => cargo.manifest_path(), - ProjectWorkspaceKind::Json(project) => project.path(), + ProjectWorkspaceKind::Json(project) => project.manifest_or_root(), ProjectWorkspaceKind::DetachedFile { file, .. } => file, } } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index dd563fe2a2..0e30d958f2 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1323,7 +1323,7 @@ impl Config { .map(Into::into) } ManifestOrProjectJson::ProjectJson(it) => { - Some(ProjectJson::new(&self.root_path, it.clone()).into()) + Some(ProjectJson::new(None, &self.root_path, it.clone()).into()) } }) .collect(), diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index 12f8e71c98..e189dbbbd6 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -2,6 +2,7 @@ #![allow(clippy::disallowed_types)] +use std::ops; use std::path::PathBuf; use ide_db::line_index::WideEncoding; @@ -494,10 +495,12 @@ impl Notification for ServerStatusNotification { } #[derive(Deserialize, Serialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] pub struct ServerStatusParams { pub health: Health, pub quiescent: bool, pub message: Option, + pub workspace_info: Option, } #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] @@ -508,6 +511,16 @@ pub enum Health { Error, } +impl ops::BitOrAssign for Health { + fn bitor_assign(&mut self, rhs: Self) { + *self = match (*self, rhs) { + (Health::Error, _) | (_, Health::Error) => Health::Error, + (Health::Warning, _) | (_, Health::Warning) => Health::Warning, + _ => Health::Ok, + } + } +} + pub enum CodeActionRequest {} impl Request for CodeActionRequest { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index f2b6e3ed03..fd14efa1da 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -103,79 +103,48 @@ impl GlobalState { health: lsp_ext::Health::Ok, quiescent: self.is_quiescent(), message: None, + workspace_info: None, }; let mut message = String::new(); + if !self.config.cargo_autoreload() + && self.is_quiescent() + && self.fetch_workspaces_queue.op_requested() + { + status.health |= lsp_ext::Health::Warning; + message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n"); + } + if self.build_deps_changed { - status.health = lsp_ext::Health::Warning; + status.health |= lsp_ext::Health::Warning; message.push_str( "Proc-macros and/or build scripts have changed and need to be rebuilt.\n\n", ); } if self.fetch_build_data_error().is_err() { - status.health = lsp_ext::Health::Warning; + status.health |= lsp_ext::Health::Warning; message.push_str("Failed to run build scripts of some packages.\n\n"); } - if self.proc_macro_clients.iter().any(|it| it.is_err()) { - status.health = lsp_ext::Health::Warning; - message.push_str("Failed to spawn one or more proc-macro servers.\n\n"); - for err in self.proc_macro_clients.iter() { - if let Err(err) = err { - format_to!(message, "- {err}\n"); - } - } - } - if !self.config.cargo_autoreload() - && self.is_quiescent() - && self.fetch_workspaces_queue.op_requested() - { - status.health = lsp_ext::Health::Warning; - message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n"); - } - if self.config.linked_or_discovered_projects().is_empty() - && self.config.detached_files().is_empty() - && self.config.notifications().cargo_toml_not_found - { - status.health = lsp_ext::Health::Warning; - message.push_str("Failed to discover workspace.\n"); - message.push_str("Consider adding the `Cargo.toml` of the workspace to the [`linkedProjects`](https://rust-analyzer.github.io/manual.html#rust-analyzer.linkedProjects) setting.\n\n"); - } + if let Some(err) = &self.config_errors { - status.health = lsp_ext::Health::Warning; + status.health |= lsp_ext::Health::Warning; format_to!(message, "{err}\n"); } if let Some(err) = &self.last_flycheck_error { - status.health = lsp_ext::Health::Warning; + status.health |= lsp_ext::Health::Warning; message.push_str(err); message.push('\n'); } - for ws in self.workspaces.iter() { - let sysroot = ws.sysroot.as_ref(); - match sysroot { - Err(None) => (), - Err(Some(e)) => { - status.health = lsp_ext::Health::Warning; - message.push_str(e); - message.push_str("\n\n"); - } - Ok(s) => { - if let Some(e) = s.loading_warning() { - status.health = lsp_ext::Health::Warning; - message.push_str(&e); - message.push_str("\n\n"); - } - } - } - if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(e)), .. } = &ws.kind { - status.health = lsp_ext::Health::Warning; - message.push_str(e); - message.push_str("\n\n"); - } + if self.config.linked_or_discovered_projects().is_empty() + && self.config.detached_files().is_empty() + { + status.health |= lsp_ext::Health::Warning; + message.push_str("Failed to discover workspace.\n"); + message.push_str("Consider adding the `Cargo.toml` of the workspace to the [`linkedProjects`](https://rust-analyzer.github.io/manual.html#rust-analyzer.linkedProjects) setting.\n\n"); } - if self.fetch_workspace_error().is_err() { - status.health = lsp_ext::Health::Error; + status.health |= lsp_ext::Health::Error; message.push_str("Failed to load workspaces."); if self.config.has_linked_projects() { @@ -191,9 +160,63 @@ impl GlobalState { message.push_str("\n\n"); } + if !self.workspaces.is_empty() { + let proc_macro_clients = + self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None)); + + let mut workspace_info = "Loaded workspaces:\n".to_owned(); + for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) { + format_to!(workspace_info, "- `{}`\n", ws.manifest_or_root()); + format_to!(workspace_info, " - sysroot:"); + + match ws.sysroot.as_ref() { + Err(None) => format_to!(workspace_info, " None"), + Err(Some(e)) => { + status.health |= lsp_ext::Health::Warning; + format_to!(workspace_info, " {e}"); + } + Ok(s) => { + format_to!(workspace_info, " `{}`", s.root().to_string()); + if let Some(err) = s + .check_has_core() + .err() + .inspect(|_| status.health |= lsp_ext::Health::Warning) + { + format_to!(workspace_info, " ({err})"); + } + if let Some(src_root) = s.src_root() { + format_to!( + workspace_info, + "\n - sysroot source: `{}`", + src_root + ); + } + format_to!(workspace_info, "\n"); + } + } + + if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(e)), .. } = &ws.kind { + status.health |= lsp_ext::Health::Warning; + format_to!(workspace_info, " - rustc workspace: {e}\n"); + }; + if let Some(proc_macro_client) = proc_macro_client { + format_to!(workspace_info, " - proc-macro server: "); + match proc_macro_client { + Ok(it) => format_to!(workspace_info, "`{}`\n", it.path()), + Err(e) => { + status.health |= lsp_ext::Health::Warning; + format_to!(workspace_info, "{e}\n") + } + } + } + } + status.workspace_info = Some(workspace_info); + } + if !message.is_empty() { status.message = Some(message.trim_end().to_owned()); } + status } @@ -520,7 +543,7 @@ impl GlobalState { }; tracing::info!("Using proc-macro server at {path}"); - ProcMacroServer::spawn(path.clone(), &env).map_err(|err| { + ProcMacroServer::spawn(&path, &env).map_err(|err| { tracing::error!( "Failed to run proc-macro server from path {path}, error: {err:?}", ); diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index f1815082e2..7e12874c47 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@