New excerpt controls (#24428)

Release Notes:

- Multibuffers now use less vertical space for excerpt boundaries.
Additionally the expand up/down arrows are hidden at the start and end
of the buffers

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Zed AI <claude-3.5-sonnet@zed.dev>
This commit is contained in:
Conrad Irwin 2025-03-13 15:52:47 -06:00 committed by GitHub
parent 3935e8343a
commit e3c0f56a96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 513 additions and 707 deletions

View file

@ -118,10 +118,8 @@ impl DisplayMap {
font: Font,
font_size: Pixels,
wrap_width: Option<Pixels>,
show_excerpt_controls: bool,
buffer_header_height: u32,
excerpt_header_height: u32,
excerpt_footer_height: u32,
fold_placeholder: FoldPlaceholder,
cx: &mut Context<Self>,
) -> Self {
@ -134,13 +132,7 @@ impl DisplayMap {
let (fold_map, snapshot) = FoldMap::new(snapshot);
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
let block_map = BlockMap::new(
snapshot,
show_excerpt_controls,
buffer_header_height,
excerpt_header_height,
excerpt_footer_height,
);
let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
@ -555,10 +547,6 @@ impl DisplayMap {
pub fn is_rewrapping(&self, cx: &gpui::App) -> bool {
self.wrap_map.read(cx).is_rewrapping()
}
pub fn show_excerpt_controls(&self) -> bool {
self.block_map.show_excerpt_controls()
}
}
#[derive(Debug, Default)]
@ -1102,8 +1090,8 @@ impl DisplaySnapshot {
.map(|(row, block)| (DisplayRow(row), block))
}
pub fn sticky_header_excerpt(&self, row: DisplayRow) -> Option<StickyHeaderExcerpt<'_>> {
self.block_snapshot.sticky_header_excerpt(row.0)
pub fn sticky_header_excerpt(&self, row: f32) -> Option<StickyHeaderExcerpt<'_>> {
self.block_snapshot.sticky_header_excerpt(row)
}
pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
@ -1301,10 +1289,6 @@ impl DisplaySnapshot {
self.block_snapshot.buffer_header_height
}
pub fn excerpt_footer_height(&self) -> u32 {
self.block_snapshot.excerpt_footer_height
}
pub fn excerpt_header_height(&self) -> u32 {
self.block_snapshot.excerpt_header_height
}
@ -1514,10 +1498,8 @@ pub mod tests {
font,
font_size,
wrap_width,
true,
buffer_start_excerpt_header_height,
excerpt_header_height,
0,
FoldPlaceholder::test(),
cx,
)
@ -1764,10 +1746,8 @@ pub mod tests {
font("Helvetica"),
font_size,
wrap_width,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -1875,10 +1855,8 @@ pub mod tests {
font("Helvetica"),
font_size,
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -1938,8 +1916,6 @@ pub mod tests {
font("Helvetica"),
font_size,
None,
true,
1,
1,
1,
FoldPlaceholder::test(),
@ -2032,8 +2008,6 @@ pub mod tests {
font("Helvetica"),
font_size,
None,
true,
1,
1,
1,
FoldPlaceholder::test(),
@ -2134,10 +2108,8 @@ pub mod tests {
font("Courier"),
px(16.0),
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -2239,10 +2211,8 @@ pub mod tests {
font("Courier"),
px(16.0),
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -2328,10 +2298,8 @@ pub mod tests {
font("Courier"),
px(16.0),
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -2472,10 +2440,8 @@ pub mod tests {
font("Courier"),
font_size,
Some(px(40.0)),
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -2556,8 +2522,6 @@ pub mod tests {
font("Courier"),
font_size,
None,
true,
1,
1,
1,
FoldPlaceholder::test(),
@ -2682,10 +2646,8 @@ pub mod tests {
font("Helvetica"),
font_size,
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
);
@ -2721,10 +2683,8 @@ pub mod tests {
font("Helvetica"),
font_size,
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
@ -2798,10 +2758,8 @@ pub mod tests {
font("Helvetica"),
font_size,
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)

View file

@ -37,10 +37,8 @@ pub struct BlockMap {
custom_blocks: Vec<Arc<CustomBlock>>,
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
transforms: RefCell<SumTree<Transform>>,
show_excerpt_controls: bool,
buffer_header_height: u32,
excerpt_header_height: u32,
excerpt_footer_height: u32,
pub(super) folded_buffers: HashSet<BufferId>,
}
@ -58,7 +56,6 @@ pub struct BlockSnapshot {
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
pub(super) buffer_header_height: u32,
pub(super) excerpt_header_height: u32,
pub(super) excerpt_footer_height: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -285,14 +282,12 @@ pub enum Block {
first_excerpt: ExcerptInfo,
prev_excerpt: Option<ExcerptInfo>,
height: u32,
show_excerpt_controls: bool,
},
ExcerptBoundary {
prev_excerpt: Option<ExcerptInfo>,
next_excerpt: Option<ExcerptInfo>,
height: u32,
starts_new_buffer: bool,
show_excerpt_controls: bool,
},
}
@ -362,13 +357,11 @@ impl Debug for Block {
first_excerpt,
prev_excerpt,
height,
show_excerpt_controls,
} => f
.debug_struct("FoldedBuffer")
.field("first_excerpt", &first_excerpt)
.field("prev_excerpt", prev_excerpt)
.field("height", height)
.field("show_excerpt_controls", show_excerpt_controls)
.finish(),
Self::ExcerptBoundary {
starts_new_buffer,
@ -413,10 +406,8 @@ pub struct BlockRows<'a> {
impl BlockMap {
pub fn new(
wrap_snapshot: WrapSnapshot,
show_excerpt_controls: bool,
buffer_header_height: u32,
excerpt_header_height: u32,
excerpt_footer_height: u32,
) -> Self {
let row_count = wrap_snapshot.max_point().row() + 1;
let mut transforms = SumTree::default();
@ -428,10 +419,8 @@ impl BlockMap {
folded_buffers: HashSet::default(),
transforms: RefCell::new(transforms),
wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
show_excerpt_controls,
buffer_header_height,
excerpt_header_height,
excerpt_footer_height,
};
map.sync(
&wrap_snapshot,
@ -454,7 +443,6 @@ impl BlockMap {
custom_blocks_by_id: self.custom_blocks_by_id.clone(),
buffer_header_height: self.buffer_header_height,
excerpt_header_height: self.excerpt_header_height,
excerpt_footer_height: self.excerpt_footer_height,
},
}
}
@ -650,8 +638,6 @@ impl BlockMap {
if buffer.show_headers() {
blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
self.show_excerpt_controls,
self.excerpt_footer_height,
self.buffer_header_height,
self.excerpt_header_height,
buffer,
@ -722,13 +708,7 @@ impl BlockMap {
}
}
pub fn show_excerpt_controls(&self) -> bool {
self.show_excerpt_controls
}
fn header_and_footer_blocks<'a, R, T>(
show_excerpt_controls: bool,
excerpt_footer_height: u32,
buffer_header_height: u32,
excerpt_header_height: u32,
buffer: &'a multi_buffer::MultiBufferSnapshot,
@ -774,11 +754,6 @@ impl BlockMap {
.filter(|prev| !folded_buffers.contains(&prev.buffer_id));
let mut height = 0;
if prev_excerpt.is_some() {
if show_excerpt_controls {
height += excerpt_footer_height;
}
}
if let Some(new_buffer_id) = new_buffer_id {
let first_excerpt = excerpt_boundary.next.clone().unwrap();
@ -812,7 +787,6 @@ impl BlockMap {
Block::FoldedBuffer {
prev_excerpt,
height: height + buffer_header_height,
show_excerpt_controls,
first_excerpt,
},
));
@ -822,9 +796,6 @@ impl BlockMap {
if excerpt_boundary.next.is_some() {
if new_buffer_id.is_some() {
height += buffer_header_height;
if show_excerpt_controls {
height += excerpt_header_height;
}
} else {
height += excerpt_header_height;
}
@ -845,7 +816,6 @@ impl BlockMap {
next_excerpt: excerpt_boundary.next,
height,
starts_new_buffer: new_buffer_id.is_some(),
show_excerpt_controls,
},
))
})
@ -1432,7 +1402,8 @@ impl BlockSnapshot {
})
}
pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
let top_row = position as u32;
let mut cursor = self.transforms.cursor::<BlockRow>(&());
cursor.seek(&BlockRow(top_row), Bias::Left, &());
@ -1445,19 +1416,13 @@ impl BlockSnapshot {
prev_excerpt,
next_excerpt,
starts_new_buffer,
show_excerpt_controls,
..
}) => {
let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
start < top_row
} else {
start <= top_row
};
let matches_start = (start as f32) < position;
if matches_start && top_row <= end {
return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
next_buffer_row: None,
next_excerpt_controls_present: *show_excerpt_controls,
excerpt,
});
}
@ -1467,7 +1432,6 @@ impl BlockSnapshot {
return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
excerpt,
next_buffer_row,
next_excerpt_controls_present: *show_excerpt_controls,
});
}
Some(Block::FoldedBuffer {
@ -1476,7 +1440,6 @@ impl BlockSnapshot {
}) if top_row <= start => {
return Some(StickyHeaderExcerpt {
next_buffer_row: Some(end),
next_excerpt_controls_present: false,
excerpt,
});
}
@ -1785,7 +1748,6 @@ impl BlockChunks<'_> {
pub struct StickyHeaderExcerpt<'a> {
pub excerpt: &'a ExcerptInfo,
pub next_excerpt_controls_present: bool,
pub next_buffer_row: Option<u32>,
}
@ -2066,7 +2028,7 @@ mod tests {
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
let (wrap_map, wraps_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
let block_ids = writer.insert(vec![
@ -2279,14 +2241,11 @@ mod tests {
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
let snapshot = block_map.read(wraps_snapshot, Default::default());
// Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
assert_eq!(
snapshot.text(),
"\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
);
assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
let blocks: Vec<_> = snapshot
.blocks_in_range(0..u32::MAX)
@ -2295,10 +2254,9 @@ mod tests {
assert_eq!(
blocks,
vec![
(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
(0..1, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
(3..4, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // path, header
(6..7, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // path, header
]
);
}
@ -2317,7 +2275,7 @@ mod tests {
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
let (_wrap_map, wraps_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
let block_ids = writer.insert(vec![
@ -2420,7 +2378,7 @@ mod tests {
let (_, wraps_snapshot) = cx.update(|cx| {
WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
});
let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
writer.insert(vec![
@ -2464,7 +2422,7 @@ mod tests {
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
let (wrap_map, wraps_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
let replace_block_id = writer.insert(vec![BlockProperties {
@ -2631,12 +2589,12 @@ mod tests {
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wrap_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
assert_eq!(
blocks_snapshot.text(),
"\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
"\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
);
assert_eq!(
blocks_snapshot
@ -2644,30 +2602,21 @@ mod tests {
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
vec![
None,
None,
None,
Some(0),
None,
None,
None,
None,
Some(1),
None,
None,
Some(2),
None,
None,
Some(3),
None,
None,
None,
None,
Some(4),
None,
None,
Some(5),
None,
]
);
@ -2715,7 +2664,7 @@ mod tests {
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
assert_eq!(
blocks_snapshot.text(),
"\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
"\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
);
assert_eq!(
blocks_snapshot
@ -2723,35 +2672,26 @@ mod tests {
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
vec![
None,
None,
None,
Some(0),
None,
None,
None,
None,
None,
Some(1),
None,
None,
None,
Some(2),
None,
None,
Some(3),
None,
None,
None,
None,
None,
None,
Some(4),
None,
None,
Some(5),
None,
None,
]
);
@ -2793,7 +2733,7 @@ mod tests {
);
assert_eq!(
blocks_snapshot.text(),
"\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
"\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
);
assert_eq!(
blocks_snapshot
@ -2806,27 +2746,20 @@ mod tests {
None,
None,
None,
None,
Some(1),
None,
None,
None,
Some(2),
None,
None,
Some(3),
None,
None,
None,
None,
None,
None,
Some(4),
None,
None,
Some(5),
None,
None,
]
);
@ -2862,7 +2795,7 @@ mod tests {
.count(),
"Should have two folded blocks, producing headers"
);
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
@ -2876,13 +2809,10 @@ mod tests {
None,
None,
None,
None,
Some(4),
None,
None,
Some(5),
None,
None,
]
);
@ -2917,7 +2847,7 @@ mod tests {
);
assert_eq!(
blocks_snapshot.text(),
"\n\n\n\n111\n\n\n\n\n\n\n\n555\n\n\n666\n\n",
"\n\n\n111\n\n\n\n\n\n555\n\n666\n",
"Should have extra newline for 111 buffer, due to a new block added when it was folded"
);
assert_eq!(
@ -2929,21 +2859,16 @@ mod tests {
None,
None,
None,
None,
Some(0),
None,
None,
None,
None,
None,
None,
None,
Some(4),
None,
None,
Some(5),
None,
None,
]
);
@ -2974,7 +2899,7 @@ mod tests {
assert_eq!(
blocks_snapshot.text(),
"\n\n\n\n111\n\n\n\n\n",
"\n\n\n111\n\n\n\n",
"Should have a single, first buffer left after folding"
);
assert_eq!(
@ -2982,18 +2907,7 @@ mod tests {
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
vec![
None,
None,
None,
None,
Some(0),
None,
None,
None,
None,
None,
]
vec![None, None, None, Some(0), None, None, None, None,]
);
}
@ -3020,10 +2934,10 @@ mod tests {
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wrap_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
assert_eq!(blocks_snapshot.text(), "\n\n\n111\n");
assert_eq!(blocks_snapshot.text(), "\n\n111");
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| {
@ -3077,11 +2991,9 @@ mod tests {
let font_size = px(14.0);
let buffer_start_header_height = rng.gen_range(1..=5);
let excerpt_header_height = rng.gen_range(1..=5);
let excerpt_footer_height = rng.gen_range(1..=5);
log::info!("Wrap width: {:?}", wrap_width);
log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
let is_singleton = rng.gen();
let buffer = if is_singleton {
let len = rng.gen_range(0..10);
@ -3108,10 +3020,8 @@ mod tests {
cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
let mut block_map = BlockMap::new(
wraps_snapshot,
true,
buffer_start_header_height,
excerpt_header_height,
excerpt_footer_height,
);
for _ in 0..operations {
@ -3329,8 +3239,6 @@ mod tests {
// Note that this needs to be synced with the related section in BlockMap::sync
expected_blocks.extend(BlockMap::header_and_footer_blocks(
true,
excerpt_footer_height,
buffer_start_header_height,
excerpt_header_height,
&buffer_snapshot,

View file

@ -983,6 +983,7 @@ impl Iterator for WrapRows<'_> {
buffer_row: None,
multibuffer_row: None,
diff_status,
expand_info: None,
}
} else {
buffer_row

View file

@ -196,7 +196,6 @@ use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
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;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const MAX_LINE_LEN: usize = 1024;
@ -1092,7 +1091,6 @@ impl Editor {
EditorMode::SingleLine { auto_width: false },
buffer,
None,
false,
window,
cx,
)
@ -1101,7 +1099,7 @@ impl Editor {
pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
let buffer = cx.new(|cx| Buffer::local("", cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
Self::new(EditorMode::Full, buffer, None, false, window, cx)
Self::new(EditorMode::Full, buffer, None, window, cx)
}
pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
@ -1111,7 +1109,6 @@ impl Editor {
EditorMode::SingleLine { auto_width: true },
buffer,
None,
false,
window,
cx,
)
@ -1124,7 +1121,6 @@ impl Editor {
EditorMode::AutoHeight { max_lines },
buffer,
None,
false,
window,
cx,
)
@ -1137,33 +1133,23 @@ impl Editor {
cx: &mut Context<Self>,
) -> Self {
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
Self::new(EditorMode::Full, buffer, project, false, window, cx)
Self::new(EditorMode::Full, buffer, project, window, cx)
}
pub fn for_multibuffer(
buffer: Entity<MultiBuffer>,
project: Option<Entity<Project>>,
show_excerpt_controls: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(
EditorMode::Full,
buffer,
project,
show_excerpt_controls,
window,
cx,
)
Self::new(EditorMode::Full, buffer, project, window, cx)
}
pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
let show_excerpt_controls = self.display_map.read(cx).show_excerpt_controls();
let mut clone = Self::new(
self.mode,
self.buffer.clone(),
self.project.clone(),
show_excerpt_controls,
window,
cx,
);
@ -1183,7 +1169,6 @@ impl Editor {
mode: EditorMode,
buffer: Entity<MultiBuffer>,
project: Option<Entity<Project>>,
show_excerpt_controls: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@ -1228,10 +1213,8 @@ impl Editor {
style.font(),
font_size,
None,
show_excerpt_controls,
FILE_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
fold_placeholder,
cx,
)
@ -4693,8 +4676,8 @@ impl Editor {
workspace.update_in(&mut cx, |workspace, window, cx| {
let project = workspace.project().clone();
let editor = cx
.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), true, window, cx));
let editor =
cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>(
@ -12381,7 +12364,6 @@ impl Editor {
Editor::for_multibuffer(
excerpt_buffer,
Some(workspace.project().clone()),
true,
window,
cx,
)

View file

@ -7676,7 +7676,6 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
EditorMode::Full,
multi_buffer,
Some(project.clone()),
true,
window,
cx,
)
@ -13501,7 +13500,6 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
EditorMode::Full,
multi_buffer,
Some(project.clone()),
true,
window,
cx,
)
@ -13984,9 +13982,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
multibuffer
});
let editor = cx.add_window(|window, cx| {
Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
});
let editor =
cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
editor
.update(cx, |editor, _window, cx| {
for (buffer, diff_base) in [
@ -14105,9 +14102,8 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
multibuffer
});
let editor = cx.add_window(|window, cx| {
Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
});
let editor =
cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
editor
.update(cx, |editor, _window, cx| {
let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
@ -15663,14 +15659,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
});
let editor = cx.add_window(|window, cx| {
Editor::new(
EditorMode::Full,
multibuffer,
Some(project),
true,
window,
cx,
)
Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
});
cx.run_until_parked();
@ -15689,9 +15678,9 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
assert_eq!(
hunks,
[
DisplayRow(3)..DisplayRow(5),
DisplayRow(10)..DisplayRow(12),
DisplayRow(17)..DisplayRow(19),
DisplayRow(2)..DisplayRow(4),
DisplayRow(7)..DisplayRow(9),
DisplayRow(12)..DisplayRow(14),
]
);
}
@ -16122,7 +16111,6 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
EditorMode::Full,
multi_buffer,
Some(project.clone()),
true,
window,
cx,
)
@ -16277,7 +16265,6 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
EditorMode::Full,
multi_buffer.clone(),
Some(project.clone()),
true,
window,
cx,
)
@ -16285,7 +16272,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
"\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
);
multi_buffer_editor.update(cx, |editor, cx| {
@ -16293,7 +16280,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
"\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
"After folding the first buffer, its text should not be displayed"
);
@ -16302,7 +16289,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
"\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
"After folding the second buffer, its text should not be displayed"
);
@ -16327,7 +16314,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
"\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
"After unfolding the second buffer, its text should be displayed"
);
@ -16349,7 +16336,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
"\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
"After unfolding the first buffer, its and 2nd buffer's text should be displayed"
);
@ -16358,7 +16345,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
"\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
"After unfolding the all buffers, all original text should be displayed"
);
}
@ -16444,13 +16431,12 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
EditorMode::Full,
multi_buffer,
Some(project.clone()),
true,
window,
cx,
)
});
let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
full_text,
@ -16461,7 +16447,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
"\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
"After folding the first buffer, its text should not be displayed"
);
@ -16471,7 +16457,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n\n\n\n\n7777\n8888\n9999\n",
"\n\n\n\n\n\n7777\n8888\n9999",
"After folding the second buffer, its text should not be displayed"
);
@ -16489,7 +16475,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n\n\n4444\n5555\n6666\n\n\n",
"\n\n\n\n4444\n5555\n6666\n\n",
"After unfolding the second buffer, its text should be displayed"
);
@ -16498,7 +16484,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
});
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
"\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
"After unfolding the first buffer, its text should be displayed"
);
@ -16564,7 +16550,6 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test
EditorMode::Full,
multi_buffer,
Some(project.clone()),
true,
window,
cx,
)
@ -16583,7 +16568,7 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test
editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
});
let full_text = format!("\n\n\n{sample_text}\n");
let full_text = format!("\n\n{sample_text}");
assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
full_text,
@ -16612,14 +16597,7 @@ async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContex
],
cx,
);
let mut editor = Editor::new(
EditorMode::Full,
multi_buffer.clone(),
None,
true,
window,
cx,
);
let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
// fold all but the second buffer, so that we test navigating between two
@ -16931,7 +16909,7 @@ async fn assert_highlighted_edits(
) {
let window = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple(text, cx);
Editor::new(EditorMode::Full, buffer, None, true, window, cx)
Editor::new(EditorMode::Full, buffer, None, window, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);

View file

@ -18,12 +18,12 @@ use crate::{
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayDiffHunk, DisplayPoint,
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
GoToPreviousHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight, LineUp,
OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight,
Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
EditorSettings, EditorSnapshot, EditorStyle, FocusedBlock, GoToHunk, GoToPreviousHunk,
GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlayHintRefreshReason,
InlineCompletion, JumpData, LineDown, LineHighlight, LineUp, OpenExcerpts, PageDown, PageUp,
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
@ -33,10 +33,10 @@ use file_icons::FileIcons;
use git::{blame::BlameEntry, status::FileStatus, Oid};
use gpui::{
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
relative, size, solid_background, svg, transparent_black, Action, AnyElement, App,
AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners,
CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
relative, size, solid_background, transparent_black, Action, AnyElement, App, AvailableSpace,
Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, Window,
@ -52,8 +52,8 @@ use language::{
};
use lsp::DiagnosticSeverity;
use multi_buffer::{
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
RowInfo,
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
MultiBufferRow, RowInfo,
};
use project::project_settings::{self, GitGutterSetting, ProjectSettings};
use settings::Settings;
@ -72,7 +72,7 @@ use sum_tree::Bias;
use text::BufferId;
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
use ui::{
h_flex, prelude::*, ButtonLike, ButtonStyle, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
h_flex, prelude::*, ButtonLike, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
POPOVER_Y_PADDING,
};
use unicode_segmentation::UnicodeSegmentation;
@ -1515,6 +1515,19 @@ impl EditorElement {
}
}
fn prepaint_expand_toggles(
&self,
expand_toggles: &mut [Option<(AnyElement, gpui::Point<Pixels>)>],
window: &mut Window,
cx: &mut App,
) {
for (expand_toggle, origin) in expand_toggles.iter_mut().flatten() {
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
expand_toggle.layout_as_root(available_space, window, cx);
expand_toggle.prepaint_as_root(*origin, available_space, window, cx);
}
}
fn prepaint_crease_trailers(
&self,
trailers: Vec<Option<AnyElement>>,
@ -2009,6 +2022,7 @@ impl EditorElement {
&self,
line_height: Pixels,
range: Range<DisplayRow>,
row_infos: &[RowInfo],
scroll_pixel_position: gpui::Point<Pixels>,
gutter_dimensions: &GutterDimensions,
gutter_hitbox: &Hitbox,
@ -2074,6 +2088,12 @@ impl EditorElement {
}
}
let display_row = multibuffer_point.to_display_point(snapshot).row();
if row_infos
.get((display_row - range.start).0 as usize)
.is_some_and(|row_info| row_info.expand_info.is_some())
{
return None;
}
let button = editor.render_run_indicator(
&self.style,
Some(display_row) == active_task_indicator_row,
@ -2098,6 +2118,82 @@ impl EditorElement {
})
}
fn layout_excerpt_gutter(
&self,
gutter_hitbox: &Hitbox,
line_height: Pixels,
scroll_position: gpui::Point<f32>,
buffer_rows: &[RowInfo],
window: &mut Window,
cx: &mut App,
) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
let icon_size = editor_font_size.round();
let button_h_padding = ((icon_size - px(1.0)) / 2.0).round() - px(2.0);
let scroll_top = scroll_position.y * line_height;
let elements = buffer_rows
.into_iter()
.enumerate()
.map(|(ix, row_info)| {
let ExpandInfo {
excerpt_id,
direction,
} = row_info.expand_info?;
let icon_name = match direction {
ExpandExcerptDirection::Up => IconName::ExpandUp,
ExpandExcerptDirection::Down => IconName::ExpandDown,
ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
};
let editor = self.editor.clone();
let max_row = self
.editor
.read(cx)
.buffer()
.read(cx)
.snapshot(cx)
.widest_line_number();
let is_wide = max_row > 999
&& row_info
.buffer_row
.is_some_and(|row| row.ilog10() == max_row.ilog10());
let toggle = IconButton::new(("expand", ix), icon_name)
.icon_color(Color::Custom(cx.theme().colors().editor_line_number))
.selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground))
.icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
.width((icon_size + button_h_padding * 2).into())
.when(is_wide, |el| {
el.width((icon_size + button_h_padding).into())
})
.on_click(move |_, _, cx| {
editor.update(cx, |editor, cx| {
editor.expand_excerpt(excerpt_id, direction, cx);
});
})
.tooltip(Tooltip::for_action_title(
"Expand excerpt",
&crate::actions::ExpandExcerpts::default(),
))
.into_any_element();
let position = point(
px(1.),
ix as f32 * line_height - (scroll_top % line_height) + px(1.),
);
let origin = gutter_hitbox.origin + position;
Some((toggle, origin))
})
.collect();
elements
}
fn layout_code_actions_indicator(
&self,
line_height: Pixels,
@ -2315,6 +2411,9 @@ impl EditorElement {
.into_iter()
.enumerate()
.map(|(ix, info)| {
if info.expand_info.is_some() {
return None;
}
let row = info.multibuffer_row?;
let display_row = DisplayRow(rows.start.0 + ix as u32);
let active = active_rows.contains_key(&display_row);
@ -2337,6 +2436,9 @@ impl EditorElement {
buffer_rows
.into_iter()
.map(|row_info| {
if row_info.expand_info.is_some() {
return None;
}
if let Some(row) = row_info.multibuffer_row {
snapshot.render_crease_trailer(row, window, cx)
} else {
@ -2514,25 +2616,11 @@ impl EditorElement {
Block::FoldedBuffer {
first_excerpt,
prev_excerpt,
show_excerpt_controls,
height,
..
} => {
let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
let mut result = v_flex().id(block_id).w_full();
if let Some(prev_excerpt) = prev_excerpt {
if *show_excerpt_controls {
result = result.child(self.render_expand_excerpt_control(
block_id,
ExpandExcerptDirection::Down,
prev_excerpt.id,
gutter_dimensions,
window,
cx,
));
}
}
let result = v_flex().id(block_id).w_full();
let jump_data = header_jump_data(snapshot, block_row_start, *height, first_excerpt);
result
@ -2540,6 +2628,7 @@ impl EditorElement {
first_excerpt,
true,
selected,
false,
jump_data,
window,
cx,
@ -2548,28 +2637,14 @@ impl EditorElement {
}
Block::ExcerptBoundary {
prev_excerpt,
next_excerpt,
show_excerpt_controls,
height,
starts_new_buffer,
..
} => {
let color = cx.theme().colors().clone();
let mut result = v_flex().id(block_id).w_full();
if let Some(prev_excerpt) = prev_excerpt {
if *show_excerpt_controls {
result = result.child(self.render_expand_excerpt_control(
block_id,
ExpandExcerptDirection::Down,
prev_excerpt.id,
gutter_dimensions,
window,
cx,
));
}
}
if let Some(next_excerpt) = next_excerpt {
let jump_data =
header_jump_data(snapshot, block_row_start, *height, next_excerpt);
@ -2582,6 +2657,7 @@ impl EditorElement {
next_excerpt,
false,
selected,
false,
jump_data,
window,
cx,
@ -2590,40 +2666,17 @@ impl EditorElement {
result = result
.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
}
if *show_excerpt_controls {
result = result.child(self.render_expand_excerpt_control(
block_id,
ExpandExcerptDirection::Up,
next_excerpt.id,
gutter_dimensions,
window,
cx,
));
}
} else {
if *show_excerpt_controls {
result = result.child(
h_flex()
.relative()
.child(
div()
.top(px(0.))
.absolute()
.w_full()
.h_px()
.bg(color.border_variant),
)
.child(self.render_expand_excerpt_control(
block_id,
ExpandExcerptDirection::Up,
next_excerpt.id,
gutter_dimensions,
window,
cx,
)),
);
}
result = result.child(
h_flex().relative().child(
div()
.top(line_height / 2.)
.absolute()
.w_full()
.h_px()
.bg(color.border_variant),
),
);
};
}
@ -2662,6 +2715,7 @@ impl EditorElement {
for_excerpt: &ExcerptInfo,
is_folded: bool,
is_selected: bool,
_is_sticky: bool,
jump_data: JumpData,
window: &mut Window,
cx: &mut App,
@ -2708,7 +2762,7 @@ impl EditorElement {
.pl_0p5()
.pr_5()
.rounded_sm()
.shadow_md()
// .when(is_sticky, |el| el.shadow_md())
.border_1()
.map(|div| {
let border_color = if is_selected
@ -2847,95 +2901,6 @@ impl EditorElement {
)
}
fn render_expand_excerpt_control(
&self,
block_id: BlockId,
direction: ExpandExcerptDirection,
excerpt_id: ExcerptId,
gutter_dimensions: &GutterDimensions,
window: &Window,
cx: &mut App,
) -> impl IntoElement {
let color = cx.theme().colors().clone();
let hover_color = color.border_variant.opacity(0.5);
let focus_handle = self.editor.focus_handle(cx).clone();
let icon_offset =
gutter_dimensions.width - (gutter_dimensions.left_padding + gutter_dimensions.margin);
let header_height = MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * window.line_height();
let group_name = if direction == ExpandExcerptDirection::Down {
"expand-down"
} else {
"expand-up"
};
let expand_area = |id: SharedString| {
h_flex()
.id(id)
.w_full()
.cursor_pointer()
.block_mouse_down()
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.hover(|style| style.bg(hover_color))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
&focus_handle,
window,
cx,
)
}
})
};
expand_area(
format!(
"block-{}-{}",
block_id,
if direction == ExpandExcerptDirection::Down {
"down"
} else {
"up"
}
)
.into(),
)
.group(group_name)
.child(
h_flex()
.w(icon_offset)
.h(header_height)
.flex_none()
.justify_end()
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(if direction == ExpandExcerptDirection::Down {
IconName::ArrowDownFromLine.path()
} else {
IconName::ArrowUpFromLine.path()
})
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group_hover(group_name, |style| {
style.text_color(cx.theme().colors().editor_active_line_number)
}),
),
),
)
.on_click(window.listener_for(&self.editor, {
move |editor, _, _, cx| {
editor.expand_excerpt(excerpt_id, direction, cx);
cx.stop_propagation();
}
}))
}
fn render_blocks(
&self,
rows: Range<DisplayRow>,
@ -3167,7 +3132,6 @@ impl EditorElement {
&self,
StickyHeaderExcerpt {
excerpt,
next_excerpt_controls_present,
next_buffer_row,
}: StickyHeaderExcerpt<'_>,
scroll_position: f32,
@ -3204,7 +3168,7 @@ impl EditorElement {
.top_0(),
)
.child(
self.render_buffer_header(excerpt, false, selected, jump_data, window, cx)
self.render_buffer_header(excerpt, false, selected, true, jump_data, window, cx)
.into_any_element(),
)
.into_any_element();
@ -3214,11 +3178,7 @@ impl EditorElement {
if let Some(next_buffer_row) = next_buffer_row {
// Push up the sticky header when the excerpt is getting close to the top of the viewport
let mut max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
if next_excerpt_controls_present {
max_row -= MULTI_BUFFER_EXCERPT_HEADER_HEIGHT;
}
let max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
let offset = scroll_position - max_row as f32;
@ -4514,6 +4474,12 @@ impl EditorElement {
}
});
window.with_element_namespace("expand_toggles", |window| {
for (expand_toggle, _) in layout.expand_toggles.iter_mut().flatten() {
expand_toggle.paint(window, cx);
}
});
for test_indicator in layout.test_indicators.iter_mut() {
test_indicator.paint(window, cx);
}
@ -6891,6 +6857,18 @@ impl Element for EditorElement {
cx,
);
let mut expand_toggles =
window.with_element_namespace("expand_toggles", |window| {
self.layout_excerpt_gutter(
&gutter_hitbox,
line_height,
scroll_position,
&row_infos,
window,
cx,
)
});
let mut crease_toggles =
window.with_element_namespace("crease_toggles", |window| {
self.layout_crease_toggles(
@ -6988,7 +6966,7 @@ impl Element for EditorElement {
let mut scroll_width = scroll_range_bounds.size.width;
let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
snapshot.sticky_header_excerpt(start_row)
snapshot.sticky_header_excerpt(scroll_position.y)
} else {
None
};
@ -7305,7 +7283,14 @@ impl Element for EditorElement {
.tasks
.contains_key(&(buffer_id, row));
if !has_test_indicator {
let has_expand_indicator = row_infos
.get(
(newest_selection_head.row() - start_row).0
as usize,
)
.is_some_and(|row_info| row_info.expand_info.is_some());
if !has_test_indicator && !has_expand_indicator {
code_actions_indicator = self
.layout_code_actions_indicator(
line_height,
@ -7338,6 +7323,7 @@ impl Element for EditorElement {
self.layout_run_indicators(
line_height,
start_row..end_row,
&row_infos,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
@ -7400,6 +7386,10 @@ impl Element for EditorElement {
)
});
window.with_element_namespace("expand_toggles", |window| {
self.prepaint_expand_toggles(&mut expand_toggles, window, cx)
});
let invisible_symbol_font_size = font_size / 2.;
let tab_invisible = window
.text_system()
@ -7501,6 +7491,7 @@ impl Element for EditorElement {
tab_invisible,
space_invisible,
sticky_buffer_header,
expand_toggles,
}
})
})
@ -7674,6 +7665,7 @@ pub struct EditorLayout {
code_actions_indicator: Option<AnyElement>,
test_indicators: Vec<AnyElement>,
crease_toggles: Vec<Option<AnyElement>>,
expand_toggles: Vec<Option<(AnyElement, gpui::Point<Pixels>)>>,
diff_hunk_controls: Vec<AnyElement>,
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
inline_completion_popover: Option<AnyElement>,
@ -8307,7 +8299,7 @@ mod tests {
init_test(cx, |_| {});
let window = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, true, window, cx)
Editor::new(EditorMode::Full, buffer, None, window, cx)
});
let editor = window.root(cx).unwrap();
@ -8407,7 +8399,7 @@ mod tests {
let window = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
Editor::new(EditorMode::Full, buffer, None, true, window, cx)
Editor::new(EditorMode::Full, buffer, None, window, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
@ -8470,90 +8462,6 @@ mod tests {
state.active_rows.keys().cloned().collect::<Vec<_>>(),
vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
);
// multi-buffer support
// in DisplayPoint coordinates, this is what we're dealing with:
// 0: [[file
// 1: header
// 2: section]]
// 3: aaaaaa
// 4: bbbbbb
// 5: cccccc
// 6:
// 7: [[footer]]
// 8: [[header]]
// 9: ffffff
// 10: gggggg
// 11: hhhhhh
// 12:
// 13: [[footer]]
// 14: [[file
// 15: header
// 16: section]]
// 17: bbbbbb
// 18: cccccc
// 19: dddddd
// 20: [[footer]]
let window = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_multi(
[
(
&(sample_text(8, 6, 'a') + "\n"),
vec![
Point::new(0, 0)..Point::new(3, 0),
Point::new(4, 0)..Point::new(7, 0),
],
),
(
&(sample_text(8, 6, 'a') + "\n"),
vec![Point::new(1, 0)..Point::new(3, 0)],
),
],
cx,
);
Editor::new(EditorMode::Full, buffer, None, true, window, cx)
});
let editor = window.root(cx).unwrap();
let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
let _state = window.update(cx, |editor, window, cx| {
editor.cursor_shape = CursorShape::Block;
editor.change_selections(None, window, cx, |s| {
s.select_display_ranges([
DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0),
DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0),
]);
});
});
let (_, state) = cx.draw(
point(px(500.), px(500.)),
size(px(500.), px(500.)),
|_, _| EditorElement::new(&editor, style),
);
assert_eq!(state.selections.len(), 1);
let local_selections = &state.selections[0].1;
assert_eq!(local_selections.len(), 2);
// moves cursor on excerpt boundary back a line
// and doesn't allow selection to bleed through
assert_eq!(
local_selections[0].range,
DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0)
);
assert_eq!(
local_selections[0].head,
DisplayPoint::new(DisplayRow(6), 0)
);
// moves cursor on buffer boundary back two lines
// and doesn't allow selection to bleed through
assert_eq!(
local_selections[1].range,
DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0)
);
assert_eq!(
local_selections[1].head,
DisplayPoint::new(DisplayRow(12), 0)
);
}
#[gpui::test]
@ -8562,7 +8470,7 @@ mod tests {
let window = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, true, window, cx)
Editor::new(EditorMode::Full, buffer, None, window, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
@ -8788,7 +8696,7 @@ mod tests {
);
let window = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple(input_text, cx);
Editor::new(editor_mode, buffer, None, true, window, cx)
Editor::new(editor_mode, buffer, None, window, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();

View file

@ -2618,7 +2618,7 @@ pub mod tests {
cx.executor().run_until_parked();
let editor = cx.add_window(|window, cx| {
Editor::for_multibuffer(multibuffer, Some(project.clone()), true, window, cx)
Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
});
let editor_edited = Arc::new(AtomicBool::new(false));
@ -2830,7 +2830,6 @@ pub mod tests {
"main hint #5".to_string(),
"other hint(edited) #0".to_string(),
"other hint(edited) #1".to_string(),
"other hint(edited) #2".to_string(),
];
assert_eq!(
expected_hints,
@ -2921,7 +2920,7 @@ pub mod tests {
cx.executor().run_until_parked();
let editor = cx.add_window(|window, cx| {
Editor::for_multibuffer(multibuffer, Some(project.clone()), true, window, cx)
Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
});
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();

View file

@ -129,13 +129,8 @@ impl FollowableItem for Editor {
});
cx.new(|cx| {
let mut editor = Editor::for_multibuffer(
multibuffer,
Some(project.clone()),
true,
window,
cx,
);
let mut editor =
Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx);
editor.remote_id = Some(remote_id);
editor
})

View file

@ -893,8 +893,6 @@ mod tests {
font,
font_size,
None,
true,
1,
1,
1,
FoldPlaceholder::test(),
@ -1110,138 +1108,136 @@ mod tests {
font,
px(14.0),
None,
true,
0,
2,
0,
FoldPlaceholder::test(),
cx,
)
});
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
assert_eq!(snapshot.text(), "abc\ndefg\nhijkl\nmn");
let col_2_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(2), 2), &text_layout_details);
.x_for_display_point(DisplayPoint::new(DisplayRow(0), 2), &text_layout_details);
// Can't move up into the first excerpt's header
assert_eq!(
up(
&snapshot,
DisplayPoint::new(DisplayRow(2), 2),
DisplayPoint::new(DisplayRow(0), 2),
SelectionGoal::HorizontalPosition(col_2_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(2), 0),
DisplayPoint::new(DisplayRow(0), 0),
SelectionGoal::HorizontalPosition(col_2_x.0),
),
);
assert_eq!(
up(
&snapshot,
DisplayPoint::new(DisplayRow(2), 0),
DisplayPoint::new(DisplayRow(0), 0),
SelectionGoal::None,
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(2), 0),
DisplayPoint::new(DisplayRow(0), 0),
SelectionGoal::HorizontalPosition(0.0),
),
);
let col_4_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(3), 4), &text_layout_details);
.x_for_display_point(DisplayPoint::new(DisplayRow(1), 4), &text_layout_details);
// Move up and down within first excerpt
assert_eq!(
up(
&snapshot,
DisplayPoint::new(DisplayRow(3), 4),
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_4_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(2), 3),
DisplayPoint::new(DisplayRow(0), 3),
SelectionGoal::HorizontalPosition(col_4_x.0)
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(2), 3),
DisplayPoint::new(DisplayRow(0), 3),
SelectionGoal::HorizontalPosition(col_4_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(3), 4),
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_4_x.0)
),
);
let col_5_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(6), 5), &text_layout_details);
.x_for_display_point(DisplayPoint::new(DisplayRow(2), 5), &text_layout_details);
// Move up and down across second excerpt's header
assert_eq!(
up(
&snapshot,
DisplayPoint::new(DisplayRow(6), 5),
DisplayPoint::new(DisplayRow(2), 5),
SelectionGoal::HorizontalPosition(col_5_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(3), 4),
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_5_x.0)
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(3), 4),
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_5_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(6), 5),
DisplayPoint::new(DisplayRow(2), 5),
SelectionGoal::HorizontalPosition(col_5_x.0)
),
);
let max_point_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(7), 2), &text_layout_details);
.x_for_display_point(DisplayPoint::new(DisplayRow(3), 2), &text_layout_details);
// Can't move down off the end, and attempting to do so leaves the selection goal unchanged
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(7), 0),
DisplayPoint::new(DisplayRow(3), 0),
SelectionGoal::HorizontalPosition(0.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(7), 2),
DisplayPoint::new(DisplayRow(3), 2),
SelectionGoal::HorizontalPosition(0.0)
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(7), 2),
DisplayPoint::new(DisplayRow(3), 2),
SelectionGoal::HorizontalPosition(max_point_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(7), 2),
DisplayPoint::new(DisplayRow(3), 2),
SelectionGoal::HorizontalPosition(max_point_x.0)
),
);

View file

@ -62,8 +62,7 @@ impl ProposedChangesEditor {
let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
let mut this = Self {
editor: cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer.clone(), project, true, window, cx);
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, window, cx);
editor.set_expand_all_diff_hunks(cx);
editor.set_completion_provider(None);
editor.clear_code_action_providers();

View file

@ -86,7 +86,7 @@ pub fn expand_macro_recursively(
cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name));
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
let mut editor = Editor::for_multibuffer(multibuffer, None, false, window, cx);
let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
editor.set_read_only(true);
editor
})),

View file

@ -61,8 +61,6 @@ pub fn marked_display_snapshot(
font,
font_size,
None,
true,
1,
1,
1,
FoldPlaceholder::test(),
@ -108,7 +106,7 @@ pub(crate) fn build_editor(
window: &mut Window,
cx: &mut Context<Editor>,
) -> Editor {
Editor::new(EditorMode::Full, buffer, None, true, window, cx)
Editor::new(EditorMode::Full, buffer, None, window, cx)
}
pub(crate) fn build_editor_with_project(
@ -117,5 +115,5 @@ pub(crate) fn build_editor_with_project(
window: &mut Window,
cx: &mut Context<Editor>,
) -> Editor {
Editor::new(EditorMode::Full, buffer, Some(project), true, window, cx)
Editor::new(EditorMode::Full, buffer, Some(project), window, cx)
}