From d1f61e109f90034e07cf26f13512ebf2f34d7733 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:03:59 +0700 Subject: [PATCH] fix(editor): give the Markdown preview a scrollbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The editor itself gets one from `Input`; the rendered-Markdown pane beside it scrolled a whole README with nothing to say how far down it was. It now carries the same shared bar as the sidebar, the file tree, the right panel and the settings pages, on a handle kept per open file — so switching away and back lands where you were reading. `with_vertical_scrollbar` grows by `flex_1`, which needs a column with a height of its own around it; dropped straight into the overlay it sizes to its content and the pane stops scrolling altogether. That requirement is now written on the helper, where the next caller will read it. --- src/ui/code_editor.rs | 35 +++++++++++++++++++++++++++-------- src/ui/scrollbar.rs | 7 +++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/ui/code_editor.rs b/src/ui/code_editor.rs index a7a0408a..8f3f9ab0 100644 --- a/src/ui/code_editor.rs +++ b/src/ui/code_editor.rs @@ -34,6 +34,11 @@ pub(crate) struct OpenFile { pub(crate) conflict: bool, pub(crate) preview: bool, pub(crate) wrap: bool, + /// The rendered-Markdown pane's own scroll. Per file, so switching away + /// and back lands where you were reading — and so the pane can carry the + /// scrollbar every other scrolling surface in tty7 has. The editor itself + /// gets one from `Input`. + pub(crate) preview_scroll: gpui::ScrollHandle, _sub: Subscription, _observe: Subscription, } @@ -500,6 +505,7 @@ impl Tty7App { conflict: false, preview: false, wrap: false, + preview_scroll: gpui::ScrollHandle::new(), _sub: sub, _observe: observe, }, @@ -894,15 +900,28 @@ impl Tty7App { None => self.render_editor_empty(cx).into_any_element(), Some(f) if f.preview => { let markdown = f.input.read(cx).text().to_string(); - div() - .id("editor-md-preview") + let scroll = f.preview_scroll.clone(); + // The bar's wrapper takes its height from `flex_1`, so it needs + // a column with a definite height to grow inside — hand it one + // rather than dropping it straight into the overlay, or the + // pane sizes to its content and there is nothing left to + // scroll. + v_flex() .size_full() - .overflow_y_scroll() - .px_4() - .py_3() - .child(gpui_component::text::TextView::markdown( - "editor-md-preview-body", - markdown, + .child(crate::ui::scrollbar::with_vertical_scrollbar( + "editor-md-preview-scrollbar", + div() + .id("editor-md-preview") + .size_full() + .overflow_y_scroll() + .track_scroll(&scroll) + .px_4() + .py_3() + .child(gpui_component::text::TextView::markdown( + "editor-md-preview-body", + markdown, + )), + &scroll, )) .into_any_element() } diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs index 194c641d..d3be8413 100644 --- a/src/ui/scrollbar.rs +++ b/src/ui/scrollbar.rs @@ -2,6 +2,13 @@ use gpui::{AnyElement, ElementId, ScrollHandle, div, prelude::*}; use gpui_component::scroll::Scrollbar; use gpui_component::v_flex; +/// Overlays the shared vertical scrollbar on a scroll area. +/// +/// The returned element takes its height from `flex_1`, so it wants a flex +/// column with a height of its own to grow inside — give it one rather than +/// dropping it straight into whatever is around it. Without that the wrapper +/// sizes to its content, the `size_full` scroll area inside grows with it, and +/// the pane stops scrolling because nothing overflows any more. pub(crate) fn with_vertical_scrollbar( id: impl Into, scroll_area: impl IntoElement,