From d0e82b0538079f69008afcfa742ad26cedc3822a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 4 Apr 2025 17:37:42 -0600 Subject: [PATCH] Introduce "Near" block type (#28032) A "Near" block acts similarly to a "Below" block, but can (if it's height is <= one line height) be shown on the end of the preceding line instead of adding an entire blank line to the editor. You can test it out by pasting this into `go_to_diagnostic_impl` and then press `F8` ``` let buffer = self.buffer.read(cx).snapshot(cx); let selection = self.selections.newest_anchor(); self.display_map.update(cx, |display_map, cx| { display_map.insert_blocks( [BlockProperties { placement: BlockPlacement::Near(selection.start), height: Some(1), style: BlockStyle::Flex, render: Arc::new(|_| { div() .w(px(100.)) .h(px(16.)) .bg(gpui::hsla(0., 0., 1., 0.5)) .into_any_element() }), priority: 0, }], cx, ) }); return; ``` Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra --- crates/agent/src/inline_assistant.rs | 6 +- crates/assistant/src/inline_assistant.rs | 6 +- .../src/context_editor.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 10 +- crates/editor/src/display_map.rs | 14 +- crates/editor/src/display_map/block_map.rs | 225 +++++++++--------- crates/editor/src/display_map/fold_map.rs | 1 + crates/editor/src/editor.rs | 7 +- crates/editor/src/editor_tests.rs | 4 +- crates/editor/src/element.rs | 154 ++++++++---- crates/multi_buffer/src/anchor.rs | 4 + crates/repl/src/session.rs | 2 +- 12 files changed, 246 insertions(+), 191 deletions(-) diff --git a/crates/agent/src/inline_assistant.rs b/crates/agent/src/inline_assistant.rs index b88a6e9c5d..45c8abdb7a 100644 --- a/crates/agent/src/inline_assistant.rs +++ b/crates/agent/src/inline_assistant.rs @@ -621,14 +621,14 @@ impl InlineAssistant { BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Above(range.start), - height: prompt_editor_height, + height: Some(prompt_editor_height), render: build_assist_editor_renderer(prompt_editor), priority: 0, }, BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Below(range.end), - height: 0, + height: None, render: Arc::new(|cx| { v_flex() .h_full() @@ -1392,7 +1392,7 @@ impl InlineAssistant { deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1); new_blocks.push(BlockProperties { placement: BlockPlacement::Above(new_row), - height, + height: Some(height), style: BlockStyle::Flex, render: Arc::new(move |cx| { div() diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 97c6d5785b..d5914292de 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -527,14 +527,14 @@ impl InlineAssistant { BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Above(range.start), - height: prompt_editor_height, + height: Some(prompt_editor_height), render: build_assist_editor_renderer(prompt_editor), priority: 0, }, BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Below(range.end), - height: 0, + height: None, render: Arc::new(|cx| { v_flex() .h_full() @@ -1301,7 +1301,7 @@ impl InlineAssistant { deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1); new_blocks.push(BlockProperties { placement: BlockPlacement::Above(new_row), - height, + height: Some(height), style: BlockStyle::Flex, render: Arc::new(move |cx| { div() diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 1c7396bd92..77fcbf0499 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -1562,7 +1562,7 @@ impl ContextEditor { }) }; let create_block_properties = |message: &Message| BlockProperties { - height: 2, + height: Some(2), style: BlockStyle::Sticky, placement: BlockPlacement::Above( buffer @@ -2111,7 +2111,7 @@ impl ContextEditor { let image = render_image.clone(); anchor.is_valid(&buffer).then(|| BlockProperties { placement: BlockPlacement::Above(anchor), - height: MAX_HEIGHT_IN_LINES, + height: Some(MAX_HEIGHT_IN_LINES), style: BlockStyle::Sticky, render: Arc::new(move |cx| { let image_size = size_for_image( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 46d67fdba7..fc336c7836 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -535,7 +535,7 @@ impl ProjectDiagnosticsEditor { group_state.block_count += 1; blocks_to_add.push(BlockProperties { placement: BlockPlacement::Above(header_position), - height: 2, + height: Some(2), style: BlockStyle::Sticky, render: diagnostic_header_renderer(primary), priority: 0, @@ -557,7 +557,9 @@ impl ProjectDiagnosticsEditor { excerpt_id, entry.range.start, )), - height: diagnostic.message.matches('\n').count() as u32 + 1, + height: Some( + diagnostic.message.matches('\n').count() as u32 + 1, + ), style: BlockStyle::Fixed, render: diagnostic_block_renderer(diagnostic, None, true), priority: 0, @@ -613,9 +615,9 @@ impl ProjectDiagnosticsEditor { excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?, ) } - BlockPlacement::Replace(_) => { + BlockPlacement::Replace(_) | BlockPlacement::Near(_) => { unreachable!( - "no Replace block should have been pushed to blocks_to_add" + "no Near/Replace block should have been pushed to blocks_to_add" ) } }; diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index b2cbf6e080..896ee0be81 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -255,7 +255,7 @@ impl DisplayMap { BlockProperties { placement: BlockPlacement::Replace(start..=end), render, - height, + height: Some(height), style, priority, } @@ -1591,7 +1591,7 @@ pub mod tests { BlockProperties { placement, style: BlockStyle::Fixed, - height, + height: Some(height), render: Arc::new(|_| div().into_any()), priority, } @@ -1953,7 +1953,7 @@ pub mod tests { placement: BlockPlacement::Above( buffer_snapshot.anchor_before(Point::new(0, 0)), ), - height: 2, + height: Some(2), style: BlockStyle::Sticky, render: Arc::new(|_| div().into_any()), priority: 0, @@ -2148,7 +2148,7 @@ pub mod tests { placement: BlockPlacement::Below( buffer_snapshot.anchor_before(Point::new(1, 0)), ), - height: 1, + height: Some(1), style: BlockStyle::Sticky, render: Arc::new(|_| div().into_any()), priority: 0, @@ -2157,7 +2157,7 @@ pub mod tests { placement: BlockPlacement::Below( buffer_snapshot.anchor_before(Point::new(2, 0)), ), - height: 0, + height: None, style: BlockStyle::Sticky, render: Arc::new(|_| div().into_any()), priority: 0, @@ -2262,7 +2262,7 @@ pub mod tests { placement: BlockPlacement::Below( buffer_snapshot.anchor_before(Point::new(1, 0)), ), - height: 1, + height: Some(1), style: BlockStyle::Sticky, render: Arc::new(|_| div().into_any()), priority: 0, @@ -2336,7 +2336,7 @@ pub mod tests { buffer_snapshot.anchor_before(Point::new(1, 2)) ..=buffer_snapshot.anchor_after(Point::new(2, 3)), ), - height: 4, + height: Some(4), style: BlockStyle::Fixed, render: Arc::new(|_| div().into_any()), priority: 0, diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index fe427c3f56..3e8aaaaefb 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -84,6 +84,7 @@ pub type RenderBlock = Arc AnyElement pub enum BlockPlacement { Above(T), Below(T), + Near(T), Replace(RangeInclusive), } @@ -92,6 +93,7 @@ impl BlockPlacement { match self { BlockPlacement::Above(position) => position, BlockPlacement::Below(position) => position, + BlockPlacement::Near(position) => position, BlockPlacement::Replace(range) => range.start(), } } @@ -100,6 +102,7 @@ impl BlockPlacement { match self { BlockPlacement::Above(position) => position, BlockPlacement::Below(position) => position, + BlockPlacement::Near(position) => position, BlockPlacement::Replace(range) => range.end(), } } @@ -108,6 +111,7 @@ impl BlockPlacement { match self { BlockPlacement::Above(position) => BlockPlacement::Above(position), BlockPlacement::Below(position) => BlockPlacement::Below(position), + BlockPlacement::Near(position) => BlockPlacement::Near(position), BlockPlacement::Replace(range) => BlockPlacement::Replace(range.start()..=range.end()), } } @@ -116,44 +120,30 @@ impl BlockPlacement { match self { BlockPlacement::Above(position) => BlockPlacement::Above(f(position)), BlockPlacement::Below(position) => BlockPlacement::Below(f(position)), + BlockPlacement::Near(position) => BlockPlacement::Near(f(position)), BlockPlacement::Replace(range) => { let (start, end) = range.into_inner(); BlockPlacement::Replace(f(start)..=f(end)) } } } + + fn sort_order(&self) -> u8 { + match self { + BlockPlacement::Above(_) => 0, + BlockPlacement::Replace(_) => 1, + BlockPlacement::Near(_) => 2, + BlockPlacement::Below(_) => 3, + } + } } impl BlockPlacement { fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering { - match (self, other) { - (BlockPlacement::Above(anchor_a), BlockPlacement::Above(anchor_b)) - | (BlockPlacement::Below(anchor_a), BlockPlacement::Below(anchor_b)) => { - anchor_a.cmp(anchor_b, buffer) - } - (BlockPlacement::Above(anchor_a), BlockPlacement::Below(anchor_b)) => { - anchor_a.cmp(anchor_b, buffer).then(Ordering::Less) - } - (BlockPlacement::Below(anchor_a), BlockPlacement::Above(anchor_b)) => { - anchor_a.cmp(anchor_b, buffer).then(Ordering::Greater) - } - (BlockPlacement::Above(anchor), BlockPlacement::Replace(range)) => { - anchor.cmp(range.start(), buffer).then(Ordering::Less) - } - (BlockPlacement::Replace(range), BlockPlacement::Above(anchor)) => { - range.start().cmp(anchor, buffer).then(Ordering::Greater) - } - (BlockPlacement::Below(anchor), BlockPlacement::Replace(range)) => { - anchor.cmp(range.start(), buffer).then(Ordering::Greater) - } - (BlockPlacement::Replace(range), BlockPlacement::Below(anchor)) => { - range.start().cmp(anchor, buffer).then(Ordering::Less) - } - (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => range_a - .start() - .cmp(range_b.start(), buffer) - .then_with(|| range_b.end().cmp(range_a.end(), buffer)), - } + self.start() + .cmp(other.start(), buffer) + .then_with(|| other.end().cmp(self.end(), buffer)) + .then_with(|| self.sort_order().cmp(&other.sort_order())) } fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option> { @@ -165,6 +155,12 @@ impl BlockPlacement { let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row()); Some(BlockPlacement::Above(wrap_row)) } + BlockPlacement::Near(position) => { + let mut position = position.to_point(buffer_snapshot); + position.column = buffer_snapshot.line_len(MultiBufferRow(position.row)); + let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row()); + Some(BlockPlacement::Near(wrap_row)) + } BlockPlacement::Below(position) => { let mut position = position.to_point(buffer_snapshot); position.column = buffer_snapshot.line_len(MultiBufferRow(position.row)); @@ -193,7 +189,7 @@ impl BlockPlacement { pub struct CustomBlock { id: CustomBlockId, placement: BlockPlacement, - height: u32, + height: Option, style: BlockStyle, render: Arc>, priority: usize, @@ -201,7 +197,9 @@ pub struct CustomBlock { pub struct BlockProperties

{ pub placement: BlockPlacement

, - pub height: u32, + // None if the block takes up no space + // (e.g. a horizontal line) + pub height: Option, pub style: BlockStyle, pub render: RenderBlock, pub priority: usize, @@ -302,9 +300,16 @@ impl Block { } } + pub fn has_height(&self) -> bool { + match self { + Block::Custom(block) => block.height.is_some(), + Block::ExcerptBoundary { .. } | Block::FoldedBuffer { .. } => true, + } + } + pub fn height(&self) -> u32 { match self { - Block::Custom(block) => block.height, + Block::Custom(block) => block.height.unwrap_or(0), Block::ExcerptBoundary { height, .. } | Block::FoldedBuffer { height, .. } => *height, } } @@ -324,9 +329,20 @@ impl Block { } } + pub fn place_near(&self) -> bool { + match self { + Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)), + Block::FoldedBuffer { .. } => false, + Block::ExcerptBoundary { .. } => false, + } + } + fn place_below(&self) -> bool { match self { - Block::Custom(block) => matches!(block.placement, BlockPlacement::Below(_)), + Block::Custom(block) => matches!( + block.placement, + BlockPlacement::Below(_) | BlockPlacement::Near(_) + ), Block::FoldedBuffer { .. } => false, Block::ExcerptBoundary { .. } => false, } @@ -669,7 +685,7 @@ impl BlockMap { BlockPlacement::Above(position) => { rows_before_block = position.0 - new_transforms.summary().input_rows; } - BlockPlacement::Below(position) => { + BlockPlacement::Near(position) | BlockPlacement::Below(position) => { rows_before_block = (position.0 + 1) - new_transforms.summary().input_rows; } BlockPlacement::Replace(range) => { @@ -803,60 +819,39 @@ impl BlockMap { fn sort_blocks(blocks: &mut Vec<(BlockPlacement, Block)>) { blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| { - let placement_comparison = match (placement_a, placement_b) { - (BlockPlacement::Above(row_a), BlockPlacement::Above(row_b)) - | (BlockPlacement::Below(row_a), BlockPlacement::Below(row_b)) => row_a.cmp(row_b), - (BlockPlacement::Above(row_a), BlockPlacement::Below(row_b)) => { - row_a.cmp(row_b).then(Ordering::Less) - } - (BlockPlacement::Below(row_a), BlockPlacement::Above(row_b)) => { - row_a.cmp(row_b).then(Ordering::Greater) - } - (BlockPlacement::Above(row), BlockPlacement::Replace(range)) => { - row.cmp(range.start()).then(Ordering::Greater) - } - (BlockPlacement::Replace(range), BlockPlacement::Above(row)) => { - range.start().cmp(row).then(Ordering::Less) - } - (BlockPlacement::Below(row), BlockPlacement::Replace(range)) => { - row.cmp(range.start()).then(Ordering::Greater) - } - (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => { - range.start().cmp(row).then(Ordering::Less) - } - (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => range_a - .start() - .cmp(range_b.start()) - .then_with(|| range_b.end().cmp(range_a.end())) - .then_with(|| { - if block_a.is_header() { - Ordering::Less - } else if block_b.is_header() { - Ordering::Greater - } else { - Ordering::Equal - } - }), - }; - placement_comparison.then_with(|| match (block_a, block_b) { - ( - Block::ExcerptBoundary { - excerpt: excerpt_a, .. - }, - Block::ExcerptBoundary { - excerpt: excerpt_b, .. - }, - ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)), - (Block::ExcerptBoundary { .. }, Block::Custom(_)) => Ordering::Less, - (Block::Custom(_), Block::ExcerptBoundary { .. }) => Ordering::Greater, - (Block::Custom(block_a), Block::Custom(block_b)) => block_a - .priority - .cmp(&block_b.priority) - .then_with(|| block_a.id.cmp(&block_b.id)), - _ => { - unreachable!() - } - }) + placement_a + .start() + .cmp(placement_b.start()) + .then_with(|| placement_b.end().cmp(placement_a.end())) + .then_with(|| { + if block_a.is_header() { + Ordering::Less + } else if block_b.is_header() { + Ordering::Greater + } else { + Ordering::Equal + } + }) + .then_with(|| placement_a.sort_order().cmp(&placement_b.sort_order())) + .then_with(|| match (block_a, block_b) { + ( + Block::ExcerptBoundary { + excerpt: excerpt_a, .. + }, + Block::ExcerptBoundary { + excerpt: excerpt_b, .. + }, + ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)), + (Block::ExcerptBoundary { .. }, Block::Custom(_)) => Ordering::Less, + (Block::Custom(_), Block::ExcerptBoundary { .. }) => Ordering::Greater, + (Block::Custom(block_a), Block::Custom(block_b)) => block_a + .priority + .cmp(&block_b.priority) + .then_with(|| block_a.id.cmp(&block_b.id)), + _ => { + unreachable!() + } + }) }); blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) { (BlockPlacement::Replace(range), BlockPlacement::Above(row)) @@ -999,7 +994,7 @@ impl BlockMapWriter<'_> { let mut previous_wrap_row_range: Option> = None; for block in blocks { if let BlockPlacement::Replace(_) = &block.placement { - debug_assert!(block.height > 0); + debug_assert!(block.height.unwrap() > 0); } let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst)); @@ -1064,11 +1059,11 @@ impl BlockMapWriter<'_> { debug_assert!(new_height > 0); } - if block.height != new_height { + if block.height != Some(new_height) { let new_block = CustomBlock { id: block.id, placement: block.placement.clone(), - height: new_height, + height: Some(new_height), style: block.style, render: block.render.clone(), priority: block.priority, @@ -1965,21 +1960,21 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))), - height: 2, + height: Some(2), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))), - height: 3, + height: Some(3), render: Arc::new(|_| div().into_any()), priority: 0, }, @@ -1992,7 +1987,7 @@ mod tests { .blocks_in_range(0..8) .map(|(start_row, block)| { let block = block.as_custom().unwrap(); - (start_row..start_row + block.height, block.id) + (start_row..start_row + block.height.unwrap(), block.id) }) .collect::>(); @@ -2203,21 +2198,21 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))), - height: 2, + height: Some(2), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))), - height: 3, + height: Some(3), render: Arc::new(|_| div().into_any()), priority: 0, }, @@ -2307,14 +2302,14 @@ mod tests { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))), render: Arc::new(|_| div().into_any()), - height: 1, + height: Some(1), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))), render: Arc::new(|_| div().into_any()), - height: 1, + height: Some(1), priority: 0, }, ]); @@ -2352,7 +2347,7 @@ mod tests { buffer_snapshot.anchor_after(Point::new(1, 3)) ..=buffer_snapshot.anchor_before(Point::new(3, 1)), ), - height: 4, + height: Some(4), render: Arc::new(|_| div().into_any()), priority: 0, }])[0]; @@ -2405,21 +2400,21 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, @@ -2433,21 +2428,21 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, @@ -2546,21 +2541,21 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, @@ -2569,14 +2564,14 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }, @@ -2623,7 +2618,7 @@ mod tests { let excerpt_blocks_1 = writer.insert(vec![BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }]); @@ -2980,7 +2975,7 @@ mod tests { BlockProperties { style: BlockStyle::Fixed, placement, - height, + height: Some(height), render: Arc::new(|_| div().into_any()), priority: 0, } @@ -3007,7 +3002,7 @@ mod tests { for (block_properties, block_id) in block_properties.iter().zip(block_ids) { log::info!( - "inserted block {:?} with height {} and id {:?}", + "inserted block {:?} with height {:?} and id {:?}", block_properties .placement .as_ref() @@ -3524,7 +3519,7 @@ mod tests { let _block_id = writer.insert(vec![BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }])[0]; diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 0ebc42cf60..a8b5d135fc 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -14,6 +14,7 @@ use std::{ fmt, iter, ops::{Add, AddAssign, Deref, DerefMut, Range, Sub}, sync::Arc, + usize, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree, Summary, TreeMap}; use ui::IntoElement as _; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9ef6a969aa..9148e28816 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8717,7 +8717,7 @@ impl Editor { let blocks = vec![BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Above(anchor), - height, + height: Some(height), render: Arc::new(move |cx| { *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions; cloned_prompt.clone().into_any_element() @@ -12801,7 +12801,6 @@ impl Editor { ) { let buffer = self.buffer.read(cx).snapshot(cx); let selection = self.selections.newest::(cx); - // If there is an active Diagnostic Popover jump to its diagnostic instead. if direction == Direction::Next { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { @@ -13806,7 +13805,7 @@ impl Editor { [BlockProperties { style: BlockStyle::Flex, placement: BlockPlacement::Below(range.start), - height: 1, + height: Some(1), render: Arc::new({ let rename_editor = rename_editor.clone(); move |cx: &mut BlockContext| { @@ -14273,7 +14272,7 @@ impl Editor { placement: BlockPlacement::Below( buffer.anchor_after(entry.range.start), ), - height: message_height, + height: Some(message_height), render: diagnostic_block_renderer(diagnostic, None, true), priority: 0, } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ae222321b3..8d986db7f8 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4234,7 +4234,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { [BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))), - height: 1, + height: Some(1), render: Arc::new(|_| div().into_any()), priority: 0, }], @@ -4275,7 +4275,7 @@ async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) { editor.insert_blocks( [BlockProperties { placement, - height: 4, + height: Some(4), style: BlockStyle::Sticky, render: Arc::new(|_| gpui::div().into_any_element()), priority: 0, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4ec4ad12e6..30dc317c64 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -27,7 +27,7 @@ use crate::{ }; use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind}; use client::ParticipantIndex; -use collections::{BTreeMap, HashMap, HashSet}; +use collections::{BTreeMap, HashMap}; use feature_flags::{Debugger, FeatureFlagAppExt}; use file_icons::FileIcons; use git::{Oid, blame::BlameEntry, status::FileStatus}; @@ -1219,7 +1219,7 @@ impl EditorElement { &self, snapshot: &EditorSnapshot, selections: &[(PlayerColor, Vec)], - block_start_rows: &HashSet, + row_block_types: &HashMap, visible_display_row_range: Range, line_layouts: &[LineWithInvisibles], text_hitbox: &Hitbox, @@ -1246,7 +1246,7 @@ impl EditorElement { let in_range = visible_display_row_range.contains(&cursor_position.row()); if (selection.is_local && !show_local_cursors) || !in_range - || block_start_rows.contains(&cursor_position.row()) + || row_block_types.get(&cursor_position.row()) == Some(&true) { continue; } @@ -1571,6 +1571,7 @@ impl EditorElement { &self, line_layouts: &[LineWithInvisibles], crease_trailers: &[Option], + row_block_types: &HashMap, content_origin: gpui::Point, scroll_pixel_position: gpui::Point, inline_completion_popover_origin: Option>, @@ -1616,6 +1617,7 @@ impl EditorElement { .map(|(point, diag)| (point.to_display_point(&snapshot), diag.clone())) .skip_while(|(point, _)| point.row() < start_row) .take_while(|(point, _)| point.row() < end_row) + .filter(|(point, _)| !row_block_types.contains_key(&point.row())) .fold(HashMap::default(), |mut acc, (point, diagnostic)| { acc.entry(point.row()) .or_insert_with(Vec::new) @@ -2614,34 +2616,41 @@ impl EditorElement { editor_width: Pixels, scroll_width: &mut Pixels, resized_blocks: &mut HashMap, + row_block_types: &mut HashMap, selections: &[Selection], selected_buffer_ids: &Vec, is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, sticky_header_excerpt_id: Option, window: &mut Window, cx: &mut App, - ) -> (AnyElement, Size) { + ) -> (AnyElement, Size, DisplayRow, Pixels) { + let mut x_position = None; let mut element = match block { Block::Custom(block) => { let block_start = block.start().to_point(&snapshot.buffer_snapshot); let block_end = block.end().to_point(&snapshot.buffer_snapshot); let align_to = block_start.to_display_point(snapshot); - let anchor_x = text_x - + if rows.contains(&align_to.row()) { - line_layouts[align_to.row().minus(rows.start) as usize] - .x_for_index(align_to.column() as usize) - } else { - layout_line( - align_to.row(), - snapshot, - &self.style, - editor_width, - is_row_soft_wrapped, - window, - cx, - ) - .x_for_index(align_to.column() as usize) - }; + let x_and_width = |layout: &LineWithInvisibles| { + Some(( + text_x + layout.x_for_index(align_to.column() as usize), + text_x + layout.width, + )) + }; + x_position = if rows.contains(&align_to.row()) { + x_and_width(&line_layouts[align_to.row().minus(rows.start) as usize]) + } else { + x_and_width(&layout_line( + align_to.row(), + snapshot, + &self.style, + editor_width, + is_row_soft_wrapped, + window, + cx, + )) + }; + + let anchor_x = x_position.unwrap().0; let selected = selections .binary_search_by(|selection| { @@ -2746,17 +2755,43 @@ impl EditorElement { element.layout_as_root(size(available_width, quantized_height.into()), window, cx) }; + let mut row = block_row_start; + let mut x_offset = px(0.); + let mut is_block = true; + if let BlockId::Custom(custom_block_id) = block_id { - if block.height() > 0 { - let element_height_in_lines = + if block.has_height() { + let mut element_height_in_lines = ((final_size.height / line_height).ceil() as u32).max(1); + + if block.place_near() && element_height_in_lines == 1 { + if let Some((x_target, line_width)) = x_position { + let margin = em_width * 2; + if line_width + final_size.width + margin + < editor_width + gutter_dimensions.full_width() + && !row_block_types.contains_key(&(row - 1)) + { + x_offset = line_width + margin; + row = row - 1; + is_block = false; + element_height_in_lines = 0; + } else { + let max_offset = + editor_width + gutter_dimensions.full_width() - final_size.width; + let min_offset = (x_target + em_width - final_size.width) + .max(gutter_dimensions.full_width()); + x_offset = x_target.min(max_offset).max(min_offset); + } + } + }; if element_height_in_lines != block.height() { resized_blocks.insert(custom_block_id, element_height_in_lines); } } } + row_block_types.insert(row, is_block); - (element, final_size) + (element, final_size, row, x_offset) } fn render_buffer_header( @@ -2962,14 +2997,14 @@ impl EditorElement { em_width: Pixels, text_x: Pixels, line_height: Pixels, - line_layouts: &[LineWithInvisibles], + line_layouts: &mut [LineWithInvisibles], selections: &[Selection], selected_buffer_ids: &Vec, is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, sticky_header_excerpt_id: Option, window: &mut Window, cx: &mut App, - ) -> Result, HashMap> { + ) -> Result<(Vec, HashMap), HashMap> { let (fixed_blocks, non_fixed_blocks) = snapshot .blocks_in_range(rows.clone()) .partition::, _>(|(_, block)| block.style() == BlockStyle::Fixed); @@ -2980,6 +3015,7 @@ impl EditorElement { let mut fixed_block_max_width = Pixels::ZERO; let mut blocks = Vec::new(); let mut resized_blocks = HashMap::default(); + let mut row_block_types = HashMap::default(); for (row, block) in fixed_blocks { let block_id = block.id(); @@ -2988,7 +3024,7 @@ impl EditorElement { focused_block = None; } - let (element, element_size) = self.render_block( + let (element, element_size, row, x_offset) = self.render_block( block, AvailableSpace::MinContent, block_id, @@ -3004,6 +3040,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + &mut row_block_types, selections, selected_buffer_ids, is_row_soft_wrapped, @@ -3011,27 +3048,32 @@ impl EditorElement { window, cx, ); + fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width); blocks.push(BlockLayout { id: block_id, + x_offset, row: Some(row), element, available_space: size(AvailableSpace::MinContent, element_size.height.into()), style: BlockStyle::Fixed, + overlaps_gutter: true, is_buffer_header: block.is_buffer_header(), }); } for (row, block) in non_fixed_blocks { let style = block.style(); - let width = match style { - BlockStyle::Sticky => hitbox.size.width, - BlockStyle::Flex => hitbox + let width = match (style, block.place_near()) { + (_, true) => AvailableSpace::MinContent, + (BlockStyle::Sticky, _) => hitbox.size.width.into(), + (BlockStyle::Flex, _) => hitbox .size .width .max(fixed_block_max_width) - .max(gutter_dimensions.width + *scroll_width), - BlockStyle::Fixed => unreachable!(), + .max(gutter_dimensions.width + *scroll_width) + .into(), + (BlockStyle::Fixed, _) => unreachable!(), }; let block_id = block.id(); @@ -3039,9 +3081,9 @@ impl EditorElement { focused_block = None; } - let (element, element_size) = self.render_block( + let (element, element_size, row, x_offset) = self.render_block( block, - width.into(), + width, block_id, row, snapshot, @@ -3055,6 +3097,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + &mut row_block_types, selections, selected_buffer_ids, is_row_soft_wrapped, @@ -3065,10 +3108,12 @@ impl EditorElement { blocks.push(BlockLayout { id: block_id, + x_offset, row: Some(row), element, - available_space: size(width.into(), element_size.height.into()), + available_space: size(width, element_size.height.into()), style, + overlaps_gutter: !block.place_near(), is_buffer_header: block.is_buffer_header(), }); } @@ -3090,7 +3135,7 @@ impl EditorElement { BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width), }; - let (element, element_size) = self.render_block( + let (element, element_size, _, x_offset) = self.render_block( &block, width, focused_block.id, @@ -3106,6 +3151,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + &mut row_block_types, selections, selected_buffer_ids, is_row_soft_wrapped, @@ -3116,10 +3162,12 @@ impl EditorElement { blocks.push(BlockLayout { id: block.id(), + x_offset, row: None, element, available_space: size(width, element_size.height.into()), style, + overlaps_gutter: true, is_buffer_header: block.is_buffer_header(), }); } @@ -3129,18 +3177,15 @@ impl EditorElement { if resized_blocks.is_empty() { *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width); - Ok(blocks) + Ok((blocks, row_block_types)) } else { Err(resized_blocks) } } - /// Returns true if any of the blocks changed size since the previous frame. This will trigger - /// a restart of rendering for the editor based on the new sizes. fn layout_blocks( &self, blocks: &mut Vec, - block_starts: &mut HashSet, hitbox: &Hitbox, line_height: Pixels, scroll_pixel_position: gpui::Point, @@ -3149,10 +3194,9 @@ impl EditorElement { ) { for block in blocks { let mut origin = if let Some(row) = block.row { - block_starts.insert(row); hitbox.origin + point( - Pixels::ZERO, + block.x_offset, row.as_f32() * line_height - scroll_pixel_position.y, ) } else { @@ -5322,7 +5366,15 @@ impl EditorElement { fn paint_blocks(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { for mut block in layout.blocks.drain(..) { - block.element.paint(window, cx); + if block.overlaps_gutter { + block.element.paint(window, cx); + } else { + let mut bounds = layout.hitbox.bounds; + bounds.origin.x += layout.gutter_hitbox.bounds.size.width; + window.with_content_mask(Some(ContentMask { bounds }), |window| { + block.element.paint(window, cx); + }) + } } } @@ -6955,7 +7007,7 @@ impl Element for EditorElement { em_width, gutter_dimensions.full_width(), line_height, - &line_layouts, + &mut line_layouts, &local_selections, &selected_buffer_ids, is_row_soft_wrapped, @@ -6964,7 +7016,7 @@ impl Element for EditorElement { cx, ) }); - let mut blocks = match blocks { + let (mut blocks, row_block_types) = match blocks { Ok(blocks) => blocks, Err(resized_blocks) => { self.editor.update(cx, |editor, cx| { @@ -7078,6 +7130,7 @@ impl Element for EditorElement { let mut inline_diagnostics = self.layout_inline_diagnostics( &line_layouts, &crease_trailers, + &row_block_types, content_origin, scroll_pixel_position, inline_completion_popover_origin, @@ -7093,7 +7146,9 @@ impl Element for EditorElement { let mut inline_blame = None; if let Some(newest_selection_head) = newest_selection_head { let display_row = newest_selection_head.row(); - if (start_row..end_row).contains(&display_row) { + if (start_row..end_row).contains(&display_row) + && !row_block_types.contains_key(&display_row) + { let line_ix = display_row.minus(start_row) as usize; let row_info = &row_infos[line_ix]; let line_layout = &line_layouts[line_ix]; @@ -7161,12 +7216,9 @@ impl Element for EditorElement { cx, ); - let mut block_start_rows = HashSet::default(); - window.with_element_namespace("blocks", |window| { self.layout_blocks( &mut blocks, - &mut block_start_rows, &hitbox, line_height, scroll_pixel_position, @@ -7184,7 +7236,7 @@ impl Element for EditorElement { let visible_cursors = self.layout_visible_cursors( &snapshot, &selections, - &block_start_rows, + &row_block_types, start_row..end_row, &line_layouts, &text_hitbox, @@ -8028,10 +8080,12 @@ impl PositionMap { struct BlockLayout { id: BlockId, + x_offset: Pixels, row: Option, element: AnyElement, available_space: Size, style: BlockStyle, + overlaps_gutter: bool, is_buffer_header: bool, } @@ -8620,7 +8674,7 @@ mod tests { [BlockProperties { style: BlockStyle::Fixed, placement: BlockPlacement::Above(Anchor::min()), - height: 3, + height: Some(3), render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()), priority: 0, }], diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index 3cb8b0302e..e59e8d1ab1 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -57,6 +57,10 @@ impl Anchor { } pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering { + if self == other { + return Ordering::Equal; + } + let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot); if excerpt_id_cmp.is_ne() { return excerpt_id_cmp; diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 82d0f915f8..c5b93c5d38 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -89,7 +89,7 @@ impl EditorBlock { let block = BlockProperties { placement: BlockPlacement::Below(code_range.end), // Take up at least one height for status, allow the editor to determine the real height based on the content from render - height: 1, + height: Some(1), style: BlockStyle::Sticky, render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()), priority: 0,