mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-11 10:04:46 +00:00
markdown: Add horizontal scrollbars to wide tables (#61745)
## Objective Add functional horizontal scrollbars to wide Markdown tables, matching the existing code-block scrollbar pattern. Fixes #61437 PR was talked about in https://github.com/zed-industries/zed/pull/61698 ## Implementation: - Added a `BTreeMap<usize, ScrollHandle>` to `Markdown` for table scroll handles, keyed by source-range start. - During `MarkdownElement` layout, each table container is connected to a `Scrollbars` widget configured for the horizontal axis, using a stable ID derived from `("markdown-table-scrollbar", range.start)`. - The inner table div uses `overflow_x_scroll()` + `track_scroll()` with `restrict_scroll_to_axis`. - Handles for removed tables are discarded after re-rendering. - No public API, setting, schema, or migration changes required. ## Recording https://github.com/user-attachments/assets/cd0f635b-a485-4de8-9059-40a05f256fb1 ## Release Notes: - Improved navigation of wide Markdown tables with horizontal scrollbars. --------- Co-authored-by: MrSubidubi <finn@zed.dev>
This commit is contained in:
parent
d0f797a38c
commit
51db7df750
3 changed files with 90 additions and 2 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -10810,6 +10810,7 @@ dependencies = [
|
|||
"gpui_platform",
|
||||
"html5ever 0.27.0",
|
||||
"image",
|
||||
"indoc",
|
||||
"language",
|
||||
"languages",
|
||||
"linkify",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ fs = {workspace = true, features = ["test-support"]}
|
|||
gpui = { workspace = true, features = ["test-support"] }
|
||||
gpui_platform = { workspace = true, features = ["wayland", "x11"] }
|
||||
image.workspace = true
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
languages = { workspace = true, features = ["load-grammars"] }
|
||||
node_runtime.workspace = true
|
||||
|
|
|
|||
|
|
@ -2816,7 +2816,14 @@ impl Element for MarkdownElement {
|
|||
.border(px(1.5))
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_sm()
|
||||
.overflow_x_scroll(),
|
||||
.restrict_scroll_to_axis()
|
||||
.custom_scrollbars(
|
||||
Scrollbars::new(ScrollAxes::Horizontal)
|
||||
.id(("markdown-table-scrollbar", range.start))
|
||||
.notify_content(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
range,
|
||||
markdown_end,
|
||||
);
|
||||
|
|
@ -4444,7 +4451,10 @@ impl RenderedText {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{RenderImage, TestAppContext, UpdateGlobal, size};
|
||||
use gpui::{
|
||||
Modifiers, RenderImage, ScrollDelta, ScrollWheelEvent, TestAppContext, TouchPhase,
|
||||
UpdateGlobal, size,
|
||||
};
|
||||
use language::{Language, LanguageConfig, LanguageMatcher};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::{
|
||||
|
|
@ -6184,4 +6194,80 @@ mod tests {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_wide_table_scrolls_horizontally(cx: &mut TestAppContext) {
|
||||
ensure_theme_initialized(cx);
|
||||
let source = indoc::indoc! {r#"
|
||||
| left | right |
|
||||
| --- | --- |
|
||||
| value | far_right_cell_content_that_is_much_wider_than_the_viewport |
|
||||
"#};
|
||||
let left_cell_start = source.find("left").expect("left cell should be present");
|
||||
let left_cell_range = left_cell_start..left_cell_start + "left".len();
|
||||
let right_cell = "far_right_cell_content_that_is_much_wider_than_the_viewport";
|
||||
let right_cell_start = source
|
||||
.find(right_cell)
|
||||
.expect("right cell should be present");
|
||||
let right_cell_range = right_cell_start..right_cell_start + right_cell.len();
|
||||
|
||||
let markdown = cx.new(|cx| Markdown::new(source.into(), None, None, cx));
|
||||
let rendered_text = Rc::new(RefCell::new(None));
|
||||
let (_, cx) = cx.add_window_view({
|
||||
let rendered_text = rendered_text.clone();
|
||||
move |_, _| MarkdownTestView {
|
||||
markdown,
|
||||
style: MarkdownStyle {
|
||||
table_columns_min_size: true,
|
||||
..MarkdownStyle::default()
|
||||
},
|
||||
code_span_link: None,
|
||||
rendered_text,
|
||||
}
|
||||
});
|
||||
cx.simulate_resize(size(px(300.), px(200.)));
|
||||
cx.run_until_parked();
|
||||
|
||||
let (event_position, right_cell_before_scroll) = {
|
||||
let rendered_text = rendered_text.borrow();
|
||||
let rendered_text = rendered_text
|
||||
.as_ref()
|
||||
.expect("markdown should be rendered before scrolling");
|
||||
let event_position = rendered_text
|
||||
.bounds_for_source_range(left_cell_range)
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("left cell should have bounds")
|
||||
.center();
|
||||
let right_cell_bounds = rendered_text
|
||||
.bounds_for_source_range(right_cell_range.clone())
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("right cell should have bounds");
|
||||
(event_position, right_cell_bounds)
|
||||
};
|
||||
|
||||
cx.simulate_event(ScrollWheelEvent {
|
||||
position: event_position,
|
||||
delta: ScrollDelta::Pixels(point(px(-100.), px(0.))),
|
||||
modifiers: Modifiers::default(),
|
||||
touch_phase: TouchPhase::Moved,
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let right_cell_after_scroll = rendered_text
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.expect("markdown should be rendered after scrolling")
|
||||
.bounds_for_source_range(right_cell_range)
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("right cell should have bounds after scrolling");
|
||||
|
||||
assert!(right_cell_after_scroll.left() < right_cell_before_scroll.left());
|
||||
assert_eq!(
|
||||
right_cell_after_scroll.top(),
|
||||
right_cell_before_scroll.top()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue