Repository navigation
Menu Keys and Routing
Most keys in Nibtree exist twice: as a handler in the page (the editor's keymaps, the window shortcuts) and as an accelerator on a native menu item. This page explains how one key press ends up running exactly one of them. The menu bar itself is in How the menus work.
sequenceDiagram
participant User
participant Web as Web view (page)
participant Menu as Native menu
User->>Web: key press
Web->>Web: CodeMirror keymaps, Workspace onKeydown
alt the page handled it (preventDefault)
Web-->>User: done, the menu item does not run
else unhandled
Web->>Menu: key equivalent
Menu->>Menu: accelerator matches an item
Menu->>Web: item action, runMenuAction
end
On macOS the web view sees a key equivalent first and passes it to the menu only when the page leaves it unhandled. That gives three simple rules:
-
A key the page handles calls
preventDefault.onKeydowninworkspaceLife.svelte.tsdoes that for every shortcutworkspaceShortcutrecognizes, and CodeMirror does it for its keymaps. The menu item with the same accelerator then never runs as well. - An accelerator only the menu knows still works everywhere. Cmd+W, Option+Cmd+S and Cmd+= reach the menu from the editor, the terminal or a text field, because nothing in the page claims them.
-
Both routes call the same function. The View and Edit items call
runWorkspaceShortcut, the Code items callrunEditorCommand, File > Save callsfileCommands.save. So it does not matter which route fires.
This is also why one key can mean different things by place. Cmd+K is Git > Commit..., but the Markdown editor makes a link with it and the terminal clears its screen; they see it first. Cmd+B is View > Files Panel, but both Markdown editors make text bold with it: their keymaps (markdownKeys in FileView.svelte, Milkdown's own) prevent the default, so workspaceShortcut skips the key and the menu never gets it.
EDITOR_SHORTCUTS in src/lib/editor/editorShortcuts.ts lists the key of every Find and Code item in CodeMirror notation, with a macOS variant where it differs and the keymap that binds it (default, find or code). The editor binds those keys itself, so they work in every editor, the merge tool included. acceleratorFor turns the same key into the menu's accelerator (Mod-Shift-d becomes CmdOrCtrl+Shift+D), so the menu always shows a key that really works.
editorFocus.svelte.ts follows focusin and focusout and records whether an editor has the keyboard, whether the caret is in its text, and whether it is writable. The menu uses it to grey the Code items; only focus changes update it, so typing costs nothing.
Edit's Undo, Redo, Delete and Select All are custom items, not the system's. textEdit in menuActions.ts:
- runs CodeMirror's command when an editor has the caret in its text, since the editor keeps its own history;
- leaves the terminal alone (it handles Cmd+A itself);
- otherwise runs the web view's
document.execCommand, so text fields behave as usual. Delete only acts when something is selected.
The Windows and Linux builds are not out yet, so the order of page and menu there is not verified. Two choices keep it safe either way:
- Undo, Redo and Select All get no accelerator outside macOS, so Ctrl+Z and Ctrl+A stay with the page and the shell in the terminal.
- The Git menu keys (Cmd+K, Cmd+T, Cmd+9, Option+Cmd+A) are macOS only, because Ctrl+K and Ctrl+T belong to the shell. Next Tab and Previous Tab use Ctrl+PageDown and Ctrl+PageUp there, since Ctrl+Shift+] and [ fold code.
UI text never writes keys itself. commandKeys(id) in commandRuntime.ts gives a command's current keys in words, custom ones included ("Shift+Cmd+G" on macOS, "Ctrl+Shift+G" elsewhere, from formatKeyWords), withCommandKeys(title, id) makes "Title (keys)" for a tooltip, and localKeys("CmdOrCtrl+Enter") writes keys a view binds itself. All three are reactive reads, so labels follow Settings > Keyboard Shortcuts.
Help > Keyboard Shortcuts sets helpDialogs.shortcutsOpen, and App.svelte loads ShortcutsDialog.svelte only then. Its rows come from shortcutSections in help/shortcuts.ts:
flowchart LR
Spec["menuSpec(platform, 'app')"] --> Menu["menuShortcuts: every item<br/>with an accelerator"]
Extra["extraShortcuts: double Shift,<br/>F7, terminal, Markdown..."] --> All["shortcutSections"]
Menu --> All
All --> Filter["filterShortcuts(filter)"]
Filter --> Dialog["ShortcutsDialog.svelte"]
Menu rows are read from the menu bar's own data, so they cannot drift from the real keys. formatKeys writes an accelerator the platform's way ("⇧⌘E" on macOS, "Ctrl+Shift+E" elsewhere). Shortcuts no menu shows are listed by hand in extraShortcuts, per platform where keys differ. Open Online Version opens SHORTCUTS_URL, the wiki's Keyboard Shortcuts page.
src/lib/help/shortcuts.test.ts covers macOS and other key spelling, every menu accelerator listed, the extra shortcuts, the tab keys per platform, the Bold row and the filter. A shortcut no menu shows goes in extraShortcuts, and on Keyboard Shortcuts.
Cmd+W never closes the main window. It is Close Tab and does nothing without a tab. In the mergetool window, Window > Close Window (Cmd+W) asks before it drops the merge result.
Push has no key. Shift+Cmd+K is Delete Line in the Code menu.
Double Shift is not a menu key. Menus cannot show it, so the label reads "Search Everywhere (Double Shift)".
Left out on purpose. IDE keys that clash with macOS or the editor, such as Cmd+Backspace for Delete Line (it deletes to the line start on macOS) and Cmd+- and Cmd+= for folding (they zoom the editor font).
One list for the shortcuts window. Help > Keyboard Shortcuts builds its menu sections from menuSpec, so a menu key is always listed as the menu has it. The keys no menu shows are written by hand in src/lib/help/shortcuts.ts and must be kept in step with their handlers; keys that differ by platform (Next Tab, Previous Tab) follow the Window menu, and shortcuts.test.ts checks that.
-
src/lib/editor/editorShortcuts.test.ts: accelerators from CodeMirror keys, each key used once, and that each menu key is really bound to the item's command. -
src/lib/menu/menuSpec.test.ts: each key on one item, Cmd+W never closing the main window, the app's editing keys, and Ctrl+Z and Ctrl+A left to the page outside macOS. -
src/lib/views/workspaceShortcuts.test.ts: which window shortcut a key press means. -
src/lib/help/shortcuts.test.ts: the shortcuts window's list and filter.
Whether a key reaches the page or the menu needs a manual check in the real app. See Testing.
- A new key goes in one place:
EDITOR_SHORTCUTSfor editor commands,workspaceShortcuts.tsplus a menu accelerator for window commands. Run the tests above. - Update Keyboard Shortcuts and Menus.
These pages are generated from docs/wiki. To correct something, open a pull request against the source file; edits made here are overwritten.
Using Nibtree
- Getting Started
- Welcome Screen
- Workspaces
- Files Panel
- File Operations
- File Icons
- Rounded Panels
- Editor and Tabs
- Pin Reorder and Wrap Tabs
- Single Tab Title
- Split Editor
- New File and Unsaved Changes
- Unload Hidden Tabs
- Editing Code
- Code Appearance
- Markdown Editor
- Image and PDF Preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and Replace
- Changes and Commits
- Sign In Prompts
- Commit Box Layout
- Repository Actions
- Commit Options
- Ignoring Files
- Diffs
- Inline Diffs
- Staging View
- Revert a Change
- Resolving Conflicts
- Merge Tool
- Blame
- History and Log
- Merge Commits
- Branches and Tags
- Branches Popup
- Remotes
- Stashes
- Shelf
- Git Menu
- Git Dialogs
- Interactive Rebase
- Worktrees
- AI Agents
- AI Tasks
- AI Usage
- Keep Computer Awake
- Submodules
- Git LFS
- GitHub
- Git Console
- Terminal
- Terminal Features
- Scripts
- Navigation
- Menus
- Settings
- Settings Search
- Color Themes
- Settings Terminal and Automation
- Settings Files
- Updates
- Status Bar and Help
- Notifications
- Memory Use
- Clear Cache
- MCP and CLI
- Git Mergetool
- Keyboard Shortcuts
- Keyboard Shortcuts Editor
- Troubleshooting
- Troubleshooting Git
Developers
- Developer Guide
- Architecture
- Project Layout
- Backend
- How Folder Watching Works
- Folder Watching Bugs We Fixed
- How Workspace Files Work
- How the Preview Scheme Works
- Backend Services
- How Terminal Features Work
- Terminal Bugs We Fixed
- Frontend
- How the Rich Markdown Editor Works
- Rich Markdown Bugs We Fixed
- Git Menu Links
- Menu Keys and Routing
- Frontend Modules
- Blame Bugs We Fixed
- Project Color And Title Bar
- One Diff View
- Diff Bugs We Fixed
- Merge Tool Layouts
- Merge Tool Bugs We Fixed
- Color Theme Bugs We Fixed
- How the Connected Diff Works
- Rounded Panels Bugs We Fixed
- Update Bugs We Fixed
- Status Bar Bugs We Fixed
- MCP and CLI Bugs We Fixed
- Repository Actions Bugs We Fixed
- How the Changes Tab Works
- CodeMirror Patterns
- How the Cursor Works
- How Editor Features Work
- How the Path Bar Works
- Commands and Events
- Settings Reference
- Commands Workspace and Changes
- Commands Branches and Remotes
- Commands History and Extras
- Commands App and Tools
- Testing
- Test Suites
- Debugging
- Debugging with MCP
- Measuring Setting Memory
- How Terminal Memory Is Measured
- How Markdown Diagrams Work
- Releases and CI
- Publishing the Wiki
- Versioning and Changelog
- Browser Version Packages
- Browser Release Checks
- Docker Image
- Platforms and Signing
- Windows Support
- Windows Bugs We Fixed
- Windows MCP and CLI
- Docs and Screenshots
- UI Library
- UI Library MCP
- UI Library Bugs
- UI Library Migration
- Contributing
How features work
- Welcome screen
- Workspaces and folders
- Files panel
- File operations
- File icons
- Editor and tabs
- Pin, reorder and wrap tabs
- Single tab title
- Split editor
- New File and unsaved changes
- Unload hidden tabs
- Editing code
- Code appearance
- Markdown editor
- Image and PDF preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and replace
- Changes and commits
- Sign-in prompts
- Commit box layout
- Repository actions
- Commit options
- Ignoring files
- Diffs
- Inline diffs
- Staging view
- Revert a change
- Resolving conflicts
- Merge tool
- Blame
- History and log
- Merge commits
- Commit tabs
- Branches and tags
- Branches popup
- Remotes
- Stashes
- Shelf
- Git menu
- Git dialogs
- Interactive rebase
- Worktrees
- AI agents
- AI usage
- Keep computer awake
- AI tasks
- Submodules
- Git LFS
- GitHub account
- Git Console
- Back and Forward
- Integrated terminal
- Scripts tool window
- Menu bar
- Settings
- Search in Settings
- Color themes
- Rounded panels
- Updates and release channels
- Status bar and help links
- Notifications
- Memory readout and memory log
- Clear Cache
- MCP server and command line tool
- git mergetool mode