Start on a DiagnosticProvider
implementation for Rust
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
393009a05c
commit
a85e400b35
6 changed files with 138 additions and 10 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5702,6 +5702,7 @@ dependencies = [
|
||||||
"chat_panel",
|
"chat_panel",
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
|
"collections",
|
||||||
"contacts_panel",
|
"contacts_panel",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"ctor",
|
"ctor",
|
||||||
|
|
|
@ -805,10 +805,13 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let start_overshoot = start - last_edit_old_end;
|
||||||
start = last_edit_new_end;
|
start = last_edit_new_end;
|
||||||
start.add_assign(&(start - last_edit_old_end));
|
start.add_assign(&start_overshoot);
|
||||||
|
|
||||||
|
let end_overshoot = end - last_edit_old_end;
|
||||||
end = last_edit_new_end;
|
end = last_edit_new_end;
|
||||||
end.add_assign(&(end - last_edit_old_end));
|
end.add_assign(&end_overshoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = start.clip(Bias::Left, content)..end.clip(Bias::Right, content);
|
let range = start.clip(Bias::Left, content)..end.clip(Bias::Right, content);
|
||||||
|
|
|
@ -9,18 +9,14 @@ use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
pub use buffer::Operation;
|
pub use buffer::Operation;
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
use collections::HashSet;
|
use collections::{HashMap, HashSet};
|
||||||
pub use diagnostic_set::DiagnosticEntry;
|
pub use diagnostic_set::DiagnosticEntry;
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
use highlight_map::HighlightMap;
|
use highlight_map::HighlightMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{path::Path, str, sync::Arc};
|
||||||
path::{Path, PathBuf},
|
|
||||||
str,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use tree_sitter::{self, Query};
|
use tree_sitter::{self, Query};
|
||||||
pub use tree_sitter::{Parser, Tree};
|
pub use tree_sitter::{Parser, Tree};
|
||||||
|
@ -69,7 +65,7 @@ pub trait DiagnosticProvider: 'static + Send + Sync {
|
||||||
async fn diagnose(
|
async fn diagnose(
|
||||||
&self,
|
&self,
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
) -> Result<Vec<(PathBuf, Vec<DiagnosticEntry<usize>>)>>;
|
) -> Result<HashMap<Arc<Path>, Vec<DiagnosticEntry<usize>>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
|
|
|
@ -791,16 +791,24 @@ impl Workspace {
|
||||||
{
|
{
|
||||||
error!("failed to save item: {:?}, ", error);
|
error!("failed to save item: {:?}, ", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle.update(&mut cx, |this, cx| {
|
||||||
|
this.project.update(cx, |project, cx| project.diagnose(cx))
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
|
if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await {
|
||||||
error!("failed to save item: {:?}, ", error);
|
error!("failed to save item: {:?}, ", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.project.update(cx, |project, cx| project.diagnose(cx))
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -832,6 +840,10 @@ impl Workspace {
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
error!("failed to save item: {:?}, ", error);
|
error!("failed to save item: {:?}, ", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle.update(&mut cx, |this, cx| {
|
||||||
|
this.project.update(cx, |project, cx| project.diagnose(cx))
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ test-support = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chat_panel = { path = "../chat_panel" }
|
chat_panel = { path = "../chat_panel" }
|
||||||
|
collections = { path = "../collections" }
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
contacts_panel = { path = "../contacts_panel" }
|
contacts_panel = { path = "../contacts_panel" }
|
||||||
|
|
|
@ -7,6 +7,120 @@ use std::{str, sync::Arc};
|
||||||
#[folder = "languages"]
|
#[folder = "languages"]
|
||||||
struct LanguageDir;
|
struct LanguageDir;
|
||||||
|
|
||||||
|
mod rust {
|
||||||
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use collections::{HashMap, HashSet};
|
||||||
|
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::Deserializer;
|
||||||
|
use smol::process::Command;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DiagnosticProvider {
|
||||||
|
reported_paths: Mutex<HashSet<Arc<Path>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Check {
|
||||||
|
message: CompilerMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct CompilerMessage {
|
||||||
|
code: ErrorCode,
|
||||||
|
spans: Vec<Span>,
|
||||||
|
message: String,
|
||||||
|
level: ErrorLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
enum ErrorLevel {
|
||||||
|
#[serde(rename = "warning")]
|
||||||
|
Warning,
|
||||||
|
#[serde(rename = "error")]
|
||||||
|
Error,
|
||||||
|
#[serde(rename = "note")]
|
||||||
|
Note,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ErrorCode {
|
||||||
|
code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Span {
|
||||||
|
is_primary: bool,
|
||||||
|
file_name: PathBuf,
|
||||||
|
byte_start: usize,
|
||||||
|
byte_end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl language::DiagnosticProvider for DiagnosticProvider {
|
||||||
|
async fn diagnose(
|
||||||
|
&self,
|
||||||
|
path: Arc<Path>,
|
||||||
|
) -> Result<HashMap<Arc<Path>, Vec<DiagnosticEntry<usize>>>> {
|
||||||
|
let output = Command::new("cargo")
|
||||||
|
.arg("check")
|
||||||
|
.args(["--message-format", "json"])
|
||||||
|
.current_dir(&path)
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut group_id = 0;
|
||||||
|
let mut diagnostics_by_path = HashMap::default();
|
||||||
|
let mut new_reported_paths = HashSet::default();
|
||||||
|
for value in
|
||||||
|
Deserializer::from_slice(&output.stdout).into_iter::<&serde_json::value::RawValue>()
|
||||||
|
{
|
||||||
|
if let Ok(check) = serde_json::from_str::<Check>(value?.get()) {
|
||||||
|
let severity = match check.message.level {
|
||||||
|
ErrorLevel::Warning => DiagnosticSeverity::WARNING,
|
||||||
|
ErrorLevel::Error => DiagnosticSeverity::ERROR,
|
||||||
|
ErrorLevel::Note => DiagnosticSeverity::INFORMATION,
|
||||||
|
};
|
||||||
|
for span in check.message.spans {
|
||||||
|
let span_path: Arc<Path> = span.file_name.into();
|
||||||
|
new_reported_paths.insert(span_path.clone());
|
||||||
|
diagnostics_by_path
|
||||||
|
.entry(span_path)
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(DiagnosticEntry {
|
||||||
|
range: span.byte_start..span.byte_end,
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
code: Some(check.message.code.code.clone()),
|
||||||
|
severity,
|
||||||
|
message: check.message.message.clone(),
|
||||||
|
group_id,
|
||||||
|
is_valid: true,
|
||||||
|
is_primary: span.is_primary,
|
||||||
|
is_disk_based: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
group_id += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let reported_paths = &mut *self.reported_paths.lock();
|
||||||
|
for old_reported_path in reported_paths.iter() {
|
||||||
|
if !diagnostics_by_path.contains_key(old_reported_path) {
|
||||||
|
diagnostics_by_path.insert(old_reported_path.clone(), Default::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*reported_paths = new_reported_paths;
|
||||||
|
|
||||||
|
Ok(diagnostics_by_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_language_registry() -> LanguageRegistry {
|
pub fn build_language_registry() -> LanguageRegistry {
|
||||||
let mut languages = LanguageRegistry::default();
|
let mut languages = LanguageRegistry::default();
|
||||||
languages.add(Arc::new(rust()));
|
languages.add(Arc::new(rust()));
|
||||||
|
@ -24,6 +138,7 @@ fn rust() -> Language {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_indents_query(load_query("rust/indents.scm").as_ref())
|
.with_indents_query(load_query("rust/indents.scm").as_ref())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.with_diagnostic_provider(rust::DiagnosticProvider::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn markdown() -> Language {
|
fn markdown() -> Language {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue