Detailed changes
@@ -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<Editor>,
@@ -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))
@@ -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<BlockId> 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<ExcerptId>),
}
impl From<BlockId> 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<CustomBlock>),
- ExcerptHeader {
- id: ExcerptId,
- buffer: BufferSnapshot,
- range: ExcerptRange<text::Anchor>,
+ ExcerptBoundary {
+ prev_excerpt: Option<ExcerptInfo>,
+ next_excerpt: Option<ExcerptInfo>,
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<B: BlockLike>(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::<Point>(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::<Point>(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::<Point>(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,
}
}
}
@@ -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<Task<()>>,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
- file_header_size: u32,
breadcrumb_header: Option<String>,
focused_block: Option<FocusedBlock>,
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,
@@ -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(
- 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(|| {
+ if let Some(prev_excerpt) = prev_excerpt {
+ if *show_excerpt_controls {
+ result = result.child(
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)
+ .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()
+ .px(header_padding)
+ .pt(header_padding)
+ .w_full()
+ .h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
.child(
- ButtonLike::new("expand-icon")
- .style(ButtonStyle::Transparent)
+ 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(
- 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,
+ 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,
+ ))
+ }),
+ ),
)
- .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,
- )
- }
+ .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,
+ );
+ }
+ }))
}),
- )
- }))
- } else {
- v_flex()
- .id(("excerpt header", EntityId::from(block_id)))
- .w_full()
- .h(snapshot.excerpt_header_height() as f32 * cx.line_height())
- .child(
- div()
- .flex()
- .v_flex()
+ ),
+ );
+ 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
@@ -952,7 +952,7 @@ mod tests {
px(14.0),
None,
true,
- 2,
+ 0,
2,
0,
FoldPlaceholder::test(),
@@ -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::<Self>())
.field("id", &self.id)
.field("buffer_id", &self.buffer_id)
+ .field("path", &self.buffer.file().map(|f| f.path()))
.field("range", &self.range)
.finish()
}
@@ -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)