Use rust-analyzer's flycheck as source of cargo diagnostics (#29779)
Follow-up of https://github.com/zed-industries/zed/pull/29706 Instead of doing `cargo check` manually, use rust-analyzer's flycheck: at the cost of more sophisticated check command configuration, we keep much less code in Zed, and get a proper progress report. User-facing UI does not change except `diagnostics_fetch_command` and `env` settings removed from the diagnostics settings. Release Notes: - N/A
This commit is contained in:
parent
672a1dd553
commit
ba59305510
20 changed files with 520 additions and 1071 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4399,7 +4399,6 @@ name = "diagnostics"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_metadata",
|
|
||||||
"client",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"component",
|
"component",
|
||||||
|
@ -4409,7 +4408,6 @@ dependencies = [
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"gpui",
|
"gpui",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools 0.14.0",
|
|
||||||
"language",
|
"language",
|
||||||
"linkme",
|
"linkme",
|
||||||
"log",
|
"log",
|
||||||
|
@ -4421,7 +4419,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"smol",
|
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
"ui",
|
"ui",
|
||||||
|
|
|
@ -935,22 +935,9 @@
|
||||||
"max_severity": null
|
"max_severity": null
|
||||||
},
|
},
|
||||||
"rust": {
|
"rust": {
|
||||||
// When enabled, Zed runs `cargo check --message-format=json`-based commands and
|
// When enabled, Zed disables rust-analyzer's check on save and starts to query
|
||||||
// collect cargo diagnostics instead of rust-analyzer.
|
// Cargo diagnostics separately.
|
||||||
"fetch_cargo_diagnostics": false,
|
"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": {}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
|
// 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>,
|
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::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(
|
.add_request_handler(
|
||||||
forward_read_only_project_request::<proto::LanguageServerIdForName>,
|
forward_read_only_project_request::<proto::LanguageServerIdForName>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,7 +25,7 @@ use language::{
|
||||||
use project::{
|
use project::{
|
||||||
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||||
lsp_store::{
|
lsp_store::{
|
||||||
lsp_ext_command::{ExpandedMacro, LspExpandMacro},
|
lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
|
||||||
rust_analyzer_ext::RUST_ANALYZER_NAME,
|
rust_analyzer_ext::RUST_ANALYZER_NAME,
|
||||||
},
|
},
|
||||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
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();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
|
|
||||||
// host
|
// host
|
||||||
let mut expand_request_a =
|
let mut expand_request_a = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
|
||||||
fake_language_server.set_request_handler::<LspExpandMacro, _, _>(|params, _| async move {
|
|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
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(),
|
name: "test_macro_name".to_string(),
|
||||||
expansion: "test_macro_expansion on the host".to_string(),
|
expansion: "test_macro_expansion on the host".to_string(),
|
||||||
}))
|
}))
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||||
expand_macro_recursively(editor, &ExpandMacroRecursively, 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
|
// client
|
||||||
let mut expand_request_b =
|
let mut expand_request_b = fake_language_server.set_request_handler::<LspExtExpandMacro, _, _>(
|
||||||
fake_language_server.set_request_handler::<LspExpandMacro, _, _>(|params, _| async move {
|
|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
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(),
|
name: "test_macro_name".to_string(),
|
||||||
expansion: "test_macro_expansion on the client".to_string(),
|
expansion: "test_macro_expansion on the client".to_string(),
|
||||||
}))
|
}))
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||||
expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
|
expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
|
||||||
|
|
|
@ -14,7 +14,6 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
cargo_metadata.workspace = true
|
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
component.workspace = true
|
component.workspace = true
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
|
@ -23,7 +22,6 @@ env_logger.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
itertools.workspace = true
|
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
linkme.workspace = true
|
linkme.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
@ -34,7 +32,6 @@ rand.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smol.workspace = true
|
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
ui.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;
|
pub mod items;
|
||||||
mod toolbar_controls;
|
mod toolbar_controls;
|
||||||
|
|
||||||
|
@ -8,18 +7,14 @@ mod diagnostic_renderer;
|
||||||
mod diagnostics_tests;
|
mod diagnostics_tests;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cargo::{
|
use collections::{BTreeSet, HashMap};
|
||||||
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 diagnostic_renderer::DiagnosticBlock;
|
use diagnostic_renderer::DiagnosticBlock;
|
||||||
use editor::{
|
use editor::{
|
||||||
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
||||||
scroll::Autoscroll,
|
scroll::Autoscroll,
|
||||||
};
|
};
|
||||||
|
use futures::future::join_all;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
|
Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||||
|
@ -28,10 +23,10 @@ use gpui::{
|
||||||
use language::{
|
use language::{
|
||||||
Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint,
|
Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint,
|
||||||
};
|
};
|
||||||
use lsp::{DiagnosticSeverity, LanguageServerId};
|
use lsp::DiagnosticSeverity;
|
||||||
use project::{
|
use project::{
|
||||||
DiagnosticSummary, Project, ProjectPath, Worktree,
|
DiagnosticSummary, Project, ProjectPath,
|
||||||
lsp_store::rust_analyzer_ext::{CARGO_DIAGNOSTICS_SOURCE_NAME, RUST_ANALYZER_NAME},
|
lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck},
|
||||||
project_settings::ProjectSettings,
|
project_settings::ProjectSettings,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -84,8 +79,9 @@ pub(crate) struct ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CargoDiagnosticsFetchState {
|
struct CargoDiagnosticsFetchState {
|
||||||
task: Option<Task<()>>,
|
fetch_task: Option<Task<()>>,
|
||||||
rust_analyzer: Option<LanguageServerId>,
|
cancel_task: Option<Task<()>>,
|
||||||
|
diagnostic_sources: Arc<Vec<ProjectPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
|
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
|
||||||
|
@ -252,8 +248,9 @@ impl ProjectDiagnosticsEditor {
|
||||||
paths_to_update: Default::default(),
|
paths_to_update: Default::default(),
|
||||||
update_excerpts_task: None,
|
update_excerpts_task: None,
|
||||||
cargo_diagnostics_fetch: CargoDiagnosticsFetchState {
|
cargo_diagnostics_fetch: CargoDiagnosticsFetchState {
|
||||||
task: None,
|
fetch_task: None,
|
||||||
rust_analyzer: None,
|
cancel_task: None,
|
||||||
|
diagnostic_sources: Arc::new(Vec::new()),
|
||||||
},
|
},
|
||||||
_subscription: project_event_subscription,
|
_subscription: project_event_subscription,
|
||||||
};
|
};
|
||||||
|
@ -346,7 +343,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
.fetch_cargo_diagnostics();
|
.fetch_cargo_diagnostics();
|
||||||
|
|
||||||
if 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);
|
self.stop_cargo_diagnostics_fetch(cx);
|
||||||
} else {
|
} else {
|
||||||
self.update_all_diagnostics(window, cx);
|
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>) {
|
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() {
|
if cargo_diagnostics_sources.is_empty() {
|
||||||
self.update_all_excerpts(window, cx);
|
self.update_all_excerpts(window, cx);
|
||||||
} else {
|
} 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(
|
fn fetch_cargo_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
diagnostics_sources: Arc<Vec<Entity<Worktree>>>,
|
diagnostics_sources: Arc<Vec<ProjectPath>>,
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.cargo_diagnostics_fetch.task = Some(cx.spawn_in(window, async move |editor, cx| {
|
let project = self.project.clone();
|
||||||
let rust_analyzer_server = editor
|
self.cargo_diagnostics_fetch.cancel_task = None;
|
||||||
.update(cx, |editor, cx| {
|
self.cargo_diagnostics_fetch.fetch_task = None;
|
||||||
editor
|
self.cargo_diagnostics_fetch.diagnostic_sources = diagnostics_sources.clone();
|
||||||
.project
|
if self.cargo_diagnostics_fetch.diagnostic_sources.is_empty() {
|
||||||
.read(cx)
|
return;
|
||||||
.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 mut worktree_diagnostics_tasks = Vec::new();
|
self.cargo_diagnostics_fetch.fetch_task = Some(cx.spawn(async move |editor, cx| {
|
||||||
let mut paths_with_reported_cargo_diagnostics = HashSet::default();
|
let mut fetch_tasks = Vec::new();
|
||||||
if let Some(rust_analyzer_server) = rust_analyzer_server {
|
for buffer_path in diagnostics_sources.iter().cloned() {
|
||||||
let can_continue = editor
|
if cx
|
||||||
.update(cx, |editor, cx| {
|
.update(|cx| {
|
||||||
editor.cargo_diagnostics_fetch.rust_analyzer = Some(rust_analyzer_server);
|
fetch_tasks.push(run_flycheck(project.clone(), buffer_path, cx));
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.is_err()
|
||||||
|
{
|
||||||
if can_continue {
|
break;
|
||||||
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"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = join_all(fetch_tasks).await;
|
||||||
let updated_urls = futures::future::join_all(worktree_diagnostics_tasks).await.into_iter().flatten().collect();
|
|
||||||
if let Some(rust_analyzer_server) = rust_analyzer_server {
|
|
||||||
editor
|
editor
|
||||||
.update_in(cx, |editor, window, cx| {
|
.update(cx, |editor, _| {
|
||||||
editor
|
editor.cargo_diagnostics_fetch.fetch_task = None;
|
||||||
.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();
|
|
||||||
})
|
})
|
||||||
.ok();
|
.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) {
|
fn stop_cargo_diagnostics_fetch(&mut self, cx: &mut App) {
|
||||||
self.update_cargo_fetch_status(FetchStatus::Finished, cx);
|
self.cargo_diagnostics_fetch.fetch_task = None;
|
||||||
self.cargo_diagnostics_fetch.task = None;
|
let mut cancel_gasks = Vec::new();
|
||||||
log::info!("Finished fetching cargo diagnostics");
|
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
|
/// 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 {
|
impl Focusable for ProjectDiagnosticsEditor {
|
||||||
|
@ -1286,7 +1070,3 @@ fn is_line_blank_or_indented_less(
|
||||||
let line_indent = snapshot.line_indent_for_row(row);
|
let line_indent = snapshot.line_indent_for_row(row);
|
||||||
line_indent.is_line_blank() || line_indent.len(tab_size) < indent_level
|
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 std::sync::Arc;
|
||||||
|
|
||||||
use crate::cargo::cargo_diagnostics_sources;
|
|
||||||
use crate::{ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh};
|
use crate::{ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh};
|
||||||
use gpui::{Context, Entity, EventEmitter, ParentElement, Render, WeakEntity, Window};
|
use gpui::{Context, Entity, EventEmitter, ParentElement, Render, WeakEntity, Window};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
|
@ -16,11 +15,9 @@ impl Render for ToolbarControls {
|
||||||
let mut include_warnings = false;
|
let mut include_warnings = false;
|
||||||
let mut has_stale_excerpts = false;
|
let mut has_stale_excerpts = false;
|
||||||
let mut is_updating = false;
|
let mut is_updating = false;
|
||||||
let cargo_diagnostics_sources = Arc::new(
|
let cargo_diagnostics_sources = Arc::new(self.diagnostics().map_or(Vec::new(), |editor| {
|
||||||
self.diagnostics()
|
editor.read(cx).cargo_diagnostics_sources(cx)
|
||||||
.map(|editor| cargo_diagnostics_sources(editor.read(cx), cx))
|
}));
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
let fetch_cargo_diagnostics = !cargo_diagnostics_sources.is_empty();
|
let fetch_cargo_diagnostics = !cargo_diagnostics_sources.is_empty();
|
||||||
|
|
||||||
if let Some(editor) = self.diagnostics() {
|
if let Some(editor) = self.diagnostics() {
|
||||||
|
@ -28,7 +25,7 @@ impl Render for ToolbarControls {
|
||||||
include_warnings = diagnostics.include_warnings;
|
include_warnings = diagnostics.include_warnings;
|
||||||
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
|
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
|
||||||
is_updating = if fetch_cargo_diagnostics {
|
is_updating = if fetch_cargo_diagnostics {
|
||||||
diagnostics.cargo_diagnostics_fetch.task.is_some()
|
diagnostics.cargo_diagnostics_fetch.fetch_task.is_some()
|
||||||
} else {
|
} else {
|
||||||
diagnostics.update_excerpts_task.is_some()
|
diagnostics.update_excerpts_task.is_some()
|
||||||
|| diagnostics
|
|| diagnostics
|
||||||
|
@ -93,7 +90,6 @@ impl Render for ToolbarControls {
|
||||||
if fetch_cargo_diagnostics {
|
if fetch_cargo_diagnostics {
|
||||||
diagnostics.fetch_cargo_diagnostics(
|
diagnostics.fetch_cargo_diagnostics(
|
||||||
cargo_diagnostics_sources,
|
cargo_diagnostics_sources,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -249,7 +249,9 @@ actions!(
|
||||||
ApplyDiffHunk,
|
ApplyDiffHunk,
|
||||||
Backspace,
|
Backspace,
|
||||||
Cancel,
|
Cancel,
|
||||||
|
CancelFlycheck,
|
||||||
CancelLanguageServerWork,
|
CancelLanguageServerWork,
|
||||||
|
ClearFlycheck,
|
||||||
ConfirmRename,
|
ConfirmRename,
|
||||||
ConfirmCompletionInsert,
|
ConfirmCompletionInsert,
|
||||||
ConfirmCompletionReplace,
|
ConfirmCompletionReplace,
|
||||||
|
@ -372,6 +374,7 @@ actions!(
|
||||||
RevertFile,
|
RevertFile,
|
||||||
ReloadFile,
|
ReloadFile,
|
||||||
Rewrap,
|
Rewrap,
|
||||||
|
RunFlycheck,
|
||||||
ScrollCursorBottom,
|
ScrollCursorBottom,
|
||||||
ScrollCursorCenter,
|
ScrollCursorCenter,
|
||||||
ScrollCursorCenterTopBottom,
|
ScrollCursorCenterTopBottom,
|
||||||
|
|
|
@ -5,18 +5,19 @@ use gpui::{App, AppContext as _, Context, Entity, Window};
|
||||||
use language::{Capability, Language, proto::serialize_anchor};
|
use language::{Capability, Language, proto::serialize_anchor};
|
||||||
use multi_buffer::MultiBuffer;
|
use multi_buffer::MultiBuffer;
|
||||||
use project::{
|
use project::{
|
||||||
|
ProjectItem,
|
||||||
lsp_command::location_link_from_proto,
|
lsp_command::location_link_from_proto,
|
||||||
lsp_store::{
|
lsp_store::{
|
||||||
lsp_ext_command::{DocsUrls, ExpandMacro, ExpandedMacro},
|
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 rpc::proto;
|
||||||
use text::ToPointUtf16;
|
use text::ToPointUtf16;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Editor, ExpandMacroRecursively, GoToParentModule, GotoDefinitionKind, OpenDocs,
|
CancelFlycheck, ClearFlycheck, Editor, ExpandMacroRecursively, GoToParentModule,
|
||||||
element::register_action, hover_links::HoverLink,
|
GotoDefinitionKind, OpenDocs, RunFlycheck, element::register_action, hover_links::HoverLink,
|
||||||
lsp_ext::find_specific_language_server_in_selection,
|
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, go_to_parent_module);
|
||||||
register_action(&editor, window, expand_macro_recursively);
|
register_action(&editor, window, expand_macro_recursively);
|
||||||
register_action(&editor, window, open_docs);
|
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);
|
.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())
|
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(
|
fn process_diagnostics(
|
||||||
&self,
|
&self,
|
||||||
params: &mut lsp::PublishDiagnosticsParams,
|
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
|
.diagnostics
|
||||||
.fetch_cargo_diagnostics();
|
.fetch_cargo_diagnostics();
|
||||||
if zed_provides_cargo_diagnostics {
|
if cargo_diagnostics_fetched_separately {
|
||||||
let disable_check_on_save = json!({
|
let disable_check_on_save = json!({
|
||||||
"checkOnSave": false,
|
"checkOnSave": false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
buffer_store::{BufferStore, BufferStoreEvent},
|
buffer_store::{BufferStore, BufferStoreEvent},
|
||||||
environment::ProjectEnvironment,
|
environment::ProjectEnvironment,
|
||||||
lsp_command::{self, *},
|
lsp_command::{self, *},
|
||||||
|
lsp_store,
|
||||||
manifest_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ManifestTree},
|
manifest_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ManifestTree},
|
||||||
prettier_store::{self, PrettierStore, PrettierStoreEvent},
|
prettier_store::{self, PrettierStore, PrettierStoreEvent},
|
||||||
project_settings::{LspSettings, ProjectSettings},
|
project_settings::{LspSettings, ProjectSettings},
|
||||||
|
@ -3396,7 +3397,7 @@ pub struct LanguageServerStatus {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub pending_work: BTreeMap<String, LanguageServerProgress>,
|
pub pending_work: BTreeMap<String, LanguageServerProgress>,
|
||||||
pub has_pending_diagnostic_updates: bool,
|
pub has_pending_diagnostic_updates: bool,
|
||||||
pub progress_tokens: HashSet<String>,
|
progress_tokens: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[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::<PerformRename>);
|
||||||
client.add_entity_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
|
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::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::OpenDocs>);
|
||||||
|
client.add_entity_request_handler(
|
||||||
|
Self::handle_lsp_command::<lsp_ext_command::GoToParentModule>,
|
||||||
|
);
|
||||||
client.add_entity_request_handler(
|
client.add_entity_request_handler(
|
||||||
Self::handle_lsp_command::<lsp_ext_command::GetLspRunnables>,
|
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>(
|
pub fn language_servers_for_local_buffer<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
|
@ -7028,37 +7028,26 @@ impl LspStore {
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncApp,
|
||||||
) -> Result<proto::LanguageServerIdForNameResponse> {
|
) -> Result<proto::LanguageServerIdForNameResponse> {
|
||||||
let name = &envelope.payload.name;
|
let name = &envelope.payload.name;
|
||||||
match envelope.payload.buffer_id {
|
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
|
||||||
Some(buffer_id) => {
|
lsp_store
|
||||||
let buffer_id = BufferId::new(buffer_id)?;
|
.update(&mut cx, |lsp_store, cx| {
|
||||||
lsp_store
|
let buffer = lsp_store.buffer_store.read(cx).get_existing(buffer_id)?;
|
||||||
.update(&mut cx, |lsp_store, cx| {
|
let server_id = buffer.update(cx, |buffer, cx| {
|
||||||
let buffer = lsp_store.buffer_store.read(cx).get_existing(buffer_id)?;
|
lsp_store
|
||||||
let server_id = buffer.update(cx, |buffer, cx| {
|
.language_servers_for_local_buffer(buffer, cx)
|
||||||
lsp_store
|
.find_map(|(adapter, server)| {
|
||||||
.language_servers_for_local_buffer(buffer, cx)
|
if adapter.name.0.as_ref() == name {
|
||||||
.find_map(|(adapter, server)| {
|
Some(server.server_id())
|
||||||
if adapter.name.0.as_ref() == name {
|
} else {
|
||||||
Some(server.server_id())
|
None
|
||||||
} else {
|
}
|
||||||
None
|
})
|
||||||
}
|
});
|
||||||
})
|
Ok(server_id)
|
||||||
});
|
})?
|
||||||
Ok(server_id)
|
.map(|server_id| proto::LanguageServerIdForNameResponse {
|
||||||
})?
|
server_id: server_id.map(|id| id.to_proto()),
|
||||||
.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()),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_rename_project_entry(
|
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(
|
pub fn disk_based_diagnostics_started(
|
||||||
&mut self,
|
&mut self,
|
||||||
language_server_id: LanguageServerId,
|
language_server_id: LanguageServerId,
|
||||||
|
@ -7534,7 +7594,7 @@ impl LspStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_lsp_progress(
|
fn on_lsp_progress(
|
||||||
&mut self,
|
&mut self,
|
||||||
progress: lsp::ProgressParams,
|
progress: lsp::ProgressParams,
|
||||||
language_server_id: LanguageServerId,
|
language_server_id: LanguageServerId,
|
||||||
|
|
|
@ -25,9 +25,9 @@ use std::{
|
||||||
use task::TaskTemplate;
|
use task::TaskTemplate;
|
||||||
use text::{BufferId, PointUtf16, ToPointUtf16};
|
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 Params = ExpandMacroParams;
|
||||||
type Result = Option<ExpandedMacro>;
|
type Result = Option<ExpandedMacro>;
|
||||||
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
||||||
|
@ -60,7 +60,7 @@ pub struct ExpandMacro {
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl LspCommand for ExpandMacro {
|
impl LspCommand for ExpandMacro {
|
||||||
type Response = ExpandedMacro;
|
type Response = ExpandedMacro;
|
||||||
type LspRequest = LspExpandMacro;
|
type LspRequest = LspExtExpandMacro;
|
||||||
type ProtoRequest = proto::LspExtExpandMacro;
|
type ProtoRequest = proto::LspExtExpandMacro;
|
||||||
|
|
||||||
fn display_name(&self) -> &str {
|
fn display_name(&self) -> &str {
|
||||||
|
@ -753,3 +753,33 @@ impl LspCommand for GetLspRunnables {
|
||||||
BufferId::new(message.buffer_id)
|
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 ::serde::{Deserialize, Serialize};
|
||||||
use gpui::{PromptLevel, WeakEntity};
|
use anyhow::Context as _;
|
||||||
|
use gpui::{App, Entity, PromptLevel, Task, WeakEntity};
|
||||||
use lsp::LanguageServer;
|
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 RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||||
pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
|
pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
|
||||||
|
@ -79,3 +83,161 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
|
||||||
})
|
})
|
||||||
.detach();
|
.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)
|
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(
|
fn adapters_for_language(
|
||||||
&self,
|
&self,
|
||||||
settings_location: SettingsLocation,
|
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(
|
pub fn language_server_id_for_name(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
|
@ -4805,7 +4769,7 @@ impl Project {
|
||||||
} else if let Some(project_id) = self.remote_id() {
|
} else if let Some(project_id) = self.remote_id() {
|
||||||
let request = self.client.request(proto::LanguageServerIdForName {
|
let request = self.client.request(proto::LanguageServerIdForName {
|
||||||
project_id,
|
project_id,
|
||||||
buffer_id: Some(buffer.remote_id().to_proto()),
|
buffer_id: buffer.remote_id().to_proto(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
});
|
});
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
|
@ -4819,7 +4783,7 @@ impl Project {
|
||||||
.proto_client()
|
.proto_client()
|
||||||
.request(proto::LanguageServerIdForName {
|
.request(proto::LanguageServerIdForName {
|
||||||
project_id: SSH_PROJECT_ID,
|
project_id: SSH_PROJECT_ID,
|
||||||
buffer_id: Some(buffer.remote_id().to_proto()),
|
buffer_id: buffer.remote_id().to_proto(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
});
|
});
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
|
|
|
@ -155,37 +155,12 @@ pub struct InlineDiagnosticsSettings {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct CargoDiagnosticsSettings {
|
pub struct CargoDiagnosticsSettings {
|
||||||
/// When enabled, Zed runs `cargo check --message-format=json`-based commands and
|
/// When enabled, Zed disables rust-analyzer's check on save and starts to query
|
||||||
/// collect cargo diagnostics instead of rust-analyzer.
|
/// Cargo diagnostics separately.
|
||||||
///
|
///
|
||||||
/// Default: false
|
/// Default: false
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub fetch_cargo_diagnostics: bool,
|
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)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
|
|
@ -706,7 +706,7 @@ message LspResponse {
|
||||||
|
|
||||||
message LanguageServerIdForName {
|
message LanguageServerIdForName {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
optional uint64 buffer_id = 2;
|
uint64 buffer_id = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -728,3 +728,22 @@ message LspRunnable {
|
||||||
bytes task_template = 1;
|
bytes task_template = 1;
|
||||||
optional LocationLink location = 2;
|
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;
|
DebugRequest debug_request = 342;
|
||||||
|
|
||||||
LspExtGoToParentModule lsp_ext_go_to_parent_module = 343;
|
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;
|
reserved 87 to 88;
|
||||||
|
|
|
@ -171,6 +171,9 @@ messages!(
|
||||||
(LspExtSwitchSourceHeaderResponse, Background),
|
(LspExtSwitchSourceHeaderResponse, Background),
|
||||||
(LspExtGoToParentModule, Background),
|
(LspExtGoToParentModule, Background),
|
||||||
(LspExtGoToParentModuleResponse, Background),
|
(LspExtGoToParentModuleResponse, Background),
|
||||||
|
(LspExtCancelFlycheck, Background),
|
||||||
|
(LspExtRunFlycheck, Background),
|
||||||
|
(LspExtClearFlycheck, Background),
|
||||||
(MarkNotificationRead, Foreground),
|
(MarkNotificationRead, Foreground),
|
||||||
(MoveChannel, Foreground),
|
(MoveChannel, Foreground),
|
||||||
(MultiLspQuery, Background),
|
(MultiLspQuery, Background),
|
||||||
|
@ -425,6 +428,9 @@ request_messages!(
|
||||||
(SynchronizeContexts, SynchronizeContextsResponse),
|
(SynchronizeContexts, SynchronizeContextsResponse),
|
||||||
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
||||||
(LspExtGoToParentModule, LspExtGoToParentModuleResponse),
|
(LspExtGoToParentModule, LspExtGoToParentModuleResponse),
|
||||||
|
(LspExtCancelFlycheck, Ack),
|
||||||
|
(LspExtRunFlycheck, Ack),
|
||||||
|
(LspExtClearFlycheck, Ack),
|
||||||
(AddWorktree, AddWorktreeResponse),
|
(AddWorktree, AddWorktreeResponse),
|
||||||
(ShutdownRemoteServer, Ack),
|
(ShutdownRemoteServer, Ack),
|
||||||
(RemoveWorktree, Ack),
|
(RemoveWorktree, Ack),
|
||||||
|
@ -548,6 +554,9 @@ entity_messages!(
|
||||||
SynchronizeContexts,
|
SynchronizeContexts,
|
||||||
LspExtSwitchSourceHeader,
|
LspExtSwitchSourceHeader,
|
||||||
LspExtGoToParentModule,
|
LspExtGoToParentModule,
|
||||||
|
LspExtCancelFlycheck,
|
||||||
|
LspExtRunFlycheck,
|
||||||
|
LspExtClearFlycheck,
|
||||||
LanguageServerLog,
|
LanguageServerLog,
|
||||||
Toast,
|
Toast,
|
||||||
HideToast,
|
HideToast,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue