Skip to content

Instantly share code, notes, and snippets.

@wojukasz
Last active March 11, 2026 09:24
Show Gist options
  • Select an option

  • Save wojukasz/d76ca3162debc66d4227b9dc6049789e to your computer and use it in GitHub Desktop.

Select an option

Save wojukasz/d76ca3162debc66d4227b9dc6049789e to your computer and use it in GitHub Desktop.
⚡VSCode + WhichKey + Vim keybindings | Works with VScode forks like AntiGravity, Cursor, etc | Copy-paste ready config | 5-min setup | Full version: https://github.com/wojukasz/VimCode
[
// ╔══════════════════════════════════════════════════════════════════════════════════╗
// ║ VSCODE VIM LAZYVIM KEYBINDINGS ║
// ║ Modifier Key Bindings (Ctrl, Alt, Shift) ║
// ╚══════════════════════════════════════════════════════════════════════════════════╝
//
// This file contains keybindings that use modifier keys (Ctrl, Alt, Shift) and special
// characters that need VSCode's key binding system (not Vim's).
//
// IMPORTANT: DO NOT put space-leader bindings here (<leader>...)
// Those belong in settings.json under vim.normalModeKeyBindingsNonRecursive
//
// FILE ORGANIZATION:
// - Window navigation (Ctrl+h/j/k/l)
// - Buffer navigation (Shift+h/l, [b, ]b)
// - Diagnostic navigation ([d, ]d, [e, ]e)
// - Quickfix navigation ([q, ]q)
// - Git hunk navigation ([h, ]h)
// - Suggestion/autocomplete navigation
// - File explorer navigation
// - Terminal toggle
// - Line movement (Alt+j/k)
//
// SPECIAL KEY CODES:
// - oem_4 = [ (left bracket)
// - oem_6 = ] (right bracket)
// - oem_2 = / (forward slash)
// - oem_1 = ; (semicolon)
//
// For keyboard layout independence, you can use scan codes instead:
// - [BracketLeft] instead of oem_4
// - [BracketRight] instead of oem_6
// - [Slash] instead of oem_2
//
// WHEN CLAUSES:
// The "when" field controls when a binding is active. Common contexts:
// - editorTextFocus: cursor is in an editor
// - vim.active: Vim extension is active
// - vim.mode: current Vim mode (Normal, Insert, Visual)
// - suggestWidgetVisible: autocomplete menu is open
// - inQuickOpen: Quick Open (Ctrl+P) is open
// - terminalFocus: terminal is focused
// - listFocus: a list (explorer, search) has focus
//
// ══════════════════════════════════════════════════════════════════════════════════════
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ WINDOW NAVIGATION - Ctrl+h/j/k/l (LazyVim: <C-h/j/k/l>) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate between editor splits using Vim-style hjkl keys with Ctrl modifier.
// These work in Normal mode and are disabled when:
// - Terminal is focused (so Ctrl+h/j/k/l work normally in terminal)
// - Suggestion widget is visible (so Ctrl+j/k navigate suggestions)
// - Quick Open is active (so Ctrl+j/k navigate file list)
//
{
"key": "ctrl+h",
"command": "workbench.action.focusLeftGroup",
"when": "!terminalFocus && vim.active && vim.mode != 'Insert'"
},
{
"key": "ctrl+l",
"command": "workbench.action.focusRightGroup",
"when": "!terminalFocus && vim.active && vim.mode != 'Insert'"
},
{
"key": "ctrl+j",
"command": "workbench.action.focusBelowGroup",
"when": "!terminalFocus && !suggestWidgetVisible && !inQuickOpen && vim.active && vim.mode != 'Insert'"
},
{
"key": "ctrl+k",
"command": "workbench.action.focusAboveGroup",
"when": "!terminalFocus && !suggestWidgetVisible && !inQuickOpen && vim.active && vim.mode != 'Insert'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ WINDOW RESIZING - Ctrl+Arrow Keys (LazyVim: <C-Up/Down/Left/Right>) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Resize editor splits using Ctrl+Arrow keys. Only active in Normal mode to avoid
// interfering with text editing shortcuts (Ctrl+Left/Right normally jump words).
//
{
"key": "ctrl+up",
"command": "workbench.action.increaseViewHeight",
"when": "vim.active && vim.mode == 'Normal'"
},
{
"key": "ctrl+down",
"command": "workbench.action.decreaseViewHeight",
"when": "vim.active && vim.mode == 'Normal'"
},
{
"key": "ctrl+left",
"command": "workbench.action.decreaseViewWidth",
"when": "vim.active && vim.mode == 'Normal'"
},
{
"key": "ctrl+right",
"command": "workbench.action.increaseViewWidth",
"when": "vim.active && vim.mode == 'Normal'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ BUFFER NAVIGATION - Shift+h/l (LazyVim: <S-h>, <S-l>) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Switch between open editor tabs using Shift+H (previous) and Shift+L (next).
// Only active in Normal/Visual modes to avoid conflicts with text selection.
// Alternative: Use [b and ]b (defined below) for the same functionality.
//
{
"key": "shift+h",
"command": "workbench.action.previousEditor",
"when": "vim.active && vim.mode != 'Insert'"
},
{
"key": "shift+l",
"command": "workbench.action.nextEditor",
"when": "vim.active && vim.mode != 'Insert'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ LINE MOVEMENT - Alt+j/k (LazyVim: <A-j>, <A-k>) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Move lines up/down using Alt+J/K. Works in Normal and Visual modes.
// In Visual mode, moves the entire selection.
//
{
"key": "alt+j",
"command": "editor.action.moveLinesDownAction",
"when": "editorTextFocus && !editorReadonly"
},
{
"key": "alt+k",
"command": "editor.action.moveLinesUpAction",
"when": "editorTextFocus && !editorReadonly"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ SAVE FILE - Ctrl+s (LazyVim: <C-s>) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Standard save shortcut. Works in all modes.
//
{
"key": "ctrl+s",
"command": "workbench.action.files.save",
"when": "editorTextFocus"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ TERMINAL TOGGLE - Ctrl+/ (LazyVim: <C-/>) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Toggle terminal panel visibility using Ctrl+/.
// - If terminal is hidden: opens and focuses it
// - If terminal is visible and focused: closes it
// - If terminal is visible but not focused: focuses it
//
// Note: oem_2 is the / (forward slash) key
//
{
"key": "ctrl+oem_2",
"command": "workbench.action.terminal.toggleTerminal"
},
// Alternative terminal toggle that focuses terminal if not focused
{
"key": "ctrl+oem_2",
"command": "workbench.action.terminal.focus",
"when": "!terminalFocus"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ TERMINAL NAVIGATION - Ctrl+; (bidirectional toggle) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Use Ctrl+; to jump between editor and terminal without closing it:
// - When in editor: jumps to terminal (opens if closed)
// - When in terminal: jumps back to editor
//
{
"key": "ctrl+;",
"command": "workbench.action.terminal.focus",
"when": "!terminalFocus"
},
{
"key": "ctrl+;",
"command": "workbench.action.focusActiveEditorGroup",
"when": "terminalFocus"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ SUGGESTION WIDGET NAVIGATION - Ctrl+j/k when autocomplete is visible │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate autocomplete suggestions using Vim-style j/k keys.
// These override the window navigation when suggestion widget is visible.
//
// Note: Ctrl+D / Ctrl+U for page-scrolling suggestions are intentionally NOT included.
// vim.handleKeys sets <C-d>: true and <C-u>: true, meaning vim intercepts those keys
// even when the suggestion widget is open. Ctrl+N / Ctrl+P (vim insert defaults) are
// the reliable alternatives for cycling through suggestions one at a time.
//
{
"key": "ctrl+j",
"command": "selectNextSuggestion",
"when": "suggestWidgetVisible"
},
{
"key": "ctrl+k",
"command": "selectPrevSuggestion",
"when": "suggestWidgetVisible"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ QUICK OPEN NAVIGATION - Ctrl+j/k when file picker is open │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate Quick Open (Ctrl+P) file list using Vim-style j/k keys.
//
{
"key": "ctrl+j",
"command": "workbench.action.quickOpenSelectNext",
"when": "inQuickOpen"
},
{
"key": "ctrl+k",
"command": "workbench.action.quickOpenSelectPrevious",
"when": "inQuickOpen"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ DIAGNOSTICS NAVIGATION - [d, ]d (all files) and [e, ]e (current file) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim diagnostic navigation using bracket prefix:
// - [d / ]d = previous/next diagnostic across all files
// - [e / ]e = previous/next diagnostic in current file only
//
// SPECIAL KEY CODES:
// - oem_4 = [ (left bracket)
// - oem_6 = ] (right bracket)
//
// On non-US keyboards, you may need to use scan codes instead:
// - "key": "[BracketLeft] d" instead of "key": "oem_4 d"
//
// Only active in Normal mode to prevent conflicts when typing brackets in Insert mode.
//
{
"key": "oem_4 d", // [d = previous diagnostic (all files)
"command": "editor.action.marker.prevInFiles",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
{
"key": "oem_6 d", // ]d = next diagnostic (all files)
"command": "editor.action.marker.nextInFiles",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
{
"key": "oem_4 e", // [e = previous diagnostic (current file)
"command": "editor.action.marker.prev",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
{
"key": "oem_6 e", // ]e = next diagnostic (current file)
"command": "editor.action.marker.next",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ QUICKFIX/SEARCH RESULTS NAVIGATION - [q, ]q │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate search results using bracket prefix.
// Only active when search results exist and in Normal mode.
//
{
"key": "oem_4 q", // [q = previous search result
"command": "search.action.focusPreviousSearchResult",
"when": "hasSearchResult && vim.active && vim.mode == 'Normal'"
},
{
"key": "oem_6 q", // ]q = next search result
"command": "search.action.focusNextSearchResult",
"when": "hasSearchResult && vim.active && vim.mode == 'Normal'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ BUFFER NAVIGATION - [b, ]b (alternate method) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Alternative buffer navigation using bracket prefix (same as Shift+H/L).
// Some users prefer this LazyVim pattern for consistency with other bracket navigation.
//
{
"key": "oem_4 b", // [b = previous buffer/editor
"command": "workbench.action.previousEditor",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
{
"key": "oem_6 b", // ]b = next buffer/editor
"command": "workbench.action.nextEditor",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ GIT HUNK NAVIGATION - [h, ]h (requires Git or GitLens) │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate between git changes/hunks in the current file.
// Works with native VSCode git or GitLens extension.
//
{
"key": "oem_4 h", // [h = previous git change
"command": "workbench.action.editor.previousChange",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
{
"key": "oem_6 h", // ]h = next git change
"command": "workbench.action.editor.nextChange",
"when": "editorTextFocus && vim.active && vim.mode == 'Normal'"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ FILE EXPLORER NAVIGATION - Ctrl+h/j/k/l when explorer is focused │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate file explorer using Vim keys when it has focus:
// - j/k = down/up
// - h = collapse folder or go to parent
// - l = expand folder or open file
//
// IMPORTANT: These must come AFTER the window navigation bindings above.
// VSCode processes keybindings from bottom to top, so more specific contexts
// (explorerViewletVisible) override more general ones (!terminalFocus).
//
{
"key": "ctrl+j",
"command": "list.focusDown",
"when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
},
{
"key": "ctrl+k",
"command": "list.focusUp",
"when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
},
{
"key": "ctrl+h",
"command": "list.collapse",
"when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
},
{
"key": "ctrl+l",
"command": "list.select",
"when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
},
// Alternative: Use j/k without Ctrl for explorer navigation (more Vim-like)
// Uncomment these if you prefer plain j/k in the explorer:
// {
// "key": "j",
// "command": "list.focusDown",
// "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
// },
// {
// "key": "k",
// "command": "list.focusUp",
// "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
// },
// {
// "key": "h",
// "command": "list.collapse",
// "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
// },
// {
// "key": "l",
// "command": "list.select",
// "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
// },
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ SEARCH/PROBLEMS PANEL NAVIGATION - j/k when panel has focus │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Navigate lists in search results and problems panel using j/k.
//
{
"key": "j",
"command": "list.focusDown",
"when": "listFocus && !inputFocus"
},
{
"key": "k",
"command": "list.focusUp",
"when": "listFocus && !inputFocus"
},
// Navigate in panels with Ctrl+j/k as alternative
{
"key": "ctrl+j",
"command": "list.focusDown",
"when": "panelFocus && !terminalFocus"
},
{
"key": "ctrl+k",
"command": "list.focusUp",
"when": "panelFocus && !terminalFocus"
},
// ┌────────────────────────────────────────────────────────────────────────────────────┐
// │ FOCUS EDITOR GROUPS - Ctrl+1/2/3 │
// └────────────────────────────────────────────────────────────────────────────────────┘
//
// Quickly jump to specific editor groups/splits.
// Useful when you have multiple splits and want to jump directly to one.
//
{
"key": "ctrl+1",
"command": "workbench.action.focusFirstEditorGroup"
},
{
"key": "ctrl+2",
"command": "workbench.action.focusSecondEditorGroup"
},
{
"key": "ctrl+3",
"command": "workbench.action.focusThirdEditorGroup"
},
{
"key": "ctrl+4",
"command": "workbench.action.focusFourthEditorGroup"
},
{
"key": "ctrl+5",
"command": "workbench.action.focusFifthEditorGroup"
}
// ══════════════════════════════════════════════════════════════════════════════════════
// TROUBLESHOOTING NOTES
// ══════════════════════════════════════════════════════════════════════════════════════
//
// BRACKET KEYS NOT WORKING ([d, ]d, etc.):
//
// 1. UK/Non-US Keyboard Layouts:
// If you're on a non-US keyboard, the oem_4 and oem_6 codes might not work.
// Replace them with scan codes:
// - "key": "[BracketLeft] d" instead of "key": "oem_4 d"
// - "key": "[BracketRight] d" instead of "key": "oem_6 d"
//
// 2. MacOS:
// Brackets might require different key codes. Try:
// - "key": "[BracketLeft] d"
// - "key": "[BracketRight] d"
//
// 3. Testing bracket keys:
// Open VSCode Keyboard Shortcuts UI (Ctrl+K Ctrl+S / Cmd+K Cmd+S)
// Try to record your bracket key to see what VSCode detects it as
//
// CONFLICTS WITH OTHER EXTENSIONS:
//
// If keybindings don't work, check for conflicts:
// 1. Open Keyboard Shortcuts UI (Ctrl+K Ctrl+S / Cmd+K Cmd+S)
// 2. Search for the key combination (e.g., "ctrl+h")
// 3. Look for duplicate bindings from other extensions
// 4. Either disable the conflicting extension or modify your binding
//
// WHEN CLAUSES NOT WORKING:
//
// To debug when clauses:
// 1. Open Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
// 2. Run "Developer: Inspect Context Keys"
// 3. This shows all active context keys for debugging
//
// VALIDATION CHECKLIST:
// □ Ctrl+h/j/k/l navigates between splits
// □ Shift+h/l switches buffers
// □ [d / ]d navigates diagnostics
// □ [b / ]b switches buffers
// □ Ctrl+j/k navigates suggestions when autocomplete is open
// □ Ctrl+j/k navigates file list when Quick Open is active
// □ Alt+j/k moves lines up/down
// □ Ctrl+/ toggles terminal
//
// ══════════════════════════════════════════════════════════════════════════════════════
]
{
// ╔══════════════════════════════════════════════════════════════════════════════════╗
// ║ VSCODE VIM LAZYVIM CONFIGURATION ║
// ║ Complete Settings for LazyVim Alignment ║
// ╚══════════════════════════════════════════════════════════════════════════════════╝
//
// This configuration provides LazyVim-style keybindings in VSCode using the Vim extension.
//
// REQUIRED EXTENSIONS:
// - vscodevim.vim (VSCode Vim) - Core requirement
//
// RECOMMENDED EXTENSIONS:
// - eamodio.gitlens (GitLens) - For git operations like blame, history
// - hoovercj.vscode-settings-cycler (Settings Cycler) - For line number toggling
// - VSpaceCode.whichkey (Which Key) - For keybinding discovery menus
// - usernamehw.errorlens (Error Lens) - Inline diagnostics display
//
// PERFORMANCE OPTIMIZATION:
// The extensions.experimental.affinity setting (at bottom of file) runs Vim extension
// in a separate thread to prevent typing lag. This is CRITICAL for performance.
//
// FILE ORGANIZATION:
// This file contains ALL space-leader keybindings (<leader>...). Standard VSCode keybindings
// that don't use the leader key go in keybindings.json to prevent conflicts.
//
// ══════════════════════════════════════════════════════════════════════════════════════
// ──────────────────────────────────────────────────────────────────────────────────────
// EDITOR SETTINGS - Visual appearance and behaviour
// ──────────────────────────────────────────────────────────────────────────────────────
"editor.lineNumbers": "relative", // Relative line numbers (Vim-style)
"editor.scrollBeyondLastLine": false, // Don't scroll past end of file
"editor.cursorSurroundingLines": 8, // Keep 8 lines above/below cursor (matches LazyVim scrolloff)
"editor.smoothScrolling": true, // Smooth scrolling animations
"editor.bracketPairColorization.enabled": true, // Colour matching brackets
"editor.guides.bracketPairs": true, // Show bracket pair guides
"editor.minimap.enabled": false, // Minimap disabled (unreliable, re-enable if needed)
"editor.minimap.renderCharacters": false, // Simplified minimap
"editor.minimap.scale": 2, // Larger minimap scale
"editor.cursorBlinking": "solid", // Solid cursor (Vim-like)
"editor.formatOnSave": false, // Format on save (set to true if desired)
// ──────────────────────────────────────────────────────────────────────────────────────
// VIM EXTENSION CORE SETTINGS - Vim behaviour and plugin emulation
// ──────────────────────────────────────────────────────────────────────────────────────
"vim.leader": "<space>", // Space as leader key (LazyVim standard)
"vim.useSystemClipboard": true, // Use system clipboard for yank/paste
"vim.useCtrlKeys": true, // Enable Vim Ctrl key bindings
"vim.smartRelativeLine": true, // Absolute line numbers in insert mode
// "vim.timeout": 600, // Wait 600ms for multi-key mappings after a prefix key
// Vim plugin emulations (built into VSCode Vim extension)
"vim.easymotion": true, // Enable EasyMotion (use <leader><leader>w etc)
"vim.sneak": true, // Enable vim-sneak (use s/S for 2-char search)
"vim.surround": true, // Enable vim-surround (ys/ds/cs commands)
"vim.foldfix": true, // Better folding support
// Search settings
"vim.incsearch": true, // Incremental search (show matches as you type)
"vim.hlsearch": true, // Highlight search results
"vim.searchHighlightColor": "rgba(180, 142, 173, 0.5)", // Search highlight colour
// Visual feedback
"vim.highlightedyank.enable": true, // Briefly highlight yanked text
"vim.highlightedyank.duration": 200, // Highlight duration in milliseconds
// ──────────────────────────────────────────────────────────────────────────────────────
// STATUS BAR COLORS - Visual mode indicators (LazyVim-inspired colours)
// ──────────────────────────────────────────────────────────────────────────────────────
"vim.statusBarColorControl": true, // Enable status bar colour changes
"vim.statusBarColors.normal": "#519aba", // Blue for normal mode
"vim.statusBarColors.insert": "#98c379", // Green for insert mode
"vim.statusBarColors.visual": "#c678dd", // Purple for visual mode
"vim.statusBarColors.visualline": "#c678dd", // Purple for visual line mode
"vim.statusBarColors.visualblock": "#c678dd", // Purple for visual block mode
"vim.statusBarColors.replace": "#e06c75", // Red for replace mode
// ──────────────────────────────────────────────────────────────────────────────────────
// HANDLE KEYS - Which keys are processed by Vim vs VSCode
// ──────────────────────────────────────────────────────────────────────────────────────
//
// IMPORTANT: This section determines whether a key combination is handled by the Vim
// extension (true) or passed to VSCode (false). This prevents conflicts between Vim
// commands and VSCode shortcuts.
//
// - true = Vim extension handles the key (e.g., <C-d> for page down in Vim)
// - false = VSCode handles the key (e.g., <C-f> for native find dialogue)
//
"vim.handleKeys": {
"<C-d>": true, // Vim page down (half-page scroll)
"<C-u>": true, // Vim page up (half-page scroll)
"<C-f>": false, // VSCode find (don't override with Vim's full page down)
"<C-b>": false, // VSCode sidebar toggle (don't override with Vim's page up)
"<C-h>": false, // VSCode window navigation (defined in keybindings.json)
"<C-j>": false, // VSCode window navigation (defined in keybindings.json)
"<C-k>": false, // VSCode window navigation (defined in keybindings.json)
"<C-l>": false, // VSCode window navigation (defined in keybindings.json)
"<C-w>": false, // VSCode close tab (don't override Vim's window commands)
"<C-s>": false, // VSCode save file (standard shortcut)
"<C-a>": false, // VSCode select all (standard shortcut)
"<C-c>": false, // VSCode copy (standard shortcut)
"<C-v>": false, // VSCode paste (standard shortcut)
"<C-z>": false // VSCode undo (standard shortcut)
},
// ──────────────────────────────────────────────────────────────────────────────────────
// INSERT MODE KEYBINDINGS - Escape alternatives
// ──────────────────────────────────────────────────────────────────────────────────────
"vim.insertModeKeyBindings": [
{
"before": ["j", "k"], // jk to escape (LazyVim default)
"after": ["<Esc>"]
},
{
"before": ["j", "j"], // jj to escape (alternative)
"after": ["<Esc>"]
}
],
// ──────────────────────────────────────────────────────────────────────────────────────
// NORMAL MODE KEYBINDINGS - Space-leader mappings (LazyVim alignment)
// ──────────────────────────────────────────────────────────────────────────────────────
//
// CRITICAL: All space-leader bindings (<leader>...) MUST be defined here in settings.json,
// NOT in keybindings.json. The Vim extension needs to intercept these before VSCode sees them.
//
// Pattern reference (matches LazyVim):
// <leader>f* = File operations
// <leader>s* = Search operations
// <leader>c* = Code actions (LSP)
// <leader>b* = Buffer management
// <leader>g* = Git operations
// <leader>w* = Window management
// <leader>x* = Diagnostics/Quickfix
// <leader>u* = UI toggles
//
"vim.normalModeKeyBindingsNonRecursive": [
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ WHICH KEY - Show popup menu on ~ (requires VSpaceCode.whichkey) │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// NOTE: <space> was the original trigger but conflicts with vim.leader = "<space>" —
// VSCodeVim resolves the leader prefix first, so whichkey.show never fires.
// <Tab> was tried next but had inconsistencies. ~ triggers reliably in both
// Normal and Visual mode with no leader conflict.
//
// To revert: change "~" back to "<Tab>" or "<space>" in both normalModeKeyBindingsNonRecursive
// and visualModeKeyBindingsNonRecursive below.
//
{
"before": ["~"], // ~ = show which-key menu
"commands": ["whichkey.show"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ EASYMOTION JUMP - <leader>j* prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// EasyMotion normally uses <leader><leader>* but that conflicts with which-key
// (pressing <space> shows the menu, so <space><space> becomes <space> in the menu
// rather than the EasyMotion double-leader trigger).
//
// Solution: wrap EasyMotion under <leader>j (j = jump) so it is accessible both
// directly and via the which-key +Jump submenu.
//
{
"before": ["<leader>", "j", "w"], // <leader>jw = EasyMotion word forward
"after": ["<leader>", "<leader>", "w"]
},
{
"before": ["<leader>", "j", "b"], // <leader>jb = EasyMotion word backward
"after": ["<leader>", "<leader>", "b"]
},
{
"before": ["<leader>", "j", "j"], // <leader>jj = EasyMotion line down
"after": ["<leader>", "<leader>", "j"]
},
{
"before": ["<leader>", "j", "k"], // <leader>jk = EasyMotion line up
"after": ["<leader>", "<leader>", "k"]
},
{
"before": ["<leader>", "j", "s"], // <leader>js = EasyMotion char search
"after": ["<leader>", "<leader>", "s"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ GENERAL UTILITY MAPPINGS │
// └────────────────────────────────────────────────────────────────────────────────┘
{
"before": ["<leader>", "d"], // <leader>d = delete line (dd shortcut)
"after": ["d", "d"]
},
{
"before": ["<Esc>"], // Escape clears search highlight
"commands": [":nohl"]
},
{
"before": ["<C-n>"], // Ctrl+n also clears search highlight
"commands": [":nohl"]
},
{
"before": ["K"], // K = show hover documentation (LSP)
"commands": ["editor.action.showHover"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ FILE OPERATIONS - <leader>f* prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim file operations pattern for finding, creating, and managing files
//
{
"before": ["<leader>", "<space>"], // <leader><space> = find files (Quick Open)
"commands": ["workbench.action.quickOpen"]
},
{
"before": ["<leader>", ","], // <leader>, = switch buffer (Quick Open Editors)
"commands": ["workbench.action.showAllEditors"]
},
{
"before": ["<leader>", "/"], // <leader>/ = search in files (Live Grep equivalent)
"commands": ["workbench.action.findInFiles"]
},
{
"before": ["<leader>", "f", "f"], // <leader>ff = find files (same as <leader><space>)
"commands": ["workbench.action.quickOpen"]
},
{
"before": ["<leader>", "f", "r"], // <leader>fr = recent files
"commands": ["workbench.action.openRecent"]
},
{
"before": ["<leader>", "f", "n"], // <leader>fn = new file
"commands": ["workbench.action.files.newUntitledFile"]
},
{
"before": ["<leader>", "f", "b"], // <leader>fb = buffers (open editors)
"commands": ["workbench.action.showAllEditors"]
},
{
"before": ["<leader>", "f", "e"], // <leader>fe = file explorer (reveal active file)
"commands": ["workbench.files.action.showActiveFileInExplorer"]
},
{
"before": ["<leader>", "f", "t"], // <leader>ft = toggle terminal
"commands": ["workbench.action.terminal.toggleTerminal"]
},
{
"before": ["<leader>", "e"], // <leader>e = toggle sidebar
"commands": ["workbench.action.toggleSidebarVisibility"]
},
{
"before": ["<leader>", "E"], // <leader>E = focus file explorer
"commands": ["workbench.files.action.focusFilesExplorer"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ SEARCH OPERATIONS - <leader>s* prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim search operations for finding text, symbols, and configuration
//
{
"before": ["<leader>", "s", "g"], // <leader>sg = grep/search in files
"commands": ["workbench.action.findInFiles"]
},
{
"before": ["<leader>", "s", "w"], // <leader>sw = search word under cursor
"commands": ["workbench.action.findInFiles"]
},
{
"before": ["<leader>", "s", "r"], // <leader>sr = search and replace in files
"commands": ["workbench.action.replaceInFiles"]
},
{
"before": ["<leader>", "s", "s"], // <leader>ss = goto symbol in file
"commands": ["workbench.action.gotoSymbol"]
},
{
"before": ["<leader>", "s", "S"], // <leader>sS = goto symbol in workspace
"commands": ["workbench.action.showAllSymbols"]
},
{
"before": ["<leader>", "s", "k"], // <leader>sk = keymaps (open keyboard shortcuts)
"commands": ["workbench.action.openGlobalKeybindings"]
},
{
"before": ["<leader>", "s", "c"], // <leader>sc = command palette
"commands": ["workbench.action.showCommands"]
},
{
"before": ["<leader>", "s", "h"], // <leader>sh = help (all commands)
"commands": ["workbench.action.showAllCommands"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ CODE ACTIONS - <leader>c* prefix (LSP operations) │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim code actions for LSP features like formatting, refactoring, diagnostics
//
{
"before": ["<leader>", "c", "a"], // <leader>ca = code action (quick fix)
"commands": ["editor.action.quickFix"]
},
{
"before": ["<leader>", "c", "A"], // <leader>cA = source action
"commands": ["editor.action.sourceAction"]
},
{
"before": ["<leader>", "c", "f"], // <leader>cf = format document
"commands": ["editor.action.formatDocument"]
},
{
"before": ["<leader>", "c", "F"], // <leader>cF = format selection
"commands": ["editor.action.formatSelection"]
},
{
"before": ["<leader>", "c", "r"], // <leader>cr = rename symbol
"commands": ["editor.action.rename"]
},
{
"before": ["<leader>", "c", "d"], // <leader>cd = line diagnostics (hover)
"commands": ["editor.action.showHover"]
},
{
"before": ["<leader>", "c", "l"], // <leader>cl = open problems panel
"commands": ["workbench.action.problems.focus"]
},
{
"before": ["<leader>", "c", "o"], // <leader>co = organise imports
"commands": ["editor.action.organizeImports"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ LSP NAVIGATION - g* prefix (goto definitions, references, etc.) │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// Standard Vim LSP navigation bindings (matches LazyVim defaults)
//
{
"before": ["g", "d"], // gd = goto definition
"commands": ["editor.action.revealDefinition"]
},
{
"before": ["g", "D"], // gD = goto declaration
"commands": ["editor.action.revealDeclaration"]
},
{
"before": ["g", "r"], // gr = goto references
"commands": ["editor.action.goToReferences"]
},
{
"before": ["g", "I"], // gI = goto implementation
"commands": ["editor.action.goToImplementation"]
},
{
"before": ["g", "y"], // gy = goto type definition
"commands": ["editor.action.goToTypeDefinition"]
},
{
"before": ["g", "K"], // gK = signature help (parameter hints)
"commands": ["editor.action.triggerParameterHints"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ BUFFER MANAGEMENT - <leader>b* prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim buffer operations for managing open files/tabs
//
{
"before": ["<leader>", "b", "b"], // <leader>bb = switch buffer (show all editors)
"commands": ["workbench.action.showAllEditors"]
},
{
"before": ["<leader>", "b", "d"], // <leader>bd = delete buffer (close tab)
"commands": ["workbench.action.closeActiveEditor"]
},
{
"before": ["<leader>", "b", "D"], // <leader>bD = delete other buffers
"commands": ["workbench.action.closeOtherEditors"]
},
{
"before": ["<leader>", "b", "o"], // <leader>bo = close other editors (same as bD)
"commands": ["workbench.action.closeOtherEditors"]
},
{
"before": ["<leader>", "b", "p"], // <leader>bp = pin buffer (pin tab)
"commands": ["workbench.action.pinEditor"]
},
{
"before": ["<leader>", "b", "P"], // <leader>bP = unpin buffer
"commands": ["workbench.action.unpinEditor"]
},
{
"before": ["<leader>", "`"], // <leader>` = last buffer (alternate file)
"commands": ["workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup"]
},
// Note: Shift+H and Shift+L (buffer prev/next) are handled in keybindings.json.
// User keybindings.json takes priority over VSCodeVim's type handler, so defining
// <S-h>/<S-l> here would be dead code — keybindings.json wins.
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ GIT OPERATIONS - <leader>g* prefix (requires GitLens extension) │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim git operations. Most features require GitLens extension for full functionality.
// Without GitLens, basic git commands (gg, gs, gd) still work with native VSCode git.
//
{
"before": ["<leader>", "g", "g"], // <leader>gg = git status (open source control)
"commands": ["workbench.view.scm"]
},
{
"before": ["<leader>", "g", "s"], // <leader>gs = git status (same as gg)
"commands": ["workbench.view.scm"]
},
{
"before": ["<leader>", "g", "b"], // <leader>gb = git blame toggle (GitLens)
"commands": ["gitlens.toggleFileBlame"]
},
{
"before": ["<leader>", "g", "B"], // <leader>gB = git browse (open on remote - GitLens)
"commands": ["gitlens.openFileOnRemote"]
},
{
"before": ["<leader>", "g", "d"], // <leader>gd = git diff (show changes)
"commands": ["git.openChange"]
},
{
"before": ["<leader>", "g", "l"], // <leader>gl = git log (file history - GitLens)
"commands": ["gitlens.showQuickFileHistory"]
},
{
"before": ["<leader>", "g", "L"], // <leader>gL = git log (branch history - GitLens)
"commands": ["gitlens.showQuickRepoHistory"]
},
{
"before": ["<leader>", "g", "c"], // <leader>gc = git commits (show commit details - GitLens)
"commands": ["gitlens.showQuickCommitDetails"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ WINDOW MANAGEMENT - <leader>w* and <leader>-/| prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim window/split operations
//
{
"before": ["<leader>", "w", "d"], // <leader>wd = close window (close split/editor)
"commands": ["workbench.action.closeActiveEditor"]
},
{
"before": ["<leader>", "w", "w"], // <leader>ww = switch window (focus other group)
"commands": ["workbench.action.focusNextGroup"]
},
{
"before": ["<leader>", "w", "m"], // <leader>wm = maximise window (toggle editor width)
"commands": ["workbench.action.toggleEditorWidths"]
},
{
"before": ["<leader>", "-"], // <leader>- = split window below
"commands": ["workbench.action.splitEditorDown"]
},
{
"before": ["<leader>", "|"], // <leader>| = split window right
"commands": ["workbench.action.splitEditorRight"]
},
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ DIAGNOSTICS/QUICKFIX - <leader>x* prefix and [ ] navigation │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim diagnostics and error navigation. Bracket prefix mappings ([d, ]d) are
// defined in keybindings.json because they use special key codes (oem_4, oem_6).
//
{
"before": ["<leader>", "x", "x"], // <leader>xx = diagnostics list (problems panel)
"commands": ["workbench.action.problems.focus"]
},
{
"before": ["<leader>", "x", "X"], // <leader>xX = workspace diagnostics
"commands": ["workbench.actions.view.problems"]
},
{
"before": ["<leader>", "x", "l"], // <leader>xl = location list (same as xx)
"commands": ["workbench.action.problems.focus"]
},
// Note: [d, ]d, [e, ]e navigation defined in keybindings.json
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ UI TOGGLES - <leader>u* prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
//
// LazyVim UI toggle operations. Line number toggling requires Settings Cycler extension.
// Install: hoovercj.vscode-settings-cycler
// Configure settings.cycle in this file (see bottom section).
//
{
"before": ["<leader>", "u", "w"], // <leader>uw = toggle word wrap
"commands": ["editor.action.toggleWordWrap"]
},
{
"before": ["<leader>", "u", "z"], // <leader>uz = toggle zen mode
"commands": ["workbench.action.toggleZenMode"]
},
{
"before": ["<leader>", "u", "r"], // <leader>ur = redraw/clear search
"commands": [":nohl"]
},
// Requires Settings Cycler extension - uncomment after installing:
// {
// "before": ["<leader>", "u", "l"], // <leader>ul = toggle line numbers
// "commands": ["settings.cycle.lineNumbers"]
// },
// {
// "before": ["<leader>", "u", "L"], // <leader>uL = toggle relative numbers
// "commands": ["settings.cycle.relativeLineNumbers"]
// },
// ┌────────────────────────────────────────────────────────────────────────────────┐
// │ QUIT - <leader>q* prefix │
// └────────────────────────────────────────────────────────────────────────────────┘
{
"before": ["<leader>", "q", "q"], // <leader>qq = quit VSCode
"commands": ["workbench.action.closeWindow"]
},
{
"before": ["<leader>", "q", "a"], // <leader>qa = quit all windows
"commands": ["workbench.action.quit"]
}
],
// ──────────────────────────────────────────────────────────────────────────────────────
// VISUAL MODE KEYBINDINGS - Selection operations
// ──────────────────────────────────────────────────────────────────────────────────────
//
// Visual mode enhancements for better selection handling and LazyVim parity
//
"vim.visualModeKeyBindingsNonRecursive": [
// ~ trigger not working reliably in visual mode — disabled for now, re-enable when fixed:
// { "before": ["~"], "commands": ["whichkey.show"] },
{
"before": ["<leader>", "/"], // <leader>/ = search selected text
"commands": ["workbench.action.findInFiles"]
},
{
"before": ["<leader>", "c", "a"], // <leader>ca = code action on selection
"commands": ["editor.action.quickFix"]
},
{
"before": ["<leader>", "c", "f"], // <leader>cf = format selection
"commands": ["editor.action.formatSelection"]
},
{
"before": ["g", "c"], // gc = comment toggle
"commands": ["editor.action.commentLine"]
}
],
// ──────────────────────────────────────────────────────────────────────────────────────
// WHICH KEY BINDINGS (requires VSpaceCode.whichkey extension)
// ──────────────────────────────────────────────────────────────────────────────────────
//
// This defines the popup menu tree that appears when you press <space> in Normal/Visual
// mode. Each entry mirrors an existing <leader>* vim binding above.
//
// Structure:
// type "command" = executes a single VS Code command directly
// type "bindings" = opens a sub-menu with nested entries
//
// Install: VSpaceCode.whichkey (from VS Code Marketplace)
//
"whichkey.bindings": [
// ── Top-level direct bindings ──────────────────────────────────────────────────────
//
// Note: <space><space> is intentionally NOT bound here. Leaving it unbound means
// double-space closes the which-key menu, preserving EasyMotion's <leader><leader>
// trigger for users who invoke it without which-key (or via <leader>j* wrappers).
// Use <leader>ff or <leader>f f for Find Files instead.
//
{ "key": ",", "name": "Switch Buffer", "type": "command", "command": "workbench.action.showAllEditors" },
{ "key": "/", "name": "Search in Files", "type": "command", "command": "workbench.action.findInFiles" },
{ "key": "`", "name": "Last Buffer", "type": "command", "command": "workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup" },
{ "key": "-", "name": "Split Below", "type": "command", "command": "workbench.action.splitEditorDown" },
{ "key": "|", "name": "Split Right", "type": "command", "command": "workbench.action.splitEditorRight" },
{ "key": "d", "name": "Delete Line", "type": "command", "command": "vim.remap", "args": { "after": ["d","d"] } },
// ↑ Uses vim's dd (not editor.action.deleteLines) so the line goes into the unnamed
// register and remains pasteable with p. editor.action.deleteLines would discard it.
{ "key": "e", "name": "Toggle Sidebar", "type": "command", "command": "workbench.action.toggleSidebarVisibility" },
{ "key": "E", "name": "Focus Explorer", "type": "command", "command": "workbench.files.action.focusFilesExplorer" },
{ "key": "K", "name": "Show Hover", "type": "command", "command": "editor.action.showHover" },
// ── f = File ───────────────────────────────────────────────────────────────────────
{
"key": "f",
"name": "+File",
"type": "bindings",
"bindings": [
{ "key": "f", "name": "Find Files", "type": "command", "command": "workbench.action.quickOpen" },
{ "key": "r", "name": "Recent Files", "type": "command", "command": "workbench.action.openRecent" },
{ "key": "n", "name": "New File", "type": "command", "command": "workbench.action.files.newUntitledFile" },
{ "key": "b", "name": "Buffers", "type": "command", "command": "workbench.action.showAllEditors" },
{ "key": "e", "name": "Show in Explorer", "type": "command", "command": "workbench.files.action.showActiveFileInExplorer" },
{ "key": "t", "name": "Toggle Terminal", "type": "command", "command": "workbench.action.terminal.toggleTerminal" }
]
},
// ── s = Search ─────────────────────────────────────────────────────────────────────
{
"key": "s",
"name": "+Search",
"type": "bindings",
"bindings": [
{ "key": "g", "name": "Grep in Files", "type": "command", "command": "workbench.action.findInFiles" },
{ "key": "w", "name": "Search Word", "type": "command", "command": "workbench.action.findInFiles" },
{ "key": "r", "name": "Replace in Files", "type": "command", "command": "workbench.action.replaceInFiles" },
{ "key": "s", "name": "Symbol in File", "type": "command", "command": "workbench.action.gotoSymbol" },
{ "key": "S", "name": "Symbol in Workspace", "type": "command", "command": "workbench.action.showAllSymbols" },
{ "key": "k", "name": "Keymaps", "type": "command", "command": "workbench.action.openGlobalKeybindings" },
{ "key": "c", "name": "Command Palette", "type": "command", "command": "workbench.action.showCommands" },
{ "key": "h", "name": "Help / All Commands", "type": "command", "command": "workbench.action.showAllCommands" }
]
},
// ── c = Code (LSP) ─────────────────────────────────────────────────────────────────
{
"key": "c",
"name": "+Code",
"type": "bindings",
"bindings": [
{ "key": "a", "name": "Code Action", "type": "command", "command": "editor.action.quickFix" },
{ "key": "A", "name": "Source Action", "type": "command", "command": "editor.action.sourceAction" },
{ "key": "f", "name": "Format Document", "type": "command", "command": "editor.action.formatDocument" },
{ "key": "F", "name": "Format Selection", "type": "command", "command": "editor.action.formatSelection" },
{ "key": "r", "name": "Rename Symbol", "type": "command", "command": "editor.action.rename" },
{ "key": "d", "name": "Line Diagnostics", "type": "command", "command": "editor.action.showHover" },
{ "key": "l", "name": "Problems Panel", "type": "command", "command": "workbench.action.problems.focus" },
{ "key": "o", "name": "Organise Imports", "type": "command", "command": "editor.action.organizeImports" },
{ "key": "c", "name": "Toggle Comment", "type": "command", "command": "editor.action.commentLine" }
// ↑ gc in visual mode; also works on current line in normal mode
]
},
// ── b = Buffer ─────────────────────────────────────────────────────────────────────
{
"key": "b",
"name": "+Buffer",
"type": "bindings",
"bindings": [
{ "key": "b", "name": "Switch Buffer", "type": "command", "command": "workbench.action.showAllEditors" },
{ "key": "n", "name": "Next Buffer [Shift+L]","type": "command", "command": "workbench.action.nextEditor" },
{ "key": "p", "name": "Prev Buffer [Shift+H]","type": "command", "command": "workbench.action.previousEditor" },
{ "key": "d", "name": "Close Buffer", "type": "command", "command": "workbench.action.closeActiveEditor" },
{ "key": "D", "name": "Close Other Buffers", "type": "command", "command": "workbench.action.closeOtherEditors" },
{ "key": "o", "name": "Close Others", "type": "command", "command": "workbench.action.closeOtherEditors" },
{ "key": "P", "name": "Pin Buffer", "type": "command", "command": "workbench.action.pinEditor" },
{ "key": "U", "name": "Unpin Buffer", "type": "command", "command": "workbench.action.unpinEditor" }
]
},
// ── g = Git (requires GitLens) ─────────────────────────────────────────────────────
{
"key": "g",
"name": "+Git",
"type": "bindings",
"bindings": [
{ "key": "g", "name": "Git Status", "type": "command", "command": "workbench.view.scm" },
{ "key": "s", "name": "Git Status", "type": "command", "command": "workbench.view.scm" },
{ "key": "b", "name": "Blame Toggle", "type": "command", "command": "gitlens.toggleFileBlame" },
{ "key": "B", "name": "Browse on Remote", "type": "command", "command": "gitlens.openFileOnRemote" },
{ "key": "d", "name": "Git Diff", "type": "command", "command": "git.openChange" },
{ "key": "l", "name": "File History", "type": "command", "command": "gitlens.showQuickFileHistory" },
{ "key": "L", "name": "Branch History", "type": "command", "command": "gitlens.showQuickRepoHistory" },
{ "key": "c", "name": "Commit Details", "type": "command", "command": "gitlens.showQuickCommitDetails" }
]
},
// ── G = Go To / LSP Navigation ────────────────────────────────────────────────────
//
// These mirror the g* bindings in normalModeKeyBindingsNonRecursive.
// Pressing g directly is always faster; this submenu exists purely so you can
// browse the available go-to commands via which-key — and execute them if you
// forget the direct key. <space>Gd does exactly the same as gd.
//
{
"key": "G",
"name": "+Go To (LSP nav)",
"type": "bindings",
"bindings": [
{ "key": "d", "name": "Definition", "type": "command", "command": "editor.action.revealDefinition" },
{ "key": "D", "name": "Declaration", "type": "command", "command": "editor.action.revealDeclaration" },
{ "key": "r", "name": "References", "type": "command", "command": "editor.action.goToReferences" },
{ "key": "I", "name": "Implementation", "type": "command", "command": "editor.action.goToImplementation" },
{ "key": "y", "name": "Type Definition", "type": "command", "command": "editor.action.goToTypeDefinition" },
{ "key": "K", "name": "Signature Help", "type": "command", "command": "editor.action.triggerParameterHints" },
{ "key": "h", "name": "Hover Docs [K]", "type": "command", "command": "editor.action.showHover" }
// ↑ key labels show the direct shortcut so the menu doubles as a cheatsheet
]
},
// ── j = Jump (EasyMotion) ─────────────────────────────────────────────────────────
//
// EasyMotion is triggered via <leader>j* wrappers (defined in normalModeKeyBindings
// above) which forward to the native <leader><leader>* EasyMotion sequences.
// Requires vim.easymotion: true (already set).
//
{
"key": "j",
"name": "+Jump (EasyMotion)",
"type": "bindings",
"bindings": [
{ "key": "w", "name": "Jump Word →", "type": "command", "command": "vim.remap", "args": {"after": ["<leader>","<leader>","w"]} },
{ "key": "b", "name": "Jump Word ←", "type": "command", "command": "vim.remap", "args": {"after": ["<leader>","<leader>","b"]} },
{ "key": "j", "name": "Jump Line ↓", "type": "command", "command": "vim.remap", "args": {"after": ["<leader>","<leader>","j"]} },
{ "key": "k", "name": "Jump Line ↑", "type": "command", "command": "vim.remap", "args": {"after": ["<leader>","<leader>","k"]} },
{ "key": "s", "name": "Jump Char", "type": "command", "command": "vim.remap", "args": {"after": ["<leader>","<leader>","s"]} }
]
},
// ── w = Window / Navigation ────────────────────────────────────────────────────────
//
// Comprehensive navigation hub — use this submenu to discover direct shortcuts.
// Focus commands mirror the Ctrl+h/j/k/l bindings in keybindings.json.
// Resize commands mirror the Ctrl+Arrow bindings in keybindings.json.
//
{
"key": "w",
"name": "+Window / Nav",
"type": "bindings",
"bindings": [
// ── Focus pane (direct: Ctrl+h/j/k/l) ─────────────────────────────────────────
{ "key": "h", "name": "Focus Left [C-h]", "type": "command", "command": "workbench.action.focusLeftGroup" },
{ "key": "j", "name": "Focus Down [C-j]", "type": "command", "command": "workbench.action.focusBelowGroup" },
{ "key": "k", "name": "Focus Up [C-k]", "type": "command", "command": "workbench.action.focusAboveGroup" },
{ "key": "l", "name": "Focus Right [C-l]", "type": "command", "command": "workbench.action.focusRightGroup" },
// ── Create split ───────────────────────────────────────────────────────────────
{ "key": "s", "name": "Split Below [-]", "type": "command", "command": "workbench.action.splitEditorDown" },
{ "key": "v", "name": "Split Right [|]", "type": "command", "command": "workbench.action.splitEditorRight" },
// ── Resize split (direct: Ctrl+Arrow) ─────────────────────────────────────────
{ "key": "=", "name": "Increase Width [C-→]","type": "command", "command": "workbench.action.increaseViewWidth" },
{ "key": "-", "name": "Decrease Width [C-←]","type": "command", "command": "workbench.action.decreaseViewWidth" },
{ "key": "+", "name": "Increase Height [C-↑]","type": "command", "command": "workbench.action.increaseViewHeight" },
{ "key": "_", "name": "Decrease Height [C-↓]","type": "command", "command": "workbench.action.decreaseViewHeight" },
// ── Manage ─────────────────────────────────────────────────────────────────────
{ "key": "d", "name": "Close Window", "type": "command", "command": "workbench.action.closeActiveEditor" },
{ "key": "w", "name": "Next Window [C-w C-w]","type": "command", "command": "workbench.action.focusNextGroup" },
{ "key": "m", "name": "Maximise Toggle", "type": "command", "command": "workbench.action.toggleEditorWidths" },
// ── Jump to specific group (direct: Ctrl+1/2/3) ───────────────────────────────
{ "key": "1", "name": "Group 1 [C-1]", "type": "command", "command": "workbench.action.focusFirstEditorGroup" },
{ "key": "2", "name": "Group 2 [C-2]", "type": "command", "command": "workbench.action.focusSecondEditorGroup" },
{ "key": "3", "name": "Group 3 [C-3]", "type": "command", "command": "workbench.action.focusThirdEditorGroup" }
]
},
// ── x = Diagnostics ────────────────────────────────────────────────────────────────
{
"key": "x",
"name": "+Diagnostics",
"type": "bindings",
"bindings": [
{ "key": "x", "name": "Diagnostics List", "type": "command", "command": "workbench.action.problems.focus" },
{ "key": "X", "name": "Workspace Diagnostics", "type": "command", "command": "workbench.actions.view.problems" },
{ "key": "l", "name": "Location List", "type": "command", "command": "workbench.action.problems.focus" }
]
},
// ── u = UI Toggles ─────────────────────────────────────────────────────────────────
{
"key": "u",
"name": "+UI Toggles",
"type": "bindings",
"bindings": [
{ "key": "w", "name": "Toggle Word Wrap", "type": "command", "command": "editor.action.toggleWordWrap" },
{ "key": "z", "name": "Toggle Zen Mode", "type": "command", "command": "workbench.action.toggleZenMode" },
{ "key": "r", "name": "Clear Search HL", "type": "command", "command": "vim.remap", "args": { "after": ["<Esc>"] } }
]
},
//
// Note on ur / Clear Search HL:
// vim.remap {"after": ["<Esc>"]} sends an Escape keypress into vim's handler.
// The vim.normalModeKeyBindingsNonRecursive binding "<Esc>" → ":nohl" (defined above)
// catches it and clears the search highlight. If this stops working after a VSCodeVim
// update, press <Esc> directly in normal mode as an equivalent fallback.
//
// ── [ = Previous / ] = Next (bracket navigation) ──────────────────────────────────
//
// These mirror the keybindings.json bracket bindings so they appear in the which-key
// menu. The VS Code commands are identical — this is purely for discoverability.
//
{
"key": "[",
"name": "Prev",
"type": "bindings",
"bindings": [
{ "key": "b", "name": "Prev Buffer", "type": "command", "command": "workbench.action.previousEditor" },
{ "key": "d", "name": "Prev Diagnostic", "type": "command", "command": "editor.action.marker.prevInFiles" },
{ "key": "e", "name": "Prev Error", "type": "command", "command": "editor.action.marker.prev" },
{ "key": "h", "name": "Prev Git Hunk", "type": "command", "command": "workbench.action.editor.previousChange" },
{ "key": "q", "name": "Prev Search Result", "type": "command", "command": "search.action.focusPreviousSearchResult" }
]
},
{
"key": "]",
"name": "Next",
"type": "bindings",
"bindings": [
{ "key": "b", "name": "Next Buffer", "type": "command", "command": "workbench.action.nextEditor" },
{ "key": "d", "name": "Next Diagnostic", "type": "command", "command": "editor.action.marker.nextInFiles" },
{ "key": "e", "name": "Next Error", "type": "command", "command": "editor.action.marker.next" },
{ "key": "h", "name": "Next Git Hunk", "type": "command", "command": "workbench.action.editor.nextChange" },
{ "key": "q", "name": "Next Search Result", "type": "command", "command": "search.action.focusNextSearchResult" }
]
},
// ── q = Quit ───────────────────────────────────────────────────────────────────────
{
"key": "q",
"name": "+Quit",
"type": "bindings",
"bindings": [
{ "key": "q", "name": "Quit VSCode", "type": "command", "command": "workbench.action.closeWindow" },
{ "key": "a", "name": "Quit All", "type": "command", "command": "workbench.action.quit" }
]
}
],
// ──────────────────────────────────────────────────────────────────────────────────────
// SETTINGS CYCLER CONFIGURATION (requires hoovercj.vscode-settings-cycler extension)
// ──────────────────────────────────────────────────────────────────────────────────────
//
// This provides LazyVim-style line number toggling with <leader>ul and <leader>uL.
// Install the extension, then uncomment the keybindings above in the UI TOGGLES section.
//
// "settings.cycle": [
// {
// "id": "lineNumbers",
// "values": [
// { "editor.lineNumbers": "on" }, // Absolute line numbers
// { "editor.lineNumbers": "relative" }, // Relative line numbers
// { "editor.lineNumbers": "off" } // No line numbers
// ]
// },
// {
// "id": "relativeLineNumbers",
// "values": [
// { "editor.lineNumbers": "relative" }, // Toggle between relative
// { "editor.lineNumbers": "on" } // and absolute only
// ]
// }
// ],
// ──────────────────────────────────────────────────────────────────────────────────────
// PERFORMANCE OPTIMISATION - CRITICAL for responsive typing
// ──────────────────────────────────────────────────────────────────────────────────────
//
// This runs the Vim extension in a dedicated thread to prevent typing lag and cursor
// delays. This is ESSENTIAL for a good Vim experience in VSCode.
//
"extensions.experimental.affinity": {
"vscodevim.vim": 1
}
// ──────────────────────────────────────────────────────────────────────────────────────
// ADDITIONAL NOTES AND TROUBLESHOOTING
// ──────────────────────────────────────────────────────────────────────────────────────
//
// MACOS USERS: Enable key repeat for hjkl navigation
// Run in terminal: defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false
// Then restart VSCode.
//
// COMMON ISSUES:
//
// 1. Leader key not working:
// - Ensure "vim.leader": "<space>" is set
// - Verify bindings are in settings.json, NOT keybindings.json
// - Check no other extension is capturing space key
//
// 2. Typing lag or cursor delay:
// - Ensure extensions.experimental.affinity is set (see above)
// - Disable unused extensions
// - Check CPU usage in Task Manager
//
// 3. GitLens commands not working:
// - Install GitLens extension: eamodio.gitlens
// - Some commands are only available in git repositories
//
// 4. Bracket navigation ([d, ]d) not working:
// - These are defined in keybindings.json
// - Check your keybindings.json file is configured correctly
//
// 5. Line number toggle not working:
// - Install Settings Cycler: hoovercj.vscode-settings-cycler
// - Uncomment the settings.cycle section above
// - Uncomment the <leader>ul keybindings in UI TOGGLES section
//
// VALIDATION CHECKLIST:
// □ Space key triggers leader (doesn't move cursor)
// □ <leader>ff opens file picker
// □ <leader>/ searches in files
// □ gd navigates to definition
// □ K shows hover
// □ <leader>ca shows code actions
// □ Shift+H/L switches buffers
// □ No typing lag or cursor delays
//
// ══════════════════════════════════════════════════════════════════════════════════════
}

VimCode — LazyVim keybindings for VS Code

Quick-start gist. Full docs, 50+ keybindings, and multi-editor support → github.com/wojukasz/VimCode

Bring the muscle memory of LazyVim to VS Code (and Cursor, Windsurf, Antigravity). Copy two config files, install four extensions, and you get modal editing with a which-key popup that guides you through every leader shortcut.


Extensions

Extension Purpose Required?
vscodevim.vim Vim emulation Yes
VSpaceCode.whichkey ~ popup menu Yes
eamodio.gitlens Git blame, log, diff Recommended
hoovercj.vscode-settings-cycler Toggle relative line numbers Optional

Quick install (terminal)

VS Code

code --install-extension vscodevim.vim && \
code --install-extension VSpaceCode.whichkey && \
code --install-extension eamodio.gitlens && \
code --install-extension hoovercj.vscode-settings-cycler

Copy the config files

Copy both files from this gist into your editor's user settings directory, replacing any existing files (back them up first).

Platform Path
macOS ~/Library/Application Support/<Editor>/User/
Linux ~/.config/<Editor>/User/
Windows %APPDATA%\<Editor>\User\

Replace <Editor> with Code, Cursor, Windsurf, etc.

  • settings.json — all <leader> bindings and which-key menu tree
  • keybindings.json — modifier key bindings (Ctrl/Alt/Shift + h/j/k/l, etc.)

Restart your editor after copying.


which-key popup

Note: ~ and <space> have distinct roles here.

  • ~ opens the which-key popup menu — use it to browse and discover keybindings.
  • <space> is the Vim leader key — use it to execute bindings directly (e.g. <space>ff finds files).

You don't need to go through the popup every time. Once you know a binding, just type it with <space> directly. ~ is there when you need a reminder.

Note: ~ normally toggles character case in Vim. If you use that frequently, see TROUBLESHOOTING.md for alternative trigger key options.

Press ~ in Normal mode → a popup appears showing every available leader shortcut.

<space>           Quick Open (find files)
,                 Switch buffer
/                 Search in files (grep)

f  →  +File       ff=Find  fr=Recent  fn=New  fb=Buffers  ft=Terminal
s  →  +Search     sg=Grep  sw=Word  sr=Replace  ss=Symbols  sc=Commands
c  →  +Code       ca=Action  cf=Format  cr=Rename  cd=Diagnostics
b  →  +Buffer     bb=Switch  bd=Delete  bn=Next  bp=Prev
g  →  +Git        gg=Status  gb=Blame  gd=Diff  gl=Log  gc=Commit
G  →  +Go To      d=Definition  r=References  I=Implementation  y=Type
w  →  +Window     h/j/k/l=Focus  -=Split↓  |=Split→  d=Close
x  →  +Diagnostics  xx=Panel  [d=Prev  ]d=Next
u  →  +UI         uw=Wrap  uz=Zen  ur=ClearHL  ul=LineNums

You never need to memorise keybindings — just press ~ and follow the menu.


Essential keybindings

Navigation

Key Action
<space><space> Find files
<space>/ Grep across project
<space>, Switch buffer
Shift+h / Shift+l Previous / next buffer
Ctrl+h/j/k/l Move between splits

Code

Key Action
<space>ca Code action
<space>cr Rename symbol
<space>cf Format document
gd Go to definition
gr Go to references
K Show hover

Git

Key Action
<space>gg Git status
<space>gb Git blame
<space>gd Git diff
[h / ]h Previous / next hunk

Vim essentials

Key Action
jk or jj Escape to Normal mode
<leader>j EasyMotion jump
s / S Sneak forward / backward
ys / ds / cs Surround add / delete / change
Ctrl+s Save file

macOS: enable key repeat

Without this, holding j won't repeat in Normal mode.

defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false
# Restart VS Code after running this

Replace com.microsoft.VSCode with the bundle ID for your editor (e.g. com.todesktop.230313mzl4w4u92 for Cursor).


Full documentation

Doc Link
Setup guide SETUP.md
All 50+ keybindings KEYBINDINGS.md
Tips & tricks TIPS_AND_TRICKS.md
Troubleshooting TROUBLESHOOTING.md
Changelog CHANGELOG.md

This gist tracks the stable core config. For the latest features, follow VimCode.

@w3sync
Copy link

w3sync commented Nov 29, 2023

how can i config this in my vscode

@LwveMike
Copy link

LwveMike commented Apr 4, 2024

how can i config this in my vscode

  1. Press cmd + shift + p or ctrl + shift +p.
  2. Write settings.
  3. Scroll to personal settings ( JSON ).
  4. Paste this config there.

@nicolidin
Copy link

hello it seems i can't have two keys after leader, only one works, how did you do?

@w3sync
Copy link

w3sync commented Sep 23, 2024

hey some features are missing like ctrl+f, ctrl+s,

@wojukasz
Copy link
Author

wojukasz commented Oct 2, 2024

@nicolidin you have to install multi-command plugin and add the multi-command settings https://gist.github.com/wojukasz/d76ca3162debc66d4227b9dc6049789e#file-settings-json

@wojukasz
Copy link
Author

hey some features are missing like ctrl+f, ctrl+s,

yeah, its not something I explored yet but I will try build upon this config over time

@DmitriiKozlov-Neura
Copy link

I changed the ', e' binding, to be able to input commas in terminal. Added !inputFocus to when:

 {
    // ", e": Toggles the sidebar's visibility when the text editor is not focused. <leader> + e
    "key": ", e",
    "command": "workbench.action.toggleSidebarVisibility",
    "when": "!editorTextFocus && !inputFocus"
  },

@wojukasz
Copy link
Author

I changed the ', e' binding, to be able to input commas in terminal. Added !inputFocus to when:

 {
    // ", e": Toggles the sidebar's visibility when the text editor is not focused. <leader> + e
    "key": ", e",
    "command": "workbench.action.toggleSidebarVisibility",
    "when": "!editorTextFocus && !inputFocus"
  },

Nice! thanks for sharing!

@Ibrahim-Azhar77
Copy link

hey some features are missing like ctrl+f, ctrl+s,

Hi!
I noticed the same when I first set up VimCode in VS Code. Some default shortcuts like Ctrl+F (search) or Ctrl+S (save) may not work if they are overridden by the Vim keybindings.

Here’s what I do:

  • Open Keyboard Shortcuts (Ctrl+K, Ctrl+S)
  • Search for the conflicting shortcut
  • Either reassign it or remove the conflict
  • Reload VS Code (Ctrl+Shift+P → Reload Window)

I also test all my keys after changes using this Free Keyboard Tester. It helps me quickly spot any missing shortcuts.

This keeps all essential shortcuts working while still enjoying Vim-style navigation!

@wojukasz
Copy link
Author

wojukasz commented Jan 17, 2026

Thanks, @Ibrahim-Azhar77. That's really good advice. I will take a look.

In this case, Ctrl + s works as a save file.

My take is to make this config Vim first and VS Code second, since I am coming from a Vim background. I am not planning to learn many VS Code shortcuts, but rather to adapt Vim to VS Code functionality.
More info in: https://github.com/wojukasz/VimCode

I am planning to keep this gist in sync with the repo, but mainly update keybindings and settings files to keep overhead minimal.

@Ibrahim-Azhar77
Copy link

Thanks for the explanation. I like the Vim-first approach, as it feels more natural to me. Using Ctrl + S as save makes sense, especially for beginners. Keeping the gist in sync with the repo also sounds like a clean and practical idea.

@wojukasz
Copy link
Author

thanks @Ibrahim-Azhar77 I am planning to refine as I go on and update, but if you find anything that is not working or you have suggestions for improvments please post in https://github.com/wojukasz/VimCode/issues

@Ibrahim-Azhar77
Copy link

Thanks for the update! I’ll keep an eye on it and post any issues or suggestions directly on this page: https://github.com/wojukasz/VimCode/issues. Keep up the great work!

@odezzshuuk
Copy link

  // ┌────────────────────────────────────────────────────────────────────────────────────┐
  // │ BUFFER NAVIGATION - Shift+h/l (LazyVim: <S-h>, <S-l>)                              │
  // └────────────────────────────────────────────────────────────────────────────────────┘
  //
  // Switch between open editor tabs using Shift+H (previous) and Shift+L (next).
  // Only active in Normal/Visual modes to avoid conflicts with text selection.
  // Alternative: Use [b and ]b (defined below) for the same functionality.
  //
  {
    "key": "shift+h",
    "command": "workbench.action.previousEditor",
    "when": "vim.active && vim.mode != 'Insert' && editorFocus"
  },
  {
    "key": "shift+l",
    "command": "workbench.action.nextEditor",
    "when": "vim.active && vim.mode != 'Insert' && editorFocus"
  },

I add editorFocus to buffer navigation when-expression to avoid tab switch when typing "L", "H" in copilot chat.

@wojukasz
Copy link
Author

Brilliant @odezzshuuk , I encountered that last week just didn't got around to fixing it, thanks will try it out coming week and update accordingly.

@shanksxz
Copy link

shanksxz commented Feb 1, 2026

can't i navigate btw file explorer and the file with ctrl h,l

@wojukasz
Copy link
Author

wojukasz commented Feb 6, 2026

@shanksxz https://gist.github.com/wojukasz/d76ca3162debc66d4227b9dc6049789e#file-keybindings-json-L334-L367

Switch to file explorer with ctrl + shift + e and then you can navigate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment