diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4fc3af636d..5eb3d108df 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -13,7 +13,7 @@ use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; use std::{collections::HashSet, ops::Range}; use sum_tree::Bias; use tab_map::TabMap; -use theme::SyntaxTheme; +use theme::{BlockStyle, SyntaxTheme}; use wrap_map::WrapMap; pub trait ToDisplayPoint { @@ -172,8 +172,8 @@ impl DisplayMapSnapshot { self.buffer_snapshot.len() == 0 } - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.blocks_snapshot.buffer_rows(start_row) + pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> { + self.blocks_snapshot.buffer_rows(start_row, cx) } pub fn buffer_row_count(&self) -> u32 { @@ -416,6 +416,13 @@ impl ToDisplayPoint for Anchor { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DisplayRow { + Buffer(u32), + Block(BlockId, Option), + Wrap, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index cf1bec487b..6f5300d391 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,4 +1,7 @@ -use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; +use super::{ + wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}, + BlockStyle, DisplayRow, +}; use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::{Buffer, Chunk}; @@ -45,11 +48,12 @@ struct BlockRow(u32); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] struct WrapRow(u32); -struct Block { +pub struct Block { id: BlockId, position: Anchor, text: Rope, build_runs: Option Vec<(usize, HighlightStyle)>>>, + build_style: Option BlockStyle>>, disposition: BlockDisposition, } @@ -62,6 +66,7 @@ where pub position: P, pub text: T, pub build_runs: Option Vec<(usize, HighlightStyle)>>>, + pub build_style: Option BlockStyle>>, pub disposition: BlockDisposition, } @@ -115,6 +120,7 @@ pub struct BufferRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, input_buffer_rows: wrap_map::BufferRows<'a>, output_row: u32, + cx: Option<&'a AppContext>, started: bool, } @@ -415,6 +421,7 @@ impl<'a> BlockMapWriter<'a> { position, text: block.text.into(), build_runs: block.build_runs, + build_style: block.build_style, disposition: block.disposition, }), ); @@ -519,7 +526,7 @@ impl BlockSnapshot { } } - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { + pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); cursor.seek(&BlockRow(start_row), Bias::Right, &()); let (output_start, input_start) = cursor.start(); @@ -530,6 +537,7 @@ impl BlockSnapshot { }; let input_start_row = input_start.0 + overshoot; BufferRows { + cx, transforms: cursor, input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row), output_row: start_row, @@ -871,7 +879,7 @@ impl<'a> Iterator for BlockChunks<'a> { } impl<'a> Iterator for BufferRows<'a> { - type Item = Option; + type Item = DisplayRow; fn next(&mut self) -> Option { if self.started { @@ -885,10 +893,13 @@ impl<'a> Iterator for BufferRows<'a> { } let transform = self.transforms.item()?; - if transform.is_isomorphic() { - Some(self.input_buffer_rows.next().unwrap()) + if let Some(block) = &transform.block { + let style = self + .cx + .and_then(|cx| block.build_style.as_ref().map(|f| f(cx))); + Some(DisplayRow::Block(block.id, style)) } else { - Some(None) + Some(self.input_buffer_rows.next().unwrap()) } } } @@ -1006,6 +1017,7 @@ mod tests { id: BlockId(0), position: Anchor::min(), text: "one!\ntwo three\nfour".into(), + build_style: None, build_runs: Some(Arc::new(move |_| { vec![(3, red.into()), (6, Default::default()), (5, blue.into())] })), @@ -1080,25 +1092,28 @@ mod tests { let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); - writer.insert( + let block_ids = writer.insert( vec![ BlockProperties { position: Point::new(1, 0), text: "BLOCK 1", disposition: BlockDisposition::Above, build_runs: None, + build_style: None, }, BlockProperties { position: Point::new(1, 2), text: "BLOCK 2", disposition: BlockDisposition::Above, build_runs: None, + build_style: None, }, BlockProperties { position: Point::new(3, 2), text: "BLOCK 3", disposition: BlockDisposition::Below, build_runs: None, + build_style: None, }, ], cx, @@ -1181,8 +1196,16 @@ mod tests { ); assert_eq!( - snapshot.buffer_rows(0).collect::>(), - &[Some(0), None, None, Some(1), Some(2), Some(3), None] + snapshot.buffer_rows(0, None).collect::>(), + &[ + DisplayRow::Buffer(0), + DisplayRow::Block(block_ids[0], None), + DisplayRow::Block(block_ids[1], None), + DisplayRow::Buffer(1), + DisplayRow::Buffer(2), + DisplayRow::Buffer(3), + DisplayRow::Block(block_ids[2], None) + ] ); // Insert a line break, separating two block decorations into separate @@ -1227,12 +1250,14 @@ mod tests { text: "BLOCK 2", disposition: BlockDisposition::Below, build_runs: None, + build_style: None, }, ], cx, @@ -1325,8 +1350,9 @@ mod tests { BlockProperties { position, text, - build_runs: None, disposition, + build_runs: None, + build_style: None, } }) .collect::>(); @@ -1402,6 +1428,7 @@ mod tests { position: BlockPoint::new(row, column), text: block.text, build_runs: block.build_runs.clone(), + build_style: None, disposition: block.disposition, }, ) @@ -1424,7 +1451,7 @@ mod tests { .to_point(WrapPoint::new(row, 0), Bias::Left) .row; - while let Some((_, block)) = sorted_blocks.peek() { + while let Some((block_id, block)) = sorted_blocks.peek() { if block.position.row == row && block.disposition == BlockDisposition::Above { let text = block.text.to_string(); let padding = " ".repeat(block.position.column as usize); @@ -1434,7 +1461,7 @@ mod tests { expected_text.push_str(line); } expected_text.push('\n'); - expected_buffer_rows.push(None); + expected_buffer_rows.push(DisplayRow::Block(*block_id, None)); } sorted_blocks.next(); } else { @@ -1443,10 +1470,14 @@ mod tests { } let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; - expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); + expected_buffer_rows.push(if soft_wrapped { + DisplayRow::Wrap + } else { + DisplayRow::Buffer(buffer_row) + }); expected_text.push_str(input_line); - while let Some((_, block)) = sorted_blocks.peek() { + while let Some((block_id, block)) = sorted_blocks.peek() { if block.position.row == row && block.disposition == BlockDisposition::Below { let text = block.text.to_string(); let padding = " ".repeat(block.position.column as usize); @@ -1456,7 +1487,7 @@ mod tests { expected_text.push_str(&padding); expected_text.push_str(line); } - expected_buffer_rows.push(None); + expected_buffer_rows.push(DisplayRow::Block(*block_id, None)); } sorted_blocks.next(); } else { @@ -1480,7 +1511,7 @@ mod tests { ); assert_eq!( blocks_snapshot - .buffer_rows(start_row as u32) + .buffer_rows(start_row as u32, None) .collect::>(), &expected_buffer_rows[start_row..] ); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6a6327a0b3..d84b25936a 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -2,6 +2,7 @@ use super::{ fold_map, patch::Patch, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint}, + DisplayRow, }; use gpui::{ fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, @@ -712,7 +713,11 @@ impl Snapshot { prev_tab_row = tab_point.row(); soft_wrapped = false; } - expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); + expected_buffer_rows.push(if soft_wrapped { + DisplayRow::Wrap + } else { + DisplayRow::Buffer(buffer_row) + }); } for start_display_row in 0..expected_buffer_rows.len() { @@ -792,7 +797,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> Iterator for BufferRows<'a> { - type Item = Option; + type Item = DisplayRow; fn next(&mut self) -> Option { if self.output_row > self.max_output_row { @@ -812,7 +817,11 @@ impl<'a> Iterator for BufferRows<'a> { self.soft_wrapped = true; } - Some(if soft_wrapped { None } else { Some(buffer_row) }) + Some(if soft_wrapped { + DisplayRow::Wrap + } else { + DisplayRow::Buffer(buffer_row) + }) } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 15b89b3f58..7b13885319 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,6 +1,6 @@ use super::{ - DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, - SelectPhase, Snapshot, MAX_LINE_LEN, + DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, + Select, SelectPhase, Snapshot, MAX_LINE_LEN, }; use clock::ReplicaId; use gpui::{ @@ -25,6 +25,7 @@ use std::{ fmt::Write, ops::Range, }; +use theme::BlockStyle; pub struct EditorElement { view: WeakViewHandle, @@ -359,6 +360,30 @@ impl EditorElement { } if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) { + // Draw blocks + for (ixs, block_style) in &layout.block_layouts { + let row = start_row + ixs.start; + let origin = content_origin + + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top); + let height = ixs.len() as f32 * layout.line_height; + cx.scene.push_quad(Quad { + bounds: RectF::new(origin, vec2f(visible_text_bounds.width(), height)), + background: block_style.background, + border: block_style + .border + .map_or(Default::default(), |color| Border { + width: 1., + color, + overlay: true, + top: true, + right: false, + bottom: true, + left: false, + }), + corner_radius: 0., + }); + } + // Draw glyphs for (ix, line) in layout.line_layouts.iter().enumerate() { let row = start_row + ix as u32; @@ -401,18 +426,24 @@ impl EditorElement { .width() } - fn layout_line_numbers( + fn layout_rows( &self, rows: Range, active_rows: &BTreeMap, snapshot: &Snapshot, cx: &LayoutContext, - ) -> Vec> { + ) -> ( + Vec>, + Vec<(Range, BlockStyle)>, + ) { let style = &self.settings.style; - let mut layouts = Vec::with_capacity(rows.len()); + let include_line_numbers = snapshot.mode == EditorMode::Full; + let mut last_block_id = None; + let mut blocks = Vec::<(Range, BlockStyle)>::new(); + let mut line_number_layouts = Vec::with_capacity(rows.len()); let mut line_number = String::new(); - for (ix, buffer_row) in snapshot - .buffer_rows(rows.start) + for (ix, row) in snapshot + .buffer_rows(rows.start, cx) .take((rows.end - rows.start) as usize) .enumerate() { @@ -422,27 +453,46 @@ impl EditorElement { } else { style.line_number }; - if let Some(buffer_row) = buffer_row { - line_number.clear(); - write!(&mut line_number, "{}", buffer_row + 1).unwrap(); - layouts.push(Some(cx.text_layout_cache.layout_str( - &line_number, - style.text.font_size, - &[( - line_number.len(), - RunStyle { - font_id: style.text.font_id, - color, - underline: None, - }, - )], - ))); - } else { - layouts.push(None); + match row { + DisplayRow::Buffer(buffer_row) => { + if include_line_numbers { + line_number.clear(); + write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + line_number_layouts.push(Some(cx.text_layout_cache.layout_str( + &line_number, + style.text.font_size, + &[( + line_number.len(), + RunStyle { + font_id: style.text.font_id, + color, + underline: None, + }, + )], + ))); + } + last_block_id = None; + } + DisplayRow::Block(block_id, style) => { + let ix = ix as u32; + if last_block_id == Some(block_id) { + if let Some((row_range, _)) = blocks.last_mut() { + row_range.end += 1; + } + } else if let Some(style) = style { + blocks.push((ix..ix + 1, style)); + } + line_number_layouts.push(None); + last_block_id = Some(block_id); + } + DisplayRow::Wrap => { + line_number_layouts.push(None); + last_block_id = None; + } } } - layouts + (line_number_layouts, blocks) } fn layout_lines( @@ -541,7 +591,7 @@ impl EditorElement { } let underline = if let Some(severity) = chunk.diagnostic { - Some(super::diagnostic_color(severity, style)) + Some(super::diagnostic_style(severity, style).text) } else { highlight_style.underline }; @@ -669,11 +719,8 @@ impl Element for EditorElement { } }); - let line_number_layouts = if snapshot.mode == EditorMode::Full { - self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx) - } else { - Vec::new() - }; + let (line_number_layouts, block_layouts) = + self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx); let mut max_visible_line_width = 0.0; let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); @@ -695,6 +742,7 @@ impl Element for EditorElement { active_rows, line_layouts, line_number_layouts, + block_layouts, line_height, em_width, selections, @@ -817,6 +865,7 @@ pub struct LayoutState { active_rows: BTreeMap, line_layouts: Vec, line_number_layouts: Vec>, + block_layouts: Vec<(Range, BlockStyle)>, line_height: f32, em_width: f32, selections: HashMap>>, @@ -1071,11 +1120,11 @@ mod tests { }); let element = EditorElement::new(editor.downgrade(), settings); - let layouts = editor.update(cx, |editor, cx| { + let (layouts, _) = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); let mut presenter = cx.build_presenter(window_id, 30.); let mut layout_cx = presenter.build_layout_context(false, cx); - element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx) + element.layout_rows(0..6, &Default::default(), &snapshot, &mut layout_cx) }); assert_eq!(layouts.len(), 6); } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 5269742782..e4eb4ffb75 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -7,12 +7,11 @@ mod test; use buffer::rope::TextDimension; use clock::ReplicaId; -pub use display_map::DisplayPoint; use display_map::*; +pub use display_map::{DisplayPoint, DisplayRow}; pub use element::*; use gpui::{ action, - color::Color, geometry::vector::{vec2f, Vector2F}, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, @@ -33,7 +32,7 @@ use std::{ time::Duration, }; use sum_tree::Bias; -use theme::{EditorStyle, SyntaxTheme}; +use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme}; use util::post_inc; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -342,6 +341,7 @@ struct BracketPairState { #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, + group_range: Range, block_ids: HashSet, } @@ -2238,21 +2238,34 @@ impl Editor { loop { let next_group = buffer .diagnostics_in_range::<_, usize>(search_start..buffer.len()) - .filter(|(_, diagnostic)| diagnostic.is_primary) - .skip_while(|(range, _)| { - Some(range.end) == active_primary_range.as_ref().map(|r| *r.end()) - }) - .next() - .map(|(range, diagnostic)| (range, diagnostic.group_id)); + .find_map(|(range, diagnostic)| { + if diagnostic.is_primary + && Some(range.end) != active_primary_range.as_ref().map(|r| *r.end()) + { + Some((range, diagnostic.group_id)) + } else { + None + } + }); if let Some((primary_range, group_id)) = next_group { self.dismiss_diagnostics(cx); self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { let buffer = self.buffer.read(cx); + + let mut group_end = Point::zero(); let diagnostic_group = buffer .diagnostic_group::(group_id) - .map(|(range, diagnostic)| (range, diagnostic.clone())) + .map(|(range, diagnostic)| { + if range.end > group_end { + group_end = range.end; + } + (range, diagnostic.clone()) + }) .collect::>(); + + let group_range = buffer.anchor_after(diagnostic_group[0].0.start) + ..buffer.anchor_before(group_end); let primary_range = buffer.anchor_after(primary_range.start) ..buffer.anchor_before(primary_range.end); @@ -2265,12 +2278,24 @@ impl Editor { BlockProperties { position: range.start, text: diagnostic.message.as_str(), - build_runs: Some(Arc::new(move |cx| { - let settings = build_settings.borrow()(cx); - vec![( - message_len, - diagnostic_color(severity, &settings.style).into(), - )] + build_runs: Some(Arc::new({ + let build_settings = build_settings.clone(); + move |cx| { + let settings = build_settings.borrow()(cx); + vec![( + message_len, + diagnostic_style(severity, &settings.style) + .text + .into(), + )] + } + })), + build_style: Some(Arc::new({ + let build_settings = build_settings.clone(); + move |cx| { + let settings = build_settings.borrow()(cx); + diagnostic_style(severity, &settings.style).block + } })), disposition: BlockDisposition::Below, } @@ -2282,6 +2307,7 @@ impl Editor { Some(ActiveDiagnosticGroup { primary_range, + group_range, block_ids, }) }); @@ -2815,8 +2841,8 @@ impl Snapshot { self.display_snapshot.buffer_row_count() } - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.display_snapshot.buffer_rows(start_row) + pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> { + self.display_snapshot.buffer_rows(start_row, Some(cx)) } pub fn chunks<'a>( @@ -2893,10 +2919,10 @@ impl EditorSettings { selection: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - error_color: Default::default(), - warning_color: Default::default(), - information_color: Default::default(), - hint_color: Default::default(), + diagnostic_error: Default::default(), + diagnostic_warning: Default::default(), + diagnostic_information: Default::default(), + diagnostic_hint: Default::default(), } }, } @@ -3020,13 +3046,13 @@ impl SelectionExt for Selection { } } -pub fn diagnostic_color(severity: DiagnosticSeverity, style: &EditorStyle) -> Color { +pub fn diagnostic_style(severity: DiagnosticSeverity, style: &EditorStyle) -> DiagnosticStyle { match severity { - DiagnosticSeverity::ERROR => style.error_color, - DiagnosticSeverity::WARNING => style.warning_color, - DiagnosticSeverity::INFORMATION => style.information_color, - DiagnosticSeverity::HINT => style.hint_color, - _ => style.text.color, + DiagnosticSeverity::ERROR => style.diagnostic_error, + DiagnosticSeverity::WARNING => style.diagnostic_warning, + DiagnosticSeverity::INFORMATION => style.diagnostic_information, + DiagnosticSeverity::HINT => style.diagnostic_hint, + _ => Default::default(), } } diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index ab451b0141..8f48ebbc13 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -227,12 +227,19 @@ pub struct EditorStyle { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, - pub error_color: Color, - pub warning_color: Color, + pub diagnostic_error: DiagnosticStyle, + pub diagnostic_warning: DiagnosticStyle, #[serde(default)] - pub information_color: Color, + pub diagnostic_information: DiagnosticStyle, #[serde(default)] - pub hint_color: Color, + pub diagnostic_hint: DiagnosticStyle, +} + +#[derive(Copy, Clone, Deserialize, Default)] +pub struct DiagnosticStyle { + pub text: Color, + #[serde(flatten)] + pub block: BlockStyle, } #[derive(Clone, Copy, Default, Deserialize)] @@ -251,6 +258,12 @@ pub struct InputEditorStyle { pub selection: SelectionStyle, } +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +pub struct BlockStyle { + pub background: Option, + pub border: Option, +} + impl EditorStyle { pub fn placeholder_text(&self) -> &TextStyle { self.placeholder_text.as_ref().unwrap_or(&self.text) @@ -273,10 +286,10 @@ impl InputEditorStyle { line_number_active: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - error_color: Default::default(), - warning_color: Default::default(), - information_color: Default::default(), - hint_color: Default::default(), + diagnostic_error: Default::default(), + diagnostic_warning: Default::default(), + diagnostic_information: Default::default(), + diagnostic_hint: Default::default(), } } } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 609228d86d..2cef216a5f 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -173,7 +173,7 @@ corner_radius = 6 [project_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] text = "$text.1" @@ -236,6 +236,7 @@ line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" error_color = "$status.bad" -warning_color = "$status.warn" -info_color = "$status.info" -hint_color = "$status.info" +diagnostic_error = { text = "$status.bad", border = "#ff0000", background = "#ffdddd" } +diagnostic_warning = { text = "$status.warn", border = "#ffff00", background = "#ffffdd" } +diagnostic_info = { text = "$status.info" } +diagnostic_hint = { text = "$status.info" }