From 9e3f4ff8cc4d8d33beb1247607badbe78a9c104c Mon Sep 17 00:00:00 2001 From: delphinus Date: Sun, 21 Jun 2026 12:54:48 +0900 Subject: [PATCH] feat(fold): toggle folds and expandable regions with za / (#21) Folds (callouts/details) and expandable regions (truncated code blocks and tables) could previously only be toggled by clicking. Bind `za` and `` in the preview buffer to toggle the block under the cursor, so the preview is usable without a mouse. - `za` toggles the fold/region under the cursor (no-op otherwise). - `` toggles the block under the cursor, otherwise closes the window when it is a close key (float/tab mode), and is a no-op in toggle mode. - Extract side-effect-free `fold_at` / `region_at` finders shared by the mouse and keyboard handlers; mouse behavior (incl. URL precedence) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.ja.md | 19 +++- README.md | 19 +++- doc/md-render.jax | 22 ++-- doc/md-render.txt | 19 ++-- lua/md-render/display_utils.lua | 86 ++++++++++++---- tests/key_toggle_test.lua | 175 ++++++++++++++++++++++++++++++++ 6 files changed, 299 insertions(+), 41 deletions(-) create mode 100644 tests/key_toggle_test.lua diff --git a/README.ja.md b/README.ja.md index 6c386d6..1ad475d 100644 --- a/README.ja.md +++ b/README.ja.md @@ -12,14 +12,14 @@ Neovim 用の Markdown レンダリングエンジンです。生の Markdown - **リッチなインライン書式** — 太字、取り消し線、インラインコード、リンク、Obsidian `==highlight==` をその場でレンダリング - **テーブル** — 罫線文字による描画、列アラインメント、比例サイズ調整、セル内インライン書式 -- **コールアウト & 折りたたみ** — GitHub / Obsidian のアラートタイプに対応。色付きボーダー・アイコン・クリックで折りたたみ切り替え -- **コードブロック** — treesitter シンタックスハイライト付きフェンスコードブロック。省略時はクリックで展開 +- **コールアウト & 折りたたみ** — GitHub / Obsidian のアラートタイプに対応。色付きボーダー・アイコン・クリックまたは `za` / `` で折りたたみ切り替え +- **コードブロック** — treesitter シンタックスハイライト付きフェンスコードブロック。省略時はクリックまたは `za` / `` で展開 - **画像** — ローカルおよび Web 画像(PNG, JPEG, WebP, GIF, アニメーション GIF)をターミナルグラフィクスプロトコルでインライン表示 - **動画** — ローカルおよび Web 動画(MP4, WebM, MOV, AVI, MKV, M4V)をアニメーションフレームとしてインライン再生 - **Mermaid ダイアグラム** — 画像としてインライン表示 - **CJK 対応ワードラップ** — JIS X 4051 禁則処理 + [BudouX](https://github.com/google/budoux)([budoux.lua](https://github.com/delphinus/budoux.lua) 経由)によるオプションのフレーズ分割 - **クリック可能リンク** — マウスクリックで URL を開く。対応ターミナルでは OSC 8 ハイパーリンク -- **`
` 対応** — クリックで折りたたみ可能なセクション。`open` 属性にも対応 +- **`
` 対応** — クリックまたは `za` / `` で折りたたみ可能なセクション。`open` 属性にも対応 - **ライブラリ API** — レンダリングエンジンを自作プラグインからプログラム的に利用可能
@@ -144,6 +144,17 @@ vim.keymap.set("n", "md", "(md-render-demo)", { desc = "Mar | `(md-render-split)` | ウィンドウを分割してソースとレンダリング表示を並べる | | `(md-render-demo)` | 対応する全 Markdown 記法のデモウィンドウを表示 | +### プレビュー内のキー + +レンダリングされたプレビュー(フローティング・タブ・その場トグル)内では、以下のバッファローカルなキーが自動で設定されます: + +| キー | 動作 | +|---|---| +| `za` | カーソル行の折りたたみ / 展開ブロックを切り替える(該当しない行では何もしない) | +| `` | カーソル行の折りたたみ / 展開ブロックを切り替える。該当しなければウィンドウを閉じる(フローティング / タブモードのみ) | +| `` | クリックで折りたたみ・展開を切り替え、リンクを開く | +| `q` / `` | ウィンドウを閉じる(フローティング / タブモードのみ) | + ## コマンド このプラグインが公開するユーザコマンドは `:MdRender` ひとつで、サブコマンドで動作モードを切り替えます。 @@ -178,7 +189,7 @@ vim.keymap.set("n", "md", "(md-render-demo)", { desc = "Mar - 同じソースが複数のウィンドウで表示されている場合、切替はそのウィンドウだけに作用し、他のウィンドウの編集は次に当該ウィンドウをレンダーモードにした時点で反映されます。 - カーソル位置は source line マップを介してソース ↔ レンダー間を往復します。 - レンダーモードのウィンドウでは `number` / `relativenumber` / `list` が無効化されます。元の値はウィンドウに保存され、ソースに戻したときに復元されます。 -- レンダーモード中、`q` / `` / `` は閉じる動作に**割り当てられません**。ソースに戻すには再度 `:MdRender toggle` を呼びます。`` は折りたたみ・展開・リンク open を引き続き処理します。 +- レンダーモード中、`q` / `` / `` は閉じる動作に**割り当てられません**。ソースに戻すには再度 `:MdRender toggle` を呼びます。`` / `za` / `` は折りたたみ・展開を引き続き切り替えます(リンク open は ``)。 ### Insert モード連動の自動トグル(実験的) diff --git a/README.md b/README.md index 0364cda..342fe59 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ A Markdown rendering engine for Neovim. Transforms raw Markdown into richly high - **Rich inline formatting** — bold, strikethrough, inline code, links, Obsidian `==highlight==`, all rendered in-place - **Tables** — box-drawing borders, column alignment, proportional sizing, and inline formatting within cells -- **Callouts & folds** — GitHub and Obsidian alert types with colored borders, icons, and click-to-toggle folding -- **Code blocks** — fenced blocks with treesitter syntax highlighting; expandable when truncated +- **Callouts & folds** — GitHub and Obsidian alert types with colored borders, icons, and folding you can toggle by clicking or with `za` / `` +- **Code blocks** — fenced blocks with treesitter syntax highlighting; expandable when truncated (click or `za` / ``) - **Images** — local and web images (PNG, JPEG, WebP, GIF, animated GIF) displayed inline via terminal graphics protocol - **Video** — local and web video (MP4, WebM, MOV, AVI, MKV, M4V) played as animated frames inline - **Mermaid diagrams** — rendered as images inline - **CJK-aware word wrapping** — JIS X 4051 kinsoku shori + optional [BudouX](https://github.com/google/budoux) phrase segmentation via [budoux.lua](https://github.com/delphinus/budoux.lua) - **Clickable links** — mouse click to open URLs; hover the mouse over a link to peek the full URL in a subtle floating window; OSC 8 hyperlink support for compatible terminals -- **`
` support** — collapsible sections with click-to-toggle, respecting the `open` attribute +- **`
` support** — collapsible sections you can toggle by clicking or with `za` / ``, respecting the `open` attribute - **Library API** — use the rendering engine programmatically from your own plugins
@@ -144,6 +144,17 @@ vim.keymap.set("n", "md", "(md-render-demo)", { desc = "Mar | `(md-render-split)` | Open a split showing source and rendered Markdown | | `(md-render-demo)` | Show a demo window with all supported Markdown notations | +### In-preview keys + +Inside a rendered preview (floating, tab, or in-place toggle), these buffer-local keys are set automatically: + +| Key | Action | +|---|---| +| `za` | Toggle the fold / expandable region under the cursor (no-op elsewhere) | +| `` | Toggle the fold / expandable region under the cursor; otherwise close the window (in floating / tab mode) | +| `` | Toggle folds, expand regions, and open links by clicking | +| `q` / `` | Close the window (floating / tab mode only) | + ## Commands The plugin exposes a single `:MdRender` command with subcommands: @@ -178,7 +189,7 @@ Behavior: - When the same source is shown in multiple windows, only the invoking window swaps; edits from other windows are reflected on the next toggle into render mode. - Cursor position round-trips between source and render via the source-line mapping. - `number`, `relativenumber`, and `list` are turned off on render-mode windows. The originals are stashed on the window and restored when toggling back to source. -- Inside render mode, `q` / `` / `` are **not** bound to close — call `:MdRender toggle` again to return to source mode. `` still toggles folds, expands regions, and opens links. +- Inside render mode, `q` / `` / `` are **not** bound to close — call `:MdRender toggle` again to return to source mode. ``, `za`, and `` still toggle folds and expand regions (and `` opens links). ### Auto-toggle on Insert mode (experimental) diff --git a/doc/md-render.jax b/doc/md-render.jax index f1b475a..9a3623c 100644 --- a/doc/md-render.jax +++ b/doc/md-render.jax @@ -39,9 +39,9 @@ md-render.nvim は Neovim 用の Markdown レンダリングエンジンです `==highlight==` ) - テーブル(罫線文字による描画、列アラインメント、比例サイズ調整) - コールアウト & 折りたたみ(GitHub / Obsidian のアラートタイプに対応。色付き - ボーダー・アイコン・クリック折りたたみ切り替え) -- コードブロック(treesitter シンタックスハイライト付き。省略時はクリックで展 - 開) + ボーダー・アイコン・クリックまたは `za` / `` で折りたたみ切り替え) +- コードブロック(treesitter シンタックスハイライト付き。省略時はクリックまた + は `za` / `` で展開) - 画像(PNG, JPEG, WebP, GIF, アニメーション GIF)をターミナルグラフィクスプロ トコルでインライン表示 - 動画(MP4, WebM, MOV, AVI, MKV, M4V)をアニメーションフレームとしてインラ @@ -51,7 +51,7 @@ md-render.nvim は Neovim 用の Markdown レンダリングエンジンです 割) - クリック可能リンク(マウスクリックで URL を開く。マウスをリンクに乗せると 完全な URL を控え目なフロートウィンドウに表示。OSC 8 ハイパーリンク対応) -- `
` 対応(クリックで折りたたみ可能なセクション) +- `
` 対応(クリックまたは `za` / `` で折りたたみ可能なセクション) - ライブラリ API(自作プラグインからプログラム的に利用可能) ============================================================================== @@ -139,7 +139,10 @@ lazy.nvim の場合: >lua プレビューウィンドウ内では以下のキーが使えます: `q` / `` プレビューウィンドウを閉じる - `` コールアウトの折りたたみ / 省略領域の展開 + `za` カーソル行のコールアウトの折りたたみ / 省略領域の + 展開を切り替える (該当しない行では何もしない) + `` カーソル行のコールアウトの折りたたみ / 省略領域の + 展開を切り替える。該当しなければウィンドウを閉じる `` リンクのクリック、折りたたみ、領域展開 `` マウスをリンクに乗せると、エディタ下部の小さな フロートウィンドウに完全な URL を表示。 @@ -148,8 +151,9 @@ lazy.nvim の場合: >lua |:MdRender-toggle| で開かれたレンダーモードのバッファでは `q` / `` / `` は閉じる動作に**割り当てられません**。ソースに戻すには再度 -|:MdRender-toggle| を呼びます。`` は折りたたみ・展開・リンクを -引き続き処理し、`` の URL 表示も有効なままです。 +|:MdRender-toggle| を呼びます。`` / `za` / `` は折りたたみ・ +展開を引き続き切り替え (リンク open は ``)、`` の URL +表示も有効なままです。 ============================================================================== コマンド *md-render-commands* @@ -253,8 +257,8 @@ lazy.nvim の場合: >lua レンダーバッファ上で効くもの: ~ 読み取り系操作: 移動 (hjkl, gg, G, ...)、検索 (/, ?)、yank (y)、 - ビジュアル選択、マーク、(折りたたみ・展開・リンク - open)。 + ビジュアル選択、マーク、`za` / ``(折りたたみ・展開の切り替え)、 + (折りたたみ・展開・リンク open)。 `:w` / `:w!` は source バッファへ転送されるため、render モード を抜けずに保存できます。`:w other.md` や `:saveas` のような別名 diff --git a/doc/md-render.txt b/doc/md-render.txt index 978526f..c240c54 100644 --- a/doc/md-render.txt +++ b/doc/md-render.txt @@ -38,8 +38,9 @@ Features: ~ Obsidian`==highlight==`) - Tables with box-drawing borders, column alignment, and proportional sizing - Callouts and folds (GitHub / Obsidian alert types with colored borders, - icons, and click-to-toggle folding) + icons, and folding you can toggle by click or `za` / ``) - Code blocks with treesitter syntax highlighting; expandable when truncated + (click or `za` / ``) - Images (PNG, JPEG, WebP, GIF, animated GIF) displayed inline via terminal graphics protocol - Video (MP4, WebM, MOV, AVI, MKV, M4V) played as animated frames inline @@ -48,7 +49,7 @@ Features: ~ segmentation) - Clickable links (mouse click to open URLs; mouse hover peeks the full URL in a subtle floating window; OSC 8 hyperlink support) -- `
` support (collapsible sections with click-to-toggle) +- `
` support (collapsible sections; toggle by click or `za` / ``) - Library API for programmatic use from other plugins ============================================================================== @@ -138,7 +139,10 @@ Map them yourself: >lua Inside the preview window, the following keys are available: `q` / `` Close the preview window - `` Toggle callout fold / expand truncated region + `za` Toggle the callout fold / expandable region under the + cursor (no-op when the cursor is not on one) + `` Toggle the callout fold / expandable region under the + cursor; otherwise close the preview window `` Click links, toggle folds, expand regions `` Hover a link to peek its full URL in a small floating window at the bottom of the editor. Requires @@ -147,8 +151,9 @@ Inside the preview window, the following keys are available: Inside a render-mode buffer opened with |:MdRender-toggle|, `q` / `` / `` are NOT bound to close — call |:MdRender-toggle| again to return to -source mode. `` still toggles folds, expands regions, and -opens links; `` hover-peek also stays active. +source mode. ``, `za`, and `` still toggle folds and expand +regions (and `` opens links); `` hover-peek also stays +active. ============================================================================== COMMANDS *md-render-commands* @@ -250,8 +255,8 @@ still registered as deprecated aliases — see |md-render-deprecated-commands|. What works on the render buffer: ~ Read-only operations: motion (hjkl, gg, G, ...), search (/, ?), - yank (y), visual selection, marks, and (folds, - expand, link open). + yank (y), visual selection, marks, `za` / `` (toggle folds + and expand regions), and (folds, expand, link open). `:w` and `:w!` are forwarded to the source buffer so you can save without leaving render mode. Different filenames (e.g. diff --git a/lua/md-render/display_utils.lua b/lua/md-render/display_utils.lua index 191b955..2711767 100644 --- a/lua/md-render/display_utils.lua +++ b/lua/md-render/display_utils.lua @@ -317,12 +317,70 @@ function M.setup_float_keymaps(buf, ns, win, content, close_handle, opts) end local close_keys = opts.close_keys or { "q", "", "" } + local cr_is_close = vim.tbl_contains(close_keys, "") for _, key in ipairs(close_keys) do - vim.api.nvim_buf_set_keymap(buf, "n", key, ":close", { noremap = true, silent = true }) + -- is bound below so it can toggle a block under the cursor first and + -- fall back to closing only when the cursor is not on a foldable region. + if key ~= "" then + vim.api.nvim_buf_set_keymap(buf, "n", key, ":close", { noremap = true, silent = true }) + end end UrlHover.attach(buf, ns, win) + -- Return the foldable callout header at the given 0-indexed rendered line, or nil. + ---@param line integer + ---@return MdRender.CalloutFold? + local function fold_at(line) + local cur_content = get_content() + if on_fold_toggle and cur_content.callout_folds then + for _, fold in ipairs(cur_content.callout_folds) do + if fold.header_line == line then return fold end + end + end + end + + -- Return the expandable region containing the given 0-indexed rendered line, or nil. + ---@param line integer + ---@return MdRender.ExpandableRegion? + local function region_at(line) + local cur_content = get_content() + if on_expand_toggle and cur_content.expandable_regions then + for _, region in ipairs(cur_content.expandable_regions) do + if line >= region.start_line and line <= region.end_line then return region end + end + end + end + + -- Toggle the foldable callout or expandable region under the cursor. + -- Returns true if something was toggled. + ---@return boolean + local function toggle_at_cursor() + local line = vim.api.nvim_win_get_cursor(0)[1] - 1 + local fold = fold_at(line) + if fold then + on_fold_toggle(fold.source_line, not fold.collapsed) + return true + end + local region = region_at(line) + if region then + on_expand_toggle(region.block_id, not region.expanded) + return true + end + return false + end + + -- `za` toggles the block under the cursor (no-op when not on one). Overriding + -- it buffer-locally also suppresses Vim's default "E490: No fold found". + vim.keymap.set("n", "za", toggle_at_cursor, { buffer = buf, noremap = true, silent = true }) + + -- `` toggles the block under the cursor; otherwise it closes the window + -- when it is a close key (float/tab mode), and is a no-op in toggle mode. + vim.keymap.set("n", "", function() + if toggle_at_cursor() then return end + if cr_is_close then vim.cmd.close() end + end, { buffer = buf, noremap = true, silent = true }) + vim.keymap.set("n", "", function() local mouse = vim.fn.getmousepos() if mouse.winid == win then @@ -383,25 +441,19 @@ function M.setup_float_keymaps(buf, ns, win, content, close_handle, opts) end -- Check for foldable callout header click - if on_fold_toggle and cur_content.callout_folds then - for _, fold in ipairs(cur_content.callout_folds) do - if fold.header_line == click_line then - on_fold_toggle(fold.source_line, not fold.collapsed) - return - end - end + local fold = fold_at(click_line) + if fold then + on_fold_toggle(fold.source_line, not fold.collapsed) + return end -- Check for expandable region click (code blocks / tables) - if on_expand_toggle and cur_content.expandable_regions then - for _, region in ipairs(cur_content.expandable_regions) do - if click_line >= region.start_line and click_line <= region.end_line then - -- If click is on a URL, open it instead of toggling expansion - if try_open_url() then return end - on_expand_toggle(region.block_id, not region.expanded) - return - end - end + local region = region_at(click_line) + if region then + -- If click is on a URL, open it instead of toggling expansion + if try_open_url() then return end + on_expand_toggle(region.block_id, not region.expanded) + return end -- In OSC 8 terminals, the terminal handles link clicks natively. diff --git a/tests/key_toggle_test.lua b/tests/key_toggle_test.lua new file mode 100644 index 0000000..d364419 --- /dev/null +++ b/tests/key_toggle_test.lua @@ -0,0 +1,175 @@ +-- Test keyboard toggling of folds / expandable regions (za and ). +-- Mirrors the mouse behavior in display_utils.setup_float_keymaps, +-- but acting on the cursor line instead of the click position. +-- Run: nvim --headless -u NONE --noplugin -l tests/key_toggle_test.lua + +package.path = vim.fn.getcwd() .. "/lua/?.lua;" .. vim.fn.getcwd() .. "/lua/?/init.lua;" .. package.path + +local display_utils = require "md-render.display_utils" + +local pass_count = 0 +local fail_count = 0 + +local function assert_eq(actual, expected, msg) + if vim.deep_equal(actual, expected) then + pass_count = pass_count + 1 + else + fail_count = fail_count + 1 + print("FAIL: " .. msg) + print(" expected: " .. vim.inspect(expected)) + print(" actual: " .. vim.inspect(actual)) + end +end + +local function assert_true(val, msg) + if val then + pass_count = pass_count + 1 + else + fail_count = fail_count + 1 + print("FAIL: " .. msg) + end +end + +local function assert_false(val, msg) + assert_true(not val, msg) +end + +local function test(name, fn) + local ok, err = pcall(fn) + if not ok then + fail_count = fail_count + 1 + print("ERROR: " .. name .. ": " .. tostring(err)) + end +end + +-- Feed keys and process them synchronously. +local function feed(keys) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), "x", false) +end + +--- Build a render-style buffer/window with synthetic content, install the +--- float keymaps, and return everything plus the captured toggle calls. +---@param content table +---@param close_keys? string[] +local function setup(content, close_keys) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, content.lines) + local win = vim.api.nvim_open_win(buf, true, { + relative = "editor", + row = 1, + col = 1, + width = 40, + height = 10, + }) + local ns = vim.api.nvim_create_namespace "md-render-key-toggle-test" + + local calls = { fold = {}, expand = {} } + display_utils.setup_float_keymaps(buf, ns, win, content, nil, { + close_keys = close_keys, + get_content = function() + return content + end, + on_fold_toggle = function(source_line, collapsed) + table.insert(calls.fold, { source_line = source_line, collapsed = collapsed }) + end, + on_expand_toggle = function(block_id, expanded) + table.insert(calls.expand, { block_id = block_id, expanded = expanded }) + end, + }) + + return buf, win, calls +end + +local function cleanup(win, buf) + if win and vim.api.nvim_win_is_valid(win) then vim.api.nvim_win_close(win, true) end + if buf and vim.api.nvim_buf_is_valid(buf) then pcall(vim.api.nvim_buf_delete, buf, { force = true }) end +end + +-- Content: line 1 = callout header (fold), lines 2-3 = expandable region, line 4 = plain. +local function make_content() + return { + lines = { "callout header", "code line 1", "code line 2", "plain text" }, + callout_folds = { { header_line = 0, source_line = 7, collapsed = false } }, + expandable_regions = { { start_line = 1, end_line = 2, block_id = 42, expanded = false } }, + } +end + +-- ---------------------------------------------------------------------- +-- Test 1: `za` on a fold header toggles the fold +-- ---------------------------------------------------------------------- +test("za on fold header toggles the fold", function() + local buf, win, calls = setup(make_content()) + vim.api.nvim_win_set_cursor(win, { 1, 0 }) + feed "za" + assert_eq(#calls.fold, 1, "za on header: on_fold_toggle fires once") + assert_eq(calls.fold[1], { source_line = 7, collapsed = true }, "za on header: toggles to collapsed") + assert_eq(#calls.expand, 0, "za on header: expand not fired") + cleanup(win, buf) +end) + +-- ---------------------------------------------------------------------- +-- Test 2: `` inside an expandable region toggles expansion +-- ---------------------------------------------------------------------- +test(" inside expandable region toggles expansion", function() + local buf, win, calls = setup(make_content()) + vim.api.nvim_win_set_cursor(win, { 2, 0 }) + feed "" + assert_eq(#calls.expand, 1, " in region: on_expand_toggle fires once") + assert_eq(calls.expand[1], { block_id = 42, expanded = true }, " in region: toggles to expanded") + assert_eq(#calls.fold, 0, " in region: fold not fired") + cleanup(win, buf) +end) + +-- ---------------------------------------------------------------------- +-- Test 3: `za` also toggles an expandable region under the cursor +-- ---------------------------------------------------------------------- +test("za inside expandable region toggles expansion", function() + local buf, win, calls = setup(make_content()) + vim.api.nvim_win_set_cursor(win, { 3, 0 }) + feed "za" + assert_eq(#calls.expand, 1, "za in region: on_expand_toggle fires once") + assert_eq(calls.expand[1], { block_id = 42, expanded = true }, "za in region: toggles to expanded") + cleanup(win, buf) +end) + +-- ---------------------------------------------------------------------- +-- Test 4: `za` on a plain line is a silent no-op +-- ---------------------------------------------------------------------- +test("za on plain line is a no-op", function() + local buf, win, calls = setup(make_content()) + vim.api.nvim_win_set_cursor(win, { 4, 0 }) + feed "za" + assert_eq(#calls.fold, 0, "za on plain line: fold not fired") + assert_eq(#calls.expand, 0, "za on plain line: expand not fired") + cleanup(win, buf) +end) + +-- ---------------------------------------------------------------------- +-- Test 5: `` on a plain line (with as a close key) closes the window +-- ---------------------------------------------------------------------- +test(" on plain line closes the window when is a close key", function() + local buf, win, calls = setup(make_content()) -- default close_keys include + vim.api.nvim_win_set_cursor(win, { 4, 0 }) + feed "" + assert_false(vim.api.nvim_win_is_valid(win), " on plain line: window closed") + assert_eq(#calls.fold, 0, " on plain line: fold not fired") + assert_eq(#calls.expand, 0, " on plain line: expand not fired") + cleanup(win, buf) +end) + +-- ---------------------------------------------------------------------- +-- Test 6: in toggle mode (no close keys), on a plain line is a no-op +-- ---------------------------------------------------------------------- +test(" on plain line is a no-op in toggle mode", function() + local buf, win, calls = setup(make_content(), {}) + vim.api.nvim_win_set_cursor(win, { 4, 0 }) + feed "" + assert_true(vim.api.nvim_win_is_valid(win), " toggle mode: window stays open") + assert_eq(#calls.fold, 0, " toggle mode: fold not fired") + cleanup(win, buf) +end) + +print(string.format("key_toggle_test: %d passed, %d failed", pass_count, fail_count)) +if fail_count > 0 then + os.exit(1) +end