Skip to content

feat(editor): add opt-in vim mode#149

Merged
jsgrrchg merged 8 commits into
jsgrrchg:mainfrom
Abdulkader-Safi:add-vim-support
May 29, 2026
Merged

feat(editor): add opt-in vim mode#149
jsgrrchg merged 8 commits into
jsgrrchg:mainfrom
Abdulkader-Safi:add-vim-support

Conversation

@Abdulkader-Safi
Copy link
Copy Markdown
Contributor

Add Obsidian/Zed-style modal editing to the CodeMirror editor via @replit/codemirror-vim, disabled by default and toggleable in settings.

  • vimModeEnabled and vimRelativeLineNumbers settings (persisted per vault)
  • vim and line-number compartments wired into the editor with live reconfigure
  • mode status bar (NORMAL/INSERT/VISUAL) and themed :command input
  • :w/:wq/:q/:x ex-commands mapped to existing save/close actions
  • relative line numbers in code mode
  • fix block cursor visibility on live-preview list lines and the black :command input on the dark theme

Add Obsidian/Zed-style modal editing to the CodeMirror editor via
@replit/codemirror-vim, disabled by default and toggleable in settings.

- vimModeEnabled and vimRelativeLineNumbers settings (persisted per vault)
- vim and line-number compartments wired into the editor with live reconfigure
- mode status bar (NORMAL/INSERT/VISUAL) and themed :command input
- :w/:wq/:q/:x ex-commands mapped to existing save/close actions
- relative line numbers in code mode
- fix block cursor visibility on live-preview list lines and the
  black :command input on the dark theme
@Abdulkader-Safi
Copy link
Copy Markdown
Contributor Author

let me review what fail :)

npm install on macOS/arm64 pruned the cross-platform optional @emnapi
transitive deps, breaking npm ci on CI runners. Restore them so the
lockfile only adds @replit/codemirror-vim over upstream.
@jsgrrchg
Copy link
Copy Markdown
Owner

Hey, no worries haha, the fix was super easy and I was about to fix it! Im sorry, I did a lot of changes today in main.

@jsgrrchg jsgrrchg reopened this May 29, 2026
@jsgrrchg
Copy link
Copy Markdown
Owner

I found two behavior issues that are worth fixing before merging:

  1. Cached editor states can keep stale Vim settings

Editor.tsx restores a saved EditorState when switching back to a note if the document text still matches. That preserves undo/selection, which is good, but it also preserves the extension graph. If the user toggles Vim mode while one note is active and then switches back to another note with a cached state, that restored state may still have the old Vim extension configuration.

Result: Vim can remain disabled in a restored tab after enabling it, or remain enabled after disabling it.

A good fix would be to either reconfigure the relevant compartments on restored states, or recreate cached states when global editor settings such as vimModeEnabled, vimRelativeLineNumbers, or live-preview line-number behavior change.

  1. Relative line numbers may not update on cursor movement

getLineNumberExtension(false, true) computes each gutter label from state.selection.main.head, but CodeMirror’s line number gutter does not automatically redraw just because the selection changed. That means relative line numbers can stay stale while moving the cursor, then update only after another redraw trigger such as scrolling, editing, or reconfiguring.

This likely needs an explicit invalidation path on selectionSet, or a custom gutter/marker implementation that depends on cursor movement.

One product question: Vim mode is currently stored per vault through the existing settings persistence. I wonder if it would be better as a global preference instead. Modal editing is usually a user-level editing habit rather than a vault-specific behavior, so making it global might better match user expectations.

Focused tests and lint passed locally:

  • npm test -- src/features/editor/editorExtensions.vim.test.ts src/app/store/settingsStore.test.ts
  • npx eslint ... on the touched files

Full npm run build is currently blocked by unrelated existing TypeScript errors in windowSession.ts and terminal tests.

@jsgrrchg
Copy link
Copy Markdown
Owner

Also I audited the Missing argument: content error against main and this PR.

The strict content validation already exists on main in the native sidecar for commands like save_note, create_note, save_vault_file, ai_send_message, etc. The Electron bridge is just surfacing the native backend rejection.

I don’t see this PR introducing a payload without content. The new Vim :w path dispatches a save event, resolves the active note tab, reads viewRef.current?.state.doc.toString() ?? tab.content, and then goes through the existing saveNow(tab, content) flow.

I also reran the app locally and could not reproduce the error. So this does not look like a blocker for the PR. If it appears again, we should capture the failing backend command along with the missing argument, since the current error only says content is missing and not which command sent the bad payload.

@jsgrrchg jsgrrchg marked this pull request as draft May 29, 2026 16:10
@jsgrrchg jsgrrchg linked an issue May 29, 2026 that may be closed by this pull request
Reconfigure the vim and line-number compartments after restoring a
cached EditorState so toggling vim mode (or relative numbers) applies to
tabs whose state was stashed under the old configuration.

Build the relative line-number gutter directly with a lineMarkerChange
that fires on selectionSet, so relative numbers recompute as the cursor
moves instead of going stale until an unrelated repaint.

Closes jsgrrchg#148
@Abdulkader-Safi
Copy link
Copy Markdown
Contributor Author

hello, I just updated the codebase can you review it again, thanks

@spamsch
Copy link
Copy Markdown
Contributor

spamsch commented May 29, 2026

I will also give it a try

@spamsch
Copy link
Copy Markdown
Contributor

spamsch commented May 29, 2026

A quick test shows that it works great! Very seamless and feels like my MacVim. All relevant commands work.

@jsgrrchg jsgrrchg marked this pull request as ready for review May 29, 2026 19:01
@jsgrrchg
Copy link
Copy Markdown
Owner

@Abdulkader-Safi I’ve been playing around with it, you did an awesome job! I’m really enjoying it.

@jsgrrchg
Copy link
Copy Markdown
Owner

I pushed a very small fix for the TypeScript build issue introduced by the relative line-number marker (erasableSyntaxOnly does not allow parameter properties).

One question for you guys, @spamsch and @Abdulkader-Safi before merging, does it make more sense for Vim mode to be a global setting instead of per-vault? My instinct is that Vim users will usually want this enabled across all their vaults, not separately per vault.

If that makes sense to you both, I can add one more commit to make this setting global and then we can close/merge the PR. This is my only remaining blocker.

Happy to hear your thoughts.

@spamsch
Copy link
Copy Markdown
Contributor

spamsch commented May 29, 2026

Good point! Per vault does not make sense at all. Did not realize it was. I want to have it active in every vault.

@jsgrrchg
Copy link
Copy Markdown
Owner

Thanks @spamsch , I agree, it doesn't make sense to me either, in my opinion is more clean as a global setting, I'll add now a commit to change that and wrap this up 😊.

@jsgrrchg
Copy link
Copy Markdown
Owner

LGTM, merging when checks go green, thank you guys, very happy to see this land in main, thanks @Abdulkader-Safi you really cooked 👨🏻‍🍳.

@jsgrrchg jsgrrchg merged commit efca077 into jsgrrchg:main May 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[New Feature] Add Vim motion support

3 participants