Deduplicate path names in the project diagnostics view

This commit is contained in:
Max Brunsfeld 2022-01-11 14:56:54 -08:00
parent a9937ee8be
commit b5ee095da9
5 changed files with 120 additions and 88 deletions

View file

@ -1,10 +1,10 @@
pub mod items; pub mod items;
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, HashSet, BTreeSet}; use collections::{BTreeSet, HashMap, HashSet};
use editor::{ use editor::{
context_header_renderer, diagnostic_block_renderer, diagnostic_header_renderer, diagnostic_block_renderer, diagnostic_style,
display_map::{BlockDisposition, BlockId, BlockProperties}, display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock},
items::BufferItemHandle, items::BufferItemHandle,
Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset,
}; };
@ -12,10 +12,10 @@ use gpui::{
action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext,
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use language::{Bias, Buffer, DiagnosticEntry, Point, Selection, SelectionGoal}; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal};
use postage::watch; use postage::watch;
use project::{Project, ProjectPath, WorktreeId}; use project::{Project, ProjectPath, WorktreeId};
use std::{cmp::Ordering, mem, ops::Range}; use std::{cmp::Ordering, mem, ops::Range, sync::Arc};
use util::TryFutureExt; use util::TryFutureExt;
use workspace::Workspace; use workspace::Workspace;
@ -48,12 +48,18 @@ struct ProjectDiagnosticsEditor {
workspace: WeakViewHandle<Workspace>, workspace: WeakViewHandle<Workspace>,
editor: ViewHandle<Editor>, editor: ViewHandle<Editor>,
excerpts: ModelHandle<MultiBuffer>, excerpts: ModelHandle<MultiBuffer>,
path_states: Vec<(ProjectPath, Vec<DiagnosticGroupState>)>, path_states: Vec<PathState>,
paths_to_update: HashMap<WorktreeId, BTreeSet<ProjectPath>>, paths_to_update: HashMap<WorktreeId, BTreeSet<ProjectPath>>,
build_settings: BuildSettings, build_settings: BuildSettings,
settings: watch::Receiver<workspace::Settings>, settings: watch::Receiver<workspace::Settings>,
} }
struct PathState {
path: ProjectPath,
header: Option<BlockId>,
diagnostic_groups: Vec<DiagnosticGroupState>,
}
struct DiagnosticGroupState { struct DiagnosticGroupState {
primary_diagnostic: DiagnosticEntry<language::Anchor>, primary_diagnostic: DiagnosticEntry<language::Anchor>,
primary_excerpt_ix: usize, primary_excerpt_ix: usize,
@ -208,11 +214,7 @@ impl ProjectDiagnosticsEditor {
} }
} }
fn update_excerpts( fn update_excerpts(&self, paths: BTreeSet<ProjectPath>, cx: &mut ViewContext<Self>) {
&self,
paths: BTreeSet<ProjectPath>,
cx: &mut ViewContext<Self>,
) {
let project = self.model.read(cx).project.clone(); let project = self.model.read(cx).project.clone();
cx.spawn(|this, mut cx| { cx.spawn(|this, mut cx| {
async move { async move {
@ -220,9 +222,7 @@ impl ProjectDiagnosticsEditor {
let buffer = project let buffer = project
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx)) .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
.await?; .await?;
this.update(&mut cx, |view, cx| { this.update(&mut cx, |view, cx| view.populate_excerpts(path, buffer, cx))
view.populate_excerpts(path, buffer, cx)
})
} }
Result::<_, anyhow::Error>::Ok(()) Result::<_, anyhow::Error>::Ok(())
} }
@ -239,32 +239,39 @@ impl ProjectDiagnosticsEditor {
) { ) {
let was_empty = self.path_states.is_empty(); let was_empty = self.path_states.is_empty();
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let path_ix = match self let path_ix = match self.path_states.binary_search_by_key(&&path, |e| &e.path) {
.path_states
.binary_search_by_key(&&path, |e| &e.0)
{
Ok(ix) => ix, Ok(ix) => ix,
Err(ix) => { Err(ix) => {
self.path_states self.path_states.insert(
.insert(ix, (path.clone(), Default::default())); ix,
PathState {
path: path.clone(),
header: None,
diagnostic_groups: Default::default(),
},
);
ix ix
} }
}; };
let mut prev_excerpt_id = if path_ix > 0 { let mut prev_excerpt_id = if path_ix > 0 {
let prev_path_last_group = &self.path_states[path_ix - 1].1.last().unwrap(); let prev_path_last_group = &self.path_states[path_ix - 1]
.diagnostic_groups
.last()
.unwrap();
prev_path_last_group.excerpts.last().unwrap().clone() prev_path_last_group.excerpts.last().unwrap().clone()
} else { } else {
ExcerptId::min() ExcerptId::min()
}; };
let groups = &mut self.path_states[path_ix].1; let path_state = &mut self.path_states[path_ix];
let mut groups_to_add = Vec::new(); let mut groups_to_add = Vec::new();
let mut group_ixs_to_remove = Vec::new(); let mut group_ixs_to_remove = Vec::new();
let mut blocks_to_add = Vec::new(); let mut blocks_to_add = Vec::new();
let mut blocks_to_remove = HashSet::default(); let mut blocks_to_remove = HashSet::default();
let mut first_excerpt_id = None;
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| { let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
let mut old_groups = groups.iter().enumerate().peekable(); let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
let mut new_groups = snapshot let mut new_groups = snapshot
.diagnostic_groups() .diagnostic_groups()
.into_iter() .into_iter()
@ -273,17 +280,17 @@ impl ProjectDiagnosticsEditor {
loop { loop {
let mut to_insert = None; let mut to_insert = None;
let mut to_invalidate = None; let mut to_remove = None;
let mut to_keep = None; let mut to_keep = None;
match (old_groups.peek(), new_groups.peek()) { match (old_groups.peek(), new_groups.peek()) {
(None, None) => break, (None, None) => break,
(None, Some(_)) => to_insert = new_groups.next(), (None, Some(_)) => to_insert = new_groups.next(),
(Some(_), None) => to_invalidate = old_groups.next(), (Some(_), None) => to_remove = old_groups.next(),
(Some((_, old_group)), Some(new_group)) => { (Some((_, old_group)), Some(new_group)) => {
let old_primary = &old_group.primary_diagnostic; let old_primary = &old_group.primary_diagnostic;
let new_primary = &new_group.entries[new_group.primary_ix]; let new_primary = &new_group.entries[new_group.primary_ix];
match compare_diagnostics(old_primary, new_primary, &snapshot) { match compare_diagnostics(old_primary, new_primary, &snapshot) {
Ordering::Less => to_invalidate = old_groups.next(), Ordering::Less => to_remove = old_groups.next(),
Ordering::Equal => { Ordering::Equal => {
to_keep = old_groups.next(); to_keep = old_groups.next();
new_groups.next(); new_groups.next();
@ -331,6 +338,7 @@ impl ProjectDiagnosticsEditor {
); );
prev_excerpt_id = excerpt_id.clone(); prev_excerpt_id = excerpt_id.clone();
first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
group_state.excerpts.push(excerpt_id.clone()); group_state.excerpts.push(excerpt_id.clone());
let header_position = (excerpt_id.clone(), language::Anchor::min()); let header_position = (excerpt_id.clone(), language::Anchor::min());
@ -343,9 +351,8 @@ impl ProjectDiagnosticsEditor {
group_state.block_count += 1; group_state.block_count += 1;
blocks_to_add.push(BlockProperties { blocks_to_add.push(BlockProperties {
position: header_position, position: header_position,
height: 3, height: 2,
render: diagnostic_header_renderer( render: diagnostic_header_renderer(
buffer.clone(),
header, header,
true, true,
self.build_settings.clone(), self.build_settings.clone(),
@ -394,12 +401,13 @@ impl ProjectDiagnosticsEditor {
} }
groups_to_add.push(group_state); groups_to_add.push(group_state);
} else if let Some((group_ix, group_state)) = to_invalidate { } else if let Some((group_ix, group_state)) = to_remove {
excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx);
group_ixs_to_remove.push(group_ix); group_ixs_to_remove.push(group_ix);
blocks_to_remove.extend(group_state.blocks.iter().copied()); blocks_to_remove.extend(group_state.blocks.iter().copied());
} else if let Some((_, group)) = to_keep { } else if let Some((_, group)) = to_keep {
prev_excerpt_id = group.excerpts.last().unwrap().clone(); prev_excerpt_id = group.excerpts.last().unwrap().clone();
first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
} }
} }
@ -407,32 +415,43 @@ impl ProjectDiagnosticsEditor {
}); });
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
blocks_to_remove.extend(path_state.header);
editor.remove_blocks(blocks_to_remove, cx); editor.remove_blocks(blocks_to_remove, cx);
let header_block = first_excerpt_id.map(|excerpt_id| BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, language::Anchor::min()),
height: 2,
render: path_header_renderer(buffer, self.build_settings.clone()),
disposition: BlockDisposition::Above,
});
let mut block_ids = editor let mut block_ids = editor
.insert_blocks( .insert_blocks(
blocks_to_add.into_iter().map(|block| { header_block
let (excerpt_id, text_anchor) = block.position; .into_iter()
BlockProperties { .chain(blocks_to_add.into_iter().map(|block| {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor), let (excerpt_id, text_anchor) = block.position;
height: block.height, BlockProperties {
render: block.render, position: excerpts_snapshot
disposition: block.disposition, .anchor_in_excerpt(excerpt_id, text_anchor),
} height: block.height,
}), render: block.render,
disposition: block.disposition,
}
})),
cx, cx,
) )
.into_iter(); .into_iter();
path_state.header = block_ids.next();
for group_state in &mut groups_to_add { for group_state in &mut groups_to_add {
group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect(); group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect();
} }
}); });
for ix in group_ixs_to_remove.into_iter().rev() { for ix in group_ixs_to_remove.into_iter().rev() {
groups.remove(ix); path_state.diagnostic_groups.remove(ix);
} }
groups.extend(groups_to_add); path_state.diagnostic_groups.extend(groups_to_add);
groups.sort_unstable_by(|a, b| { path_state.diagnostic_groups.sort_unstable_by(|a, b| {
let range_a = &a.primary_diagnostic.range; let range_a = &a.primary_diagnostic.range;
let range_b = &b.primary_diagnostic.range; let range_b = &b.primary_diagnostic.range;
range_a range_a
@ -442,7 +461,7 @@ impl ProjectDiagnosticsEditor {
.then_with(|| range_a.end.cmp(&range_b.end, &snapshot).unwrap()) .then_with(|| range_a.end.cmp(&range_b.end, &snapshot).unwrap())
}); });
if groups.is_empty() { if path_state.diagnostic_groups.is_empty() {
self.path_states.remove(path_ix); self.path_states.remove(path_ix);
} }
@ -451,7 +470,7 @@ impl ProjectDiagnosticsEditor {
let mut selections; let mut selections;
let new_excerpt_ids_by_selection_id; let new_excerpt_ids_by_selection_id;
if was_empty { if was_empty {
groups = self.path_states.first()?.1.as_slice(); groups = self.path_states.first()?.diagnostic_groups.as_slice();
new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect(); new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect();
selections = vec![Selection { selections = vec![Selection {
id: 0, id: 0,
@ -461,7 +480,7 @@ impl ProjectDiagnosticsEditor {
goal: SelectionGoal::None, goal: SelectionGoal::None,
}]; }];
} else { } else {
groups = self.path_states.get(path_ix)?.1.as_slice(); groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
new_excerpt_ids_by_selection_id = editor.refresh_selections(cx); new_excerpt_ids_by_selection_id = editor.refresh_selections(cx);
selections = editor.local_selections::<usize>(cx); selections = editor.local_selections::<usize>(cx);
} }
@ -575,6 +594,57 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
} }
} }
fn path_header_renderer(buffer: ModelHandle<Buffer>, build_settings: BuildSettings) -> RenderBlock {
Arc::new(move |cx| {
let settings = build_settings(cx);
let file_path = if let Some(file) = buffer.read(&**cx).file() {
file.path().to_string_lossy().to_string()
} else {
"untitled".to_string()
};
Label::new(file_path, settings.style.text.clone())
.aligned()
.left()
.contained()
.with_padding_left(cx.line_number_x)
.expanded()
.boxed()
})
}
fn diagnostic_header_renderer(
diagnostic: Diagnostic,
is_valid: bool,
build_settings: BuildSettings,
) -> RenderBlock {
Arc::new(move |cx| {
let settings = build_settings(cx);
let mut text_style = settings.style.text.clone();
let diagnostic_style = diagnostic_style(diagnostic.severity, is_valid, &settings.style);
text_style.color = diagnostic_style.text;
Text::new(diagnostic.message.clone(), text_style)
.with_soft_wrap(false)
.aligned()
.left()
.contained()
.with_style(diagnostic_style.header)
.with_padding_left(cx.line_number_x)
.expanded()
.boxed()
})
}
fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock {
Arc::new(move |cx| {
let settings = build_settings(cx);
let text_style = settings.style.text.clone();
Label::new("".to_string(), text_style)
.contained()
.with_padding_left(cx.line_number_x)
.boxed()
})
}
fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>( fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
lhs: &DiagnosticEntry<L>, lhs: &DiagnosticEntry<L>,
rhs: &DiagnosticEntry<R>, rhs: &DiagnosticEntry<R>,

View file

@ -69,6 +69,7 @@ where
pub struct BlockContext<'a> { pub struct BlockContext<'a> {
pub cx: &'a AppContext, pub cx: &'a AppContext,
pub anchor_x: f32, pub anchor_x: f32,
pub line_number_x: f32,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@ -939,7 +940,7 @@ mod tests {
start_row..start_row + block.height(), start_row..start_row + block.height(),
block.column(), block.column(),
block block
.render(&BlockContext { cx, anchor_x: 0. }) .render(&BlockContext { cx, anchor_x: 0., line_number_x: 0., })
.name() .name()
.unwrap() .unwrap()
.to_string(), .to_string(),

View file

@ -3851,47 +3851,6 @@ pub fn diagnostic_block_renderer(
}) })
} }
pub fn diagnostic_header_renderer(
buffer: ModelHandle<Buffer>,
diagnostic: Diagnostic,
is_valid: bool,
build_settings: BuildSettings,
) -> RenderBlock {
Arc::new(move |cx| {
let settings = build_settings(cx);
let mut text_style = settings.style.text.clone();
let diagnostic_style = diagnostic_style(diagnostic.severity, is_valid, &settings.style);
text_style.color = diagnostic_style.text;
let file_path = if let Some(file) = buffer.read(&**cx).file() {
file.path().to_string_lossy().to_string()
} else {
"untitled".to_string()
};
Flex::column()
.with_child(
Text::new(diagnostic.message.clone(), text_style)
.with_soft_wrap(false)
.boxed(),
)
.with_child(Label::new(file_path, settings.style.text.clone()).boxed())
.aligned()
.left()
.contained()
.with_style(diagnostic_style.header)
.expanded()
.boxed()
})
}
pub fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock {
Arc::new(move |cx| {
let settings = build_settings(cx);
let text_style = settings.style.text.clone();
Label::new("...".to_string(), text_style).boxed()
})
}
pub fn diagnostic_style( pub fn diagnostic_style(
severity: DiagnosticSeverity, severity: DiagnosticSeverity,
valid: bool, valid: bool,

View file

@ -624,6 +624,7 @@ impl EditorElement {
rows: Range<u32>, rows: Range<u32>,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
width: f32, width: f32,
line_number_x: f32,
text_x: f32, text_x: f32,
line_height: f32, line_height: f32,
style: &EditorStyle, style: &EditorStyle,
@ -647,7 +648,7 @@ impl EditorElement {
.x_for_index(block.column() as usize) .x_for_index(block.column() as usize)
}; };
let mut element = block.render(&BlockContext { cx, anchor_x }); let mut element = block.render(&BlockContext { cx, anchor_x, line_number_x, });
element.layout( element.layout(
SizeConstraint { SizeConstraint {
min: Vector2F::zero(), min: Vector2F::zero(),
@ -812,6 +813,7 @@ impl Element for EditorElement {
start_row..end_row, start_row..end_row,
&snapshot, &snapshot,
size.x(), size.x(),
gutter_padding,
gutter_width + text_offset.x(), gutter_width + text_offset.x(),
line_height, line_height,
&style, &style,

View file

@ -187,7 +187,7 @@ corner_radius = 6
[project_panel] [project_panel]
extends = "$panel" extends = "$panel"
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
[project_panel.entry] [project_panel.entry]
text = "$text.1" text = "$text.1"