Detailed changes
@@ -4399,7 +4399,6 @@ name = "diagnostics"
version = "0.1.0"
dependencies = [
"anyhow",
- "cargo_metadata",
"client",
"collections",
"component",
@@ -4409,7 +4408,6 @@ dependencies = [
"futures 0.3.31",
"gpui",
"indoc",
- "itertools 0.14.0",
"language",
"linkme",
"log",
@@ -4421,7 +4419,6 @@ dependencies = [
"serde",
"serde_json",
"settings",
- "smol",
"text",
"theme",
"ui",
@@ -935,22 +935,9 @@
"max_severity": null
},
"rust": {
- // When enabled, Zed runs `cargo check --message-format=json`-based commands and
- // collect cargo diagnostics instead of rust-analyzer.
- "fetch_cargo_diagnostics": false,
- // A command override for fetching the cargo diagnostics.
- // First argument is the command, followed by the arguments.
- "diagnostics_fetch_command": [
- "cargo",
- "check",
- "--quiet",
- "--workspace",
- "--message-format=json",
- "--all-targets",
- "--keep-going"
- ],
- // Extra environment variables to pass to the diagnostics fetch command.
- "env": {}
+ // When enabled, Zed disables rust-analyzer's check on save and starts to query
+ // Cargo diagnostics separately.
+ "fetch_cargo_diagnostics": false
}
},
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
@@ -328,6 +328,9 @@ impl Server {
forward_read_only_project_request::<proto::LspExtSwitchSourceHeader>,
)
.add_request_handler(forward_read_only_project_request::<proto::LspExtGoToParentModule>)
+ .add_request_handler(forward_read_only_project_request::<proto::LspExtCancelFlycheck>)
+ .add_request_handler(forward_read_only_project_request::<proto::LspExtRunFlycheck>)
+ .add_request_handler(forward_read_only_project_request::<proto::LspExtClearFlycheck>)
.add_request_handler(
forward_read_only_project_request::<proto::LanguageServerIdForName>,
)
@@ -25,7 +25,7 @@ use language::{
use project::{
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
lsp_store::{
- lsp_ext_command::{ExpandedMacro, LspExpandMacro},
+ lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
rust_analyzer_ext::RUST_ANALYZER_NAME,
},
project_settings::{InlineBlameSettings, ProjectSettings},
@@ -2704,8 +2704,8 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
let fake_language_server = fake_language_servers.next().await.unwrap();
// host
- let mut expand_request_a =
- fake_language_server.set_request_handler::<LspExpandMacro, _, _>(|params, _| async move {
+ let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
+ |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
@@ -2715,7 +2715,8 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
name: "test_macro_name".to_string(),
expansion: "test_macro_expansion on the host".to_string(),
}))
- });
+ },
+ );
editor_a.update_in(cx_a, |editor, window, cx| {
expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
@@ -2738,8 +2739,8 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
});
// client
- let mut expand_request_b =
- fake_language_server.set_request_handler::<LspExpandMacro, _, _>(|params, _| async move {
+ let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
+ |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
@@ -2749,7 +2750,8 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
name: "test_macro_name".to_string(),
expansion: "test_macro_expansion on the client".to_string(),
}))
- });
+ },
+ );
editor_b.update_in(cx_b, |editor, window, cx| {
expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
@@ -14,7 +14,6 @@ doctest = false
[dependencies]
anyhow.workspace = true
-cargo_metadata.workspace = true
collections.workspace = true
component.workspace = true
ctor.workspace = true
@@ -23,7 +22,6 @@ env_logger.workspace = true
futures.workspace = true
gpui.workspace = true
indoc.workspace = true
-itertools.workspace = true
language.workspace = true
linkme.workspace = true
log.workspace = true
@@ -34,7 +32,6 @@ rand.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
-smol.workspace = true
text.workspace = true
theme.workspace = true
ui.workspace = true
@@ -1,603 +0,0 @@
-use std::{
- path::{Component, Path, Prefix},
- process::Stdio,
- sync::atomic::{self, AtomicUsize},
-};
-
-use cargo_metadata::{
- Message,
- diagnostic::{Applicability, Diagnostic as CargoDiagnostic, DiagnosticLevel, DiagnosticSpan},
-};
-use collections::HashMap;
-use gpui::{AppContext, Entity, Task};
-use itertools::Itertools as _;
-use language::Diagnostic;
-use project::{
- Worktree, lsp_store::rust_analyzer_ext::CARGO_DIAGNOSTICS_SOURCE_NAME,
- project_settings::ProjectSettings,
-};
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use smol::{
- channel::Receiver,
- io::{AsyncBufReadExt, BufReader},
- process::Command,
-};
-use ui::App;
-use util::ResultExt;
-
-use crate::ProjectDiagnosticsEditor;
-
-#[derive(Debug, serde::Deserialize)]
-#[serde(untagged)]
-enum CargoMessage {
- Cargo(Message),
- Rustc(CargoDiagnostic),
-}
-
-/// Appends formatted string to a `String`.
-macro_rules! format_to {
- ($buf:expr) => ();
- ($buf:expr, $lit:literal $($arg:tt)*) => {
- {
- use ::std::fmt::Write as _;
- // We can't do ::std::fmt::Write::write_fmt($buf, format_args!($lit $($arg)*))
- // unfortunately, as that loses out on autoref behavior.
- _ = $buf.write_fmt(format_args!($lit $($arg)*))
- }
- };
-}
-
-pub fn cargo_diagnostics_sources(
- editor: &ProjectDiagnosticsEditor,
- cx: &App,
-) -> Vec<Entity<Worktree>> {
- let fetch_cargo_diagnostics = ProjectSettings::get_global(cx)
- .diagnostics
- .fetch_cargo_diagnostics();
- if !fetch_cargo_diagnostics {
- return Vec::new();
- }
- editor
- .project
- .read(cx)
- .worktrees(cx)
- .filter(|worktree| worktree.read(cx).entry_for_path("Cargo.toml").is_some())
- .collect()
-}
-
-#[derive(Debug)]
-pub enum FetchUpdate {
- Diagnostic(CargoDiagnostic),
- Progress(String),
-}
-
-#[derive(Debug)]
-pub enum FetchStatus {
- Started,
- Progress { message: String },
- Finished,
-}
-
-pub fn fetch_worktree_diagnostics(
- worktree_root: &Path,
- cx: &App,
-) -> Option<(Task<()>, Receiver<FetchUpdate>)> {
- let diagnostics_settings = ProjectSettings::get_global(cx)
- .diagnostics
- .cargo
- .as_ref()
- .filter(|cargo_diagnostics| cargo_diagnostics.fetch_cargo_diagnostics)?;
- let command_string = diagnostics_settings
- .diagnostics_fetch_command
- .iter()
- .join(" ");
- let mut command_parts = diagnostics_settings.diagnostics_fetch_command.iter();
- let mut command = Command::new(command_parts.next()?)
- .args(command_parts)
- .envs(diagnostics_settings.env.clone())
- .current_dir(worktree_root)
- .stdout(Stdio::piped())
- .stderr(Stdio::null())
- .kill_on_drop(true)
- .spawn()
- .log_err()?;
-
- let stdout = command.stdout.take()?;
- let mut reader = BufReader::new(stdout);
- let (tx, rx) = smol::channel::unbounded();
- let error_threshold = 10;
-
- let cargo_diagnostics_fetch_task = cx.background_spawn(async move {
- let _command = command;
- let mut errors = 0;
- loop {
- let mut line = String::new();
- match reader.read_line(&mut line).await {
- Ok(0) => {
- return;
- },
- Ok(_) => {
- errors = 0;
- let mut deserializer = serde_json::Deserializer::from_str(&line);
- deserializer.disable_recursion_limit();
- let send_result = match CargoMessage::deserialize(&mut deserializer) {
- Ok(CargoMessage::Cargo(Message::CompilerMessage(message))) => tx.send(FetchUpdate::Diagnostic(message.message)).await,
- Ok(CargoMessage::Cargo(Message::CompilerArtifact(artifact))) => tx.send(FetchUpdate::Progress(format!("Compiled {:?}", artifact.manifest_path.parent().unwrap_or(&artifact.manifest_path)))).await,
- Ok(CargoMessage::Cargo(_)) => Ok(()),
- Ok(CargoMessage::Rustc(rustc_message)) => tx.send(FetchUpdate::Diagnostic(rustc_message)).await,
- Err(_) => {
- log::debug!("Failed to parse cargo diagnostics from line '{line}'");
- Ok(())
- },
- };
- if send_result.is_err() {
- return;
- }
- },
- Err(e) => {
- log::error!("Failed to read line from {command_string} command output when fetching cargo diagnostics: {e}");
- errors += 1;
- if errors >= error_threshold {
- log::error!("Failed {error_threshold} times, aborting the diagnostics fetch");
- return;
- }
- },
- }
- }
- });
-
- Some((cargo_diagnostics_fetch_task, rx))
-}
-
-static CARGO_DIAGNOSTICS_FETCH_GENERATION: AtomicUsize = AtomicUsize::new(0);
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-struct CargoFetchDiagnosticData {
- generation: usize,
-}
-
-pub fn next_cargo_fetch_generation() {
- CARGO_DIAGNOSTICS_FETCH_GENERATION.fetch_add(1, atomic::Ordering::Release);
-}
-
-pub fn is_outdated_cargo_fetch_diagnostic(diagnostic: &Diagnostic) -> bool {
- if let Some(data) = diagnostic
- .data
- .clone()
- .and_then(|data| serde_json::from_value::<CargoFetchDiagnosticData>(data).ok())
- {
- let current_generation = CARGO_DIAGNOSTICS_FETCH_GENERATION.load(atomic::Ordering::Acquire);
- data.generation < current_generation
- } else {
- false
- }
-}
-
-/// Converts a Rust root diagnostic to LSP form
-///
-/// This flattens the Rust diagnostic by:
-///
-/// 1. Creating a LSP diagnostic with the root message and primary span.
-/// 2. Adding any labelled secondary spans to `relatedInformation`
-/// 3. Categorising child diagnostics as either `SuggestedFix`es,
-/// `relatedInformation` or additional message lines.
-///
-/// If the diagnostic has no primary span this will return `None`
-///
-/// Taken from https://github.com/rust-lang/rust-analyzer/blob/fe7b4f2ad96f7c13cc571f45edc2c578b35dddb4/crates/rust-analyzer/src/diagnostics/to_proto.rs#L275-L285
-pub(crate) fn map_rust_diagnostic_to_lsp(
- worktree_root: &Path,
- cargo_diagnostic: &CargoDiagnostic,
-) -> Vec<(lsp::Url, lsp::Diagnostic)> {
- let primary_spans: Vec<&DiagnosticSpan> = cargo_diagnostic
- .spans
- .iter()
- .filter(|s| s.is_primary)
- .collect();
- if primary_spans.is_empty() {
- return Vec::new();
- }
-
- let severity = diagnostic_severity(cargo_diagnostic.level);
-
- let mut source = String::from(CARGO_DIAGNOSTICS_SOURCE_NAME);
- let mut code = cargo_diagnostic.code.as_ref().map(|c| c.code.clone());
-
- if let Some(code_val) = &code {
- // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
- let scoped_code: Vec<&str> = code_val.split("::").collect();
- if scoped_code.len() == 2 {
- source = String::from(scoped_code[0]);
- code = Some(String::from(scoped_code[1]));
- }
- }
-
- let mut needs_primary_span_label = true;
- let mut subdiagnostics = Vec::new();
- let mut tags = Vec::new();
-
- for secondary_span in cargo_diagnostic.spans.iter().filter(|s| !s.is_primary) {
- if let Some(label) = secondary_span.label.clone() {
- subdiagnostics.push(lsp::DiagnosticRelatedInformation {
- location: location(worktree_root, secondary_span),
- message: label,
- });
- }
- }
-
- let mut message = cargo_diagnostic.message.clone();
- for child in &cargo_diagnostic.children {
- let child = map_rust_child_diagnostic(worktree_root, child);
- match child {
- MappedRustChildDiagnostic::SubDiagnostic(sub) => {
- subdiagnostics.push(sub);
- }
- MappedRustChildDiagnostic::MessageLine(message_line) => {
- format_to!(message, "\n{message_line}");
-
- // These secondary messages usually duplicate the content of the
- // primary span label.
- needs_primary_span_label = false;
- }
- }
- }
-
- if let Some(code) = &cargo_diagnostic.code {
- let code = code.code.as_str();
- if matches!(
- code,
- "dead_code"
- | "unknown_lints"
- | "unreachable_code"
- | "unused_attributes"
- | "unused_imports"
- | "unused_macros"
- | "unused_variables"
- ) {
- tags.push(lsp::DiagnosticTag::UNNECESSARY);
- }
-
- if matches!(code, "deprecated") {
- tags.push(lsp::DiagnosticTag::DEPRECATED);
- }
- }
-
- let code_description = match source.as_str() {
- "rustc" => rustc_code_description(code.as_deref()),
- "clippy" => clippy_code_description(code.as_deref()),
- _ => None,
- };
-
- let generation = CARGO_DIAGNOSTICS_FETCH_GENERATION.load(atomic::Ordering::Acquire);
- let data = Some(
- serde_json::to_value(CargoFetchDiagnosticData { generation })
- .expect("Serializing a regular Rust struct"),
- );
-
- primary_spans
- .iter()
- .flat_map(|primary_span| {
- let primary_location = primary_location(worktree_root, primary_span);
- let message = {
- let mut message = message.clone();
- if needs_primary_span_label {
- if let Some(primary_span_label) = &primary_span.label {
- format_to!(message, "\n{primary_span_label}");
- }
- }
- message
- };
- // Each primary diagnostic span may result in multiple LSP diagnostics.
- let mut diagnostics = Vec::new();
-
- let mut related_info_macro_calls = vec![];
-
- // If error occurs from macro expansion, add related info pointing to
- // where the error originated
- // Also, we would generate an additional diagnostic, so that exact place of macro
- // will be highlighted in the error origin place.
- let span_stack = std::iter::successors(Some(*primary_span), |span| {
- Some(&span.expansion.as_ref()?.span)
- });
- for (i, span) in span_stack.enumerate() {
- if is_dummy_macro_file(&span.file_name) {
- continue;
- }
-
- // First span is the original diagnostic, others are macro call locations that
- // generated that code.
- let is_in_macro_call = i != 0;
-
- let secondary_location = location(worktree_root, span);
- if secondary_location == primary_location {
- continue;
- }
- related_info_macro_calls.push(lsp::DiagnosticRelatedInformation {
- location: secondary_location.clone(),
- message: if is_in_macro_call {
- "Error originated from macro call here".to_owned()
- } else {
- "Actual error occurred here".to_owned()
- },
- });
- // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
- let information_for_additional_diagnostic =
- vec![lsp::DiagnosticRelatedInformation {
- location: primary_location.clone(),
- message: "Exact error occurred here".to_owned(),
- }];
-
- let diagnostic = lsp::Diagnostic {
- range: secondary_location.range,
- // downgrade to hint if we're pointing at the macro
- severity: Some(lsp::DiagnosticSeverity::HINT),
- code: code.clone().map(lsp::NumberOrString::String),
- code_description: code_description.clone(),
- source: Some(source.clone()),
- message: message.clone(),
- related_information: Some(information_for_additional_diagnostic),
- tags: if tags.is_empty() {
- None
- } else {
- Some(tags.clone())
- },
- data: data.clone(),
- };
- diagnostics.push((secondary_location.uri, diagnostic));
- }
-
- // Emit the primary diagnostic.
- diagnostics.push((
- primary_location.uri.clone(),
- lsp::Diagnostic {
- range: primary_location.range,
- severity,
- code: code.clone().map(lsp::NumberOrString::String),
- code_description: code_description.clone(),
- source: Some(source.clone()),
- message,
- related_information: {
- let info = related_info_macro_calls
- .iter()
- .cloned()
- .chain(subdiagnostics.iter().cloned())
- .collect::<Vec<_>>();
- if info.is_empty() { None } else { Some(info) }
- },
- tags: if tags.is_empty() {
- None
- } else {
- Some(tags.clone())
- },
- data: data.clone(),
- },
- ));
-
- // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
- // This is useful because they will show up in the user's editor, unlike
- // `related_information`, which just produces hard-to-read links, at least in VS Code.
- let back_ref = lsp::DiagnosticRelatedInformation {
- location: primary_location,
- message: "original diagnostic".to_owned(),
- };
- for sub in &subdiagnostics {
- diagnostics.push((
- sub.location.uri.clone(),
- lsp::Diagnostic {
- range: sub.location.range,
- severity: Some(lsp::DiagnosticSeverity::HINT),
- code: code.clone().map(lsp::NumberOrString::String),
- code_description: code_description.clone(),
- source: Some(source.clone()),
- message: sub.message.clone(),
- related_information: Some(vec![back_ref.clone()]),
- tags: None, // don't apply modifiers again
- data: data.clone(),
- },
- ));
- }
-
- diagnostics
- })
- .collect()
-}
-
-fn rustc_code_description(code: Option<&str>) -> Option<lsp::CodeDescription> {
- code.filter(|code| {
- let mut chars = code.chars();
- chars.next() == Some('E')
- && chars.by_ref().take(4).all(|c| c.is_ascii_digit())
- && chars.next().is_none()
- })
- .and_then(|code| {
- lsp::Url::parse(&format!(
- "https://doc.rust-lang.org/error-index.html#{code}"
- ))
- .ok()
- .map(|href| lsp::CodeDescription { href })
- })
-}
-
-fn clippy_code_description(code: Option<&str>) -> Option<lsp::CodeDescription> {
- code.and_then(|code| {
- lsp::Url::parse(&format!(
- "https://rust-lang.github.io/rust-clippy/master/index.html#{code}"
- ))
- .ok()
- .map(|href| lsp::CodeDescription { href })
- })
-}
-
-/// Determines the LSP severity from a diagnostic
-fn diagnostic_severity(level: DiagnosticLevel) -> Option<lsp::DiagnosticSeverity> {
- let res = match level {
- DiagnosticLevel::Ice => lsp::DiagnosticSeverity::ERROR,
- DiagnosticLevel::Error => lsp::DiagnosticSeverity::ERROR,
- DiagnosticLevel::Warning => lsp::DiagnosticSeverity::WARNING,
- DiagnosticLevel::Note => lsp::DiagnosticSeverity::INFORMATION,
- DiagnosticLevel::Help => lsp::DiagnosticSeverity::HINT,
- _ => return None,
- };
- Some(res)
-}
-
-enum MappedRustChildDiagnostic {
- SubDiagnostic(lsp::DiagnosticRelatedInformation),
- MessageLine(String),
-}
-
-fn map_rust_child_diagnostic(
- worktree_root: &Path,
- cargo_diagnostic: &CargoDiagnostic,
-) -> MappedRustChildDiagnostic {
- let spans: Vec<&DiagnosticSpan> = cargo_diagnostic
- .spans
- .iter()
- .filter(|s| s.is_primary)
- .collect();
- if spans.is_empty() {
- // `rustc` uses these spanless children as a way to print multi-line
- // messages
- return MappedRustChildDiagnostic::MessageLine(cargo_diagnostic.message.clone());
- }
-
- let mut edit_map: HashMap<lsp::Url, Vec<lsp::TextEdit>> = HashMap::default();
- let mut suggested_replacements = Vec::new();
- for &span in &spans {
- if let Some(suggested_replacement) = &span.suggested_replacement {
- if !suggested_replacement.is_empty() {
- suggested_replacements.push(suggested_replacement);
- }
- let location = location(worktree_root, span);
- let edit = lsp::TextEdit::new(location.range, suggested_replacement.clone());
-
- // Only actually emit a quickfix if the suggestion is "valid enough".
- // We accept both "MaybeIncorrect" and "MachineApplicable". "MaybeIncorrect" means that
- // the suggestion is *complete* (contains no placeholders where code needs to be
- // inserted), but might not be what the user wants, or might need minor adjustments.
- if matches!(
- span.suggestion_applicability,
- None | Some(Applicability::MaybeIncorrect | Applicability::MachineApplicable)
- ) {
- edit_map.entry(location.uri).or_default().push(edit);
- }
- }
- }
-
- // rustc renders suggestion diagnostics by appending the suggested replacement, so do the same
- // here, otherwise the diagnostic text is missing useful information.
- let mut message = cargo_diagnostic.message.clone();
- if !suggested_replacements.is_empty() {
- message.push_str(": ");
- let suggestions = suggested_replacements
- .iter()
- .map(|suggestion| format!("`{suggestion}`"))
- .join(", ");
- message.push_str(&suggestions);
- }
-
- MappedRustChildDiagnostic::SubDiagnostic(lsp::DiagnosticRelatedInformation {
- location: location(worktree_root, spans[0]),
- message,
- })
-}
-
-/// Converts a Rust span to a LSP location
-fn location(worktree_root: &Path, span: &DiagnosticSpan) -> lsp::Location {
- let file_name = worktree_root.join(&span.file_name);
- let uri = url_from_abs_path(&file_name);
-
- let range = {
- lsp::Range::new(
- position(span, span.line_start, span.column_start.saturating_sub(1)),
- position(span, span.line_end, span.column_end.saturating_sub(1)),
- )
- };
- lsp::Location::new(uri, range)
-}
-
-/// Returns a `Url` object from a given path, will lowercase drive letters if present.
-/// This will only happen when processing windows paths.
-///
-/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
-pub(crate) fn url_from_abs_path(path: &Path) -> lsp::Url {
- let url = lsp::Url::from_file_path(path).unwrap();
- match path.components().next() {
- Some(Component::Prefix(prefix))
- if matches!(prefix.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)) =>
- {
- // Need to lowercase driver letter
- }
- _ => return url,
- }
-
- let driver_letter_range = {
- let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
- Some(it) => it,
- None => return url,
- };
- let start = scheme.len() + ':'.len_utf8();
- start..(start + drive_letter.len())
- };
-
- // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
- // machinery *also* canonicalizes the drive letter. So, just massage the
- // string in place.
- let mut url: String = url.into();
- url[driver_letter_range].make_ascii_lowercase();
- lsp::Url::parse(&url).unwrap()
-}
-
-fn position(
- span: &DiagnosticSpan,
- line_number: usize,
- column_offset_utf32: usize,
-) -> lsp::Position {
- let line_index = line_number - span.line_start;
-
- let column_offset_encoded = match span.text.get(line_index) {
- // Fast path.
- Some(line) if line.text.is_ascii() => column_offset_utf32,
- Some(line) => {
- let line_prefix_len = line
- .text
- .char_indices()
- .take(column_offset_utf32)
- .last()
- .map(|(pos, c)| pos + c.len_utf8())
- .unwrap_or(0);
- let line_prefix = &line.text[..line_prefix_len];
- line_prefix.len()
- }
- None => column_offset_utf32,
- };
-
- lsp::Position {
- line: (line_number as u32).saturating_sub(1),
- character: column_offset_encoded as u32,
- }
-}
-
-/// Checks whether a file name is from macro invocation and does not refer to an actual file.
-fn is_dummy_macro_file(file_name: &str) -> bool {
- file_name.starts_with('<') && file_name.ends_with('>')
-}
-
-/// Extracts a suitable "primary" location from a rustc diagnostic.
-///
-/// This takes locations pointing into the standard library, or generally outside the current
-/// workspace into account and tries to avoid those, in case macros are involved.
-fn primary_location(worktree_root: &Path, span: &DiagnosticSpan) -> lsp::Location {
- let span_stack = std::iter::successors(Some(span), |span| Some(&span.expansion.as_ref()?.span));
- for span in span_stack.clone() {
- let abs_path = worktree_root.join(&span.file_name);
- if !is_dummy_macro_file(&span.file_name) && abs_path.starts_with(worktree_root) {
- return location(worktree_root, span);
- }
- }
-
- // Fall back to the outermost macro invocation if no suitable span comes up.
- let last_span = span_stack.last().unwrap();
- location(worktree_root, last_span)
-}
@@ -1,4 +1,3 @@
-mod cargo;
pub mod items;
mod toolbar_controls;
@@ -8,18 +7,14 @@ mod diagnostic_renderer;
mod diagnostics_tests;
use anyhow::Result;
-use cargo::{
- FetchStatus, FetchUpdate, cargo_diagnostics_sources, fetch_worktree_diagnostics,
- is_outdated_cargo_fetch_diagnostic, map_rust_diagnostic_to_lsp, next_cargo_fetch_generation,
- url_from_abs_path,
-};
-use collections::{BTreeSet, HashMap, HashSet};
+use collections::{BTreeSet, HashMap};
use diagnostic_renderer::DiagnosticBlock;
use editor::{
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
scroll::Autoscroll,
};
+use futures::future::join_all;
use gpui::{
AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
@@ -28,10 +23,10 @@ use gpui::{
use language::{
Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint,
};
-use lsp::{DiagnosticSeverity, LanguageServerId};
+use lsp::DiagnosticSeverity;
use project::{
- DiagnosticSummary, Project, ProjectPath, Worktree,
- lsp_store::rust_analyzer_ext::{CARGO_DIAGNOSTICS_SOURCE_NAME, RUST_ANALYZER_NAME},
+ DiagnosticSummary, Project, ProjectPath,
+ lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck},
project_settings::ProjectSettings,
};
use settings::Settings;
@@ -84,8 +79,9 @@ pub(crate) struct ProjectDiagnosticsEditor {
}
struct CargoDiagnosticsFetchState {
- task: Option<Task<()>>,
- rust_analyzer: Option<LanguageServerId>,
+ fetch_task: Option<Task<()>>,
+ cancel_task: Option<Task<()>>,
+ diagnostic_sources: Arc<Vec<ProjectPath>>,
}
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
@@ -252,8 +248,9 @@ impl ProjectDiagnosticsEditor {
paths_to_update: Default::default(),
update_excerpts_task: None,
cargo_diagnostics_fetch: CargoDiagnosticsFetchState {
- task: None,
- rust_analyzer: None,
+ fetch_task: None,
+ cancel_task: None,
+ diagnostic_sources: Arc::new(Vec::new()),
},
_subscription: project_event_subscription,
};
@@ -346,7 +343,7 @@ impl ProjectDiagnosticsEditor {
.fetch_cargo_diagnostics();
if fetch_cargo_diagnostics {
- if self.cargo_diagnostics_fetch.task.is_some() {
+ if self.cargo_diagnostics_fetch.fetch_task.is_some() {
self.stop_cargo_diagnostics_fetch(cx);
} else {
self.update_all_diagnostics(window, cx);
@@ -375,300 +372,63 @@ impl ProjectDiagnosticsEditor {
}
fn update_all_diagnostics(&mut self, window: &mut Window, cx: &mut Context<Self>) {
- let cargo_diagnostics_sources = cargo_diagnostics_sources(self, cx);
+ let cargo_diagnostics_sources = self.cargo_diagnostics_sources(cx);
if cargo_diagnostics_sources.is_empty() {
self.update_all_excerpts(window, cx);
} else {
- self.fetch_cargo_diagnostics(Arc::new(cargo_diagnostics_sources), window, cx);
+ self.fetch_cargo_diagnostics(Arc::new(cargo_diagnostics_sources), cx);
}
}
fn fetch_cargo_diagnostics(
&mut self,
- diagnostics_sources: Arc<Vec<Entity<Worktree>>>,
- window: &mut Window,
+ diagnostics_sources: Arc<Vec<ProjectPath>>,
cx: &mut Context<Self>,
) {
- self.cargo_diagnostics_fetch.task = Some(cx.spawn_in(window, async move |editor, cx| {
- let rust_analyzer_server = editor
- .update(cx, |editor, cx| {
- editor
- .project
- .read(cx)
- .language_server_with_name(RUST_ANALYZER_NAME, cx)
- })
- .ok();
- let rust_analyzer_server = match rust_analyzer_server {
- Some(rust_analyzer_server) => rust_analyzer_server.await,
- None => None,
- };
+ let project = self.project.clone();
+ self.cargo_diagnostics_fetch.cancel_task = None;
+ self.cargo_diagnostics_fetch.fetch_task = None;
+ self.cargo_diagnostics_fetch.diagnostic_sources = diagnostics_sources.clone();
+ if self.cargo_diagnostics_fetch.diagnostic_sources.is_empty() {
+ return;
+ }
- let mut worktree_diagnostics_tasks = Vec::new();
- let mut paths_with_reported_cargo_diagnostics = HashSet::default();
- if let Some(rust_analyzer_server) = rust_analyzer_server {
- let can_continue = editor
- .update(cx, |editor, cx| {
- editor.cargo_diagnostics_fetch.rust_analyzer = Some(rust_analyzer_server);
- let status_inserted =
- editor
- .project
- .read(cx)
- .lsp_store()
- .update(cx, |lsp_store, cx| {
- if let Some(rust_analyzer_status) = lsp_store
- .language_server_statuses
- .get_mut(&rust_analyzer_server)
- {
- rust_analyzer_status
- .progress_tokens
- .insert(fetch_cargo_diagnostics_token());
- paths_with_reported_cargo_diagnostics.extend(editor.diagnostics.iter().filter_map(|(buffer_id, diagnostics)| {
- if diagnostics.iter().any(|d| d.diagnostic.source.as_deref() == Some(CARGO_DIAGNOSTICS_SOURCE_NAME)) {
- Some(*buffer_id)
- } else {
- None
- }
- }).filter_map(|buffer_id| {
- let buffer = lsp_store.buffer_store().read(cx).get(buffer_id)?;
- let path = buffer.read(cx).file()?.as_local()?.abs_path(cx);
- Some(url_from_abs_path(&path))
- }));
- true
- } else {
- false
- }
- });
- if status_inserted {
- editor.update_cargo_fetch_status(FetchStatus::Started, cx);
- next_cargo_fetch_generation();
- true
- } else {
- false
- }
+ self.cargo_diagnostics_fetch.fetch_task = Some(cx.spawn(async move |editor, cx| {
+ let mut fetch_tasks = Vec::new();
+ for buffer_path in diagnostics_sources.iter().cloned() {
+ if cx
+ .update(|cx| {
+ fetch_tasks.push(run_flycheck(project.clone(), buffer_path, cx));
})
- .unwrap_or(false);
-
- if can_continue {
- for worktree in diagnostics_sources.iter() {
- if let Some(((_task, worktree_diagnostics), worktree_root)) = cx
- .update(|_, cx| {
- let worktree_root = worktree.read(cx).abs_path();
- log::info!("Fetching cargo diagnostics for {worktree_root:?}");
- fetch_worktree_diagnostics(&worktree_root, cx)
- .zip(Some(worktree_root))
- })
- .ok()
- .flatten()
- {
- let editor = editor.clone();
- worktree_diagnostics_tasks.push(cx.spawn(async move |cx| {
- let _task = _task;
- let mut file_diagnostics = HashMap::default();
- let mut diagnostics_total = 0;
- let mut updated_urls = HashSet::default();
- while let Ok(fetch_update) = worktree_diagnostics.recv().await {
- match fetch_update {
- FetchUpdate::Diagnostic(diagnostic) => {
- for (url, diagnostic) in map_rust_diagnostic_to_lsp(
- &worktree_root,
- &diagnostic,
- ) {
- let file_diagnostics = file_diagnostics
- .entry(url)
- .or_insert_with(Vec::<lsp::Diagnostic>::new);
- let i = file_diagnostics
- .binary_search_by(|probe| {
- probe.range.start.cmp(&diagnostic.range.start)
- .then(probe.range.end.cmp(&diagnostic.range.end))
- .then(Ordering::Greater)
- })
- .unwrap_or_else(|i| i);
- file_diagnostics.insert(i, diagnostic);
- }
-
- let file_changed = file_diagnostics.len() > 1;
- if file_changed {
- if editor
- .update_in(cx, |editor, window, cx| {
- editor
- .project
- .read(cx)
- .lsp_store()
- .update(cx, |lsp_store, cx| {
- for (uri, mut diagnostics) in
- file_diagnostics.drain()
- {
- diagnostics.dedup();
- diagnostics_total += diagnostics.len();
- updated_urls.insert(uri.clone());
-
- lsp_store.merge_diagnostics(
- rust_analyzer_server,
- lsp::PublishDiagnosticsParams {
- uri,
- diagnostics,
- version: None,
- },
- &[],
- |diagnostic, _| {
- !is_outdated_cargo_fetch_diagnostic(diagnostic)
- },
- cx,
- )?;
- }
- anyhow::Ok(())
- })?;
- editor.update_all_excerpts(window, cx);
- anyhow::Ok(())
- })
- .ok()
- .transpose()
- .ok()
- .flatten()
- .is_none()
- {
- break;
- }
- }
- }
- FetchUpdate::Progress(message) => {
- if editor
- .update(cx, |editor, cx| {
- editor.update_cargo_fetch_status(
- FetchStatus::Progress { message },
- cx,
- );
- })
- .is_err()
- {
- return updated_urls;
- }
- }
- }
- }
-
- editor
- .update_in(cx, |editor, window, cx| {
- editor
- .project
- .read(cx)
- .lsp_store()
- .update(cx, |lsp_store, cx| {
- for (uri, mut diagnostics) in
- file_diagnostics.drain()
- {
- diagnostics.dedup();
- diagnostics_total += diagnostics.len();
- updated_urls.insert(uri.clone());
-
- lsp_store.merge_diagnostics(
- rust_analyzer_server,
- lsp::PublishDiagnosticsParams {
- uri,
- diagnostics,
- version: None,
- },
- &[],
- |diagnostic, _| {
- !is_outdated_cargo_fetch_diagnostic(diagnostic)
- },
- cx,
- )?;
- }
- anyhow::Ok(())
- })?;
- editor.update_all_excerpts(window, cx);
- anyhow::Ok(())
- })
- .ok();
- log::info!("Fetched {diagnostics_total} cargo diagnostics for worktree {worktree_root:?}");
- updated_urls
- }));
- }
- }
- } else {
- log::info!(
- "No rust-analyzer language server found, skipping diagnostics fetch"
- );
+ .is_err()
+ {
+ break;
}
}
-
- let updated_urls = futures::future::join_all(worktree_diagnostics_tasks).await.into_iter().flatten().collect();
- if let Some(rust_analyzer_server) = rust_analyzer_server {
+ let _ = join_all(fetch_tasks).await;
editor
- .update_in(cx, |editor, window, cx| {
- editor
- .project
- .read(cx)
- .lsp_store()
- .update(cx, |lsp_store, cx| {
- for uri_to_cleanup in paths_with_reported_cargo_diagnostics.difference(&updated_urls).cloned() {
- lsp_store.merge_diagnostics(
- rust_analyzer_server,
- lsp::PublishDiagnosticsParams {
- uri: uri_to_cleanup,
- diagnostics: Vec::new(),
- version: None,
- },
- &[],
- |diagnostic, _| {
- !is_outdated_cargo_fetch_diagnostic(diagnostic)
- },
- cx,
- ).ok();
- }
- });
- editor.update_all_excerpts(window, cx);
-
- editor.stop_cargo_diagnostics_fetch(cx);
- cx.notify();
+ .update(cx, |editor, _| {
+ editor.cargo_diagnostics_fetch.fetch_task = None;
})
.ok();
- }
}));
}
- fn update_cargo_fetch_status(&self, status: FetchStatus, cx: &mut App) {
- let Some(rust_analyzer) = self.cargo_diagnostics_fetch.rust_analyzer else {
- return;
- };
-
- let work_done = match status {
- FetchStatus::Started => lsp::WorkDoneProgress::Begin(lsp::WorkDoneProgressBegin {
- title: "cargo".to_string(),
- cancellable: None,
- message: Some("Fetching cargo diagnostics".to_string()),
- percentage: None,
- }),
- FetchStatus::Progress { message } => {
- lsp::WorkDoneProgress::Report(lsp::WorkDoneProgressReport {
- message: Some(message),
- cancellable: None,
- percentage: None,
- })
- }
- FetchStatus::Finished => {
- lsp::WorkDoneProgress::End(lsp::WorkDoneProgressEnd { message: None })
- }
- };
- let progress = lsp::ProgressParams {
- token: lsp::NumberOrString::String(fetch_cargo_diagnostics_token()),
- value: lsp::ProgressParamsValue::WorkDone(work_done),
- };
-
- self.project
- .read(cx)
- .lsp_store()
- .update(cx, |lsp_store, cx| {
- lsp_store.on_lsp_progress(progress, rust_analyzer, None, cx)
- });
- }
-
fn stop_cargo_diagnostics_fetch(&mut self, cx: &mut App) {
- self.update_cargo_fetch_status(FetchStatus::Finished, cx);
- self.cargo_diagnostics_fetch.task = None;
- log::info!("Finished fetching cargo diagnostics");
+ self.cargo_diagnostics_fetch.fetch_task = None;
+ let mut cancel_gasks = Vec::new();
+ for buffer_path in std::mem::take(&mut self.cargo_diagnostics_fetch.diagnostic_sources)
+ .iter()
+ .cloned()
+ {
+ cancel_gasks.push(cancel_flycheck(self.project.clone(), buffer_path, cx));
+ }
+
+ self.cargo_diagnostics_fetch.cancel_task = Some(cx.background_spawn(async move {
+ let _ = join_all(cancel_gasks).await;
+ log::info!("Finished fetching cargo diagnostics");
+ }));
}
/// Enqueue an update of all excerpts. Updates all paths that either
@@ -897,6 +657,30 @@ impl ProjectDiagnosticsEditor {
})
})
}
+
+ pub fn cargo_diagnostics_sources(&self, cx: &App) -> Vec<ProjectPath> {
+ let fetch_cargo_diagnostics = ProjectSettings::get_global(cx)
+ .diagnostics
+ .fetch_cargo_diagnostics();
+ if !fetch_cargo_diagnostics {
+ return Vec::new();
+ }
+ self.project
+ .read(cx)
+ .worktrees(cx)
+ .filter_map(|worktree| {
+ let _cargo_toml_entry = worktree.read(cx).entry_for_path("Cargo.toml")?;
+ let rust_file_entry = worktree.read(cx).entries(false, 0).find(|entry| {
+ entry
+ .path
+ .extension()
+ .and_then(|extension| extension.to_str())
+ == Some("rs")
+ })?;
+ self.project.read(cx).path_for_entry(rust_file_entry.id, cx)
+ })
+ .collect()
+ }
}
impl Focusable for ProjectDiagnosticsEditor {
@@ -1286,7 +1070,3 @@ fn is_line_blank_or_indented_less(
let line_indent = snapshot.line_indent_for_row(row);
line_indent.is_line_blank() || line_indent.len(tab_size) < indent_level
}
-
-fn fetch_cargo_diagnostics_token() -> String {
- "fetch_cargo_diagnostics".to_string()
-}
@@ -1,6 +1,5 @@
use std::sync::Arc;
-use crate::cargo::cargo_diagnostics_sources;
use crate::{ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh};
use gpui::{Context, Entity, EventEmitter, ParentElement, Render, WeakEntity, Window};
use ui::prelude::*;
@@ -16,11 +15,9 @@ impl Render for ToolbarControls {
let mut include_warnings = false;
let mut has_stale_excerpts = false;
let mut is_updating = false;
- let cargo_diagnostics_sources = Arc::new(
- self.diagnostics()
- .map(|editor| cargo_diagnostics_sources(editor.read(cx), cx))
- .unwrap_or_default(),
- );
+ let cargo_diagnostics_sources = Arc::new(self.diagnostics().map_or(Vec::new(), |editor| {
+ editor.read(cx).cargo_diagnostics_sources(cx)
+ }));
let fetch_cargo_diagnostics = !cargo_diagnostics_sources.is_empty();
if let Some(editor) = self.diagnostics() {
@@ -28,7 +25,7 @@ impl Render for ToolbarControls {
include_warnings = diagnostics.include_warnings;
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
is_updating = if fetch_cargo_diagnostics {
- diagnostics.cargo_diagnostics_fetch.task.is_some()
+ diagnostics.cargo_diagnostics_fetch.fetch_task.is_some()
} else {
diagnostics.update_excerpts_task.is_some()
|| diagnostics
@@ -93,7 +90,6 @@ impl Render for ToolbarControls {
if fetch_cargo_diagnostics {
diagnostics.fetch_cargo_diagnostics(
cargo_diagnostics_sources,
- window,
cx,
);
} else {
@@ -249,7 +249,9 @@ actions!(
ApplyDiffHunk,
Backspace,
Cancel,
+ CancelFlycheck,
CancelLanguageServerWork,
+ ClearFlycheck,
ConfirmRename,
ConfirmCompletionInsert,
ConfirmCompletionReplace,
@@ -372,6 +374,7 @@ actions!(
RevertFile,
ReloadFile,
Rewrap,
+ RunFlycheck,
ScrollCursorBottom,
ScrollCursorCenter,
ScrollCursorCenterTopBottom,
@@ -5,18 +5,19 @@ use gpui::{App, AppContext as _, Context, Entity, Window};
use language::{Capability, Language, proto::serialize_anchor};
use multi_buffer::MultiBuffer;
use project::{
+ ProjectItem,
lsp_command::location_link_from_proto,
lsp_store::{
lsp_ext_command::{DocsUrls, ExpandMacro, ExpandedMacro},
- rust_analyzer_ext::RUST_ANALYZER_NAME,
+ rust_analyzer_ext::{RUST_ANALYZER_NAME, cancel_flycheck, clear_flycheck, run_flycheck},
},
};
use rpc::proto;
use text::ToPointUtf16;
use crate::{
- Editor, ExpandMacroRecursively, GoToParentModule, GotoDefinitionKind, OpenDocs,
- element::register_action, hover_links::HoverLink,
+ CancelFlycheck, ClearFlycheck, Editor, ExpandMacroRecursively, GoToParentModule,
+ GotoDefinitionKind, OpenDocs, RunFlycheck, element::register_action, hover_links::HoverLink,
lsp_ext::find_specific_language_server_in_selection,
};
@@ -37,6 +38,9 @@ pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &
register_action(&editor, window, go_to_parent_module);
register_action(&editor, window, expand_macro_recursively);
register_action(&editor, window, open_docs);
+ register_action(&editor, window, cancel_flycheck_action);
+ register_action(&editor, window, run_flycheck_action);
+ register_action(&editor, window, clear_flycheck_action);
}
}
@@ -300,3 +304,87 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
})
.detach_and_log_err(cx);
}
+
+fn cancel_flycheck_action(
+ editor: &mut Editor,
+ _: &CancelFlycheck,
+ _: &mut Window,
+ cx: &mut Context<Editor>,
+) {
+ let Some(project) = &editor.project else {
+ return;
+ };
+ let Some(buffer_id) = editor
+ .selections
+ .disjoint_anchors()
+ .iter()
+ .find_map(|selection| {
+ let buffer_id = selection.start.buffer_id.or(selection.end.buffer_id)?;
+ let project = project.read(cx);
+ let entry_id = project
+ .buffer_for_id(buffer_id, cx)?
+ .read(cx)
+ .entry_id(cx)?;
+ project.path_for_entry(entry_id, cx)
+ })
+ else {
+ return;
+ };
+ cancel_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
+}
+
+fn run_flycheck_action(
+ editor: &mut Editor,
+ _: &RunFlycheck,
+ _: &mut Window,
+ cx: &mut Context<Editor>,
+) {
+ let Some(project) = &editor.project else {
+ return;
+ };
+ let Some(buffer_id) = editor
+ .selections
+ .disjoint_anchors()
+ .iter()
+ .find_map(|selection| {
+ let buffer_id = selection.start.buffer_id.or(selection.end.buffer_id)?;
+ let project = project.read(cx);
+ let entry_id = project
+ .buffer_for_id(buffer_id, cx)?
+ .read(cx)
+ .entry_id(cx)?;
+ project.path_for_entry(entry_id, cx)
+ })
+ else {
+ return;
+ };
+ run_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
+}
+
+fn clear_flycheck_action(
+ editor: &mut Editor,
+ _: &ClearFlycheck,
+ _: &mut Window,
+ cx: &mut Context<Editor>,
+) {
+ let Some(project) = &editor.project else {
+ return;
+ };
+ let Some(buffer_id) = editor
+ .selections
+ .disjoint_anchors()
+ .iter()
+ .find_map(|selection| {
+ let buffer_id = selection.start.buffer_id.or(selection.end.buffer_id)?;
+ let project = project.read(cx);
+ let entry_id = project
+ .buffer_for_id(buffer_id, cx)?
+ .read(cx)
+ .entry_id(cx)?;
+ project.path_for_entry(entry_id, cx)
+ })
+ else {
+ return;
+ };
+ clear_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
+}
@@ -260,15 +260,6 @@ impl LspAdapter for RustLspAdapter {
Some("rust-analyzer/flycheck".into())
}
- fn retain_old_diagnostic(&self, previous_diagnostic: &Diagnostic, cx: &App) -> bool {
- let zed_provides_cargo_diagnostics = ProjectSettings::get_global(cx)
- .diagnostics
- .fetch_cargo_diagnostics();
- // Zed manages the lifecycle of cargo diagnostics when configured so.
- zed_provides_cargo_diagnostics
- && previous_diagnostic.source.as_deref() == Some(CARGO_DIAGNOSTICS_SOURCE_NAME)
- }
-
fn process_diagnostics(
&self,
params: &mut lsp::PublishDiagnosticsParams,
@@ -516,10 +507,10 @@ impl LspAdapter for RustLspAdapter {
}
}
- let zed_provides_cargo_diagnostics = ProjectSettings::get_global(cx)
+ let cargo_diagnostics_fetched_separately = ProjectSettings::get_global(cx)
.diagnostics
.fetch_cargo_diagnostics();
- if zed_provides_cargo_diagnostics {
+ if cargo_diagnostics_fetched_separately {
let disable_check_on_save = json!({
"checkOnSave": false,
});
@@ -8,6 +8,7 @@ use crate::{
buffer_store::{BufferStore, BufferStoreEvent},
environment::ProjectEnvironment,
lsp_command::{self, *},
+ lsp_store,
manifest_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ManifestTree},
prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings},
@@ -3396,7 +3397,7 @@ pub struct LanguageServerStatus {
pub name: String,
pub pending_work: BTreeMap<String, LanguageServerProgress>,
pub has_pending_diagnostic_updates: bool,
- pub progress_tokens: HashSet<String>,
+ progress_tokens: HashSet<String>,
}
#[derive(Clone, Debug)]
@@ -3449,8 +3450,14 @@ impl LspStore {
client.add_entity_request_handler(Self::handle_lsp_command::<PerformRename>);
client.add_entity_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
+ client.add_entity_request_handler(Self::handle_lsp_ext_cancel_flycheck);
+ client.add_entity_request_handler(Self::handle_lsp_ext_run_flycheck);
+ client.add_entity_request_handler(Self::handle_lsp_ext_clear_flycheck);
client.add_entity_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
client.add_entity_request_handler(Self::handle_lsp_command::<lsp_ext_command::OpenDocs>);
+ client.add_entity_request_handler(
+ Self::handle_lsp_command::<lsp_ext_command::GoToParentModule>,
+ );
client.add_entity_request_handler(
Self::handle_lsp_command::<lsp_ext_command::GetLspRunnables>,
);
@@ -6236,13 +6243,6 @@ impl LspStore {
})
}
- pub fn language_server_with_name(&self, name: &str, cx: &App) -> Option<LanguageServerId> {
- self.as_local()?
- .lsp_tree
- .read(cx)
- .server_id_for_name(&LanguageServerName::from(name))
- }
-
pub fn language_servers_for_local_buffer<'a>(
&'a self,
buffer: &Buffer,
@@ -7028,37 +7028,26 @@ impl LspStore {
mut cx: AsyncApp,
) -> Result<proto::LanguageServerIdForNameResponse> {
let name = &envelope.payload.name;
- match envelope.payload.buffer_id {
- Some(buffer_id) => {
- let buffer_id = BufferId::new(buffer_id)?;
- lsp_store
- .update(&mut cx, |lsp_store, cx| {
- let buffer = lsp_store.buffer_store.read(cx).get_existing(buffer_id)?;
- let server_id = buffer.update(cx, |buffer, cx| {
- lsp_store
- .language_servers_for_local_buffer(buffer, cx)
- .find_map(|(adapter, server)| {
- if adapter.name.0.as_ref() == name {
- Some(server.server_id())
- } else {
- None
- }
- })
- });
- Ok(server_id)
- })?
- .map(|server_id| proto::LanguageServerIdForNameResponse {
- server_id: server_id.map(|id| id.to_proto()),
- })
- }
- None => lsp_store.update(&mut cx, |lsp_store, cx| {
- proto::LanguageServerIdForNameResponse {
- server_id: lsp_store
- .language_server_with_name(name, cx)
- .map(|id| id.to_proto()),
- }
- }),
- }
+ let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
+ lsp_store
+ .update(&mut cx, |lsp_store, cx| {
+ let buffer = lsp_store.buffer_store.read(cx).get_existing(buffer_id)?;
+ let server_id = buffer.update(cx, |buffer, cx| {
+ lsp_store
+ .language_servers_for_local_buffer(buffer, cx)
+ .find_map(|(adapter, server)| {
+ if adapter.name.0.as_ref() == name {
+ Some(server.server_id())
+ } else {
+ None
+ }
+ })
+ });
+ Ok(server_id)
+ })?
+ .map(|server_id| proto::LanguageServerIdForNameResponse {
+ server_id: server_id.map(|id| id.to_proto()),
+ })
}
async fn handle_rename_project_entry(
@@ -7282,6 +7271,77 @@ impl LspStore {
})
}
+ async fn handle_lsp_ext_cancel_flycheck(
+ lsp_store: Entity<Self>,
+ envelope: TypedEnvelope<proto::LspExtCancelFlycheck>,
+ mut cx: AsyncApp,
+ ) -> Result<proto::Ack> {
+ let server_id = LanguageServerId(envelope.payload.language_server_id as usize);
+ lsp_store.update(&mut cx, |lsp_store, _| {
+ if let Some(server) = lsp_store.language_server_for_id(server_id) {
+ server
+ .notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&())
+ .context("handling lsp ext cancel flycheck")
+ } else {
+ anyhow::Ok(())
+ }
+ })??;
+
+ Ok(proto::Ack {})
+ }
+
+ async fn handle_lsp_ext_run_flycheck(
+ lsp_store: Entity<Self>,
+ envelope: TypedEnvelope<proto::LspExtRunFlycheck>,
+ mut cx: AsyncApp,
+ ) -> Result<proto::Ack> {
+ let server_id = LanguageServerId(envelope.payload.language_server_id as usize);
+ lsp_store.update(&mut cx, |lsp_store, cx| {
+ if let Some(server) = lsp_store.language_server_for_id(server_id) {
+ let text_document = if envelope.payload.current_file_only {
+ let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
+ lsp_store
+ .buffer_store()
+ .read(cx)
+ .get(buffer_id)
+ .and_then(|buffer| Some(buffer.read(cx).file()?.as_local()?.abs_path(cx)))
+ .map(|path| make_text_document_identifier(&path))
+ .transpose()?
+ } else {
+ None
+ };
+ server
+ .notify::<lsp_store::lsp_ext_command::LspExtRunFlycheck>(
+ &lsp_store::lsp_ext_command::RunFlycheckParams { text_document },
+ )
+ .context("handling lsp ext run flycheck")
+ } else {
+ anyhow::Ok(())
+ }
+ })??;
+
+ Ok(proto::Ack {})
+ }
+
+ async fn handle_lsp_ext_clear_flycheck(
+ lsp_store: Entity<Self>,
+ envelope: TypedEnvelope<proto::LspExtClearFlycheck>,
+ mut cx: AsyncApp,
+ ) -> Result<proto::Ack> {
+ let server_id = LanguageServerId(envelope.payload.language_server_id as usize);
+ lsp_store.update(&mut cx, |lsp_store, _| {
+ if let Some(server) = lsp_store.language_server_for_id(server_id) {
+ server
+ .notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&())
+ .context("handling lsp ext clear flycheck")
+ } else {
+ anyhow::Ok(())
+ }
+ })??;
+
+ Ok(proto::Ack {})
+ }
+
pub fn disk_based_diagnostics_started(
&mut self,
language_server_id: LanguageServerId,
@@ -7534,7 +7594,7 @@ impl LspStore {
}
}
- pub fn on_lsp_progress(
+ fn on_lsp_progress(
&mut self,
progress: lsp::ProgressParams,
language_server_id: LanguageServerId,
@@ -25,9 +25,9 @@ use std::{
use task::TaskTemplate;
use text::{BufferId, PointUtf16, ToPointUtf16};
-pub enum LspExpandMacro {}
+pub enum LspExtExpandMacro {}
-impl lsp::request::Request for LspExpandMacro {
+impl lsp::request::Request for LspExtExpandMacro {
type Params = ExpandMacroParams;
type Result = Option<ExpandedMacro>;
const METHOD: &'static str = "rust-analyzer/expandMacro";
@@ -60,7 +60,7 @@ pub struct ExpandMacro {
#[async_trait(?Send)]
impl LspCommand for ExpandMacro {
type Response = ExpandedMacro;
- type LspRequest = LspExpandMacro;
+ type LspRequest = LspExtExpandMacro;
type ProtoRequest = proto::LspExtExpandMacro;
fn display_name(&self) -> &str {
@@ -753,3 +753,33 @@ impl LspCommand for GetLspRunnables {
BufferId::new(message.buffer_id)
}
}
+
+#[derive(Debug)]
+pub struct LspExtCancelFlycheck {}
+
+#[derive(Debug)]
+pub struct LspExtRunFlycheck {}
+
+#[derive(Debug)]
+pub struct LspExtClearFlycheck {}
+
+impl lsp::notification::Notification for LspExtCancelFlycheck {
+ type Params = ();
+ const METHOD: &'static str = "rust-analyzer/cancelFlycheck";
+}
+
+impl lsp::notification::Notification for LspExtRunFlycheck {
+ type Params = RunFlycheckParams;
+ const METHOD: &'static str = "rust-analyzer/runFlycheck";
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct RunFlycheckParams {
+ pub text_document: Option<lsp::TextDocumentIdentifier>,
+}
+
+impl lsp::notification::Notification for LspExtClearFlycheck {
+ type Params = ();
+ const METHOD: &'static str = "rust-analyzer/clearFlycheck";
+}
@@ -1,8 +1,12 @@
use ::serde::{Deserialize, Serialize};
-use gpui::{PromptLevel, WeakEntity};
+use anyhow::Context as _;
+use gpui::{App, Entity, PromptLevel, Task, WeakEntity};
use lsp::LanguageServer;
+use rpc::proto;
-use crate::{LanguageServerPromptRequest, LspStore, LspStoreEvent};
+use crate::{
+ LanguageServerPromptRequest, LspStore, LspStoreEvent, Project, ProjectPath, lsp_store,
+};
pub const RUST_ANALYZER_NAME: &str = "rust-analyzer";
pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
@@ -79,3 +83,161 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
})
.detach();
}
+
+pub fn cancel_flycheck(
+ project: Entity<Project>,
+ buffer_path: ProjectPath,
+ cx: &mut App,
+) -> Task<anyhow::Result<()>> {
+ let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
+ let lsp_store = project.read(cx).lsp_store();
+ let buffer = project.update(cx, |project, cx| {
+ project.buffer_store().update(cx, |buffer_store, cx| {
+ buffer_store.open_buffer(buffer_path, cx)
+ })
+ });
+
+ cx.spawn(async move |cx| {
+ let buffer = buffer.await?;
+ let Some(rust_analyzer_server) = project
+ .update(cx, |project, cx| {
+ buffer.update(cx, |buffer, cx| {
+ project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
+ })
+ })?
+ .await
+ else {
+ return Ok(());
+ };
+ let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id().to_proto())?;
+
+ if let Some((client, project_id)) = upstream_client {
+ let request = proto::LspExtCancelFlycheck {
+ project_id,
+ buffer_id,
+ language_server_id: rust_analyzer_server.to_proto(),
+ };
+ client
+ .request(request)
+ .await
+ .context("lsp ext cancel flycheck proto request")?;
+ } else {
+ lsp_store
+ .update(cx, |lsp_store, _| {
+ if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
+ server.notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&())?;
+ }
+ anyhow::Ok(())
+ })?
+ .context("lsp ext cancel flycheck")?;
+ };
+ anyhow::Ok(())
+ })
+}
+
+pub fn run_flycheck(
+ project: Entity<Project>,
+ buffer_path: ProjectPath,
+ cx: &mut App,
+) -> Task<anyhow::Result<()>> {
+ let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
+ let lsp_store = project.read(cx).lsp_store();
+ let buffer = project.update(cx, |project, cx| {
+ project.buffer_store().update(cx, |buffer_store, cx| {
+ buffer_store.open_buffer(buffer_path, cx)
+ })
+ });
+
+ cx.spawn(async move |cx| {
+ let buffer = buffer.await?;
+ let Some(rust_analyzer_server) = project
+ .update(cx, |project, cx| {
+ buffer.update(cx, |buffer, cx| {
+ project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
+ })
+ })?
+ .await
+ else {
+ return Ok(());
+ };
+ let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id().to_proto())?;
+
+ if let Some((client, project_id)) = upstream_client {
+ let request = proto::LspExtRunFlycheck {
+ project_id,
+ buffer_id,
+ language_server_id: rust_analyzer_server.to_proto(),
+ current_file_only: false,
+ };
+ client
+ .request(request)
+ .await
+ .context("lsp ext run flycheck proto request")?;
+ } else {
+ lsp_store
+ .update(cx, |lsp_store, _| {
+ if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
+ server.notify::<lsp_store::lsp_ext_command::LspExtRunFlycheck>(
+ &lsp_store::lsp_ext_command::RunFlycheckParams {
+ text_document: None,
+ },
+ )?;
+ }
+ anyhow::Ok(())
+ })?
+ .context("lsp ext run flycheck")?;
+ };
+ anyhow::Ok(())
+ })
+}
+
+pub fn clear_flycheck(
+ project: Entity<Project>,
+ buffer_path: ProjectPath,
+ cx: &mut App,
+) -> Task<anyhow::Result<()>> {
+ let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
+ let lsp_store = project.read(cx).lsp_store();
+ let buffer = project.update(cx, |project, cx| {
+ project.buffer_store().update(cx, |buffer_store, cx| {
+ buffer_store.open_buffer(buffer_path, cx)
+ })
+ });
+
+ cx.spawn(async move |cx| {
+ let buffer = buffer.await?;
+ let Some(rust_analyzer_server) = project
+ .update(cx, |project, cx| {
+ buffer.update(cx, |buffer, cx| {
+ project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
+ })
+ })?
+ .await
+ else {
+ return Ok(());
+ };
+ let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id().to_proto())?;
+
+ if let Some((client, project_id)) = upstream_client {
+ let request = proto::LspExtClearFlycheck {
+ project_id,
+ buffer_id,
+ language_server_id: rust_analyzer_server.to_proto(),
+ };
+ client
+ .request(request)
+ .await
+ .context("lsp ext clear flycheck proto request")?;
+ } else {
+ lsp_store
+ .update(cx, |lsp_store, _| {
+ if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
+ server.notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&())?;
+ }
+ anyhow::Ok(())
+ })?
+ .context("lsp ext clear flycheck")?;
+ };
+ anyhow::Ok(())
+ })
+}
@@ -247,20 +247,6 @@ impl LanguageServerTree {
self.languages.adapter_for_name(name)
}
- pub fn server_id_for_name(&self, name: &LanguageServerName) -> Option<LanguageServerId> {
- self.instances
- .values()
- .flat_map(|instance| instance.roots.values())
- .flatten()
- .find_map(|(server_name, (data, _))| {
- if server_name == name {
- data.id.get().copied()
- } else {
- None
- }
- })
- }
-
fn adapters_for_language(
&self,
settings_location: SettingsLocation,
@@ -4748,42 +4748,6 @@ impl Project {
})
}
- pub fn language_server_with_name(
- &self,
- name: &str,
- cx: &App,
- ) -> Task<Option<LanguageServerId>> {
- if self.is_local() {
- Task::ready(self.lsp_store.read(cx).language_server_with_name(name, cx))
- } else if let Some(project_id) = self.remote_id() {
- let request = self.client.request(proto::LanguageServerIdForName {
- project_id,
- buffer_id: None,
- name: name.to_string(),
- });
- cx.background_spawn(async move {
- let response = request.await.log_err()?;
- response.server_id.map(LanguageServerId::from_proto)
- })
- } else if let Some(ssh_client) = self.ssh_client.as_ref() {
- let request =
- ssh_client
- .read(cx)
- .proto_client()
- .request(proto::LanguageServerIdForName {
- project_id: SSH_PROJECT_ID,
- buffer_id: None,
- name: name.to_string(),
- });
- cx.background_spawn(async move {
- let response = request.await.log_err()?;
- response.server_id.map(LanguageServerId::from_proto)
- })
- } else {
- Task::ready(None)
- }
- }
-
pub fn language_server_id_for_name(
&self,
buffer: &Buffer,
@@ -4805,7 +4769,7 @@ impl Project {
} else if let Some(project_id) = self.remote_id() {
let request = self.client.request(proto::LanguageServerIdForName {
project_id,
- buffer_id: Some(buffer.remote_id().to_proto()),
+ buffer_id: buffer.remote_id().to_proto(),
name: name.to_string(),
});
cx.background_spawn(async move {
@@ -4819,7 +4783,7 @@ impl Project {
.proto_client()
.request(proto::LanguageServerIdForName {
project_id: SSH_PROJECT_ID,
- buffer_id: Some(buffer.remote_id().to_proto()),
+ buffer_id: buffer.remote_id().to_proto(),
name: name.to_string(),
});
cx.background_spawn(async move {
@@ -155,37 +155,12 @@ pub struct InlineDiagnosticsSettings {
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct CargoDiagnosticsSettings {
- /// When enabled, Zed runs `cargo check --message-format=json`-based commands and
- /// collect cargo diagnostics instead of rust-analyzer.
+ /// When enabled, Zed disables rust-analyzer's check on save and starts to query
+ /// Cargo diagnostics separately.
///
/// Default: false
#[serde(default)]
pub fetch_cargo_diagnostics: bool,
-
- /// A command override for fetching the cargo diagnostics.
- /// First argument is the command, followed by the arguments.
- ///
- /// Default: ["cargo", "check", "--quiet", "--workspace", "--message-format=json", "--all-targets", "--keep-going"]
- #[serde(default = "default_diagnostics_fetch_command")]
- pub diagnostics_fetch_command: Vec<String>,
-
- /// Extra environment variables to pass to the diagnostics fetch command.
- ///
- /// Default: {}
- #[serde(default)]
- pub env: HashMap<String, String>,
-}
-
-fn default_diagnostics_fetch_command() -> Vec<String> {
- vec![
- "cargo".to_string(),
- "check".to_string(),
- "--quiet".to_string(),
- "--workspace".to_string(),
- "--message-format=json".to_string(),
- "--all-targets".to_string(),
- "--keep-going".to_string(),
- ]
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
@@ -706,7 +706,7 @@ message LspResponse {
message LanguageServerIdForName {
uint64 project_id = 1;
- optional uint64 buffer_id = 2;
+ uint64 buffer_id = 2;
string name = 3;
}
@@ -728,3 +728,22 @@ message LspRunnable {
bytes task_template = 1;
optional LocationLink location = 2;
}
+
+message LspExtCancelFlycheck {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+ uint64 language_server_id = 3;
+}
+
+message LspExtRunFlycheck {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+ uint64 language_server_id = 3;
+ bool current_file_only = 4;
+}
+
+message LspExtClearFlycheck {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+ uint64 language_server_id = 3;
+}
@@ -381,7 +381,10 @@ message Envelope {
DebugRequest debug_request = 342;
LspExtGoToParentModule lsp_ext_go_to_parent_module = 343;
- LspExtGoToParentModuleResponse lsp_ext_go_to_parent_module_response = 344;// current max
+ LspExtGoToParentModuleResponse lsp_ext_go_to_parent_module_response = 344;
+ LspExtCancelFlycheck lsp_ext_cancel_flycheck = 345;
+ LspExtRunFlycheck lsp_ext_run_flycheck = 346;
+ LspExtClearFlycheck lsp_ext_clear_flycheck = 347; // current max
}
reserved 87 to 88;
@@ -171,6 +171,9 @@ messages!(
(LspExtSwitchSourceHeaderResponse, Background),
(LspExtGoToParentModule, Background),
(LspExtGoToParentModuleResponse, Background),
+ (LspExtCancelFlycheck, Background),
+ (LspExtRunFlycheck, Background),
+ (LspExtClearFlycheck, Background),
(MarkNotificationRead, Foreground),
(MoveChannel, Foreground),
(MultiLspQuery, Background),
@@ -425,6 +428,9 @@ request_messages!(
(SynchronizeContexts, SynchronizeContextsResponse),
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(LspExtGoToParentModule, LspExtGoToParentModuleResponse),
+ (LspExtCancelFlycheck, Ack),
+ (LspExtRunFlycheck, Ack),
+ (LspExtClearFlycheck, Ack),
(AddWorktree, AddWorktreeResponse),
(ShutdownRemoteServer, Ack),
(RemoveWorktree, Ack),
@@ -548,6 +554,9 @@ entity_messages!(
SynchronizeContexts,
LspExtSwitchSourceHeader,
LspExtGoToParentModule,
+ LspExtCancelFlycheck,
+ LspExtRunFlycheck,
+ LspExtClearFlycheck,
LanguageServerLog,
Toast,
HideToast,