WIP - Start work on updating project diagnostics view
This commit is contained in:
parent
a888620e5f
commit
2c3efdea8c
5 changed files with 213 additions and 54 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1404,13 +1404,16 @@ name = "diagnostics"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
|
"serde_json",
|
||||||
"unindent",
|
"unindent",
|
||||||
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,15 @@ editor = { path = "../editor" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
postage = { version = "0.4", features = ["futures-traits"] }
|
postage = { version = "0.4", features = ["futures-traits"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
unindent = "0.1"
|
unindent = "0.1"
|
||||||
|
client = { path = "../client", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
serde_json = { version = "1", features = ["preserve_order"] }
|
||||||
|
|
|
@ -12,6 +12,7 @@ use language::{Bias, Buffer, Point};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use util::TryFutureExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
action!(Toggle);
|
action!(Toggle);
|
||||||
|
@ -65,11 +66,49 @@ impl View for ProjectDiagnosticsEditor {
|
||||||
|
|
||||||
impl ProjectDiagnosticsEditor {
|
impl ProjectDiagnosticsEditor {
|
||||||
fn new(
|
fn new(
|
||||||
replica_id: u16,
|
project: ModelHandle<Project>,
|
||||||
settings: watch::Receiver<workspace::Settings>,
|
settings: watch::Receiver<workspace::Settings>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> 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 build_settings = editor::settings_builder(excerpts.downgrade(), settings.clone());
|
||||||
let editor =
|
let editor =
|
||||||
cx.add_view(|cx| Editor::for_buffer(excerpts.clone(), build_settings.clone(), cx));
|
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>) {
|
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||||
let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone()));
|
let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone()));
|
||||||
workspace.add_item(diagnostics, cx);
|
workspace.add_item(diagnostics, cx);
|
||||||
|
@ -193,6 +237,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,27 +250,7 @@ impl workspace::Item for ProjectDiagnostics {
|
||||||
cx: &mut ViewContext<Self::View>,
|
cx: &mut ViewContext<Self::View>,
|
||||||
) -> Self::View {
|
) -> Self::View {
|
||||||
let project = handle.read(cx).project.clone();
|
let project = handle.read(cx).project.clone();
|
||||||
let project_paths = project
|
ProjectDiagnosticsEditor::new(project, settings, cx)
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_path(&self) -> Option<project::ProjectPath> {
|
fn project_path(&self) -> Option<project::ProjectPath> {
|
||||||
|
@ -282,35 +307,68 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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 unindent::Unindent as _;
|
||||||
use workspace::WorkspaceParams;
|
use workspace::WorkspaceParams;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_diagnostics(cx: &mut MutableAppContext) {
|
async fn test_diagnostics(mut cx: TestAppContext) {
|
||||||
let settings = WorkspaceParams::test(cx).settings;
|
let settings = cx.update(WorkspaceParams::test).settings;
|
||||||
let view = cx.add_view(Default::default(), |cx| {
|
let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) });
|
||||||
ProjectDiagnosticsEditor::new(0, settings, cx)
|
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 = "
|
fs.insert_tree(
|
||||||
fn main() {
|
"/test",
|
||||||
let x = vec![];
|
json!({
|
||||||
let y = vec![];
|
"a.rs": "
|
||||||
a(x);
|
const a: i32 = 'a';
|
||||||
b(y);
|
".unindent(),
|
||||||
// comment 1
|
|
||||||
// comment 2
|
|
||||||
c(y);
|
|
||||||
d(x);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
.unindent();
|
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| {
|
"main.rs": "
|
||||||
let mut buffer = Buffer::new(0, text, cx);
|
fn main() {
|
||||||
buffer
|
let x = vec![];
|
||||||
.update_diagnostics(
|
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,
|
None,
|
||||||
vec![
|
vec![
|
||||||
DiagnosticEntry {
|
DiagnosticEntry {
|
||||||
|
@ -381,11 +439,16 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
buffer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
let view = cx.add_view(Default::default(), |cx| {
|
||||||
view.populate_excerpts(buffer, 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));
|
let editor = view.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||||
|
|
||||||
assert_eq!(
|
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
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,9 +56,11 @@ pub struct Collaborator {
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ActiveEntryChanged(Option<ProjectEntry>),
|
ActiveEntryChanged(Option<ProjectEntry>),
|
||||||
WorktreeRemoved(usize),
|
WorktreeRemoved(usize),
|
||||||
|
DiagnosticsUpdated(ProjectPath),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
@ -473,6 +475,15 @@ impl Project {
|
||||||
|
|
||||||
fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
|
fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
|
||||||
cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
|
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);
|
self.worktrees.push(worktree);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,9 @@ pub enum Worktree {
|
||||||
Remote(RemoteWorktree),
|
Remote(RemoteWorktree),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Closed,
|
DiagnosticsUpdated(Arc<Path>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Worktree {
|
impl Entity for Worktree {
|
||||||
|
@ -671,7 +672,7 @@ impl Worktree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut params: lsp::PublishDiagnosticsParams,
|
mut params: lsp::PublishDiagnosticsParams,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
|
@ -736,17 +737,28 @@ impl Worktree {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.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() {
|
for buffer in this.open_buffers.values() {
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
if buffer
|
if buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.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| {
|
let (remote_id, operation) = buffer.update(cx, |buffer, cx| {
|
||||||
(
|
(
|
||||||
buffer.remote_id(),
|
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);
|
self.send_buffer_update(remote_id, operation?, cx);
|
||||||
|
@ -757,8 +769,9 @@ impl Worktree {
|
||||||
|
|
||||||
let this = self.as_local_mut().unwrap();
|
let this = self.as_local_mut().unwrap();
|
||||||
this.diagnostic_summaries
|
this.diagnostic_summaries
|
||||||
.insert(worktree_path.clone(), DiagnosticSummary::new(&diagnostics));
|
.insert(path.clone(), DiagnosticSummary::new(&diagnostics));
|
||||||
this.diagnostics.insert(worktree_path.clone(), diagnostics);
|
this.diagnostics.insert(path.clone(), diagnostics);
|
||||||
|
cx.emit(Event::DiagnosticsUpdated(path.clone()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue