diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index 75bfd6415c..1daffffb4e 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -962,7 +962,6 @@ fn random_diagnostic( const FILE_HEADER: &str = "file header"; const EXCERPT_HEADER: &str = "excerpt header"; -const EXCERPT_FOOTER: &str = "excerpt footer"; fn editor_blocks( editor: &View, @@ -998,7 +997,7 @@ fn editor_blocks( .ok()? } - Block::ExcerptHeader { + Block::ExcerptBoundary { starts_new_buffer, .. } => { if *starts_new_buffer { @@ -1007,7 +1006,6 @@ fn editor_blocks( EXCERPT_HEADER.into() } } - Block::ExcerptFooter { .. } => EXCERPT_FOOTER.into(), }; Some((row, name)) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 52e0ca2486..f4ee57408b 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -5,8 +5,8 @@ use super::{ use crate::{EditorStyle, GutterDimensions}; use collections::{Bound, HashMap, HashSet}; use gpui::{AnyElement, EntityId, Pixels, WindowContext}; -use language::{BufferSnapshot, Chunk, Patch, Point}; -use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _}; +use language::{Chunk, Patch, Point}; +use multi_buffer::{Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, ToPoint as _}; use parking_lot::Mutex; use std::{ cell::RefCell, @@ -128,26 +128,17 @@ pub struct BlockContext<'a, 'b> { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BlockId { Custom(CustomBlockId), - ExcerptHeader(ExcerptId), - ExcerptFooter(ExcerptId), -} - -impl From for EntityId { - fn from(value: BlockId) -> Self { - match value { - BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64), - BlockId::ExcerptHeader(id) => id.into(), - BlockId::ExcerptFooter(id) => id.into(), - } - } + ExcerptBoundary(Option), } impl From for ElementId { fn from(value: BlockId) -> Self { match value { BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(), - BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(), - BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(), + BlockId::ExcerptBoundary(next_excerpt) => match next_excerpt { + Some(id) => ("ExcerptBoundary", EntityId::from(id)).into(), + None => "LastExcerptBoundary".into(), + }, } } } @@ -156,8 +147,7 @@ impl std::fmt::Display for BlockId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Custom(id) => write!(f, "Block({id:?})"), - Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"), - Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"), + Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"), } } } @@ -177,8 +167,7 @@ struct Transform { pub(crate) enum BlockType { Custom(CustomBlockId), - Header, - Footer, + ExcerptBoundary, } pub(crate) trait BlockLike { @@ -191,27 +180,20 @@ pub(crate) trait BlockLike { #[derive(Clone)] pub enum Block { Custom(Arc), - ExcerptHeader { - id: ExcerptId, - buffer: BufferSnapshot, - range: ExcerptRange, + ExcerptBoundary { + prev_excerpt: Option, + next_excerpt: Option, height: u32, starts_new_buffer: bool, show_excerpt_controls: bool, }, - ExcerptFooter { - id: ExcerptId, - disposition: BlockDisposition, - height: u32, - }, } impl BlockLike for Block { fn block_type(&self) -> BlockType { match self { Block::Custom(block) => BlockType::Custom(block.id), - Block::ExcerptHeader { .. } => BlockType::Header, - Block::ExcerptFooter { .. } => BlockType::Footer, + Block::ExcerptBoundary { .. } => BlockType::ExcerptBoundary, } } @@ -222,8 +204,7 @@ impl BlockLike for Block { fn priority(&self) -> usize { match self { Block::Custom(block) => block.priority, - Block::ExcerptHeader { .. } => usize::MAX, - Block::ExcerptFooter { .. } => 0, + Block::ExcerptBoundary { .. } => usize::MAX, } } } @@ -232,32 +213,36 @@ impl Block { pub fn id(&self) -> BlockId { match self { Block::Custom(block) => BlockId::Custom(block.id), - Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id), - Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id), + Block::ExcerptBoundary { next_excerpt, .. } => { + BlockId::ExcerptBoundary(next_excerpt.as_ref().map(|info| info.id)) + } } } fn disposition(&self) -> BlockDisposition { match self { Block::Custom(block) => block.disposition, - Block::ExcerptHeader { .. } => BlockDisposition::Above, - Block::ExcerptFooter { disposition, .. } => *disposition, + Block::ExcerptBoundary { next_excerpt, .. } => { + if next_excerpt.is_some() { + BlockDisposition::Above + } else { + BlockDisposition::Below + } + } } } pub fn height(&self) -> u32 { match self { Block::Custom(block) => block.height, - Block::ExcerptHeader { height, .. } => *height, - Block::ExcerptFooter { height, .. } => *height, + Block::ExcerptBoundary { height, .. } => *height, } } pub fn style(&self) -> BlockStyle { match self { Block::Custom(block) => block.style, - Block::ExcerptHeader { .. } => BlockStyle::Sticky, - Block::ExcerptFooter { .. } => BlockStyle::Sticky, + Block::ExcerptBoundary { .. } => BlockStyle::Sticky, } } } @@ -266,24 +251,17 @@ impl Debug for Block { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(), - Self::ExcerptHeader { - buffer, + Self::ExcerptBoundary { starts_new_buffer, - id, + next_excerpt, + prev_excerpt, .. } => f - .debug_struct("ExcerptHeader") - .field("id", &id) - .field("path", &buffer.file().map(|f| f.path())) + .debug_struct("ExcerptBoundary") + .field("prev_excerpt", &prev_excerpt) + .field("next_excerpt", &next_excerpt) .field("starts_new_buffer", &starts_new_buffer) .finish(), - Block::ExcerptFooter { - id, disposition, .. - } => f - .debug_struct("ExcerptFooter") - .field("id", &id) - .field("disposition", &disposition) - .finish(), } } } @@ -595,66 +573,62 @@ impl BlockMap { { buffer .excerpt_boundaries_in_range(range) - .flat_map(move |excerpt_boundary| { - let mut wrap_row = wrap_snapshot - .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left) - .row(); - - [ - show_excerpt_controls - .then(|| { - let disposition; - if excerpt_boundary.next.is_some() { - disposition = BlockDisposition::Above; - } else { - wrap_row = wrap_snapshot - .make_wrap_point( - Point::new( - excerpt_boundary.row.0, - buffer.line_len(excerpt_boundary.row), - ), - Bias::Left, - ) - .row(); - disposition = BlockDisposition::Below; - } - - excerpt_boundary.prev.as_ref().map(|prev| { - ( - wrap_row, - Block::ExcerptFooter { - id: prev.id, - height: excerpt_footer_height, - disposition, - }, - ) - }) - }) - .flatten(), - excerpt_boundary.next.map(|next| { - let starts_new_buffer = excerpt_boundary - .prev - .map_or(true, |prev| prev.buffer_id != next.buffer_id); - - ( - wrap_row, - Block::ExcerptHeader { - id: next.id, - buffer: next.buffer, - range: next.range, - height: if starts_new_buffer { - buffer_header_height - } else { - excerpt_header_height - }, - starts_new_buffer, - show_excerpt_controls, - }, + .filter_map(move |excerpt_boundary| { + let wrap_row; + if excerpt_boundary.next.is_some() { + wrap_row = wrap_snapshot + .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left) + .row(); + } else { + wrap_row = wrap_snapshot + .make_wrap_point( + Point::new( + excerpt_boundary.row.0, + buffer.line_len(excerpt_boundary.row), + ), + Bias::Left, ) - }), - ] + .row(); + } + + let starts_new_buffer = match (&excerpt_boundary.prev, &excerpt_boundary.next) { + (_, None) => false, + (None, Some(_)) => true, + (Some(prev), Some(next)) => prev.buffer_id != next.buffer_id, + }; + + let mut height = 0; + if excerpt_boundary.prev.is_some() { + if show_excerpt_controls { + height += excerpt_footer_height; + } + } + if excerpt_boundary.next.is_some() { + if starts_new_buffer { + height += buffer_header_height; + if show_excerpt_controls { + height += excerpt_header_height; + } + } else { + height += excerpt_header_height; + } + } + + if height == 0 { + return None; + } + + Some(( + wrap_row, + Block::ExcerptBoundary { + prev_excerpt: excerpt_boundary.prev, + next_excerpt: excerpt_boundary.next, + height, + starts_new_buffer, + show_excerpt_controls, + }, + )) }) - .flatten() } pub(crate) fn sort_blocks(blocks: &mut [(u32, B)]) { @@ -665,12 +639,9 @@ impl BlockMap { .disposition() .cmp(&block_b.disposition()) .then_with(|| match ((block_a.block_type()), (block_b.block_type())) { - (BlockType::Footer, BlockType::Footer) => Ordering::Equal, - (BlockType::Footer, _) => Ordering::Less, - (_, BlockType::Footer) => Ordering::Greater, - (BlockType::Header, BlockType::Header) => Ordering::Equal, - (BlockType::Header, _) => Ordering::Less, - (_, BlockType::Header) => Ordering::Greater, + (BlockType::ExcerptBoundary, BlockType::ExcerptBoundary) => Ordering::Equal, + (BlockType::ExcerptBoundary, _) => Ordering::Less, + (_, BlockType::ExcerptBoundary) => Ordering::Greater, (BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b .priority() .cmp(&block_a.priority()) @@ -1045,33 +1016,19 @@ impl BlockSnapshot { let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?; Some(Block::Custom(custom_block.clone())) } - BlockId::ExcerptHeader(excerpt_id) => { - let excerpt_range = buffer.range_for_excerpt::(excerpt_id)?; - let wrap_point = self - .wrap_snapshot - .make_wrap_point(excerpt_range.start, Bias::Left); - let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&()); - cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &()); - while let Some(transform) = cursor.item() { - if let Some(block) = transform.block.as_ref() { - if block.id() == block_id { - return Some(block.clone()); - } - } else if cursor.start().0 > WrapRow(wrap_point.row()) { - break; - } - - cursor.next(&()); + BlockId::ExcerptBoundary(next_excerpt_id) => { + let wrap_point; + if let Some(next_excerpt_id) = next_excerpt_id { + let excerpt_range = buffer.range_for_excerpt::(next_excerpt_id)?; + wrap_point = self + .wrap_snapshot + .make_wrap_point(excerpt_range.start, Bias::Left); + } else { + wrap_point = self + .wrap_snapshot + .make_wrap_point(buffer.max_point(), Bias::Left); } - None - } - BlockId::ExcerptFooter(excerpt_id) => { - let excerpt_range = buffer.range_for_excerpt::(excerpt_id)?; - let wrap_point = self - .wrap_snapshot - .make_wrap_point(excerpt_range.end, Bias::Left); - let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &()); while let Some(transform) = cursor.item() { @@ -1468,7 +1425,7 @@ mod tests { }; use gpui::{div, font, px, AppContext, Context as _, Element}; use language::{Buffer, Capability}; - use multi_buffer::MultiBuffer; + use multi_buffer::{ExcerptRange, MultiBuffer}; use rand::prelude::*; use settings::SettingsStore; use std::env; @@ -1724,22 +1681,20 @@ mod tests { // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline. assert_eq!( snapshot.text(), - "\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n" + "\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n" ); let blocks: Vec<_> = snapshot .blocks_in_range(0..u32::MAX) - .map(|(row, block)| (row, block.id())) + .map(|(row, block)| (row..row + block.height(), block.id())) .collect(); assert_eq!( blocks, vec![ - (0, BlockId::ExcerptHeader(excerpt_ids[0])), - (3, BlockId::ExcerptFooter(excerpt_ids[0])), - (4, BlockId::ExcerptHeader(excerpt_ids[1])), - (7, BlockId::ExcerptFooter(excerpt_ids[1])), - (8, BlockId::ExcerptHeader(excerpt_ids[2])), - (11, BlockId::ExcerptFooter(excerpt_ids[2])) + (0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header + (4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header + (9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header + (14..15, BlockId::ExcerptBoundary(None)), // footer ] ); } @@ -2283,13 +2238,10 @@ mod tests { #[derive(Debug, Eq, PartialEq)] enum ExpectedBlock { - ExcerptHeader { + ExcerptBoundary { height: u32, starts_new_buffer: bool, - }, - ExcerptFooter { - height: u32, - disposition: BlockDisposition, + is_last: bool, }, Custom { disposition: BlockDisposition, @@ -2303,8 +2255,7 @@ mod tests { fn block_type(&self) -> BlockType { match self { ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id), - ExpectedBlock::ExcerptHeader { .. } => BlockType::Header, - ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer, + ExpectedBlock::ExcerptBoundary { .. } => BlockType::ExcerptBoundary, } } @@ -2315,8 +2266,7 @@ mod tests { fn priority(&self) -> usize { match self { ExpectedBlock::Custom { priority, .. } => *priority, - ExpectedBlock::ExcerptHeader { .. } => usize::MAX, - ExpectedBlock::ExcerptFooter { .. } => 0, + ExpectedBlock::ExcerptBoundary { .. } => usize::MAX, } } } @@ -2324,17 +2274,21 @@ mod tests { impl ExpectedBlock { fn height(&self) -> u32 { match self { - ExpectedBlock::ExcerptHeader { height, .. } => *height, + ExpectedBlock::ExcerptBoundary { height, .. } => *height, ExpectedBlock::Custom { height, .. } => *height, - ExpectedBlock::ExcerptFooter { height, .. } => *height, } } fn disposition(&self) -> BlockDisposition { match self { - ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above, + ExpectedBlock::ExcerptBoundary { is_last, .. } => { + if *is_last { + BlockDisposition::Below + } else { + BlockDisposition::Above + } + } ExpectedBlock::Custom { disposition, .. } => *disposition, - ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition, } } } @@ -2348,21 +2302,15 @@ mod tests { height: block.height, priority: block.priority, }, - Block::ExcerptHeader { + Block::ExcerptBoundary { height, starts_new_buffer, + next_excerpt, .. - } => ExpectedBlock::ExcerptHeader { + } => ExpectedBlock::ExcerptBoundary { height, starts_new_buffer, - }, - Block::ExcerptFooter { - height, - disposition, - .. - } => ExpectedBlock::ExcerptFooter { - height, - disposition, + is_last: next_excerpt.is_none(), }, } } @@ -2380,8 +2328,7 @@ mod tests { fn as_custom(&self) -> Option<&CustomBlock> { match self { Block::Custom(block) => Some(block), - Block::ExcerptHeader { .. } => None, - Block::ExcerptFooter { .. } => None, + Block::ExcerptBoundary { .. } => None, } } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9d9cfde7b9..ba3841b4e2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -73,12 +73,12 @@ use git::blame::GitBlame; use gpui::{ div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry, - ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle, - FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, - KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, - SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, - UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, - VisualContext, WeakFocusHandle, WeakView, WindowContext, + ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, + FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, + ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, + Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UTF16Selection, + UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, + WeakFocusHandle, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -171,7 +171,7 @@ use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSetti use crate::hover_links::find_url; use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState}; -pub const FILE_HEADER_HEIGHT: u32 = 1; +pub const FILE_HEADER_HEIGHT: u32 = 2; pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1; pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1; pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2; @@ -640,7 +640,6 @@ pub struct Editor { tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, previous_search_ranges: Option]>>, - file_header_size: u32, breadcrumb_header: Option, focused_block: Option, next_scroll_position: NextScrollCursorCenterTopBottom, @@ -1846,7 +1845,6 @@ impl Editor { }), merge_adjacent: true, }; - let file_header_size = if show_excerpt_controls { 3 } else { 2 }; let display_map = cx.new_model(|cx| { DisplayMap::new( buffer.clone(), @@ -1854,7 +1852,7 @@ impl Editor { font_size, None, show_excerpt_controls, - file_header_size, + FILE_HEADER_HEIGHT, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT, fold_placeholder, @@ -2038,7 +2036,6 @@ impl Editor { .restore_unsaved_buffers, blame: None, blame_subscription: None, - file_header_size, tasks: Default::default(), _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -12808,7 +12805,7 @@ impl Editor { } pub fn file_header_size(&self) -> u32 { - self.file_header_size + FILE_HEADER_HEIGHT } pub fn revert( @@ -14120,7 +14117,7 @@ pub fn diagnostic_block_renderer( let multi_line_diagnostic = diagnostic.message.contains('\n'); - let buttons = |diagnostic: &Diagnostic, block_id: BlockId| { + let buttons = |diagnostic: &Diagnostic| { if multi_line_diagnostic { v_flex() } else { @@ -14128,7 +14125,7 @@ pub fn diagnostic_block_renderer( } .when(allow_closing, |div| { div.children(diagnostic.is_primary.then(|| { - IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle) + IconButton::new("close-block", IconName::XCircle) .icon_color(Color::Muted) .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) @@ -14138,7 +14135,7 @@ pub fn diagnostic_block_renderer( })) }) .child( - IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy) + IconButton::new("copy-block", IconName::Copy) .icon_color(Color::Muted) .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) @@ -14153,7 +14150,7 @@ pub fn diagnostic_block_renderer( ) }; - let icon_size = buttons(&diagnostic, cx.block_id) + let icon_size = buttons(&diagnostic) .into_any_element() .layout_as_root(AvailableSpace::min_size(), cx); @@ -14170,7 +14167,7 @@ pub fn diagnostic_block_renderer( .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width) .flex_shrink(), ) - .child(buttons(&diagnostic, cx.block_id)) + .child(buttons(&diagnostic)) .child(div().flex().flex_shrink_0().child( StyledText::new(text_without_backticks.clone()).with_highlights( &text_style, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a9dfe7e435..77b78d059c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -21,7 +21,8 @@ use crate::{ EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint, - CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, + CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use client::ParticipantIndex; use collections::{BTreeMap, HashMap}; @@ -31,7 +32,7 @@ use gpui::{ anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, - EntityId, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length, + FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, @@ -46,7 +47,7 @@ use language::{ ChunkRendererContext, }; use lsp::DiagnosticSeverity; -use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow}; +use multi_buffer::{Anchor, ExcerptId, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow}; use project::{ project_settings::{GitGutterSetting, ProjectSettings}, ProjectPath, @@ -1632,7 +1633,7 @@ impl EditorElement { let mut block_offset = 0; let mut found_excerpt_header = false; for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) { - if matches!(block, Block::ExcerptHeader { .. }) { + if matches!(block, Block::ExcerptBoundary { .. }) { found_excerpt_header = true; break; } @@ -1649,7 +1650,7 @@ impl EditorElement { let mut block_height = 0; let mut found_excerpt_header = false; for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) { - if matches!(block, Block::ExcerptHeader { .. }) { + if matches!(block, Block::ExcerptBoundary { .. }) { found_excerpt_header = true; } block_height += block.height(); @@ -2100,23 +2101,14 @@ impl EditorElement { .into_any_element() } - Block::ExcerptHeader { - buffer, - range, + Block::ExcerptBoundary { + prev_excerpt, + next_excerpt, + show_excerpt_controls, starts_new_buffer, height, - id, - show_excerpt_controls, .. } => { - let include_root = self - .editor - .read(cx) - .project - .as_ref() - .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) - .unwrap_or_default(); - #[derive(Clone)] struct JumpData { position: Point, @@ -2125,233 +2117,227 @@ impl EditorElement { line_offset_from_top: u32, } - let jump_data = project::File::from_dyn(buffer.file()).map(|file| { - let jump_path = ProjectPath { - worktree_id: file.worktree_id(cx), - path: file.path.clone(), - }; - let jump_anchor = range - .primary - .as_ref() - .map_or(range.context.start, |primary| primary.start); - - let excerpt_start = range.context.start; - let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); - let offset_from_excerpt_start = if jump_anchor == excerpt_start { - 0 - } else { - let excerpt_start_row = - language::ToPoint::to_point(&jump_anchor, buffer).row; - jump_position.row - excerpt_start_row - }; - - let line_offset_from_top = - block_row_start.0 + *height + offset_from_excerpt_start - - snapshot - .scroll_anchor - .scroll_position(&snapshot.display_snapshot) - .y as u32; - - JumpData { - position: jump_position, - anchor: jump_anchor, - path: jump_path, - line_offset_from_top, - } - }); - let icon_offset = gutter_dimensions.width - (gutter_dimensions.left_padding + gutter_dimensions.margin); - let element = if *starts_new_buffer { - let path = buffer.resolve_file_path(cx, include_root); - let mut filename = None; - let mut parent_path = None; - // Can't use .and_then() because `.file_name()` and `.parent()` return references :( - if let Some(path) = path { - filename = path.file_name().map(|f| f.to_string_lossy().to_string()); - parent_path = path - .parent() - .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/")); - } + let header_padding = px(6.0); - let header_padding = px(6.0); + let mut result = v_flex().id(block_id).w_full(); - v_flex() - .id(("path excerpt header", EntityId::from(block_id))) - .w_full() - .px(header_padding) - .pt(header_padding) - .child( + if let Some(prev_excerpt) = prev_excerpt { + if *show_excerpt_controls { + result = result.child( h_flex() - .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667))) - .id("path header block") - .h(2. * cx.line_height()) - .px(gpui::px(12.)) - .rounded_md() - .shadow_md() - .border_1() - .border_color(cx.theme().colors().border) - .bg(cx.theme().colors().editor_subheader_background) - .justify_between() - .hover(|style| style.bg(cx.theme().colors().element_hover)) - .child( - h_flex().gap_3().child( - h_flex() - .gap_2() - .child( - filename - .map(SharedString::from) - .unwrap_or_else(|| "untitled".into()), - ) - .when_some(parent_path, |then, path| { - then.child( - div() - .child(path) - .text_color(cx.theme().colors().text_muted), - ) - }), - ), - ) - .when_some(jump_data.clone(), |el, jump_data| { - el.child(Icon::new(IconName::ArrowUpRight)) - .cursor_pointer() - .tooltip(|cx| { - Tooltip::for_action("Jump to File", &OpenExcerpts, cx) - }) - .on_mouse_down(MouseButton::Left, |_, cx| { - cx.stop_propagation() - }) - .on_click(cx.listener_for(&self.editor, { - move |editor, _, cx| { - editor.jump( - jump_data.path.clone(), - jump_data.position, - jump_data.anchor, - jump_data.line_offset_from_top, - cx, - ); - } - })) - }), - ) - .children(show_excerpt_controls.then(|| { - h_flex() - .flex_basis(Length::Definite(DefiniteLength::Fraction(0.333))) - .h(1. * cx.line_height()) - .pt_1() - .justify_end() + .w(icon_offset) + .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height()) .flex_none() - .w(icon_offset - header_padding) - .child( - ButtonLike::new("expand-icon") - .style(ButtonStyle::Transparent) - .child( - svg() - .path(IconName::ArrowUpFromLine.path()) - .size(IconSize::XSmall.rems()) - .text_color(cx.theme().colors().editor_line_number) - .group("") - .hover(|style| { - style.text_color( - cx.theme() - .colors() - .editor_active_line_number, - ) - }), - ) - .on_click(cx.listener_for(&self.editor, { - let id = *id; - move |editor, _, cx| { - editor.expand_excerpt( - id, - multi_buffer::ExpandExcerptDirection::Up, - cx, - ); - } - })) - .tooltip({ - move |cx| { - Tooltip::for_action( - "Expand Excerpt", - &ExpandExcerpts { lines: 0 }, - cx, - ) - } - }), - ) - })) - } else { - v_flex() - .id(("excerpt header", EntityId::from(block_id))) - .w_full() - .h(snapshot.excerpt_header_height() as f32 * cx.line_height()) - .child( + .justify_end() + .child(self.render_expand_excerpt_button( + prev_excerpt.id, + ExpandExcerptDirection::Down, + IconName::ArrowDownFromLine, + cx, + )), + ); + } + } + + if let Some(next_excerpt) = next_excerpt { + let buffer = &next_excerpt.buffer; + let range = &next_excerpt.range; + let jump_data = project::File::from_dyn(buffer.file()).map(|file| { + let jump_path = ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path.clone(), + }; + let jump_anchor = range + .primary + .as_ref() + .map_or(range.context.start, |primary| primary.start); + + let excerpt_start = range.context.start; + let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); + let offset_from_excerpt_start = if jump_anchor == excerpt_start { + 0 + } else { + let excerpt_start_row = + language::ToPoint::to_point(&jump_anchor, buffer).row; + jump_position.row - excerpt_start_row + }; + + let line_offset_from_top = + block_row_start.0 + *height + offset_from_excerpt_start + - snapshot + .scroll_anchor + .scroll_position(&snapshot.display_snapshot) + .y as u32; + + JumpData { + position: jump_position, + anchor: jump_anchor, + path: jump_path, + line_offset_from_top, + } + }); + + if *starts_new_buffer { + let include_root = self + .editor + .read(cx) + .project + .as_ref() + .map(|project| project.read(cx).visible_worktrees(cx).count() > 1) + .unwrap_or_default(); + let path = buffer.resolve_file_path(cx, include_root); + let filename = path + .as_ref() + .and_then(|path| Some(path.file_name()?.to_string_lossy().to_string())); + let parent_path = path.as_ref().and_then(|path| { + Some(path.parent()?.to_string_lossy().to_string() + "/") + }); + + result = result.child( div() - .flex() - .v_flex() + .px(header_padding) + .pt(header_padding) + .w_full() + .h(FILE_HEADER_HEIGHT as f32 * cx.line_height()) + .child( + h_flex() + .id("path header block") + .size_full() + .flex_basis(Length::Definite(DefiniteLength::Fraction( + 0.667, + ))) + .px(gpui::px(12.)) + .rounded_md() + .shadow_md() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().editor_subheader_background) + .justify_between() + .hover(|style| style.bg(cx.theme().colors().element_hover)) + .child( + h_flex().gap_3().child( + h_flex() + .gap_2() + .child( + filename + .map(SharedString::from) + .unwrap_or_else(|| "untitled".into()), + ) + .when_some(parent_path, |then, path| { + then.child(div().child(path).text_color( + cx.theme().colors().text_muted, + )) + }), + ), + ) + .when_some(jump_data, |el, jump_data| { + el.child(Icon::new(IconName::ArrowUpRight)) + .cursor_pointer() + .tooltip(|cx| { + Tooltip::for_action( + "Jump to File", + &OpenExcerpts, + cx, + ) + }) + .on_mouse_down(MouseButton::Left, |_, cx| { + cx.stop_propagation() + }) + .on_click(cx.listener_for(&self.editor, { + move |editor, _, cx| { + editor.jump( + jump_data.path.clone(), + jump_data.position, + jump_data.anchor, + jump_data.line_offset_from_top, + cx, + ); + } + })) + }), + ), + ); + if *show_excerpt_controls { + result = result.child( + h_flex() + .w(icon_offset) + .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height()) + .flex_none() + .justify_end() + .child(self.render_expand_excerpt_button( + next_excerpt.id, + ExpandExcerptDirection::Up, + IconName::ArrowUpFromLine, + cx, + )), + ); + } + } else { + result = result.child( + h_flex() + .id("excerpt header block") + .group("excerpt-jump-action") .justify_start() - .id("jump to collapsed context") - .w(relative(1.0)) - .h_full() + .w_full() + .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height()) + .relative() .child( div() - .h_px() + .top(px(0.)) + .absolute() .w_full() + .h_px() .bg(cx.theme().colors().border_variant) .group_hover("excerpt-jump-action", |style| { style.bg(cx.theme().colors().border) }), - ), - ) - .child( - h_flex() - .justify_end() - .flex_none() - .w(icon_offset) - .h_full() + ) + .cursor_pointer() + .when_some(jump_data.clone(), |this, jump_data| { + this.on_click(cx.listener_for(&self.editor, { + let path = jump_data.path.clone(); + move |editor, _, cx| { + cx.stop_propagation(); + + editor.jump( + path.clone(), + jump_data.position, + jump_data.anchor, + jump_data.line_offset_from_top, + cx, + ); + } + })) + .tooltip(move |cx| { + Tooltip::for_action( + format!( + "Jump to {}:L{}", + jump_data.path.path.display(), + jump_data.position.row + 1 + ), + &OpenExcerpts, + cx, + ) + }) + }) .child( - show_excerpt_controls - .then(|| { - ButtonLike::new("expand-icon") - .style(ButtonStyle::Transparent) - .child( - svg() - .path(IconName::ArrowUpFromLine.path()) - .size(IconSize::XSmall.rems()) - .text_color( - cx.theme().colors().editor_line_number, - ) - .group("") - .hover(|style| { - style.text_color( - cx.theme() - .colors() - .editor_active_line_number, - ) - }), - ) - .on_click(cx.listener_for(&self.editor, { - let id = *id; - move |editor, _, cx| { - editor.expand_excerpt( - id, - multi_buffer::ExpandExcerptDirection::Up, - cx, - ); - } - })) - .tooltip({ - move |cx| { - Tooltip::for_action( - "Expand Excerpt", - &ExpandExcerpts { lines: 0 }, - cx, - ) - } - }) - }) - .unwrap_or_else(|| { + h_flex() + .w(icon_offset) + .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 + * cx.line_height()) + .flex_none() + .justify_end() + .child(if *show_excerpt_controls { + self.render_expand_excerpt_button( + next_excerpt.id, + ExpandExcerptDirection::Up, + IconName::ArrowUpFromLine, + cx, + ) + } else { ButtonLike::new("jump-icon") .style(ButtonStyle::Transparent) .child( @@ -2361,7 +2347,6 @@ impl EditorElement { .text_color( cx.theme().colors().border_variant, ) - .group("excerpt-jump-action") .group_hover( "excerpt-jump-action", |style| { @@ -2371,118 +2356,13 @@ impl EditorElement { }, ), ) - .when_some(jump_data.clone(), |this, jump_data| { - this.on_click(cx.listener_for(&self.editor, { - let path = jump_data.path.clone(); - move |editor, _, cx| { - cx.stop_propagation(); - - editor.jump( - path.clone(), - jump_data.position, - jump_data.anchor, - jump_data.line_offset_from_top, - cx, - ); - } - })) - .tooltip(move |cx| { - Tooltip::for_action( - format!( - "Jump to {}:L{}", - jump_data.path.path.display(), - jump_data.position.row + 1 - ), - &OpenExcerpts, - cx, - ) - }) - }) }), ), - ) - .group("excerpt-jump-action") - .cursor_pointer() - .when_some(jump_data.clone(), |this, jump_data| { - this.on_click(cx.listener_for(&self.editor, { - let path = jump_data.path.clone(); - move |editor, _, cx| { - cx.stop_propagation(); + ); + } + } - editor.jump( - path.clone(), - jump_data.position, - jump_data.anchor, - jump_data.line_offset_from_top, - cx, - ); - } - })) - .tooltip(move |cx| { - Tooltip::for_action( - format!( - "Jump to {}:L{}", - jump_data.path.path.display(), - jump_data.position.row + 1 - ), - &OpenExcerpts, - cx, - ) - }) - }) - }; - element.into_any() - } - - Block::ExcerptFooter { id, .. } => { - let element = v_flex() - .id(("excerpt footer", EntityId::from(block_id))) - .w_full() - .h(snapshot.excerpt_footer_height() as f32 * cx.line_height()) - .child( - h_flex() - .justify_end() - .flex_none() - .w(gutter_dimensions.width - - (gutter_dimensions.left_padding + gutter_dimensions.margin)) - .h_full() - .child( - ButtonLike::new("expand-icon") - .style(ButtonStyle::Transparent) - .child( - svg() - .path(IconName::ArrowDownFromLine.path()) - .size(IconSize::XSmall.rems()) - .text_color(cx.theme().colors().editor_line_number) - .group("") - .hover(|style| { - style.text_color( - cx.theme().colors().editor_active_line_number, - ) - }), - ) - .on_click(cx.listener_for(&self.editor, { - let id = *id; - move |editor, _, cx| { - editor.expand_excerpt( - id, - multi_buffer::ExpandExcerptDirection::Down, - cx, - ); - } - })) - .tooltip({ - move |cx| { - Tooltip::for_action( - "Expand Excerpt", - &ExpandExcerpts { lines: 0 }, - cx, - ) - } - }), - ), - ); - element.into_any() + result.into_any() } }; @@ -2509,6 +2389,33 @@ impl EditorElement { (element, final_size) } + fn render_expand_excerpt_button( + &self, + excerpt_id: ExcerptId, + direction: ExpandExcerptDirection, + icon: IconName, + cx: &mut WindowContext, + ) -> ButtonLike { + ButtonLike::new("expand-icon") + .style(ButtonStyle::Transparent) + .child( + svg() + .path(icon.path()) + .size(IconSize::XSmall.rems()) + .text_color(cx.theme().colors().editor_line_number) + .group("") + .hover(|style| style.text_color(cx.theme().colors().editor_active_line_number)), + ) + .on_click(cx.listener_for(&self.editor, { + move |editor, _, cx| { + editor.expand_excerpt(excerpt_id, direction, cx); + } + })) + .tooltip({ + move |cx| Tooltip::for_action("Expand Excerpt", &ExpandExcerpts { lines: 0 }, cx) + }) + } + #[allow(clippy::too_many_arguments)] fn render_blocks( &self, @@ -3367,7 +3274,7 @@ impl EditorElement { let end_row_in_current_excerpt = snapshot .blocks_in_range(start_row..end_row) .find_map(|(start_row, block)| { - if matches!(block, Block::ExcerptHeader { .. }) { + if matches!(block, Block::ExcerptBoundary { .. }) { Some(start_row) } else { None diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 19e2a4ea95..19ba147e16 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -952,7 +952,7 @@ mod tests { px(14.0), None, true, - 2, + 0, 2, 0, FoldPlaceholder::test(), diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index a239b5b770..f091c86ed9 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -189,6 +189,7 @@ pub struct MultiBufferSnapshot { show_headers: bool, } +#[derive(Clone)] pub struct ExcerptInfo { pub id: ExcerptId, pub buffer: BufferSnapshot, @@ -201,6 +202,7 @@ impl std::fmt::Debug for ExcerptInfo { f.debug_struct(type_name::()) .field("id", &self.id) .field("buffer_id", &self.buffer_id) + .field("path", &self.buffer.file().map(|f| f.path())) .field("range", &self.range) .finish() } diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index fcfc717efb..7f312023c3 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -17,8 +17,7 @@ use editor::{ use futures::io::BufReader; use futures::{AsyncBufReadExt as _, FutureExt as _, StreamExt as _}; use gpui::{ - div, prelude::*, EntityId, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, - WeakView, + div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView, }; use language::Point; use project::Fs; @@ -149,23 +148,21 @@ impl EditorBlock { .w(text_line_height) .h(text_line_height) .child( - IconButton::new( - ("close_output_area", EntityId::from(cx.block_id)), - IconName::Close, - ) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .size(ButtonSize::Compact) - .shape(IconButtonShape::Square) - .tooltip(|cx| Tooltip::text("Close output area", cx)) - .on_click(move |_, cx| { - if let BlockId::Custom(block_id) = block_id { - (on_close)(block_id, cx) - } - }), + IconButton::new("close_output_area", IconName::Close) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .size(ButtonSize::Compact) + .shape(IconButtonShape::Square) + .tooltip(|cx| Tooltip::text("Close output area", cx)) + .on_click(move |_, cx| { + if let BlockId::Custom(block_id) = block_id { + (on_close)(block_id, cx) + } + }), ); div() + .id(cx.block_id) .flex() .items_start() .min_h(text_line_height)