Sticky multibuffer headers (#22391)
https://github.com/user-attachments/assets/92cc5ff7-d8be-4e4b-ac6e-68eb310fffce Release Notes: - Multibuffer headers will now stick to the top of the viewport as you scroll - Added support for expanding diagnostic excerpts --------- Co-authored-by: Michael <michael@zed.dev>
This commit is contained in:
parent
4c84600630
commit
45c714110e
5 changed files with 268 additions and 69 deletions
|
@ -166,7 +166,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
|
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
|
||||||
let editor = cx.new_view(|cx| {
|
let editor = cx.new_view(|cx| {
|
||||||
let mut editor =
|
let mut editor =
|
||||||
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
|
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), true, cx);
|
||||||
editor.set_vertical_scroll_margin(5, cx);
|
editor.set_vertical_scroll_margin(5, cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
|
@ -167,10 +167,10 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(15), EXCERPT_HEADER.into()),
|
(DisplayRow(16), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(16), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(18), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(25), EXCERPT_HEADER.into()),
|
(DisplayRow(27), EXCERPT_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -184,6 +184,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
" let x = vec![];\n",
|
" let x = vec![];\n",
|
||||||
" let y = vec![];\n",
|
" let y = vec![];\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
|
@ -195,6 +196,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
" c(y);\n",
|
" c(y);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
" d(x);\n",
|
" d(x);\n",
|
||||||
|
"\n", // expand
|
||||||
"\n", // context ellipsis
|
"\n", // context ellipsis
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
|
@ -206,11 +208,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
" a(x);\n",
|
" a(x);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
" b(y);\n",
|
" b(y);\n",
|
||||||
|
"\n", // expand
|
||||||
"\n", // context ellipsis
|
"\n", // context ellipsis
|
||||||
" c(y);\n",
|
" c(y);\n",
|
||||||
" d(x);\n",
|
" d(x);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
"}"
|
"}",
|
||||||
|
"\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -218,7 +222,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.selections.display_ranges(cx),
|
editor.selections.display_ranges(cx),
|
||||||
[DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
|
[DisplayPoint::new(DisplayRow(13), 6)..DisplayPoint::new(DisplayRow(13), 6)]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -253,12 +257,12 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(7), FILE_HEADER.into()),
|
(DisplayRow(8), FILE_HEADER.into()),
|
||||||
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(12), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(22), EXCERPT_HEADER.into()),
|
(DisplayRow(25), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(23), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(27), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(32), EXCERPT_HEADER.into()),
|
(DisplayRow(36), EXCERPT_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -273,6 +277,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"const a: i32 = 'a';\n",
|
"const a: i32 = 'a';\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
"const b: i32 = c;\n",
|
"const b: i32 = c;\n",
|
||||||
|
@ -284,6 +289,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
|
"\n", // expand
|
||||||
" let x = vec![];\n",
|
" let x = vec![];\n",
|
||||||
" let y = vec![];\n",
|
" let y = vec![];\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
|
@ -299,6 +306,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // filename
|
"\n", // filename
|
||||||
|
"\n", // expand
|
||||||
"fn main() {\n",
|
"fn main() {\n",
|
||||||
" let x = vec![];\n",
|
" let x = vec![];\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
|
@ -306,11 +314,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
" a(x);\n",
|
" a(x);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
" b(y);\n",
|
" b(y);\n",
|
||||||
|
"\n", // expand
|
||||||
"\n", // context ellipsis
|
"\n", // context ellipsis
|
||||||
" c(y);\n",
|
" c(y);\n",
|
||||||
" d(x);\n",
|
" d(x);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
"}"
|
"}",
|
||||||
|
"\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -318,7 +328,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.selections.display_ranges(cx),
|
editor.selections.display_ranges(cx),
|
||||||
[DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
|
[DisplayPoint::new(DisplayRow(22), 6)..DisplayPoint::new(DisplayRow(22), 6)]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -366,14 +376,14 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(13), FILE_HEADER.into()),
|
(DisplayRow(15), FILE_HEADER.into()),
|
||||||
(DisplayRow(15), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(19), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(28), EXCERPT_HEADER.into()),
|
(DisplayRow(32), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(29), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(34), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(38), EXCERPT_HEADER.into()),
|
(DisplayRow(43), EXCERPT_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -388,6 +398,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"const a: i32 = 'a';\n",
|
"const a: i32 = 'a';\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
"const b: i32 = c;\n",
|
"const b: i32 = c;\n",
|
||||||
|
@ -395,6 +406,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"const a: i32 = 'a';\n",
|
"const a: i32 = 'a';\n",
|
||||||
"const b: i32 = c;\n",
|
"const b: i32 = c;\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
|
@ -406,6 +418,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
|
"\n", // expand
|
||||||
" let x = vec![];\n",
|
" let x = vec![];\n",
|
||||||
" let y = vec![];\n",
|
" let y = vec![];\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
|
@ -421,6 +435,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // filename
|
"\n", // filename
|
||||||
|
"\n", // expand
|
||||||
"fn main() {\n",
|
"fn main() {\n",
|
||||||
" let x = vec![];\n",
|
" let x = vec![];\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
|
@ -428,11 +443,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
" a(x);\n",
|
" a(x);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
" b(y);\n",
|
" b(y);\n",
|
||||||
|
"\n", // expand
|
||||||
"\n", // context ellipsis
|
"\n", // context ellipsis
|
||||||
" c(y);\n",
|
" c(y);\n",
|
||||||
" d(x);\n",
|
" d(x);\n",
|
||||||
"\n", // supporting diagnostic
|
"\n", // supporting diagnostic
|
||||||
"}"
|
"}",
|
||||||
|
"\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -513,7 +530,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -524,8 +541,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"a();\n", //
|
"a();\n", //
|
||||||
"b();",
|
"b();", "\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -561,9 +579,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(6), EXCERPT_HEADER.into()),
|
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(7), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -574,8 +592,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"a();\n", // location
|
"a();\n", // location
|
||||||
"b();\n", //
|
"b();\n", //
|
||||||
|
"\n", // expand
|
||||||
"\n", // collapsed context
|
"\n", // collapsed context
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
|
@ -583,6 +603,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
"a();\n", // context
|
"a();\n", // context
|
||||||
"b();\n", //
|
"b();\n", //
|
||||||
"c();", // context
|
"c();", // context
|
||||||
|
"\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -629,9 +650,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -642,9 +663,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"a();\n", // location
|
"a();\n", // location
|
||||||
"b();\n", //
|
"b();\n", //
|
||||||
"c();\n", // context
|
"c();\n", // context
|
||||||
|
"\n", // expand
|
||||||
"\n", // collapsed context
|
"\n", // collapsed context
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
|
@ -652,6 +675,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
"b();\n", // context
|
"b();\n", // context
|
||||||
"c();\n", //
|
"c();\n", //
|
||||||
"d();", // context
|
"d();", // context
|
||||||
|
"\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -687,9 +711,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
editor_blocks(&editor, cx),
|
editor_blocks(&editor, cx),
|
||||||
[
|
[
|
||||||
(DisplayRow(0), FILE_HEADER.into()),
|
(DisplayRow(0), FILE_HEADER.into()),
|
||||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -700,9 +724,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
// diagnostic group 1
|
// diagnostic group 1
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
"\n", // padding
|
"\n", // padding
|
||||||
|
"\n", // expand
|
||||||
"b();\n", // location
|
"b();\n", // location
|
||||||
"c();\n", //
|
"c();\n", //
|
||||||
"d();\n", // context
|
"d();\n", // context
|
||||||
|
"\n", // expand
|
||||||
"\n", // collapsed context
|
"\n", // collapsed context
|
||||||
// diagnostic group 2
|
// diagnostic group 2
|
||||||
"\n", // primary message
|
"\n", // primary message
|
||||||
|
@ -710,6 +736,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
"c();\n", // context
|
"c();\n", // context
|
||||||
"d();\n", //
|
"d();\n", //
|
||||||
"e();", // context
|
"e();", // context
|
||||||
|
"\n", // expand
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ use crate::{
|
||||||
pub use block_map::{
|
pub use block_map::{
|
||||||
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
||||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||||
|
StickyHeaderExcerpt,
|
||||||
};
|
};
|
||||||
use block_map::{BlockRow, BlockSnapshot};
|
use block_map::{BlockRow, BlockSnapshot};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
@ -1105,6 +1106,10 @@ impl DisplaySnapshot {
|
||||||
.map(|(row, block)| (DisplayRow(row), block))
|
.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 block_for_id(&self, id: BlockId) -> Option<Block> {
|
pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
|
||||||
self.block_snapshot.block_for_id(id)
|
self.block_snapshot.block_for_id(id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1411,6 +1411,66 @@ impl BlockSnapshot {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
|
||||||
|
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||||
|
cursor.seek(&BlockRow(top_row), Bias::Left, &());
|
||||||
|
|
||||||
|
while let Some(transform) = cursor.item() {
|
||||||
|
let start = cursor.start().0;
|
||||||
|
let end = cursor.end(&()).0;
|
||||||
|
|
||||||
|
match &transform.block {
|
||||||
|
Some(Block::ExcerptBoundary {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_buffer_row = if *starts_new_buffer { Some(end) } else { None };
|
||||||
|
|
||||||
|
return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
|
||||||
|
excerpt,
|
||||||
|
next_buffer_row,
|
||||||
|
next_excerpt_controls_present: *show_excerpt_controls,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(Block::FoldedBuffer {
|
||||||
|
prev_excerpt: Some(excerpt),
|
||||||
|
..
|
||||||
|
}) if top_row <= start => {
|
||||||
|
return Some(StickyHeaderExcerpt {
|
||||||
|
next_buffer_row: Some(end),
|
||||||
|
next_excerpt_controls_present: false,
|
||||||
|
excerpt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(Block::FoldedBuffer { .. }) | Some(Block::Custom(_)) | None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is needed to iterate past None / FoldedBuffer / Custom blocks. For FoldedBuffer,
|
||||||
|
// if scrolled slightly past the header of a folded block, the next block is needed for
|
||||||
|
// the sticky header.
|
||||||
|
cursor.next(&());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
|
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
|
||||||
let buffer = self.wrap_snapshot.buffer_snapshot();
|
let buffer = self.wrap_snapshot.buffer_snapshot();
|
||||||
let wrap_point = match block_id {
|
let wrap_point = match block_id {
|
||||||
|
@ -1694,6 +1754,12 @@ impl<'a> BlockChunks<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct StickyHeaderExcerpt<'a> {
|
||||||
|
pub excerpt: &'a ExcerptInfo,
|
||||||
|
pub next_excerpt_controls_present: bool,
|
||||||
|
pub next_buffer_row: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for BlockChunks<'a> {
|
impl<'a> Iterator for BlockChunks<'a> {
|
||||||
type Item = Chunk<'a>;
|
type Item = Chunk<'a>;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::{
|
||||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
|
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
|
||||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
|
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
|
||||||
SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||||
};
|
};
|
||||||
use client::ParticipantIndex;
|
use client::ParticipantIndex;
|
||||||
|
@ -30,14 +30,14 @@ use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
|
||||||
transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem,
|
relative, size, svg, transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds,
|
||||||
ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler,
|
ClickEvent, ClipboardItem, ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges,
|
||||||
Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
Element, ElementInputHandler, Entity, FontId, GlobalElementId, Hitbox, Hsla,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent,
|
||||||
StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, View,
|
ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription,
|
||||||
ViewContext, WeakView, WindowContext,
|
TextRun, TextStyleRefinement, View, ViewContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -2210,9 +2210,9 @@ impl EditorElement {
|
||||||
resized_blocks: &mut HashMap<CustomBlockId, u32>,
|
resized_blocks: &mut HashMap<CustomBlockId, u32>,
|
||||||
selections: &[Selection<Point>],
|
selections: &[Selection<Point>],
|
||||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||||
|
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (AnyElement, Size<Pixels>) {
|
) -> (AnyElement, Size<Pixels>) {
|
||||||
let header_padding = px(6.0);
|
|
||||||
let mut element = match block {
|
let mut element = match block {
|
||||||
Block::Custom(block) => {
|
Block::Custom(block) => {
|
||||||
let block_start = block.start().to_point(&snapshot.buffer_snapshot);
|
let block_start = block.start().to_point(&snapshot.buffer_snapshot);
|
||||||
|
@ -2305,14 +2305,7 @@ impl EditorElement {
|
||||||
|
|
||||||
let jump_data = jump_data(snapshot, block_row_start, *height, first_excerpt, cx);
|
let jump_data = jump_data(snapshot, block_row_start, *height, first_excerpt, cx);
|
||||||
result
|
result
|
||||||
.child(self.render_buffer_header(
|
.child(self.render_buffer_header(first_excerpt, true, selected, jump_data, cx))
|
||||||
first_excerpt,
|
|
||||||
header_padding,
|
|
||||||
true,
|
|
||||||
selected,
|
|
||||||
jump_data,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
Block::ExcerptBoundary {
|
Block::ExcerptBoundary {
|
||||||
|
@ -2347,14 +2340,19 @@ impl EditorElement {
|
||||||
if let Some(next_excerpt) = next_excerpt {
|
if let Some(next_excerpt) = next_excerpt {
|
||||||
let jump_data = jump_data(snapshot, block_row_start, *height, next_excerpt, cx);
|
let jump_data = jump_data(snapshot, block_row_start, *height, next_excerpt, cx);
|
||||||
if *starts_new_buffer {
|
if *starts_new_buffer {
|
||||||
result = result.child(self.render_buffer_header(
|
if sticky_header_excerpt_id != Some(next_excerpt.id) {
|
||||||
next_excerpt,
|
result = result.child(self.render_buffer_header(
|
||||||
header_padding,
|
next_excerpt,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
jump_data,
|
jump_data,
|
||||||
cx,
|
cx,
|
||||||
));
|
));
|
||||||
|
} else {
|
||||||
|
result =
|
||||||
|
result.child(div().h(FILE_HEADER_HEIGHT as f32 * cx.line_height()));
|
||||||
|
}
|
||||||
|
|
||||||
if *show_excerpt_controls {
|
if *show_excerpt_controls {
|
||||||
result = result.child(
|
result = result.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -2507,7 +2505,6 @@ impl EditorElement {
|
||||||
fn render_buffer_header(
|
fn render_buffer_header(
|
||||||
&self,
|
&self,
|
||||||
for_excerpt: &ExcerptInfo,
|
for_excerpt: &ExcerptInfo,
|
||||||
header_padding: Pixels,
|
|
||||||
is_folded: bool,
|
is_folded: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
jump_data: JumpData,
|
jump_data: JumpData,
|
||||||
|
@ -2531,8 +2528,8 @@ impl EditorElement {
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.px(header_padding)
|
.px_2()
|
||||||
.pt(header_padding)
|
.pt_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
|
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
|
||||||
.child(
|
.child(
|
||||||
|
@ -2686,6 +2683,7 @@ impl EditorElement {
|
||||||
line_layouts: &[LineWithInvisibles],
|
line_layouts: &[LineWithInvisibles],
|
||||||
selections: &[Selection<Point>],
|
selections: &[Selection<Point>],
|
||||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||||
|
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
|
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
|
||||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||||
|
@ -2724,6 +2722,7 @@ impl EditorElement {
|
||||||
&mut resized_blocks,
|
&mut resized_blocks,
|
||||||
selections,
|
selections,
|
||||||
is_row_soft_wrapped,
|
is_row_soft_wrapped,
|
||||||
|
sticky_header_excerpt_id,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
|
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
|
||||||
|
@ -2735,6 +2734,7 @@ impl EditorElement {
|
||||||
style: BlockStyle::Fixed,
|
style: BlockStyle::Fixed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (row, block) in non_fixed_blocks {
|
for (row, block) in non_fixed_blocks {
|
||||||
let style = block.style();
|
let style = block.style();
|
||||||
let width = match style {
|
let width = match style {
|
||||||
|
@ -2770,6 +2770,7 @@ impl EditorElement {
|
||||||
&mut resized_blocks,
|
&mut resized_blocks,
|
||||||
selections,
|
selections,
|
||||||
is_row_soft_wrapped,
|
is_row_soft_wrapped,
|
||||||
|
sticky_header_excerpt_id,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2817,6 +2818,7 @@ impl EditorElement {
|
||||||
&mut resized_blocks,
|
&mut resized_blocks,
|
||||||
selections,
|
selections,
|
||||||
is_row_soft_wrapped,
|
is_row_soft_wrapped,
|
||||||
|
sticky_header_excerpt_id,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2883,6 +2885,71 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_sticky_buffer_header(
|
||||||
|
&self,
|
||||||
|
StickyHeaderExcerpt {
|
||||||
|
excerpt,
|
||||||
|
next_excerpt_controls_present,
|
||||||
|
next_buffer_row,
|
||||||
|
}: StickyHeaderExcerpt<'_>,
|
||||||
|
scroll_position: f32,
|
||||||
|
line_height: Pixels,
|
||||||
|
snapshot: &EditorSnapshot,
|
||||||
|
hitbox: &Hitbox,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> AnyElement {
|
||||||
|
let jump_data = jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt, cx);
|
||||||
|
|
||||||
|
let editor_bg_color = cx.theme().colors().editor_background;
|
||||||
|
|
||||||
|
let mut header = v_flex()
|
||||||
|
.relative()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(hitbox.bounds.size.width)
|
||||||
|
.h(FILE_HEADER_HEIGHT as f32 * line_height)
|
||||||
|
.bg(linear_gradient(
|
||||||
|
0.,
|
||||||
|
linear_color_stop(editor_bg_color.opacity(0.), 0.),
|
||||||
|
linear_color_stop(editor_bg_color, 0.6),
|
||||||
|
))
|
||||||
|
.absolute()
|
||||||
|
.top_0(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
self.render_buffer_header(excerpt, false, false, jump_data, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.into_any_element();
|
||||||
|
|
||||||
|
let mut origin = hitbox.origin;
|
||||||
|
|
||||||
|
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 offset = scroll_position - max_row as f32;
|
||||||
|
|
||||||
|
if offset > 0.0 {
|
||||||
|
origin.y -= Pixels(offset) * line_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = size(
|
||||||
|
AvailableSpace::Definite(hitbox.size.width),
|
||||||
|
AvailableSpace::MinContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
header.prepaint_as_root(origin, size, cx);
|
||||||
|
|
||||||
|
header
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn layout_context_menu(
|
fn layout_context_menu(
|
||||||
&self,
|
&self,
|
||||||
|
@ -4945,11 +5012,14 @@ fn jump_data(
|
||||||
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
|
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
|
||||||
jump_position.row - excerpt_start_row
|
jump_position.row - excerpt_start_row
|
||||||
};
|
};
|
||||||
let line_offset_from_top = block_row_start.0 + height + offset_from_excerpt_start
|
let line_offset_from_top = block_row_start.0
|
||||||
- snapshot
|
+ height
|
||||||
.scroll_anchor
|
+ offset_from_excerpt_start.saturating_sub(
|
||||||
.scroll_position(&snapshot.display_snapshot)
|
snapshot
|
||||||
.y as u32;
|
.scroll_anchor
|
||||||
|
.scroll_position(&snapshot.display_snapshot)
|
||||||
|
.y as u32,
|
||||||
|
);
|
||||||
JumpData {
|
JumpData {
|
||||||
excerpt_id: for_excerpt.id,
|
excerpt_id: for_excerpt.id,
|
||||||
anchor: jump_anchor,
|
anchor: jump_anchor,
|
||||||
|
@ -6096,6 +6166,14 @@ impl Element for EditorElement {
|
||||||
let scroll_range_bounds = scrollbar_range_data.scroll_range;
|
let scroll_range_bounds = scrollbar_range_data.scroll_range;
|
||||||
let mut scroll_width = scroll_range_bounds.size.width;
|
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)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let sticky_header_excerpt_id =
|
||||||
|
sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
|
||||||
|
|
||||||
let blocks = cx.with_element_namespace("blocks", |cx| {
|
let blocks = cx.with_element_namespace("blocks", |cx| {
|
||||||
self.render_blocks(
|
self.render_blocks(
|
||||||
start_row..end_row,
|
start_row..end_row,
|
||||||
|
@ -6111,6 +6189,7 @@ impl Element for EditorElement {
|
||||||
&line_layouts,
|
&line_layouts,
|
||||||
&local_selections,
|
&local_selections,
|
||||||
is_row_soft_wrapped,
|
is_row_soft_wrapped,
|
||||||
|
sticky_header_excerpt_id,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -6124,6 +6203,19 @@ impl Element for EditorElement {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
|
||||||
|
cx.with_element_namespace("blocks", |cx| {
|
||||||
|
self.layout_sticky_buffer_header(
|
||||||
|
sticky_header_excerpt,
|
||||||
|
scroll_position.y,
|
||||||
|
line_height,
|
||||||
|
&snapshot,
|
||||||
|
&hitbox,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let start_buffer_row =
|
let start_buffer_row =
|
||||||
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
|
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||||
let end_buffer_row =
|
let end_buffer_row =
|
||||||
|
@ -6251,6 +6343,7 @@ impl Element for EditorElement {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut block_start_rows = HashSet::default();
|
let mut block_start_rows = HashSet::default();
|
||||||
|
|
||||||
cx.with_element_namespace("blocks", |cx| {
|
cx.with_element_namespace("blocks", |cx| {
|
||||||
self.layout_blocks(
|
self.layout_blocks(
|
||||||
&mut blocks,
|
&mut blocks,
|
||||||
|
@ -6542,6 +6635,7 @@ impl Element for EditorElement {
|
||||||
crease_trailers,
|
crease_trailers,
|
||||||
tab_invisible,
|
tab_invisible,
|
||||||
space_invisible,
|
space_invisible,
|
||||||
|
sticky_buffer_header,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -6623,6 +6717,12 @@ impl Element for EditorElement {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.with_element_namespace("blocks", |cx| {
|
||||||
|
if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
|
||||||
|
sticky_header.paint(cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
self.paint_scrollbars(layout, cx);
|
self.paint_scrollbars(layout, cx);
|
||||||
self.paint_inline_completion_popover(layout, cx);
|
self.paint_inline_completion_popover(layout, cx);
|
||||||
self.paint_mouse_context_menu(layout, cx);
|
self.paint_mouse_context_menu(layout, cx);
|
||||||
|
@ -6730,6 +6830,7 @@ pub struct EditorLayout {
|
||||||
mouse_context_menu: Option<AnyElement>,
|
mouse_context_menu: Option<AnyElement>,
|
||||||
tab_invisible: ShapedLine,
|
tab_invisible: ShapedLine,
|
||||||
space_invisible: ShapedLine,
|
space_invisible: ShapedLine,
|
||||||
|
sticky_buffer_header: Option<AnyElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorLayout {
|
impl EditorLayout {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue