From 711aa654cbe86f9139c3734e86709311bf8c7b93 Mon Sep 17 00:00:00 2001 From: LioSergent Date: Wed, 27 May 2026 11:49:15 +0200 Subject: [PATCH] feat(vim): zz/zt/zb scroll for notebook viewport (#9701) Override the built-in no-op zz/zt/zb vim commands (which target CodeMirror's own scroll container, unused in marimo) to instead scroll the current in-cell cursor to the center/top/bottom of the notebook viewport. --- frontend/src/core/codemirror/keymaps/vim.ts | 63 +++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/frontend/src/core/codemirror/keymaps/vim.ts b/frontend/src/core/codemirror/keymaps/vim.ts index b6563abca10..77e7a7b615b 100644 --- a/frontend/src/core/codemirror/keymaps/vim.ts +++ b/frontend/src/core/codemirror/keymaps/vim.ts @@ -112,6 +112,35 @@ export function vimKeymapExtension(): Extension[] { ]; } +function scrollCursorTo(cm: CodeMirror, position: "center" | "start" | "end") { + const view = cm.cm6; + if (!view) { + return; + } + const coords = view.coordsAtPos(view.state.selection.main.head); + if (!coords) { + return; + } + const appEl = document.getElementById("App"); + if (!appEl) { + return; + } + const viewportHeight = appEl.clientHeight; + let delta: number; + switch (position) { + case "center": + delta = (coords.top + coords.bottom) / 2 - viewportHeight / 2; + break; + case "start": + delta = coords.top; + break; + case "end": + delta = coords.bottom - viewportHeight; + break; + } + appEl.scrollBy({ top: delta, behavior: "smooth" }); +} + const addCustomVimCommandsOnce = once(() => { // Go to definition Vim.defineAction("goToDefinition", (cm: CodeMirror) => { @@ -120,6 +149,40 @@ const addCustomVimCommandsOnce = once(() => { }); Vim.mapCommand("gd", "action", "goToDefinition", {}, { context: "normal" }); + // Scroll cursor to center/top/bottom of viewport (mirrors zz/zt/zb in classic vim) + Vim.defineAction("scrollCursorToCenter", (cm: CodeMirror) => + scrollCursorTo(cm, "center"), + ); + Vim.mapCommand( + "zz", + "action", + "scrollCursorToCenter", + {}, + { context: "normal" }, + ); + + Vim.defineAction("scrollCursorToTop", (cm: CodeMirror) => + scrollCursorTo(cm, "start"), + ); + Vim.mapCommand( + "zt", + "action", + "scrollCursorToTop", + {}, + { context: "normal" }, + ); + + Vim.defineAction("scrollCursorToBottom", (cm: CodeMirror) => + scrollCursorTo(cm, "end"), + ); + Vim.mapCommand( + "zb", + "action", + "scrollCursorToBottom", + {}, + { context: "normal" }, + ); + // Save command Vim.defineEx("write", "w", (cm: CodeMirror) => { const view = cm.cm6;