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 b88a6e9c5d726b8383ec556490124fd8f35ab925..45c8abdb7ae9d675e66d7ea5f61101bd31d1a4a5 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 97c6d5785be9f0b89d74eeb6ca392980206e910d..d5914292dec0975665b1a1c8cc64ce4ec046185c 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 9ef6a969aa0cad8ed6738d911fc81f2aef863668..9148e2881616c8f2ece80d57f1ea932934bc5b8c 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 ae222321b305a977a39749538114f14db1f671f4..8d986db7f83cc8eabd67bfbf00af921cc86bc089 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 4ec4ad12e68ba53f0a9f8b62b477dd5059a4b256..30dc317c64e5f0805d339ed68f49f9b896560b1a 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 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,