WIP - Start work on updating project diagnostics view

This commit is contained in:
Max Brunsfeld 2021-12-21 16:39:23 -08:00
parent a888620e5f
commit 2c3efdea8c
5 changed files with 213 additions and 54 deletions

3
Cargo.lock generated
View File

@ -1404,13 +1404,16 @@ name = "diagnostics"
version = "0.1.0"
dependencies = [
"anyhow",
"client",
"collections",
"editor",
"gpui",
"language",
"postage",
"project",
"serde_json",
"unindent",
"util",
"workspace",
]

View File

@ -13,12 +13,15 @@ editor = { path = "../editor" }
language = { path = "../language" }
gpui = { path = "../gpui" }
project = { path = "../project" }
util = { path = "../util" }
workspace = { path = "../workspace" }
postage = { version = "0.4", features = ["futures-traits"] }
[dev-dependencies]
unindent = "0.1"
client = { path = "../client", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
serde_json = { version = "1", features = ["preserve_order"] }

View File

@ -12,6 +12,7 @@ use language::{Bias, Buffer, Point};
use postage::watch;
use project::Project;
use std::ops::Range;
use util::TryFutureExt;
use workspace::Workspace;
action!(Toggle);
@ -65,11 +66,49 @@ impl View for ProjectDiagnosticsEditor {
impl ProjectDiagnosticsEditor {
fn new(
replica_id: u16,
project: ModelHandle<Project>,
settings: watch::Receiver<workspace::Settings>,
cx: &mut ViewContext<Self>,
) -> Self {
let excerpts = cx.add_model(|_| MultiBuffer::new(replica_id));
let project_paths = project
.read(cx)
.diagnostic_summaries(cx)
.map(|e| e.0)
.collect::<Vec<_>>();
cx.spawn(|this, mut cx| {
let project = project.clone();
async move {
for project_path in project_paths {
let buffer = project
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))
.await?;
this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx))
}
Result::<_, anyhow::Error>::Ok(())
}
})
.detach();
cx.subscribe(&project, |_, project, event, cx| {
if let project::Event::DiagnosticsUpdated(project_path) = event {
let project_path = project_path.clone();
cx.spawn(|this, mut cx| {
async move {
let buffer = project
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))
.await?;
this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx));
Ok(())
}
.log_err()
})
.detach();
}
})
.detach();
let excerpts = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id()));
let build_settings = editor::settings_builder(excerpts.downgrade(), settings.clone());
let editor =
cx.add_view(|cx| Editor::for_buffer(excerpts.clone(), build_settings.clone(), cx));
@ -82,6 +121,11 @@ impl ProjectDiagnosticsEditor {
}
}
#[cfg(test)]
fn text(&self, cx: &AppContext) -> String {
self.editor.read(cx).text(cx)
}
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone()));
workspace.add_item(diagnostics, cx);
@ -193,6 +237,7 @@ impl ProjectDiagnosticsEditor {
cx,
);
});
cx.notify();
}
}
@ -205,27 +250,7 @@ impl workspace::Item for ProjectDiagnostics {
cx: &mut ViewContext<Self::View>,
) -> Self::View {
let project = handle.read(cx).project.clone();
let project_paths = project
.read(cx)
.diagnostic_summaries(cx)
.map(|e| e.0)
.collect::<Vec<_>>();
cx.spawn(|view, mut cx| {
let project = project.clone();
async move {
for project_path in project_paths {
let buffer = project
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))
.await?;
view.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx))
}
Result::<_, anyhow::Error>::Ok(())
}
})
.detach();
ProjectDiagnosticsEditor::new(project.read(cx).replica_id(), settings, cx)
ProjectDiagnosticsEditor::new(project, settings, cx)
}
fn project_path(&self) -> Option<project::ProjectPath> {
@ -282,35 +307,68 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
#[cfg(test)]
mod tests {
use super::*;
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
use client::{http::ServerResponse, test::FakeHttpClient, Client, UserStore};
use gpui::TestAppContext;
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry, PointUtf16};
use project::FakeFs;
use serde_json::json;
use std::sync::Arc;
use unindent::Unindent as _;
use workspace::WorkspaceParams;
#[gpui::test]
fn test_diagnostics(cx: &mut MutableAppContext) {
let settings = WorkspaceParams::test(cx).settings;
let view = cx.add_view(Default::default(), |cx| {
ProjectDiagnosticsEditor::new(0, settings, cx)
async fn test_diagnostics(mut cx: TestAppContext) {
let settings = cx.update(WorkspaceParams::test).settings;
let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) });
let client = Client::new();
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
let fs = Arc::new(FakeFs::new());
let project = cx.update(|cx| {
Project::local(
client.clone(),
user_store,
Arc::new(LanguageRegistry::new()),
fs.clone(),
cx,
)
});
let text = "
fn main() {
let x = vec![];
let y = vec![];
a(x);
b(y);
// comment 1
// comment 2
c(y);
d(x);
}
"
.unindent();
fs.insert_tree(
"/test",
json!({
"a.rs": "
const a: i32 = 'a';
".unindent(),
let buffer = cx.add_model(|cx| {
let mut buffer = Buffer::new(0, text, cx);
buffer
.update_diagnostics(
"main.rs": "
fn main() {
let x = vec![];
let y = vec![];
a(x);
b(y);
// comment 1
// comment 2
c(y);
d(x);
}
"
.unindent(),
}),
)
.await;
let worktree = project
.update(&mut cx, |project, cx| {
project.add_local_worktree("/test", cx)
})
.await
.unwrap();
worktree.update(&mut cx, |worktree, cx| {
worktree
.update_diagnostic_entries(
Arc::from("/test/main.rs".as_ref()),
None,
vec![
DiagnosticEntry {
@ -381,11 +439,16 @@ mod tests {
cx,
)
.unwrap();
buffer
});
view.update(cx, |view, cx| {
view.populate_excerpts(buffer, cx);
let view = cx.add_view(Default::default(), |cx| {
ProjectDiagnosticsEditor::new(project.clone(), settings, cx)
});
view.condition(&mut cx, |view, cx| view.text(cx).contains("fn main()"))
.await;
view.update(&mut cx, |view, cx| {
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
assert_eq!(
@ -423,5 +486,71 @@ mod tests {
)
);
});
worktree.update(&mut cx, |worktree, cx| {
worktree
.update_diagnostic_entries(
Arc::from("/test/a.rs".as_ref()),
None,
vec![DiagnosticEntry {
range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15),
diagnostic: Diagnostic {
message: "mismatched types\nexpected `usize`, found `char`".to_string(),
severity: DiagnosticSeverity::ERROR,
is_primary: true,
group_id: 0,
..Default::default()
},
}],
cx,
)
.unwrap();
});
view.condition(&mut cx, |view, cx| view.text(cx).contains("const a"))
.await;
view.update(&mut cx, |view, cx| {
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
assert_eq!(
editor.text(),
concat!(
// a.rs
"\n", // primary message
"\n", // filename
"const a: i32 = 'a';\n",
// main.rs, diagnostic group 1
"\n", // primary message
"\n", // filename
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
// main.rs, diagnostic group 2
"\n", // primary message
"\n", // filename
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
});
}
}

View File

@ -56,9 +56,11 @@ pub struct Collaborator {
pub replica_id: ReplicaId,
}
#[derive(Debug)]
pub enum Event {
ActiveEntryChanged(Option<ProjectEntry>),
WorktreeRemoved(usize),
DiagnosticsUpdated(ProjectPath),
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
@ -473,6 +475,15 @@ impl Project {
fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
cx.subscribe(&worktree, |_, worktree, event, cx| match event {
worktree::Event::DiagnosticsUpdated(path) => {
cx.emit(Event::DiagnosticsUpdated(ProjectPath {
worktree_id: worktree.id(),
path: path.clone(),
}));
}
})
.detach();
self.worktrees.push(worktree);
cx.notify();
}

View File

@ -64,8 +64,9 @@ pub enum Worktree {
Remote(RemoteWorktree),
}
#[derive(Debug)]
pub enum Event {
Closed,
DiagnosticsUpdated(Arc<Path>),
}
impl Entity for Worktree {
@ -671,7 +672,7 @@ impl Worktree {
}
}
fn update_diagnostics(
pub fn update_diagnostics(
&mut self,
mut params: lsp::PublishDiagnosticsParams,
cx: &mut ModelContext<Worktree>,
@ -736,17 +737,28 @@ impl Worktree {
})
.collect::<Vec<_>>();
self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)
}
pub fn update_diagnostic_entries(
&mut self,
path: Arc<Path>,
version: Option<i32>,
diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
cx: &mut ModelContext<Worktree>,
) -> Result<()> {
let this = self.as_local_mut().unwrap();
for buffer in this.open_buffers.values() {
if let Some(buffer) = buffer.upgrade(cx) {
if buffer
.read(cx)
.file()
.map_or(false, |file| *file.path() == worktree_path)
.map_or(false, |file| *file.path() == path)
{
let (remote_id, operation) = buffer.update(cx, |buffer, cx| {
(
buffer.remote_id(),
buffer.update_diagnostics(params.version, diagnostics.clone(), cx),
buffer.update_diagnostics(version, diagnostics.clone(), cx),
)
});
self.send_buffer_update(remote_id, operation?, cx);
@ -757,8 +769,9 @@ impl Worktree {
let this = self.as_local_mut().unwrap();
this.diagnostic_summaries
.insert(worktree_path.clone(), DiagnosticSummary::new(&diagnostics));
this.diagnostics.insert(worktree_path.clone(), diagnostics);
.insert(path.clone(), DiagnosticSummary::new(&diagnostics));
this.diagnostics.insert(path.clone(), diagnostics);
cx.emit(Event::DiagnosticsUpdated(path.clone()));
Ok(())
}