From 07748b7baedc3f5dbd5867797545243a5ba583d4 Mon Sep 17 00:00:00 2001 From: KyleBarton Date: Thu, 11 Dec 2025 14:18:38 -0800 Subject: [PATCH] Add scrolling functionality to markdown preview mode (#44585) Closes #21324 Adds four new commands: - `markdown::MoveUp`, `markdown::MoveDown` - these scroll up and down in markdown preview mode, by no more than the height of a large headline. - `markdown::MoveUpByItem`, and `markdown::MoveDownByItem` - these scroll up and down by the height of the item at the top of the markdown preview window. So headlines and large codeblocks, for instance, scroll further than individual paragraph lines. Also attempts to create sensible defaults: `down` -> `markdown::ScrollDown` `up` -> `markdown::ScrollUp` `alt-down` -> `markdown::ScrollDownByItem` `alt-up` -> `markdown::ScrollUpByItem` And in Vim: `ctrl-u` -> `markdown::ScrollPageUp` `ctrl-d` -> `markdown::ScrollPageDown` `ctrl-e` -> `markdown::ScrollDown` `ctrl-y` -> `markdown::ScrollUp` Release Notes: - Added commands `markdown::ScrollUp`, `markdown::ScrollDown`, `markdown::ScrollUpByItem`, and `markdown::ScrollDownByItem` - Changed commands `markdown::MovePageUp` to `markdown::ScrollPageUp` and `markdown::MovePageDown` to `markdown::ScrollPageDown` --- Cargo.lock | 1 + assets/keymaps/default-linux.json | 8 ++- assets/keymaps/default-macos.json | 8 ++- assets/keymaps/default-windows.json | 8 ++- assets/keymaps/vim.json | 9 +++ .../markdown_preview/src/markdown_preview.rs | 14 +++- .../src/markdown_preview_view.rs | 67 ++++++++++++++++++- crates/vim/Cargo.toml | 1 + crates/vim/src/test/vim_test_context.rs | 1 + 9 files changed, 106 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff1041695e1f1e95bcbc05798d1a1e0f953533ff..5de8cdd6a648e5ab1f43f8ddfc2c0e37983b279f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18097,6 +18097,7 @@ dependencies = [ "language", "log", "lsp", + "markdown_preview", "menu", "multi_buffer", "nvim-rs", diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 1fb02e6c1f1bf41349af7af0a38d41ba2e260072..0bcbb455b502642237347cf9fc36b91eab83f20b 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -1192,8 +1192,12 @@ { "context": "MarkdownPreview", "bindings": { - "pageup": "markdown::MovePageUp", - "pagedown": "markdown::MovePageDown" + "pageup": "markdown::ScrollPageUp", + "pagedown": "markdown::ScrollPageDown", + "up": "markdown::ScrollUp", + "down": "markdown::ScrollDown", + "alt-up": "markdown::ScrollUpByItem", + "alt-down": "markdown::ScrollDownByItem" } }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 9edfaa03f8d7c9609d7b642ee7ddf61973f75e76..65ac280ba7f782cef417aef220dacd7f32f9e6ff 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -1296,8 +1296,12 @@ { "context": "MarkdownPreview", "bindings": { - "pageup": "markdown::MovePageUp", - "pagedown": "markdown::MovePageDown" + "pageup": "markdown::ScrollPageUp", + "pagedown": "markdown::ScrollPageDown", + "up": "markdown::ScrollUp", + "down": "markdown::ScrollDown", + "alt-up": "markdown::ScrollUpByItem", + "alt-down": "markdown::ScrollDownByItem" } }, { diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index a4211e3c25a58f0ca485cae82c39d0018fc5ed46..990902df14ad0beccc0c79db2a006368017f5965 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1220,8 +1220,12 @@ "context": "MarkdownPreview", "use_key_equivalents": true, "bindings": { - "pageup": "markdown::MovePageUp", - "pagedown": "markdown::MovePageDown" + "pageup": "markdown::ScrollPageUp", + "pagedown": "markdown::ScrollPageDown", + "up": "markdown::ScrollUp", + "down": "markdown::ScrollDown", + "alt-up": "markdown::ScrollUpByItem", + "alt-down": "markdown::ScrollDownByItem" } }, { diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 7e5bc30c1ff64a637aeefbb92063d2abed7e56cd..533db14a5f7bba4196f6a45cabfbe5d9052f796a 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -1046,5 +1046,14 @@ "g g": "settings_editor::FocusFirstNavEntry", "shift-g": "settings_editor::FocusLastNavEntry" } + }, + { + "context": "MarkdownPreview", + "bindings": { + "ctrl-u": "markdown::ScrollPageUp", + "ctrl-d": "markdown::ScrollPageDown", + "ctrl-y": "markdown::ScrollUp", + "ctrl-e": "markdown::ScrollDown" + } } ] diff --git a/crates/markdown_preview/src/markdown_preview.rs b/crates/markdown_preview/src/markdown_preview.rs index 77bad89a629cbb1f660e1cd16158d4dbca03361e..61c99764add0a96135730d3cccfe4ef744a63d40 100644 --- a/crates/markdown_preview/src/markdown_preview.rs +++ b/crates/markdown_preview/src/markdown_preview.rs @@ -11,9 +11,19 @@ actions!( markdown, [ /// Scrolls up by one page in the markdown preview. - MovePageUp, + #[action(deprecated_aliases = ["markdown::MovePageUp"])] + ScrollPageUp, /// Scrolls down by one page in the markdown preview. - MovePageDown, + #[action(deprecated_aliases = ["markdown::MovePageDown"])] + ScrollPageDown, + /// Scrolls up by approximately one visual line. + ScrollUp, + /// Scrolls down by approximately one visual line. + ScrollDown, + /// Scrolls up by one markdown element in the markdown preview + ScrollUpByItem, + /// Scrolls down by one markdown element in the markdown preview + ScrollDownByItem, /// Opens a markdown preview for the current file. OpenPreview, /// Opens a markdown preview in a split pane. diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index df8201dc7a3dad18c279582d668304ce9e1cf77b..20613b112eeccf76ec8be12bddc49c12b600ff9b 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -1,3 +1,4 @@ +use std::cmp::min; use std::sync::Arc; use std::time::Duration; use std::{ops::Range, path::PathBuf}; @@ -20,11 +21,12 @@ use workspace::{Pane, Workspace}; use crate::markdown_elements::ParsedMarkdownElement; use crate::markdown_renderer::CheckboxClickedEvent; use crate::{ - MovePageDown, MovePageUp, OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide, + OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide, ScrollPageDown, ScrollPageUp, markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown, markdown_renderer::{RenderContext, render_markdown_block}, }; +use crate::{ScrollDown, ScrollDownByItem, ScrollUp, ScrollUpByItem}; const REPARSE_DEBOUNCE: Duration = Duration::from_millis(200); @@ -425,7 +427,7 @@ impl MarkdownPreviewView { !(current_block.is_list_item() && next_block.map(|b| b.is_list_item()).unwrap_or(false)) } - fn scroll_page_up(&mut self, _: &MovePageUp, _window: &mut Window, cx: &mut Context) { + fn scroll_page_up(&mut self, _: &ScrollPageUp, _window: &mut Window, cx: &mut Context) { let viewport_height = self.list_state.viewport_bounds().size.height; if viewport_height.is_zero() { return; @@ -435,7 +437,12 @@ impl MarkdownPreviewView { cx.notify(); } - fn scroll_page_down(&mut self, _: &MovePageDown, _window: &mut Window, cx: &mut Context) { + fn scroll_page_down( + &mut self, + _: &ScrollPageDown, + _window: &mut Window, + cx: &mut Context, + ) { let viewport_height = self.list_state.viewport_bounds().size.height; if viewport_height.is_zero() { return; @@ -444,6 +451,56 @@ impl MarkdownPreviewView { self.list_state.scroll_by(viewport_height); cx.notify(); } + + fn scroll_up(&mut self, _: &ScrollUp, window: &mut Window, cx: &mut Context) { + let scroll_top = self.list_state.logical_scroll_top(); + if let Some(bounds) = self.list_state.bounds_for_item(scroll_top.item_ix) { + let item_height = bounds.size.height; + // Scroll no more than the rough equivalent of a large headline + let max_height = window.rem_size() * 2; + let scroll_height = min(item_height, max_height); + self.list_state.scroll_by(-scroll_height); + } + cx.notify(); + } + + fn scroll_down(&mut self, _: &ScrollDown, window: &mut Window, cx: &mut Context) { + let scroll_top = self.list_state.logical_scroll_top(); + if let Some(bounds) = self.list_state.bounds_for_item(scroll_top.item_ix) { + let item_height = bounds.size.height; + // Scroll no more than the rough equivalent of a large headline + let max_height = window.rem_size() * 2; + let scroll_height = min(item_height, max_height); + self.list_state.scroll_by(scroll_height); + } + cx.notify(); + } + + fn scroll_up_by_item( + &mut self, + _: &ScrollUpByItem, + _window: &mut Window, + cx: &mut Context, + ) { + let scroll_top = self.list_state.logical_scroll_top(); + if let Some(bounds) = self.list_state.bounds_for_item(scroll_top.item_ix) { + self.list_state.scroll_by(-bounds.size.height); + } + cx.notify(); + } + + fn scroll_down_by_item( + &mut self, + _: &ScrollDownByItem, + _window: &mut Window, + cx: &mut Context, + ) { + let scroll_top = self.list_state.logical_scroll_top(); + if let Some(bounds) = self.list_state.bounds_for_item(scroll_top.item_ix) { + self.list_state.scroll_by(bounds.size.height); + } + cx.notify(); + } } impl Focusable for MarkdownPreviewView { @@ -496,6 +553,10 @@ impl Render for MarkdownPreviewView { .track_focus(&self.focus_handle(cx)) .on_action(cx.listener(MarkdownPreviewView::scroll_page_up)) .on_action(cx.listener(MarkdownPreviewView::scroll_page_down)) + .on_action(cx.listener(MarkdownPreviewView::scroll_up)) + .on_action(cx.listener(MarkdownPreviewView::scroll_down)) + .on_action(cx.listener(MarkdownPreviewView::scroll_up_by_item)) + .on_action(cx.listener(MarkdownPreviewView::scroll_down_by_item)) .size_full() .bg(cx.theme().colors().editor_background) .p_4() diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index a5834f855034ef97b7d0d34b01a7d13f50369a1e..74409a6c255645378b0b2829f4d0045776bfa019 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -63,6 +63,7 @@ indoc.workspace = true language = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] } lsp = { workspace = true, features = ["test-support"] } +markdown_preview.workspace = true parking_lot.workspace = true project_panel.workspace = true release_channel.workspace = true diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 80208fb23ee229c4dc90a7d792ce0348f59ed950..acd77839f2d8cc09ed72993638ff4ec66f79d3fc 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -28,6 +28,7 @@ impl VimTestContext { search::init(cx); theme::init(theme::LoadThemes::JustBase, cx); settings_ui::init(cx); + markdown_preview::init(cx); }); }