github saberzero1/motions 1.3.1

4 hours ago

Fixed

  • A FileType handler in your own Neovim config silently disabled the rendered-frontmatter fold from the second note onward — activateDocument() runs filetype detect on every activation, which re-fires the user's FileType handlers, and setting a window-local fold expression there is ordinary Neovim configuration. NeovimFrontmatterFold.sync() installed its expression once at connect and then early-returned for the rest of the session, so the handler landed after it on every subsequent activation and nothing put it back: the first note was folded correctly, and every note after it was not. The frontmatter guard is a correctness property rather than a preference — the properties widget stays owned by Obsidian and Neovim's cursor must not enter the region — so it silently stopped holding after the first pane switch. Measured on Neovim 0.12.5 against both routes into that handler: g:markdown_folding, where the stock Markdown ftplugin sets foldexpr=MarkdownFold(), and a treesitter foldexpr in a personal ftplugin, which is the commoner one. MarkdownFold() is additionally wrong for frontmatter rather than merely different — tag: alpha followed by the closing --- matches its setext-H2 rule, measured foldlevel(2)=2, so the properties line starts a fold instead of sitting inside a protected one. The fold expression is now reapplied per activation. foldlevel and foldenable deliberately are not: no FileType handler writes them, and rewriting them there would undo a user's own zm/zM on every pane switch. sync() keeps its early return because prepareKeyInput() awaits it before every delegated keystroke; the reapply lives on a separate activation-path method so the per-keystroke cost is unchanged. (#199)
    • Plugin: src/rpc/frontmatter-fold.ts (syncForActivation(), splitting the connect-time install from the per-activation expression reapply), src/rpc/document-sync.ts (activation call site)
  • Every fold in the focused pane arrived closed under the Neovim backend, and appeared to spring open again when focus moved to another pane — the mirror window was activated with foldlevel set to 0 whenever Settings → Editor → Properties in document was anything other than Source, which is Obsidian's default, so a note opened as if zM had been pressed. 0 was chosen to close the frontmatter fold, and it does, but it closes every heading fold with it. The expression fold marks frontmatter with a sentinel written as 100, and Vim caps an expression fold at MAX_LEVEL, 20 — foldnestmax does not move that cap, measured at 5 and 10, both still reporting 20 — so the level that closes the frontmatter and nothing else is 19, not 0. Raising it to 99 instead is the other wrong answer: the sentinel never reaches 99 either, so the frontmatter fold opens and Neovim's cursor gets the properties widget back. The sentinel is now written as the cap it resolves to and the window's level as one below it. The apparent zR on the pane being left needs no separate cause and is not a defect: CM6 folds are per-pane and the decoration bridge unfolds the pane it stops mirroring, so an all-closed pane visibly reopens as focus leaves it. Reproduced on Linux against Neovim 0.12.5; never platform-specific. (#199)
    • Plugin: src/rpc/frontmatter-fold.ts

Tests

  • A new spec measures fold state as the activation leaves it, which no existing spec did. rpc-folds-undo.e2e.ts sets foldmethod, foldexpr, foldlevel=99 and foldenable and runs zR in its beforeEach, so the window it measures is never the window the product produced — the same fixture-hides-product shape as the vim.opt.swapfile = false line removed for the swap-file defect two entries above. Nothing in the new spec sets a fold option or runs zR. It also runs with propertiesInDocument set to visible; every other RPC spec sets source, which is the branch that already worked, and the mode has to be in place before connecting because NeovimFrontmatterFold caches it and reapplies nothing once it matches. Red first: all four scenarios failed, foldclosed() per line returning [1, 1, 1, 1, 1, 1, 7, 7] over the eight-line heading fixture where -1 was expected — both top-level heading folds closed on arrival. Two further controls, each proven independently. Setting the window level to 99 failed the frontmatter scenario and nothing else, at closed: -1, closedEnd: -1 against 1 and 3, which is what makes that scenario the guard against the tempting fix rather than a duplicate of the others. Setting foldmethod to manual, so no folds exist at all, failed three of the four and left the pane-focus scenario passing vacuously — deliberate, and recorded: foldclosed() cannot tell a pane with no folds from a pane with no closed folds, and the level-ladder assertion [1, 1, 1, 1, 2, 2, 1, 1] in the first scenario is what refuses to let folding be absent. The pane-focus scenario seeds each leaf through its own editor rather than setupEditor, which re-focuses until it sees any focused CM6 editor and with two visible panes can already be the other one; seeding through it settled the mirror on the wrong pane and reported six lines where eight were expected. (#199)
    • Tests: test/specs/rpc-fold-focus.e2e.ts (new), test/specs/rpc-fold-focus-negative-controls.md (new)
  • Two further scenarios in the same spec are opposing controls on the per-activation reapply, because restoring the fold expression and leaving the fold level alone are separable and a fix that does one without the other is wrong in a different direction. Red first for the first of them: with a FileType handler installed and a pane switched, the frontmatter line returned level: 0, closed: -1, closedEnd: -1 against 20, 1 and 3 — the plugin's sentinel replaced by the user's expression. The handler is a constant 0 rather than a real treesitter expression so the effect is unambiguous and no parser has to be installed on the test machine; both real routes were measured separately against Neovim 0.12.5 and reach the window the same way. Each sabotage then failed exactly one scenario and left the other green: deleting the applyFoldExpression() call failed only the handler scenario, and adding foldlevel to the reapply failed only leaves a user fold level alone across a pane switch, at [-1, -1, -1, -1, -1, -1, -1, -1] against the [1, 1, 1, 1, 1, 1, 7, 7] a zM had produced. That second scenario is green both before and after the reapply was added — its job is to stay green, and the sabotage is what shows it is not decorative. (#199)

Documentation

  • AGENTS.md, CONTRIBUTING.md: the new spec and its negative-control file, and why it does not overlap rpc-folds-undo.e2e.ts.
  • KNOWN_LIMITATIONS.md: the RPC frontmatter paragraph now states that the window's fold level sits one below the frontmatter fold so the body arrives unfolded, that Vim's MAX_LEVEL cap is what decides that number, and that the fold expression is reapplied per activation because filetype detect re-fires the user's FileType handlers.
  • docs/features/neovim-backend.md: a sentence in the properties-mode paragraph stating that only the frontmatter fold is closed on activation, which pairs with the existing note that fold persistence is unavailable in RPC mode, and a note that the plugin reclaims foldmethod/foldexpr on the mirror window while leaving foldlevel to you.

Full Changelog: 1.3.0...1.3.1

Don't miss a new motions release

NewReleases is sending notifications on new releases.