mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 19:54:10 +03:00
WIP - Improve project diagnostic context rendering
This commit is contained in:
parent
fb492a9fb8
commit
528d64d3cc
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1421,6 +1421,7 @@ dependencies = [
|
||||
"language",
|
||||
"postage",
|
||||
"project",
|
||||
"unindent",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
|
@ -15,3 +15,10 @@ gpui = { path = "../gpui" }
|
||||
project = { path = "../project" }
|
||||
workspace = { path = "../workspace" }
|
||||
postage = { version = "0.4", features = ["futures-traits"] }
|
||||
|
||||
[dev-dependencies]
|
||||
unindent = "0.1"
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
@ -1,16 +1,16 @@
|
||||
use editor::{
|
||||
diagnostic_block_renderer, diagnostic_header_renderer,
|
||||
context_header_renderer, diagnostic_block_renderer, diagnostic_header_renderer,
|
||||
display_map::{BlockDisposition, BlockProperties},
|
||||
Editor, ExcerptProperties, MultiBuffer,
|
||||
BuildSettings, Editor, ExcerptProperties, MultiBuffer,
|
||||
};
|
||||
use gpui::{
|
||||
action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext,
|
||||
RenderContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use language::Point;
|
||||
use language::{Bias, Buffer, Point};
|
||||
use postage::watch;
|
||||
use project::Project;
|
||||
use std::cmp;
|
||||
use std::ops::Range;
|
||||
use workspace::Workspace;
|
||||
|
||||
action!(Toggle);
|
||||
@ -27,6 +27,7 @@ struct ProjectDiagnostics {
|
||||
struct ProjectDiagnosticsEditor {
|
||||
editor: ViewHandle<Editor>,
|
||||
excerpts: ModelHandle<MultiBuffer>,
|
||||
build_settings: BuildSettings,
|
||||
}
|
||||
|
||||
impl ProjectDiagnostics {
|
||||
@ -58,10 +59,114 @@ impl View for ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
impl ProjectDiagnosticsEditor {
|
||||
fn new(
|
||||
replica_id: u16,
|
||||
settings: watch::Receiver<workspace::Settings>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let excerpts = cx.add_model(|_| MultiBuffer::new(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));
|
||||
Self {
|
||||
excerpts,
|
||||
editor,
|
||||
build_settings,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fn populate_excerpts(&mut self, buffer: ModelHandle<Buffer>, cx: &mut ViewContext<Self>) {
|
||||
let mut blocks = Vec::new();
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
|
||||
for group in snapshot.diagnostic_groups::<Point>() {
|
||||
let mut pending_range: Option<(Range<Point>, usize)> = None;
|
||||
let mut is_first_excerpt = true;
|
||||
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
|
||||
if let Some((range, start_ix)) = &mut pending_range {
|
||||
if let Some(entry) = entry {
|
||||
if entry.range.start.row <= range.end.row + 1 {
|
||||
range.end = range.end.max(entry.range.end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let excerpt_start = Point::new(range.start.row.saturating_sub(1), 0);
|
||||
let excerpt_end = snapshot
|
||||
.clip_point(Point::new(range.end.row + 1, u32::MAX), Bias::Left);
|
||||
|
||||
let mut excerpt = ExcerptProperties {
|
||||
buffer: &buffer,
|
||||
range: excerpt_start..excerpt_end,
|
||||
header_height: 0,
|
||||
render_header: None,
|
||||
};
|
||||
|
||||
if is_first_excerpt {
|
||||
let primary = &group.entries[group.primary_ix].diagnostic;
|
||||
excerpt.header_height = primary.message.matches('\n').count() as u8 + 1;
|
||||
excerpt.render_header = Some(diagnostic_header_renderer(
|
||||
primary.clone(),
|
||||
self.build_settings.clone(),
|
||||
));
|
||||
} else {
|
||||
excerpt.header_height = 1;
|
||||
excerpt.render_header =
|
||||
Some(context_header_renderer(self.build_settings.clone()));
|
||||
}
|
||||
|
||||
is_first_excerpt = false;
|
||||
let excerpt_id = excerpts.push_excerpt(excerpt, excerpts_cx);
|
||||
for entry in &group.entries[*start_ix..ix] {
|
||||
if !entry.diagnostic.is_primary {
|
||||
let buffer_anchor = snapshot.anchor_before(entry.range.start);
|
||||
blocks.push(BlockProperties {
|
||||
position: (excerpt_id.clone(), buffer_anchor),
|
||||
height: entry.diagnostic.message.matches('\n').count() as u8
|
||||
+ 1,
|
||||
render: diagnostic_block_renderer(
|
||||
entry.diagnostic.clone(),
|
||||
true,
|
||||
self.build_settings.clone(),
|
||||
),
|
||||
disposition: BlockDisposition::Below,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pending_range.take();
|
||||
}
|
||||
|
||||
if let Some(entry) = entry {
|
||||
pending_range = Some((entry.range.clone(), ix));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
excerpts.snapshot(excerpts_cx)
|
||||
});
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.insert_blocks(
|
||||
blocks.into_iter().map(|block| {
|
||||
let (excerpt_id, text_anchor) = block.position;
|
||||
BlockProperties {
|
||||
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
|
||||
height: block.height,
|
||||
render: block.render,
|
||||
disposition: block.disposition,
|
||||
}
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl workspace::Item for ProjectDiagnostics {
|
||||
@ -73,113 +178,27 @@ impl workspace::Item for ProjectDiagnostics {
|
||||
cx: &mut ViewContext<Self::View>,
|
||||
) -> Self::View {
|
||||
let project = handle.read(cx).project.clone();
|
||||
let excerpts = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx)));
|
||||
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));
|
||||
|
||||
let project_paths = project
|
||||
.read(cx)
|
||||
.diagnostic_summaries(cx)
|
||||
.map(|e| e.0)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn(|this, mut cx| {
|
||||
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?;
|
||||
let snapshot = buffer.read_with(&cx, |b, _| b.snapshot());
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let mut blocks = Vec::new();
|
||||
let excerpts_snapshot =
|
||||
this.excerpts.update(cx, |excerpts, excerpts_cx| {
|
||||
for group in snapshot.diagnostic_groups::<Point>() {
|
||||
let excerpt_start = cmp::min(
|
||||
group.primary.range.start.row,
|
||||
group
|
||||
.supporting
|
||||
.first()
|
||||
.map_or(u32::MAX, |entry| entry.range.start.row),
|
||||
);
|
||||
let excerpt_end = cmp::max(
|
||||
group.primary.range.end.row,
|
||||
group
|
||||
.supporting
|
||||
.last()
|
||||
.map_or(0, |entry| entry.range.end.row),
|
||||
);
|
||||
|
||||
let primary_diagnostic = group.primary.diagnostic;
|
||||
let excerpt_id = excerpts.push_excerpt(
|
||||
ExcerptProperties {
|
||||
buffer: &buffer,
|
||||
range: Point::new(excerpt_start, 0)
|
||||
..Point::new(
|
||||
excerpt_end,
|
||||
snapshot.line_len(excerpt_end),
|
||||
),
|
||||
header_height: primary_diagnostic
|
||||
.message
|
||||
.matches('\n')
|
||||
.count()
|
||||
as u8
|
||||
+ 1,
|
||||
render_header: Some(diagnostic_header_renderer(
|
||||
primary_diagnostic,
|
||||
build_settings.clone(),
|
||||
)),
|
||||
},
|
||||
excerpts_cx,
|
||||
);
|
||||
|
||||
for entry in group.supporting {
|
||||
let buffer_anchor =
|
||||
snapshot.anchor_before(entry.range.start);
|
||||
blocks.push(BlockProperties {
|
||||
position: (excerpt_id.clone(), buffer_anchor),
|
||||
height: entry.diagnostic.message.matches('\n').count()
|
||||
as u8
|
||||
+ 1,
|
||||
render: diagnostic_block_renderer(
|
||||
entry.diagnostic,
|
||||
true,
|
||||
build_settings.clone(),
|
||||
),
|
||||
disposition: BlockDisposition::Below,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
excerpts.snapshot(excerpts_cx)
|
||||
});
|
||||
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
editor.insert_blocks(
|
||||
blocks.into_iter().map(|block| {
|
||||
let (excerpt_id, text_anchor) = block.position;
|
||||
BlockProperties {
|
||||
position: excerpts_snapshot
|
||||
.anchor_in_excerpt(excerpt_id, text_anchor),
|
||||
height: block.height,
|
||||
render: block.render,
|
||||
disposition: block.disposition,
|
||||
}
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
})
|
||||
view.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx))
|
||||
}
|
||||
Result::Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
ProjectDiagnosticsEditor { editor, excerpts }
|
||||
ProjectDiagnosticsEditor::new(project.read(cx).replica_id(cx), settings, cx)
|
||||
}
|
||||
|
||||
fn project_path(&self) -> Option<project::ProjectPath> {
|
||||
@ -212,3 +231,83 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
||||
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)
|
||||
});
|
||||
|
||||
let text = "
|
||||
fn main() {
|
||||
let x = vec![];
|
||||
let y = vec![];
|
||||
a(x);
|
||||
b(y);
|
||||
c();
|
||||
d(y);
|
||||
e(x);
|
||||
}
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, text, cx);
|
||||
buffer
|
||||
.update_diagnostics(
|
||||
None,
|
||||
vec![
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
|
||||
diagnostic: Diagnostic {
|
||||
message:
|
||||
"move occurs because `x` has type `Vec<char>`, which does not implement the `Copy` trait"
|
||||
.to_string(),
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
group_id: 0,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(2, 8)..PointUtf16::new(2, 9),
|
||||
diagnostic: Diagnostic {
|
||||
message:
|
||||
"move occurs because `y` has type `Vec<char>`, which does not implement the `Copy` trait"
|
||||
.to_string(),
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(3, 6)..PointUtf16::new(3, 7),
|
||||
diagnostic: Diagnostic {
|
||||
message: "value moved here".to_string(),
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
group_id: 0,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
buffer
|
||||
});
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.populate_excerpts(buffer, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -357,7 +357,7 @@ pub enum SoftWrap {
|
||||
Column(u32),
|
||||
}
|
||||
|
||||
type BuildSettings = Arc<dyn 'static + Send + Sync + Fn(&AppContext) -> EditorSettings>;
|
||||
pub type BuildSettings = Arc<dyn 'static + Send + Sync + Fn(&AppContext) -> EditorSettings>;
|
||||
|
||||
pub struct Editor {
|
||||
handle: WeakViewHandle<Self>,
|
||||
@ -3794,6 +3794,14 @@ pub fn diagnostic_header_renderer(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn context_header_renderer(build_settings: BuildSettings) -> RenderHeaderFn {
|
||||
Arc::new(move |cx| {
|
||||
let settings = build_settings(cx);
|
||||
let text_style = settings.style.text.clone();
|
||||
Text::new("...".to_string(), text_style).boxed()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diagnostic_style(
|
||||
severity: DiagnosticSeverity,
|
||||
valid: bool,
|
||||
|
@ -20,8 +20,8 @@ pub struct DiagnosticEntry<T> {
|
||||
}
|
||||
|
||||
pub struct DiagnosticGroup<T> {
|
||||
pub primary: DiagnosticEntry<T>,
|
||||
pub supporting: Vec<DiagnosticEntry<T>>,
|
||||
pub entries: Vec<DiagnosticEntry<T>>,
|
||||
pub primary_ix: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -108,33 +108,29 @@ impl DiagnosticSet {
|
||||
where
|
||||
O: FromAnchor + Ord + Copy,
|
||||
{
|
||||
let mut groups =
|
||||
HashMap::<usize, (Option<DiagnosticEntry<O>>, Vec<DiagnosticEntry<O>>)>::default();
|
||||
|
||||
let mut groups = HashMap::default();
|
||||
for entry in self.diagnostics.iter() {
|
||||
let entry = entry.resolve(buffer);
|
||||
let (ref mut primary, ref mut supporting) = groups
|
||||
groups
|
||||
.entry(entry.diagnostic.group_id)
|
||||
.or_insert((None, Vec::new()));
|
||||
if entry.diagnostic.is_primary {
|
||||
*primary = Some(entry);
|
||||
} else {
|
||||
supporting.push(entry);
|
||||
}
|
||||
.or_insert(Vec::new())
|
||||
.push(entry);
|
||||
}
|
||||
|
||||
let mut groups = groups
|
||||
.into_values()
|
||||
.map(|(primary, mut supporting)| {
|
||||
supporting.sort_unstable_by_key(|entry| entry.range.start);
|
||||
DiagnosticGroup {
|
||||
primary: primary.unwrap(),
|
||||
supporting,
|
||||
}
|
||||
.filter_map(|mut entries| {
|
||||
entries.sort_unstable_by_key(|entry| entry.range.start);
|
||||
entries
|
||||
.iter()
|
||||
.position(|entry| entry.diagnostic.is_primary)
|
||||
.map(|primary_ix| DiagnosticGroup {
|
||||
entries,
|
||||
primary_ix,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
groups.sort_unstable_by_key(|group| group.primary.range.start);
|
||||
|
||||
groups.sort_unstable_by_key(|group| group.entries[group.primary_ix].range.start);
|
||||
groups
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user