From 992d79185272d64bd08db881168de304a1c7a57d 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 b0356f9048243e598008a43f9201460668d48dd2..25eb4e0f2f3080bc583900f06e2298e1c83a1e67 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 9a64528a70733a81d310f7caaf529a06fb63f9b5..9d08bc5d5816f6f0cfb02b9a8021dbfa594f3b91 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 1c7396bd92a5116c40c13843b887f7ebd109d01d..77fcbf049900965503c933c10dff97883f757126 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 46d67fdba7376f69c8441fd80c5b595d5ea6ad3b..fc336c783679cf1631b55c2ebb6b822fa55a90b9 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 b2cbf6e080b74fa95009a59b37552cf2235a02a5..896ee0be81a2dff22a184fd1149f5c78ab09afe3 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 fe427c3f560767761c785f7ed9e0d4b9c22f1270..3e8aaaaefbe118f42c6d76270e993002208f5c26 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 0ebc42cf6062b25efd7341a40b6be1e868f4c45d..a8b5d135fc9cf87239c776e63d76cf7159d1c712 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 6fcfe983d8a1f38e64d99922fe15e972266de2ad..db8772dab81d5c35e12a15e8615d8ea4d65a1f7a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8713,7 +8713,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() @@ -12797,7 +12797,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() { @@ -13802,7 +13801,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| { @@ -14250,7 +14249,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 61870d0fb8a0c67d12adf325782e558874a3c496..ea82be65d563232fe8a2c5447a8ea6d27f7eb115 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 53dc175a66a515efebd34f3d9ab52a2e6544ef76..e27fee930ca4616f051d2aa8c481e69c07f029a5 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}; @@ -1218,7 +1218,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, @@ -1245,7 +1245,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; } @@ -1570,6 +1570,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>, @@ -1615,6 +1616,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) @@ -2615,34 +2617,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| { @@ -2747,17 +2756,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( @@ -2963,14 +2998,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); @@ -2981,6 +3016,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(); @@ -2989,7 +3025,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, @@ -3005,6 +3041,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + &mut row_block_types, selections, selected_buffer_ids, is_row_soft_wrapped, @@ -3012,27 +3049,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(); @@ -3040,9 +3082,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, @@ -3056,6 +3098,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + &mut row_block_types, selections, selected_buffer_ids, is_row_soft_wrapped, @@ -3066,10 +3109,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(), }); } @@ -3091,7 +3136,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, @@ -3107,6 +3152,7 @@ impl EditorElement { editor_width, scroll_width, &mut resized_blocks, + &mut row_block_types, selections, selected_buffer_ids, is_row_soft_wrapped, @@ -3117,10 +3163,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(), }); } @@ -3130,18 +3178,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, @@ -3150,10 +3195,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 { @@ -5324,7 +5368,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); + }) + } } } @@ -6957,7 +7009,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, @@ -6966,7 +7018,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| { @@ -7080,6 +7132,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, @@ -7095,7 +7148,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]; @@ -7163,12 +7218,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, @@ -7186,7 +7238,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, @@ -8030,10 +8082,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, } @@ -8622,7 +8676,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 3cb8b0302edc9adef6378ad55f62294c25ed286f..e59e8d1ab188071580e970d6142636446a7affad 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 82d0f915f8acbc6c91a7cade810fb92aeefddab5..c5b93c5d389e81719f4d1da0a199c3d4a16d8249 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,