Personal Neovim config, built on kickstart.nvim.
Single init.lua, meant to be read top to bottom.
git clone <this repo> ~/.config/nvim
nvimThat's it. This config uses vim.pack, Neovim's own built-in plugin manager
(new in 0.12) — there's no separate plugin manager to install and no sync
step to run. The first time you launch nvim it will clone every plugin
listed in init.lua automatically, then you're up and running.
Requires Neovim ≥ 0.12.
For Swift and Xcode support (macOS only — the whole of init.lua's SECTION 22
is skipped elsewhere): sourcekit-lsp comes with the Xcode command line tools
(xcode-select --install), so LSP alone works without Xcode. xcodebuild does
not — it needs full Xcode installed and selected (xcode-select -p must
point inside an Xcode.app, not at /Library/Developer/CommandLineTools), so
the build/run/test/debug half of SECTION 22 requires it. Two optional extras
make it noticeably nicer:
brew install xcbeautify xcode-build-serverxcbeautify formats the build log into something readable; xcode-build-server
is what teaches sourcekit-lsp to understand an .xcodeproj/.xcworkspace
(without it, completion works in Swift packages but not in an Xcode app
target). Both are detected at runtime — the config degrades quietly if they're
missing rather than erroring. Everything else (LSP servers, formatters) installs
itself via Mason — including swiftformat and swiftlint, though only on Apple
Silicon: neither publishes an x86_64 macOS binary Mason can fetch, so on an
Intel Mac use brew install swiftformat swiftlint and the rest is unaffected.
Rust is the other exception: it expects rustup's own toolchain (rustup component add rust-analyzer rustfmt) rather than a Mason install, so the
server always matches the compiler.
Also install ripgrep, fd, and tree-sitter-cli (brew install ripgrep fd tree-sitter-cli) — Telescope's live grep (<leader>sg, <C-S-f>)
hard-requires ripgrep and won't work without it; fd just makes file
search faster; tree-sitter-cli is required to compile treesitter parsers
(note: Homebrew's tree-sitter formula only installs the library, not the
CLI — it's a separate package).
Branch naming and other repo conventions live in CLAUDE.md.
run.sh opens a new Ghostty window running Neovim with this repo's
init.lua (nvim -u init.lua .) so you can try out config changes without
touching ~/.config/nvim. build.sh is still a hello-world script for testing
the <C-S-b> chord below.
test.sh is the headless smoke test: it boots nvim --headless -u init.lua
and asserts that the plugins, keymaps, formatters, linters, and LSP this config
wires up actually landed. It prints one JSON object per check and exits
non-zero if any fail, so it works as a pre-merge gate:
./test.sh
./test.sh | jq -c 'select(.ok == false)'UI / core
guess-indent.nvim— auto-detect file indentation (overrides the 4-space, spaces-only default when a file disagrees;.editorconfigand modelines override too)gitsigns.nvim— git gutter signs + hunk stage/reset/preview/navwhich-key.nvim— shows pending keybinds as you type themrose-pine/neovim— colorschemetodo-comments.nvim— highlights TODO/NOTE/etc in commentsmini.nvim— grab-bag:mini.ai(textobjects),mini.surround,mini.statusline
Search
telescope.nvim(+plenary.nvim,telescope-ui-select.nvim,telescope-fzf-native.nvim) — fuzzy finder for files, grep, LSP, help, etc.
LSP
nvim-lspconfig,mason.nvim,mason-lspconfig.nvim,mason-tool-installer.nvim— LSP server managementfidget.nvim— LSP progress notificationstiny-code-action.nvim— code-action picker: same telescope UI, but every action shows a diff preview of what it would change, best fix ranked firstnvim-lightbulb— gutter lightbulb when a code action is available at the cursor- Servers:
lua_ls,ts_ls(JS/TS/React Native),eslint,sourcekit(Swift, via Xcode'ssourcekit-lsp— not managed by Mason),dartls(Flutter, owned byflutter-tools.nvim— also not Mason's),rust_analyzer(Rust, via rustup's own binary — not Mason's, so it always matches the toolchain)
Formatting & linting
conform.nvim—styluafor Lua,prettierfor JS/TS/JSX/TSX/JSON/CSS/HTML/Markdown/YAML,swiftformatfor Swift,dart formatfor Dart; Rust formats through rust-analyzer's own rustfmt pass (no conform entry needed)nvim-lint—swiftlinton Swift buffers (write / read / leaving insert mode)
Mobile — one <leader>m* lane over three stacks, see Mobile
xcodebuild.nvim(macOS only) — build, run, test, debug, and manage an iOS/macOS/Swift-package project without opening Xcode: scheme and device pickers, test explorer, code coverage, log parsingflutter-tools.nvim—dartls, device/emulator pickers, hot reload, widget guides, the widget outline, and the Dart debugger- React Native / Expo has no build/run plugin — it's
ts_ls+eslint+prettier+nvim-ts-autotagfor the code, and the mobile lane shells out tonpx expofor the dev loop nvim-dap,nvim-dap-ui(+nvim-nio) — the debugger, shared by the Swift lane (via Xcode'slldb-dap) and the Flutter lane
Completion
blink.cmp,LuaSnip— autocomplete + snippets; docs pane auto-shows, buffer-word fallback when no language server is attached, treesitter-colored labelsllama.vim— Cursor-style AI ghost text from a fully local llama.cpp server (Qwen2.5-Coder-3B, fill-in-the-middle): no account, no revocable free tier, code never leaves the machine. Needsbrew install llama.cpp; the server auto-starts detached on first insert (see AI ghost text);<leader>tatoggles suggestions
Treesitter
nvim-treesitter— parsers for bash, c, diff, html, lua, luadoc, markdown(+inline), query, vim, vimdoc, swift, dart, javascript, typescript, tsx, json, yaml, rust, tomlnvim-ts-autotag— auto-close and auto-rename JSX/TSX tags (<View>gets its</View>, editing either side of the pair updates the other); mini.pairs closes brackets and quotes but has no concept of a tag
Personal extras
harpoon(harpoon2 branch) — jump between a handful of pinned filesundotree— visualize and navigate undo historyvim-fugitive— full git porcelain (status/commit/push/log), complementing gitsigns' hunk-level workneo-tree.nvim(+nui.nvim) — sidebar file explorer, kickstart's optional module, enabledrender-markdown.nvim— renders markdown (READMEs, notes) prettily in the buffernvim-treesitter-context— sticky headers: pins the enclosing function/class/struct (or markdown heading) to the top of the windowhardtime.nvim— habit coach: flags inefficient key use (mashingj/k/arrows, repeatedw/b, …) and hints a better motion. Gentle "hint" mode by default — it suggests, never blocks;:Hardtime reportlists your top habits,:Hardtime toggle/<leader>tHturns it off. Flip thestrictlocal in init.lua's SECTION 21 totruefor blocking mode.
Leader key is Space.
| Key | Action |
|---|---|
<leader>sh |
Search help |
<leader>sk |
Search keymaps |
<leader>sf |
Search files |
<leader>ss |
Select Telescope picker |
<leader>sw |
Search current word |
<leader>sg |
Live grep |
<leader>sd |
Search diagnostics |
<leader>sr |
Resume last search |
<leader>s. |
Search recent files |
<leader>sc |
Search commands |
<leader>sn |
Search Neovim config files |
<leader>s/ |
Live grep in open files |
<leader>/ |
Fuzzy search in current buffer |
<leader><leader> |
Find existing buffers |
| Key | Action |
|---|---|
grn |
Rename symbol, and save the files it changed. Files you already had unsaved edits in are left for you to save, and anything it can't write is named in a warning. (The buffer you're standing in is the exception: the autosave writes it a moment later, unsaved work and all.) |
<C-.> / <leader>ca |
Quick fix -- code actions for the whole line the cursor is on, each with a diff preview of what it would change, best fix first (normal and visual; says so when no language server is attached). In insert mode <C-.> instead opens the completion menu with the line's quick fixes ranked on top, Xcode-style |
<leader>co / <leader>cm / <leader>cf |
Whole-file source actions: organize imports / add missing imports / fix all auto-fixable (applies directly when the server offers exactly one) |
gra |
Quick fix / code action, same line-wide behaviour (Neovim's own default key) |
grD |
Goto declaration |
grr |
Goto references |
gri |
Goto implementation |
grd |
Goto definition |
grt |
Goto type definition |
gO |
Document symbols |
gW |
Workspace symbols |
<leader>th |
Toggle inlay hints |
<leader>q |
Diagnostics to quickfix LIST (the list of problems, not the quick fix menu -- that's <C-.>) |
]d / [d |
Next / previous diagnostic (crosses files) |
yd |
Yank the diagnostics on this line to the clipboard |
yD |
Yank every diagnostic in the buffer, line-numbered |
yd / yD copy the language server's own message as text, prefixed with the
file and line, rather than the rendered virtual text — so a ts_ls complaint
lands on the clipboard as:
broken.ts:1
ERROR: Type 'string' is not assignable to type 'number'.
which pastes into an issue or a chat as-is. Note clipboard=unnamedplus is on,
so these write to the real system clipboard.
| Key | Action |
|---|---|
<leader>th |
Toggle inlay hints |
<leader>tb |
Toggle git blame line |
<leader>tw |
Toggle git word diff |
<leader>tm |
Toggle markdown render (on by default in markdown buffers) |
<leader>tc |
Toggle sticky scope context (on by default) |
<leader>tH |
Toggle hardtime habit coach (on by default) |
<leader>ta |
Toggle AI ghost text (on by default) |
Fully local fill-in-the-middle completion via llama.cpp — no account, and no
free tier anyone can revoke. The model server starts itself in the background
on the first insert if nothing is answering on port 8012 (:LlamaServer does
the same by hand; a first-ever run downloads Qwen2.5-Coder-3B, ~2GB). It runs
detached, so it survives quitting nvim — one spawn per boot, no window to
babysit. Tab accepts, but only when a suggestion is showing — otherwise it
falls through to blink.cmp's snippet-jump and then to a normal tab, so
nothing else loses the key. The Alt chords work everywhere regardless.
| Key (insert mode) | Action |
|---|---|
Tab / Alt-f |
Accept the whole suggestion |
Alt-w |
Accept one word |
Alt-a |
Accept one line |
Alt-e / Alt-r |
Cycle to next / previous suggestion |
Ctrl-F |
Manually (re)trigger a suggestion |
| Key | Action |
|---|---|
]c / [c |
Next / previous git change |
<leader>hs |
Stage hunk |
<leader>hr |
Reset hunk |
<leader>hS |
Stage buffer |
<leader>hR |
Reset buffer |
<leader>hp |
Preview hunk |
<leader>hi |
Preview hunk inline |
<leader>hb |
Blame line |
<leader>hd |
Diff against index |
<leader>hD |
Diff against last commit |
<leader>hq / <leader>hQ |
Hunk quickfix list (file / whole repo) |
ih (in o/x mode) |
Hunk textobject |
| Key | Action |
|---|---|
<leader>gh / <leader>gs |
Git status |
<leader>ga |
Git add . |
<leader>gu |
Git restore --staged . (unstage all) |
<leader>gc |
Git commit |
<leader>gp |
Git push |
<leader>gl |
Git log |
| Key | Action |
|---|---|
<leader>a |
Add current file to Harpoon list |
<C-e> |
Toggle Harpoon quick menu |
<C-1> .. <C-4> |
Jump to Harpoon file 1-4 |
| Key | Action |
|---|---|
<leader>u |
Toggle Undotree |
| Key | Action |
|---|---|
<leader>f |
Format buffer |
One set of verbs over three stacks. Every key figures out what kind of project you're in and talks to the right tool, so "run my app" is the same keystroke whether it's an iOS app, a Flutter app, or an Expo app.
Detection walks up from the cwd and the nearest marker wins: pubspec.yaml
→ Flutter, an expo (or react-native) dependency in package.json → Expo/RN,
Package.swift / *.xcodeproj / *.xcworkspace → Xcode. Nearest-wins rather
than a fixed stack order, so an Expo app's own ios/ xcodeproj doesn't hijack
it, and a native app sitting in a JS monorepo doesn't get dragged up to the
monorepo's package.json.
| Key | Action |
|---|---|
<leader>mr |
Run this app. Nothing chosen yet? It asks first (Xcode: scheme + device; Flutter: device) and then runs, rather than failing at you. The choice is reused after that — :MobileForget re-asks. With no SDK installed it says so instead of throwing |
<leader>mb |
Build without launching. Xcode builds; Flutter builds for the platform of the device you picked (matched by prefix, so darwin-arm64 → macos); Expo bundles via expo export |
<leader>mR |
Hot reload (Flutter); rebuild-and-run for Xcode; Metro reloads on save |
<leader>md |
Pick a device / emulator |
<leader>mt |
Run tests |
<leader>ms |
Setup: scheme+device (Xcode), device (Flutter), npm install (Expo) |
<leader>m? |
Doctor — what's wrong with this project |
<leader>mn |
New project (flutter create / create-expo-app) |
<leader>m? is the one to remember. Each stack fails in ways the editor can't
fix but can name, and it checks those before handing off to the real tool
(flutter doctor, npx expo-doctor, :checkhealth xcodebuild): an asdf
install with no Flutter version pinned, an Xcode project with no scheme chosen,
a missing node_modules, a missing xcbeautify or xcode-build-server.
Whichever way it resolved, the lane then moves you into that project
(:tcd, so it's per-tab). It has to: every one of these tools re-derives its
own root from where Neovim is sitting, so dispatching from a monorepo root
without moving first gets you flutter run in the repo root and "No pubspec.yaml
file found".
From a monorepo root there's nothing to walk up to, so the lane searches
down instead (4 levels, skipping node_modules, Pods, .dart_tool, build
and friends). One project found means it just acts; several means it asks once
and remembers per directory — :MobileForget re-asks (it clears the remembered
Flutter device too). A Flutter or RN app's
generated ios//macos/ shells are folded into their parent rather than
offered as separate projects.
Opening ios/Runner/AppDelegate.swift shows a red No such module 'Flutter' on
a file that builds fine. sourcekit-lsp attaches, but with no compile flags it
can't see the Flutter framework. <leader>m? flags this.
It is not unresolved packages — Pods can be fully installed and it still
happens. Two separate things have to be true: sourcekit must know to launch the
build server (buildServer.json), and that server must have flags to answer
with. Bind it first:
brew install xcode-build-server
cd ios && xcode-build-server config -workspace Runner.xcworkspace -scheme RunnerThat writes buildServer.json with kind: "xcode", meaning flags are harvested
from Xcode's own builds and cached under ~/Library/Caches/xcode-build-server/.
Build once from Xcode.app and you're done.
If you build from the terminal instead, harvest them yourself — but note a clean build is required, because an incremental one recompiles nothing and so logs no compile commands:
xcodebuild -workspace Runner.xcworkspace -scheme Runner \
-destination 'generic/platform=iOS' -configuration Debug \
CODE_SIGNING_ALLOWED=NO clean build > /tmp/xcb.log 2>&1
xcode-build-server parse /tmp/xcb.log # writes ios/.compile, flips kind to "manual"Things that look sufficient and aren't: config on its own (binds the server,
gives it no flags); flutter build ios (leaves no parseable log — it drives
xcodebuild through its own build dir); and an incremental xcodebuild (0 compile
commands, so .compile comes out empty). <leader>m? knows both layouts and
tells you which step is missing rather than guessing.
Afterwards sourcekit type-checks the file properly — on a real app the phantom
error was replaced by a genuine unused-variable warning. Re-harvest after changing
dependencies. Add ios/.compile and ios/buildServer.json to the app's
.gitignore; they're machine-specific. SwiftLint's warnings in these files were
always real — only the SourceKit module errors were phantom.
Inside nvim the whole recipe is automated: picking a scheme (<leader>xs)
writes buildServer.json next to the project file, every successful build
(<leader>xb) feeds its log through xcode-build-server parse -a — a merge,
so incremental builds only ever add coverage, across targets and schemes —
and opening a Swift file in an unbound checkout tells you which step is
missing. Both artifacts embed absolute paths (hence the gitignore), so every
new clone or worktree starts unbound; with the automation that costs one
<leader>xs and one build. The terminal recipe above remains for sessions
outside nvim.
Things you reach for a few times a year, so they're commands rather than keys.
:Flutter<Tab> and :Xcodebuild<Tab> complete the full list; <leader>X is a
searchable picker over every Xcode action.
| Command | Does |
|---|---|
:FlutterPubGet / :FlutterPubUpgrade |
Resolve / upgrade package versions |
:FlutterLspRestart / :FlutterReanalyze |
Restart dartls / re-run analysis after a dependency change |
:FlutterEmulators |
Launch an emulator (vs <leader>md, which picks an already-running device) |
:FlutterOutlineToggle |
Widget tree sidebar |
:FlutterDevTools |
Start DevTools |
:XcodebuildCleanDerivedData |
Nuke DerivedData — the "why won't it build" button |
:XcodebuildCleanBuild |
Clean, then build |
:XcodebuildBuildForTesting |
Build the test bundle without running it |
:XcodebuildAssetsManager |
Manage image/colour/data assets |
:XcodebuildShowConfig |
Print the current scheme/device/test plan |
For Expo, package resolution is plain npm install — that's what <leader>ms
runs. Metro's cache is npx expo start --clear.
Flutter needs an SDK version pinned. Under a version manager, flutter on
your PATH is a shim, and the analysis server lives inside the SDK itself. The
config resolves it with asdf where flutter, which respects a per-project
.tool-versions, but errors if nothing is pinned anywhere:
asdf list flutter # what's installed
asdf set flutter <version> # globally, or in the project for per-project pinning<leader>m? in a Flutter project tells you if this is your problem.
Start in the project root and run <leader>xs once — it walks you through
picking the project file, scheme, device, and test plan, and saves them to
.nvim/xcodebuild/settings.json so it's a one-time step per project. The
project file may live in a subfolder (searched four levels deep); its directory
becomes the build's working directory. :cd-ing to another project mid-session
switches settings automatically.
| Key | Action |
|---|---|
<leader>X |
Picker with every Xcode action, including the ones not bound below |
<leader>xs |
Setup project (file, scheme, device, test plan) |
<leader>xb / <leader>xr |
Build / build & run |
<leader>xk |
Kill the running build or test |
<leader>xt |
Run tests (visual mode: run the selected tests) |
<leader>xT |
Run the current test class |
<leader>xe |
Toggle test explorer |
<leader>xl |
Toggle build logs |
<leader>xc / <leader>xC |
Toggle coverage marks / show coverage report |
<leader>xd |
Select device |
<leader>xf |
Project file manager (add/rename/delete, updating the Xcode project) |
<leader>xa |
Xcode code actions |
<leader>xL |
Run SwiftLint on this buffer now |
<leader>xo |
Open the current file in Xcode |
The F-keys and <leader>du are shared by every debugger; the <leader>d* verbs
below are the Swift ones (macOS only). Flutter debugging runs through
<leader>mr, which starts the app under the debugger via flutter-tools.
| Key | Action |
|---|---|
<leader>dd / <leader>dr |
Build & debug / debug without building (Swift) |
<leader>dt / <leader>dT |
Debug tests / debug the current test class |
<leader>db / <leader>dB |
Toggle breakpoint / message breakpoint |
<leader>du |
Toggle the debugger UI panels |
<leader>dx |
Terminate the debug session |
<F5> |
Continue |
<F10> / <F11> / <F12> |
Step over / into / out |
On macOS the physical Cmd key is rebound to Ctrl in Karabiner-Elements, so
these are the old Cmd-chords under the same fingers. The shifted and
punctuation ones (<C-S-f>, <C-[>, <C-.>) additionally need a terminal
speaking the Kitty keyboard protocol — Ghostty does this, plain Terminal.app
does not. If a chord doesn't fire, check Karabiner and Ghostty's keybind
config before suspecting init.lua.
| Key | Action |
|---|---|
<C-S-f> |
Project-wide search (live grep) |
<C-S-o> / <C-p> |
Find files |
<C-[> / <C-]> |
Back / forward in jump history (in :help buffers <C-]> keeps its native follow-link job) |
<C-S-r> |
Run run.sh next to the current file, in a horizontal split terminal |
<C-S-b> |
Run build.sh next to the current file, in a horizontal split terminal |
| Key | Action |
|---|---|
<C-h/j/k/l> |
Move focus between windows |
<leader>w + s/v/h/j/k/l/c/o/= |
Window commands prefix (split, move focus, close, etc. — see :help CTRL-W) |
\ |
Toggle Neo-tree file explorer |
<leader>wt |
Open a terminal in a new split |
<leader>` |
Autoscroll toggle — hands-free word-by-word reading mode |
` (while autoscrolling) |
Pause / unpause autoscroll |
:Autoscroll speed 2.0 |
Set autoscroll pace in words per second (default 10, applies mid-session) |
<Esc> |
Clear search highlight |
<Esc><Esc> (terminal mode) |
Exit terminal mode — a single <Esc> just gets sent to the shell |
| Key | Action |
|---|---|
a / A |
Add file / add directory |
d |
Delete |
r |
Rename |
y / x / p |
Copy / cut / paste |
c / m |
Copy to / move to (typed destination path) |
T / u / U |
Trash / undo trash / restore from trash |
? |
Show every Neo-tree keymap (in-app help) |
A few core vim motions worth having muscle memory for:
hjkl— left, down, up, rightw/b/e— jump forward/back by word, or to end of word0/^/$— start of line, first non-blank char, end of linegg/G— top / bottom of filef{char}/t{char}— jump to (find) / before (till) a character on the line;;/,repeat/pattern+n/N— search forward, repeat next/previous matchciw,daw,yiw— change/delete/yank "inner word" (works with lots of other textobjects too, e.g.ci",da()
If any of this is unfamiliar, run :Tutor inside Neovim — it's an
interactive walkthrough and the fastest way to build the muscle memory.
This config is authored on macOS but runs on Windows too. With Karabiner
rebinding Cmd to Ctrl on the Mac, most chords are now the same Ctrl chords
on every platform; the few that still differ are in the table below. Launch
Neovim from Git Bash so
the shell and PATH match the .sh helper scripts.
Windows looks for the config at %LOCALAPPDATA%\nvim. Point it at this repo
with a directory junction (no admin required):
:: back up any existing config first
if exist "%LOCALAPPDATA%\nvim" ren "%LOCALAPPDATA%\nvim" nvim.bak
mklink /J "%LOCALAPPDATA%\nvim" "C:\Users\Owais\Documents\github\nvim"Edits in the repo now apply immediately — the junction is the live config.
winget install --id Neovim.Neovim -e # must be >= 0.12 for vim.pack
winget install --id BurntSushi.ripgrep.MSVC -e # live grep (<leader>sg) hard-requires this
winget install --id sharkdp.fd -e # faster file search
winget install --id tree-sitter.tree-sitter-cli -eThe Swift/Xcode lane is not available on Windows — sourcekit-lsp,
xcodebuild, swiftformat, and swiftlint are Mac/Xcode only, so init.lua's
SECTION 22 skips itself and Mason isn't asked to install those tools. Flutter and
React Native/Expo work fine: SECTIONS 23 and 24 are cross-platform, and the
mobile lane simply reports "no mobile project here" for an Xcode project it
can't build. Everything else (LSP servers, stylua, prettier, eslint)
auto-installs via Mason on first use.
The config's nvim-treesitter (main branch) compiles each parser from C
source, so it needs a C compiler. There's no C toolchain on a stock Windows
box, and tree-sitter build won't find one. The lightest fix is zig, whose
zig cc frontend ships its own libc headers (no Visual Studio / Windows SDK
required):
winget install --id zig.zig -etree-sitter build reads the CC environment variable but passes its host
triple x86_64-pc-windows-msvc, which zig cc rejects (and the msvc ABI would
need the absent SDK anyway). So CC points at a tiny shim,
zcc.c → zcc.exe, that invokes zig cc and rewrites that
triple to x86_64-windows-gnu (zig's self-contained mingw libc). Build and
register it once:
zig cc "C:\Users\Owais\bin\zcc.c" -o "C:\Users\Owais\bin\zcc.exe" -O2
[Environment]::SetEnvironmentVariable("CC", "C:\Users\Owais\bin\zcc.exe", "User")(A copy of the shim source lives at tools/zcc.c in this repo.)
With CC set, :TSInstall/auto-install compiles every parser cleanly. If you
later install the Visual Studio C++ Build Tools, you can drop the shim and unset
CC — tree-sitter build will use cl.exe natively.
Install any Nerd Font and select it in your Git Bash / Windows Terminal
profile so the statusline and file-explorer icons render (vim.g.have_nerd_font
is on).
Run nvim from Git Bash. vim.pack clones every plugin (a few minutes the
first time), then Mason and treesitter pull servers/parsers on demand. Run
:checkhealth to confirm.
Most chords are now identical on both platforms (<C-s> save-all, <C-z>
undo, <C-S-f> live grep, <C-p>/<C-S-o> find files, <C-[>/<C-]>
jump history). The exceptions:
| Action | macOS | Windows |
|---|---|---|
| Redo | <C-S-z> |
<C-y> |
| Delete word (insert) | <M-BS> |
<C-BS> |
| Delete to line start (insert) | <C-BS> |
<C-u> |
| Quick fix (code action) | <C-.> |
<leader>ca |
Run run.sh beside file |
<C-S-r> |
<leader>wr |
Run build.sh beside file |
<C-S-b> |
<leader>wb |
The run.sh/build.sh runners execute the scripts through Git Bash's bash
on Windows (they run directly via ./script.sh on macOS), so no .bat
equivalents are needed.