diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 9e028c5321..dda69cbf9c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1,8 +1,9 @@ use anyhow::Result; +use collections::{hash_map, HashMap, HashSet}; use editor::{ context_header_renderer, diagnostic_block_renderer, diagnostic_header_renderer, - display_map::{BlockDisposition, BlockProperties}, - BuildSettings, Editor, ExcerptProperties, MultiBuffer, + display_map::{BlockDisposition, BlockId, BlockProperties}, + BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, }; use gpui::{ action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, @@ -11,7 +12,7 @@ use gpui::{ use language::{Bias, Buffer, Point}; use postage::watch; use project::Project; -use std::ops::Range; +use std::{ops::Range, path::Path, sync::Arc}; use util::TryFutureExt; use workspace::Workspace; @@ -33,9 +34,22 @@ struct ProjectDiagnostics { struct ProjectDiagnosticsEditor { editor: ViewHandle, excerpts: ModelHandle, + path_states: Vec<(Arc, PathState)>, build_settings: BuildSettings, } +#[derive(Default)] +struct PathState { + last_excerpt: ExcerptId, + diagnostic_group_states: HashMap, +} + +#[derive(Default)] +struct DiagnosticGroupState { + excerpts: Vec, + blocks: Vec, +} + impl ProjectDiagnostics { fn new(project: ModelHandle) -> Self { Self { project } @@ -118,6 +132,7 @@ impl ProjectDiagnosticsEditor { excerpts, editor, build_settings, + path_states: Default::default(), } } @@ -132,13 +147,66 @@ impl ProjectDiagnosticsEditor { } fn populate_excerpts(&mut self, buffer: ModelHandle, cx: &mut ViewContext) { - let mut blocks = Vec::new(); - let snapshot = buffer.read(cx).snapshot(); + let snapshot; + let path; + { + let buffer = buffer.read(cx); + snapshot = buffer.snapshot(); + if let Some(file) = buffer.file() { + path = file.path().clone(); + } else { + return; + } + } + let path_ix = match self + .path_states + .binary_search_by_key(&path.as_ref(), |e| e.0.as_ref()) + { + Ok(ix) => ix, + Err(ix) => { + self.path_states.insert( + ix, + ( + path.clone(), + PathState { + last_excerpt: ExcerptId::max(), + diagnostic_group_states: Default::default(), + }, + ), + ); + ix + } + }; + let mut prev_excerpt_id = if path_ix > 0 { + self.path_states[path_ix - 1].1.last_excerpt.clone() + } else { + ExcerptId::min() + }; + let path_state = &mut self.path_states[path_ix].1; + + let mut blocks_to_add = Vec::new(); + let mut blocks_to_remove = HashSet::default(); + let mut excerpts_to_remove = Vec::new(); + let mut block_counts_by_group = Vec::new(); + + let diagnostic_groups = snapshot.diagnostic_groups::(); let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| { - for group in snapshot.diagnostic_groups::() { + for group in &diagnostic_groups { + let group_id = group.entries[0].diagnostic.group_id; + + let group_state = match path_state.diagnostic_group_states.entry(group_id) { + hash_map::Entry::Occupied(e) => { + prev_excerpt_id = e.get().excerpts.last().unwrap().clone(); + block_counts_by_group.push(0); + continue; + } + hash_map::Entry::Vacant(e) => e.insert(DiagnosticGroupState::default()), + }; + + let mut block_count = 0; let mut pending_range: Option<(Range, usize)> = None; - let mut is_first_excerpt = true; + let mut is_first_excerpt_for_group = 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 { @@ -154,7 +222,8 @@ impl ProjectDiagnosticsEditor { Point::new(range.end.row + CONTEXT_LINE_COUNT, u32::MAX), Bias::Left, ); - let excerpt_id = excerpts.push_excerpt( + let excerpt_id = excerpts.insert_excerpt_after( + &prev_excerpt_id, ExcerptProperties { buffer: &buffer, range: excerpt_start..excerpt_end, @@ -162,13 +231,18 @@ impl ProjectDiagnosticsEditor { excerpts_cx, ); + prev_excerpt_id = excerpt_id.clone(); + group_state.excerpts.push(excerpt_id.clone()); let header_position = (excerpt_id.clone(), language::Anchor::min()); - if is_first_excerpt { + + if is_first_excerpt_for_group { + is_first_excerpt_for_group = false; let primary = &group.entries[group.primary_ix].diagnostic; let mut header = primary.clone(); header.message = primary.message.split('\n').next().unwrap().to_string(); - blocks.push(BlockProperties { + block_count += 1; + blocks_to_add.push(BlockProperties { position: header_position, height: 2, render: diagnostic_header_renderer( @@ -179,7 +253,8 @@ impl ProjectDiagnosticsEditor { disposition: BlockDisposition::Above, }); } else { - blocks.push(BlockProperties { + block_count += 1; + blocks_to_add.push(BlockProperties { position: header_position, height: 1, render: context_header_renderer(self.build_settings.clone()), @@ -187,7 +262,6 @@ impl ProjectDiagnosticsEditor { }); } - is_first_excerpt = false; for entry in &group.entries[*start_ix..ix] { let mut diagnostic = entry.diagnostic.clone(); if diagnostic.is_primary { @@ -198,7 +272,8 @@ impl ProjectDiagnosticsEditor { if !diagnostic.message.is_empty() { let buffer_anchor = snapshot.anchor_before(entry.range.start); - blocks.push(BlockProperties { + block_count += 1; + blocks_to_add.push(BlockProperties { position: (excerpt_id.clone(), buffer_anchor), height: diagnostic.message.matches('\n').count() as u8 + 1, render: diagnostic_block_renderer( @@ -218,14 +293,36 @@ impl ProjectDiagnosticsEditor { pending_range = Some((entry.range.clone(), ix)); } } + + block_counts_by_group.push(block_count); } + path_state + .diagnostic_group_states + .retain(|group_id, group_state| { + if diagnostic_groups + .iter() + .any(|group| group.entries[0].diagnostic.group_id == *group_id) + { + true + } else { + excerpts_to_remove.extend(group_state.excerpts.drain(..)); + blocks_to_remove.extend(group_state.blocks.drain(..)); + false + } + }); + + excerpts_to_remove.sort(); + excerpts.remove_excerpts(excerpts_to_remove.iter(), excerpts_cx); excerpts.snapshot(excerpts_cx) }); + path_state.last_excerpt = prev_excerpt_id; + self.editor.update(cx, |editor, cx| { - editor.insert_blocks( - blocks.into_iter().map(|block| { + editor.remove_blocks(blocks_to_remove, cx); + let block_ids = editor.insert_blocks( + blocks_to_add.into_iter().map(|block| { let (excerpt_id, text_anchor) = block.position; BlockProperties { position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor), @@ -236,6 +333,20 @@ impl ProjectDiagnosticsEditor { }), cx, ); + + let mut block_ids = block_ids.into_iter(); + let mut block_counts_by_group = block_counts_by_group.into_iter(); + for group in &diagnostic_groups { + let group_id = group.entries[0].diagnostic.group_id; + let block_count = block_counts_by_group.next().unwrap(); + let group_state = path_state + .diagnostic_group_states + .get_mut(&group_id) + .unwrap(); + group_state + .blocks + .extend(block_ids.by_ref().take(block_count)); + } }); cx.notify(); } @@ -454,7 +565,9 @@ mod tests { assert_eq!( editor.text(), concat!( - // Diagnostic group 1 (error for `y`) + // + // main.rs, diagnostic group 1 + // "\n", // primary message "\n", // filename " let x = vec![];\n", @@ -468,7 +581,9 @@ mod tests { " c(y);\n", "\n", // supporting diagnostic " d(x);\n", - // Diagnostic group 2 (error for `x`) + // + // main.rs, diagnostic group 2 + // "\n", // primary message "\n", // filename "fn main() {\n", @@ -516,11 +631,15 @@ mod tests { 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", @@ -534,7 +653,9 @@ mod tests { " c(y);\n", "\n", // supporting diagnostic " d(x);\n", + // // main.rs, diagnostic group 2 + // "\n", // primary message "\n", // filename "fn main() {\n",