nvim windows support + multicursors

This commit is contained in:
alejo committed 2026-11-06 19:56:13 -03:00
1 parent b6968a10b3
commit 93c97e65e2
10 files changed
+764 -10

No files matched your search

+126 -2
View File
@@ -1,9 +1,11 @@
# Neovim Setup
A scripted Neovim setup built around C/C++ development (CMake + clangd + codelldb), with LSP support for Go, C#, TypeScript/JavaScript, HTML, and CSS alongside it. `setup_neovim.sh` handles installing dependencies and putting everything in place; the actual config lives in `config/` as plain files, not embedded in the script.
A scripted Neovim setup built around C/C++ development (CMake + clangd + codelldb), with LSP support for Go, C#, TypeScript/JavaScript, HTML, and CSS alongside it. `setup_neovim.sh` (Linux) and `setup_neovim.ps1` (Windows) handle installing dependencies and putting everything in place; the actual config lives in `config/` as plain files, shared by both, not embedded in either script.
Supports **Fedora, Ubuntu, Debian, and Arch Linux** (and their common derivatives — Pop!_OS, Linux Mint, Manjaro, EndeavourOS, etc. — via `/etc/os-release`'s `ID_LIKE`) — the script detects which one you're on and uses the right package manager and package names automatically.
Also supports **Windows 10/11**, via `setup_neovim.ps1` and [winget](https://aka.ms/getwinget) — see [Installing → Windows](#windows) and [Windows notes](#windows-notes).
## Contents
- [Installing](#installing)
@@ -11,6 +13,7 @@ Supports **Fedora, Ubuntu, Debian, and Arch Linux** (and their common derivative
- [Config layout & adding plugins](#config-layout--adding-plugins)
- [Terminal vs. desktop (Neovide)](#terminal-vs-desktop-neovide)
- [Editor basics](#editor-basics)
- [Multiple cursors](#multiple-cursors-multicursornvim)
- [Look & feel](#look--feel)
- [Buffers & tabs](#buffers--tabs-bufferline)
- [Text search (Telescope)](#text-search-telescope)
@@ -25,10 +28,13 @@ Supports **Fedora, Ubuntu, Debian, and Arch Linux** (and their common derivative
- [Database (MariaDB)](#database-mariadb)
- [Hex viewer](#hex-viewer)
- [Formatting and linting](#formatting-and-linting)
- [Windows notes](#windows-notes)
- [Known quirks](#known-quirks)
## Installing
### Linux
```bash
chmod +x setup_neovim.sh
./setup_neovim.sh
@@ -53,9 +59,65 @@ First launch takes a minute or two: `lazy.nvim` bootstraps itself and installs a
- **Older distro releases** (Debian stable, older Ubuntu LTS): this config relies on Neovim 0.11+ APIs throughout. If your distro's packaged Neovim is older, the script prints a warning after install — see [Neovim's install docs](https://github.com/neovim/neovim/blob/master/INSTALL.md) for an AppImage/PPA/prebuilt build if so.
- **Neovide** (if you choose it — see [Terminal vs. desktop (Neovide)](#terminal-vs-desktop-neovide)): Arch installs the packaged `neovide` binary directly; Fedora and Debian/Ubuntu have no native package for it, so the script builds it from source via `cargo` (installing Rust via `rustup` first if `cargo` isn't already on `PATH`), per [Neovide's own install docs](https://neovide.dev/installation.html#linux-source). This can take several minutes and, like the .NET SDK step, is best-effort — a failed build prints a warning rather than stopping the rest of the script.
### Windows
Needs Windows 10 (1809+) or Windows 11 with `winget` (the "App Installer" — preinstalled on current Windows; if it's missing, get it from the Microsoft Store). From PowerShell, in this folder:
```powershell
powershell -ExecutionPolicy Bypass -File .\setup_neovim.ps1
```
(`-ExecutionPolicy Bypass` applies to this one run only; it also gets around the "downloaded from the internet" block on a freshly unzipped script.) No administrator shell is needed — individual installers (LLVM, the .NET SDK, …) may still show their own UAC prompt.
As on Linux, keep `setup_neovim.ps1` and `config/` next to each other, and edit the files under `config/` rather than the script to customize. The script:
1. Installs everything through `winget` (Neovim and Git are required and abort the script if they fail; the rest are best-effort and just print a warning), asks which frontend(s) you want — see [Terminal vs. desktop (Neovide)](#terminal-vs-desktop-neovide) — and then installs npm-based language servers.
2. Installs the JetBrainsMono and Cascadia Code Nerd Fonts for the current user (no admin needed).
3. Removes the existing Neovim config **and data** folders, then copies `config/nvim` into `%LOCALAPPDATA%\nvim` (or `%XDG_CONFIG_HOME%\nvim` if you've set that — the same places Neovim itself looks).
4. Copies `.clang-format`, `.clang-tidy` and `cpp-style-guide.md` into your user folder (`%USERPROFILE%`).
**Like the Linux script, this deletes the old config and data (`%LOCALAPPDATA%\nvim` and `%LOCALAPPDATA%\nvim-data`) with no backup by default** — but when run interactively it asks for confirmation first. Options:
| Option | Effect |
|---|---|
| `-Frontend terminal\|desktop\|both` | Skip the frontend question (the default when non-interactive is `terminal`) |
| `-Backup` | Move the old config/data folders aside (`nvim.bak-<timestamp>`) instead of deleting them |
| `-Force` | Don't ask for confirmation before deleting |
| `-SkipMariaDB` | Don't offer to install the MariaDB client |
Open a **new** terminal afterwards so the PATH changes take effect. First launch takes a minute or two, as on Linux (`lazy.nvim` plugins, then Mason's language servers and `codelldb`; treesitter also compiles its parsers with `gcc`).
If you'd rather have the Linux experience, `setup_neovim.sh` also works unchanged inside a WSL Debian/Ubuntu distro.
#### What the Windows script installs
The same set of tools as on Linux, with Windows equivalents where the Linux package doesn't exist:
| Linux | Windows (winget id) |
|---|---|
| gcc, g++, make, gdb | WinLibs GCC / MinGW-w64 (`BrechtSanders.WinLibs.POSIX.UCRT`) — gcc, g++, gdb and a make — plus Ninja (`Ninja-build.Ninja`), the CMake generator to use on Windows |
| cmake | `Kitware.CMake` |
| clang-tools-extra (clang-format, clang-tidy) | `LLVM.LLVM` |
| lldb | not installed separately — `codelldb` (via Mason) brings its own |
| nodejs, npm / python3-pip / golang / dotnet-sdk-8.0 | `OpenJS.NodeJS.LTS` / `Python.Python.3.12` / `GoLang.Go` / `Microsoft.DotNet.SDK.8` |
| ripgrep, fd-find | `BurntSushi.ripgrep.MSVC`, `sharkdp.fd` |
| git, curl, wget, unzip | `Git.Git`; the rest ship with Windows |
| fish | PowerShell 7 (`Microsoft.PowerShell`) — see [Terminal](#terminal) |
| mariadb client | `MariaDB.Server`, run through its own installer wizard (opt-in prompt) — winget has no client-only package |
| Neovide (optional) | `Neovide.Neovide` |
Where things end up:
| `config/` | Copied to |
|---|---|
| `nvim/` | `%LOCALAPPDATA%\nvim\...` (same layout as on Linux) |
| `clang-format` | `%USERPROFILE%\.clang-format` |
| `clang-tidy` | `%USERPROFILE%\.clang-tidy` |
| `cpp-style-guide.md` | `%USERPROFILE%\cpp-style-guide.md` |
## What gets installed
Package names vary by distro (see the script for the exact list per package manager), but the same set gets installed everywhere:
On Linux, package names vary by distro (see the script for the exact list per package manager), but the same set gets installed everywhere:
- **Compilers/tools**: gcc, g++, make, cmake, clang tooling, lldb, gdb, .NET SDK 8.0, Go, nodejs/npm, python3-pip
- **CLI tools**: ripgrep, fd, git, curl, wget, unzip, fish (used only for Neovim's embedded terminal — see [Terminal](#terminal)), and the MariaDB *client* (`mariadb`; client only, no server — see [Database (MariaDB)](#database-mariadb))
@@ -82,6 +144,7 @@ config/nvim/
│ ├── lsp.lua nvim-lspconfig + Mason
│ ├── completion.lua nvim-cmp + LuaSnip
│ ├── database.lua vim-dadbod + UI + completion (MariaDB)
│ ├── multicursor.lua multicursor.nvim (multiple cursors)
│ ├── dap.lua nvim-dap + UI
│ └── … telescope, nvim-tree, bufferline, lualine, and so on
├── config/lsp/ helper modules that are *not* plugin specs
@@ -156,6 +219,44 @@ Once installed, launch the desktop app with `neovide` from a terminal — if tha
| `<leader>se` | Normal | Equalize split sizes |
| `<leader>sx` | Normal | Close current split |
## Multiple cursors (`multicursor.nvim`)
[`multicursor.nvim`](https://github.com/jake-stewart/multicursor.nvim) (`lua/plugins/multicursor.lua`). The idea: add cursors, then use Vim normally — motions, operators, insert mode, macros and undo all act on every cursor at once. `<Esc>` collapses back to a single cursor.
The everyday actions sit on Ctrl keys (VS Code-style); the rest live under a `<leader>m` "Multicursor" group, so they show up in the which-key popup. These differ from the plugin's own README example on purpose — its `<leader>n/s/S/x` would collide with the `<leader>s…` Splits group, and bare `<Up>`/`<Down>` are ordinary movement here.
**Adding cursors**
| Key | Mode | Action |
|---|---|---|
| `<C-n>` / `<C-p>` | Normal/Visual | Add a cursor at the next / previous match of the word under the cursor (or of the visual selection) — like Ctrl+D in VS Code |
| `<C-Down>` / `<C-Up>` | Normal/Visual | Add a cursor on the line below / above |
| `<C-LeftMouse>` | Normal | Add or remove a cursor by clicking; `Ctrl`+drag adds a column of them |
| `<C-q>` | Normal/Visual | Disable the cursors so only the main one moves; press it again to drop a cursor where the main one is, so move → `<C-q>` → move → `<C-q>` places cursors anywhere. `<Esc>` re-enables them. (This shadows `<C-q>` as the alias for `<C-v>`; `<C-v>` itself is untouched.) |
| `ga{motion}` | Normal/Visual | Add a cursor on every line of the motion — `gaip` for a paragraph; in Visual mode, every selected line |
| `<leader>ma` | Normal/Visual | Add cursors at **all** matches of the word/selection in the file |
| `<leader>ms` / `<leader>mS` | Normal/Visual | Skip the next / previous match (jump there without adding a cursor) |
| `<leader>mj` / `<leader>mk` | Normal/Visual | Skip the line below / above (move the main cursor without adding one) |
| `<leader>mr` | Normal | Bring the cursors back if you cleared them by accident |
| `I` / `A` | Visual | Insert at the start / append at the end of every selected line — like block-selection `I`/`A`, but for any selection |
**Managing cursors** (these only exist while there is more than one cursor)
| Key | Mode | Action |
|---|---|---|
| `<leader>mh` / `<leader>ml` | Normal/Visual | Make the previous / next cursor the main one |
| `<leader>mx` | Normal/Visual | Delete the main cursor, keeping the rest |
| `g<C-a>` / `g<C-x>` | Normal/Visual | Increment / decrement treating the cursors as one sequence (1, 2, 3, … down a column) |
| `<Esc>` | Normal | Re-enable disabled cursors, otherwise collapse to one (also clears the search highlight, like the global `<Esc>`) |
Typical uses: `<C-n>` a few times on a variable name, then `ciw` to rename those occurrences; `Vjjj` + `A` to append to several lines; `<leader>ma` then `cw` to rename every occurrence; `gaip` then `I` to prefix a whole paragraph.
Notes:
- The cursors appear as reversed-color blocks (`MultiCursorCursor`); the main cursor is the one your terminal / Neovide draws.
- Some terminals and window managers intercept `Ctrl`+click or `Ctrl`+arrow chords. `<C-n>` and `<C-q>` depend on neither.
- The plugin lists autocompletion as supported, but it can only replay snippets expanded through Neovim's built-in `vim.snippet.expand`, and this config's snippets come from LuaSnip via nvim-cmp (as does the custom insert-mode `<CR>` comment continuation) — so if completion, snippets or Enter behave oddly with several cursors, press `<Esc>` to collapse back to one and do that part with a single cursor.
## Look & feel
- **Theme**: [`vscode.nvim`](https://github.com/Mofiqul/vscode.nvim) (dark variant, VS Code Dark+ palette), on a near-black `#090909` background (explorer, tabs, statusline and popups use near-black shades too, not VS Code's grays), with macros in Visual Studio's muted purple (`#BEB7FF`) and custom tinted backgrounds for error/warn/info/hint diagnostic virtual lines. lualine uses the plugin's own `vscode` theme.
@@ -311,6 +412,8 @@ Default launch configs prompt for a path to the executable, or let you attach to
Neovim's embedded terminal launches **fish** instead of your login shell, purely for its built-in inline "ghost text" suggestions for paths and command history (no config needed, accept with `→`/`End`) — a terminal-side equivalent of the completion ghost text above, since a `:terminal` buffer is a raw PTY that `nvim-cmp` has no visibility into. Your actual system login shell is untouched.
On **Windows** there is no native fish, so the embedded terminal launches **PowerShell 7** (`pwsh`) instead — PSReadLine's grey inline prediction from your command history is on by default there, which is the same idea — falling back to Windows PowerShell if `pwsh` isn't installed.
## Git
`gitsigns` shows added/changed/removed lines in the sign column against the current git state.
@@ -388,6 +491,27 @@ The mapping also makes sure the buffer is actually loaded in binary mode before
`<leader>lf` (LSP format) only consumes `.clang-format` — clangd's formatting is pure clang-format and has nothing to do with clang-tidy. `.clang-tidy` is picked up two separate ways: automatically by clangd for live diagnostics (clangd's clang-tidy integration is explicitly enabled via `--clang-tidy`, alongside `--background-index` so checks run against the whole project's semantic info rather than just what's open), and directly by `<leader>lt`, which shells out to the real `clang-tidy --fix` on the file instead of going through clangd. That's a deliberate difference, not an oversight: clangd filters code actions by LSP "kind", and clang-tidy fixes only ever surface from clangd as individual `quickfix` actions — one per diagnostic — never as a single batched "fix everything" action, because clangd doesn't implement the `source.fixAll` kind that would take ([clangd/clangd#1446](https://github.com/clangd/clangd/issues/1446)). Asking for that kind specifically is why this used to just return "No code actions available" no matter what the file contained. Running the actual `clang-tidy` binary sidesteps that entirely and does what the key was always meant to: fix everything the tool can in one pass. See the [LSP table](#lsp--code-intelligence) above for the compile-database caveat that governs how much either `<leader>la` or `<leader>lt` can actually catch.
## Windows notes
Everything under `config/nvim/` is shared between Linux and Windows. The handful of places that had Linux assumptions now branch on `vim.fn.has("win32")`, so nothing changes on Linux:
- **Embedded terminal** — `pwsh` instead of `fish` (`plugins/toggleterm.lua`).
- **Clipboard** — the OSC 52 override in `init.lua` (meant for SSH) is skipped on Windows, where Neovim's native clipboard provider is used instead. OSC 52 can't work in Neovide at all, since Neovide isn't a terminal. Neovim's Windows build bundles both `win32yank` (clipboard) and `xxd` (the [hex viewer](#hex-viewer)).
- **nvim-tree `gM`** (copy marked files with a regex rename) — Windows has no `cp`, so a Lua recursive copy is used there. One difference: if the target folder already exists, contents are merged into it instead of the source being nested inside it.
- **DB UI save location** — `%LOCALAPPDATA%\db_ui\connections.json` instead of `~/.local/share/db_ui/`; still outside both folders the setup script wipes. Still plain text, password included.
- **Dashboard "Edit Config"** — opens `init.lua` from wherever Neovim's config actually lives (`:echo stdpath("config")`).
- **CMake debug template** — appends `.exe` to the target names (`lua/dap/configurations/cmake.lua`).
Things worth knowing:
- **Use the Ninja generator for CMake** — e.g. `cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`. Multi-config generators (Visual Studio) put binaries under `build\Debug\...`, which the debug template's `build/...` paths don't expect. The template also mentions `scripts/build.sh`, a bash script — run it from Git Bash, or call `cmake` directly.
- **MSVC isn't installed.** The setup uses GCC (WinLibs) because it's a single unattended install; if you want `cl.exe`, install the Visual Studio Build Tools yourself.
- **Treesitter parsers need a C compiler on `PATH`.** If `:TSInstall` complains, check `gcc --version` in a fresh terminal. The script puts WinLibs' real `bin` folder on your user `PATH` for this reason.
- **Fonts:** set your terminal's font face to `JetBrainsMono Nerd Font` (Windows Terminal: Settings → Profiles → Defaults → Appearance) and restart it. Neovide picks the font up from `init.lua` on its own.
- **sqls config** lives in your user config folder — on Windows `%APPDATA%\sqls\config.yml` rather than `~/.config/sqls/config.yml` (sqls uses Go's per-OS user config directory; if the file isn't picked up, check the sqls docs). The Unix-socket DSN form from the [Language server (sqls)](#language-server-sqls) section doesn't apply; use TCP. For credentials outside the URL, use a MariaDB option file — see MariaDB's docs for where the Windows client looks.
- **`.clang-format` / `.clang-tidy` fallbacks** are in `%USERPROFILE%`, and both tools only walk *upward* from the file being processed — so projects on another drive (`D:\...`) won't pick them up. Copy them to the project, or to that drive's root, if so.
- **`MariaDB`**: the client's `bin` folder is added to your user `PATH` if the installer didn't do it. `mariadb --version` in a new terminal confirms it.
## Known quirks
- **`nvim-treesitter` is pinned to its `master` branch**, not the current upstream default (`main`). `main` is an incompatible rewrite that removed the `nvim-treesitter.configs` API this config (and most existing configs/plugins) are built on. `master` is kept frozen upstream specifically for this kind of backward compatibility.
+7
View File
@@ -113,6 +113,12 @@ vim.api.nvim_create_autocmd("FileType", {
})
-- SSH Clipboard Support (OSC 52)
-- Skipped on native Windows: Neovim's own clipboard provider (win32yank,
-- bundled with the Windows builds) works there for both the terminal and
-- Neovide, whereas OSC 52 can't work at all in Neovide (it isn't a
-- terminal). Over SSH you'd be running the Linux config on the remote
-- machine anyway.
if vim.fn.has('win32') == 0 then
vim.g.clipboard = {
name = 'OSC 52',
copy = {
@@ -126,6 +132,7 @@ vim.g.clipboard = {
['*'] = function() return { vim.fn.getreg('"', 1, true), vim.fn.getregtype('"') } end,
},
}
end
-- GENERAL KEYMAPS --
@@ -82,6 +82,9 @@ local function shared_lib_ext()
end
return '.so'
end
local function exe_ext()
return vim.loop.os_uname().sysname == 'Windows_NT' and '.exe' or ''
end
local function fixture_lib_path()
return build_dir() .. '/tests/' .. shared_lib_prefix() .. 'dynlib_test_fixture' .. shared_lib_ext()
end
@@ -97,7 +100,7 @@ local configurations = {
type = 'codelldb',
request = 'launch',
program = function()
return build_dir() .. '/tests/test_dynamic_library'
return build_dir() .. '/tests/test_dynamic_library' .. exe_ext()
end,
args = function()
return { fixture_lib_path() }
@@ -112,7 +115,7 @@ local configurations = {
type = 'codelldb',
request = 'launch',
program = function()
return build_dir() .. '/demo/example_host'
return build_dir() .. '/demo/example_host' .. exe_ext()
end,
args = function()
return { demo_plugin_path() }
@@ -127,7 +130,7 @@ local configurations = {
type = 'codelldb',
request = 'launch',
program = function()
return build_dir() .. '/demo/example_host'
return build_dir() .. '/demo/example_host' .. exe_ext()
end,
args = function()
local path = vim.fn.input('Plugin path: ', demo_plugin_path(), 'file')
+1 -1
View File
@@ -23,7 +23,7 @@ return {
dashboard.button("r", " Recent Files", "<cmd>Telescope oldfiles<CR>"),
dashboard.button("w", " Live Grep", "<cmd>Telescope live_grep<CR>"),
dashboard.button("e", " New File", "<cmd>ene<CR>"),
dashboard.button("c", " Edit Config", "<cmd>edit ~/.config/nvim/init.lua<CR>"),
dashboard.button("c", " Edit Config", "<cmd>edit " .. vim.fn.fnameescape(vim.fs.normalize(vim.fn.stdpath("config")) .. "/init.lua") .. "<CR>"),
dashboard.button("q", " Quit", "<cmd>qa<CR>"),
}
@@ -62,10 +62,17 @@ return {
-- plain text in connections.json here, password included -- leave the
-- password out of the URL and put it in ~/.my.cnf's [client] section
-- if that bothers you (the mariadb client reads it from there).
-- (On Windows that means %LOCALAPPDATA%\db_ui -- Neovim itself uses
-- %LOCALAPPDATA%\nvim and %LOCALAPPDATA%\nvim-data there, so it's
-- outside both of those too.)
local data_home = vim.env.XDG_DATA_HOME
if not data_home or data_home == "" then
if vim.fn.has("win32") == 1 then
data_home = vim.env.LOCALAPPDATA or (vim.env.USERPROFILE .. "\\AppData\\Local")
else
data_home = vim.env.HOME .. "/.local/share"
end
end
vim.g.db_ui_save_location = data_home .. "/db_ui"
-- Table helpers for mariadb:// connections: the entries listed under
@@ -0,0 +1,112 @@
return {
-- Multiple cursors (multicursor.nvim). Add cursors, then use Vim
-- normally -- motions, operators, insert mode, macros and undo all act
-- on every cursor at once. Esc collapses back to one cursor.
--
-- The key layout below deliberately differs from the plugin's README
-- example, which would collide with this config: <leader>n/s/S/x clash
-- with the <leader>s* "Splits" group, and bare <Up>/<Down> are normal
-- cursor movement. Instead, the everyday actions sit on Ctrl keys
-- (VS Code-style), and the rest live under a <leader>m "Multicursor"
-- group (registered in which-key.lua) so they show up in the popup.
--
-- <leader>m is NOT also bound to the plugin's own `operator` action
-- (which its README suggests) -- that would make every <leader>m...
-- key ambiguous. `ga{motion}` (below) covers the same ground.
{
"jake-stewart/multicursor.nvim",
branch = "1.0",
event = "VeryLazy",
config = function()
local mc = require("multicursor-nvim")
mc.setup()
local set = vim.keymap.set
-- Add cursors: above / below the main cursor.
set({ "n", "x" }, "<C-Up>", function() mc.lineAddCursor(-1) end, { desc = "Add cursor above" })
set({ "n", "x" }, "<C-Down>", function() mc.lineAddCursor(1) end, { desc = "Add cursor below" })
-- Add cursors: at the next / previous match of the word under the
-- cursor, or of the visual selection (like Ctrl+D in VS Code).
set({ "n", "x" }, "<C-n>", function() mc.matchAddCursor(1) end, { desc = "Add cursor at next match" })
set({ "n", "x" }, "<C-p>", function() mc.matchAddCursor(-1) end, { desc = "Add cursor at previous match" })
-- Add cursors with the mouse: Ctrl+click adds or removes one, and
-- Ctrl+drag adds a column of them. (If your terminal swallows
-- Ctrl+click, use <C-q> below instead.)
set("n", "<C-LeftMouse>", mc.handleMouse, { desc = "Add/remove cursor" })
set("n", "<C-LeftDrag>", mc.handleMouseDrag)
set("n", "<C-LeftRelease>", mc.handleMouseRelease)
-- Disable the cursors so only the main one moves; while disabled,
-- pressing this again drops a cursor at the main cursor's position.
-- Move, press, move, press... places cursors anywhere. <Esc>
-- re-enables them. (Shadows Neovim's <C-q> alias for <C-v>;
-- <C-v> itself is untouched.)
set({ "n", "x" }, "<C-q>", mc.toggleCursor, { desc = "Toggle cursor (place / disable)" })
-- `gaip` adds a cursor on every line of a paragraph (any motion or
-- text object works); in Visual mode, on every selected line.
set({ "n", "x" }, "ga", mc.addCursorOperator, { desc = "Add cursors over motion" })
-- Visual mode: insert at the start / append at the end of every
-- selected line, like a block-selection I/A but for any selection.
set("x", "I", mc.insertVisual, { desc = "Insert on each selected line" })
set("x", "A", mc.appendVisual, { desc = "Append on each selected line" })
-- <leader>m group (see which-key.lua).
set({ "n", "x" }, "<leader>ms", function() mc.matchSkipCursor(1) end, { desc = "Skip next match" })
set({ "n", "x" }, "<leader>mS", function() mc.matchSkipCursor(-1) end, { desc = "Skip previous match" })
set({ "n", "x" }, "<leader>mj", function() mc.lineSkipCursor(1) end, { desc = "Skip line below" })
set({ "n", "x" }, "<leader>mk", function() mc.lineSkipCursor(-1) end, { desc = "Skip line above" })
set({ "n", "x" }, "<leader>ma", mc.matchAllAddCursors, { desc = "Add cursors at ALL matches" })
set("n", "<leader>mr", mc.restoreCursors, { desc = "Restore cleared cursors" })
-- Mappings in a keymap layer only exist while there is more than
-- one cursor, so they can overlap with ordinary mappings.
mc.addKeymapLayer(function(layerSet)
-- Make a different cursor the main one.
layerSet({ "n", "x" }, "<leader>mh", mc.prevCursor, { desc = "Previous cursor" })
layerSet({ "n", "x" }, "<leader>ml", mc.nextCursor, { desc = "Next cursor" })
-- Drop the main cursor, keeping the rest.
layerSet({ "n", "x" }, "<leader>mx", mc.deleteCursor, { desc = "Delete main cursor" })
-- Treat all the cursors as one sequence: 1, 2, 3, ... down the
-- column. Kept in the layer so Vim's own Visual-mode g<C-a> /
-- g<C-x> are untouched when there is only one cursor.
layerSet({ "n", "x" }, "g<C-a>", mc.sequenceIncrement, { desc = "Increment as sequence" })
layerSet({ "n", "x" }, "g<C-x>", mc.sequenceDecrement, { desc = "Decrement as sequence" })
-- Esc: re-enable disabled cursors, otherwise collapse to one. This
-- shadows the global <Esc> (clear search highlight) while the
-- layer is active, so it does that too.
layerSet("n", "<Esc>", function()
vim.cmd.nohlsearch()
if not mc.cursorsEnabled() then
mc.enableCursors()
else
mc.clearCursors()
end
end, { desc = "Collapse cursors" })
end)
-- Cursor look. `reverse` stays visible on any background (this
-- theme is transparent in the terminal), and a theme change would
-- otherwise wipe these, so they're re-applied on ColorScheme.
local function set_highlights()
local hl = vim.api.nvim_set_hl
hl(0, "MultiCursorCursor", { reverse = true })
hl(0, "MultiCursorVisual", { link = "Visual" })
hl(0, "MultiCursorSign", { link = "SignColumn" })
hl(0, "MultiCursorMatchPreview", { link = "Search" })
hl(0, "MultiCursorDisabledCursor", { reverse = true })
hl(0, "MultiCursorDisabledVisual", { link = "Visual" })
hl(0, "MultiCursorDisabledSign", { link = "SignColumn" })
end
set_highlights()
vim.api.nvim_create_autocmd("ColorScheme", { callback = set_highlights })
end,
},
}
+48 -3
View File
@@ -1,3 +1,48 @@
-- Recursive copy used by the gM mapping below. Unix keeps using `cp -r`
-- (unchanged behavior); Windows has no `cp`, so it gets an equivalent
-- written against libuv. One difference there: if the destination
-- directory already exists, contents are merged into it rather than the
-- source being nested inside it, which is what `cp -r` would do.
-- Returns true on success, or false plus an error message.
local function copy_path(src, dest)
if vim.fn.has("win32") == 0 then
local result = vim.fn.system({ "cp", "-r", src, dest })
return vim.v.shell_error == 0, result
end
local uv = vim.uv or vim.loop
local function copy(s, d)
local stat = uv.fs_stat(s)
if not stat then
return false, "no such file or directory: " .. s
end
if stat.type == "directory" then
local made, mkerr = uv.fs_mkdir(d, 493) -- 0755
if not made then
local existing = uv.fs_stat(d)
if not (existing and existing.type == "directory") then
return false, mkerr
end
end
local handle = uv.fs_scandir(s)
while handle do
local name = uv.fs_scandir_next(handle)
if not name then
break
end
local ok, err = copy(s .. "/" .. name, d .. "/" .. name)
if not ok then
return false, err
end
end
return true
end
local ok, err = uv.fs_copyfile(s, d)
return ok and true or false, err
end
return copy(src, dest)
end
return {
-- NvimTree (On the right)
{
@@ -198,9 +243,9 @@ return {
-- instead of surfacing cp's raw "same file" error.
vim.notify("Skipped " .. base .. ": pattern didn't match, and destination is the same as source", vim.log.levels.WARN)
else
local result = vim.fn.system({ "cp", "-r", node.absolute_path, dest_path })
if vim.v.shell_error ~= 0 then
vim.notify("Copy failed for " .. base .. ": " .. result, vim.log.levels.ERROR)
local copied, result = copy_path(node.absolute_path, dest_path)
if not copied then
vim.notify("Copy failed for " .. base .. ": " .. tostring(result), vim.log.levels.ERROR)
end
end
end
+11 -1
View File
@@ -9,8 +9,18 @@ return {
-- built in (paths, directory contents, and history) with zero
-- config -- accept with -> or <End>. This only affects terminals
-- opened from Neovim; your system login shell is untouched.
--
-- Windows: fish has no native Windows build, so the terminal
-- launches PowerShell 7 (pwsh) instead -- it has the same kind of
-- grey inline prediction (PSReadLine, from command history) on by
-- default -- falling back to Windows PowerShell 5.1 if pwsh isn't
-- installed. setup_neovim.ps1 installs pwsh.
local shell = "fish"
if vim.fn.has("win32") == 1 then
shell = vim.fn.executable("pwsh") == 1 and "pwsh" or "powershell"
end
require("toggleterm").setup({
shell = "fish",
shell = shell,
})
vim.keymap.set('n', '<leader>tf', '<cmd>ToggleTerm direction=float<cr>', { desc = "Floating Terminal" })
vim.keymap.set('n', '<leader>th', '<cmd>ToggleTerm direction=horizontal<cr>', { desc = "Bottom Terminal" })
@@ -14,6 +14,7 @@ return {
{ "<leader>c", group = "Comments" },
{ "<leader>f", group = "Find / Grep" },
{ "<leader>l", group = "LSP" },
{ "<leader>m", group = "Multicursor" },
{ "<leader>o", group = "OOP" },
{ "<leader>s", group = "Splits" },
{ "<leader>t", group = "Terminal" },
+445
View File
@@ -0,0 +1,445 @@
<#
.SYNOPSIS
Windows counterpart of setup_neovim.sh: installs Neovim plus the tooling
this config relies on, then copies config/ into place.
.DESCRIPTION
Same job as the Linux script -- install dependencies, install fonts,
wipe the old Neovim config, copy the new one -- using winget instead of
dnf/apt/pacman. Nothing here needs an administrator shell: winget
installs per-user where it can (individual installers such as LLVM or
the .NET SDK may still show their own UAC prompt), fonts go into your
per-user font folder, and npm packages go into %APPDATA%\npm.
config\ is shared with the Linux script and has to sit next to this file.
It is copied as-is; edit those files, not this script, to customize.
Run it from PowerShell (Windows PowerShell 5.1 or PowerShell 7):
powershell -ExecutionPolicy Bypass -File .\setup_neovim.ps1
.PARAMETER Frontend
terminal | desktop | both. Which Neovim frontend(s) to set up. Neovide
(desktop) is a GUI that runs the real nvim as its backend, so nvim is
installed either way. If omitted, you're asked (or 'terminal' is used
when the shell is non-interactive).
.PARAMETER Backup
Move the existing Neovim config/data folders aside (name.bak-<timestamp>)
instead of deleting them.
.PARAMETER Force
Don't ask for confirmation before deleting the existing Neovim config
and data folders.
.PARAMETER SkipMariaDB
Don't offer to install the MariaDB client (used by vim-dadbod).
#>
[CmdletBinding()]
param(
[ValidateSet('terminal', 'desktop', 'both')]
[string]$Frontend,
[switch]$Backup,
[switch]$Force,
[switch]$SkipMariaDB
)
# 'Continue' on purpose: native commands (winget, npm) write progress to
# stderr, and 'Stop' would turn that into terminating errors in Windows
# PowerShell 5.1. Anything that must abort uses -ErrorAction Stop / throw.
$ErrorActionPreference = 'Continue'
$ProgressPreference = 'SilentlyContinue' # makes Invoke-WebRequest much faster on 5.1
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
# ---------------------------------------------------------------------
# Locate config/ next to this script
# ---------------------------------------------------------------------
$ScriptDir = $PSScriptRoot
if (-not $ScriptDir) { $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path }
$ConfigDir = Join-Path $ScriptDir 'config'
if ($env:OS -ne 'Windows_NT') {
Write-Host 'ERROR: This script is for Windows. On Linux, use setup_neovim.sh.' -ForegroundColor Red
exit 1
}
if (-not (Test-Path $ConfigDir -PathType Container)) {
Write-Host "ERROR: $ConfigDir not found." -ForegroundColor Red
Write-Host 'This script expects a config\ folder next to it (keep the two together'
Write-Host "however you copy/clone this -- e.g. don't move setup_neovim.ps1 on its own)."
exit 1
}
# ---------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------
function Write-Warn([string]$Message) {
Write-Host "WARNING: $Message" -ForegroundColor Yellow
}
function Test-Command([string]$Name) {
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
}
# winget updates the machine/user PATH in the registry, but this already
# running session never sees that. Re-read it (keeping anything that was
# only set in this session) so freshly installed tools can be found.
function Update-SessionPath {
$fresh = @(
[Environment]::GetEnvironmentVariable('Path', 'Machine'),
[Environment]::GetEnvironmentVariable('Path', 'User')
) -join ';'
$seen = @{}
$parts = New-Object System.Collections.Generic.List[string]
foreach ($p in (($fresh + ';' + $env:Path) -split ';')) {
$t = $p.Trim()
if ($t -and -not $seen.ContainsKey($t.ToLowerInvariant())) {
$seen[$t.ToLowerInvariant()] = $true
$parts.Add($t)
}
}
$env:Path = $parts -join ';'
}
# Persistently add a folder to the user PATH (and this session's), if it
# exists and isn't already there. Used for the few installers that don't
# put their own bin folder on PATH.
function Add-ToUserPath([string]$Dir, [switch]$Prepend) {
if (-not (Test-Path $Dir -PathType Container)) { return }
$user = [Environment]::GetEnvironmentVariable('Path', 'User')
$entries = @()
if ($user) { $entries = @($user -split ';' | Where-Object { $_ }) }
$already = $entries | Where-Object { $_.TrimEnd('\') -ieq $Dir.TrimEnd('\') }
if ($already) { return }
if ($Prepend) { $entries = @($Dir) + $entries } else { $entries = $entries + @($Dir) }
[Environment]::SetEnvironmentVariable('Path', ($entries -join ';'), 'User')
Write-Host " Added $Dir to your user PATH."
Update-SessionPath
}
# Returns $true if the package is (now) installed. -Required aborts the
# whole script on failure; everything else is best-effort, like the
# .NET/Neovide steps in the Linux script: a WARNING, not a reason to stop.
function Install-WingetPackage {
param(
[Parameter(Mandatory = $true)][string]$Id,
[Parameter(Mandatory = $true)][string]$Name,
[string]$Command,
[switch]$Required,
[switch]$Interactive
)
if ($Command -and (Test-Command $Command)) {
Write-Host " $Name already installed ($Command found on PATH)."
return $true
}
& winget list --id $Id --exact --accept-source-agreements *> $null
if ($LASTEXITCODE -eq 0) {
Write-Host " $Name already installed."
return $true
}
Write-Host " Installing $Name ($Id)..."
$wingetArgs = @('install', '--id', $Id, '--exact', '--accept-package-agreements', '--accept-source-agreements')
if ($Interactive) { $wingetArgs += '--interactive' } else { $wingetArgs += '--silent' }
& winget @wingetArgs | Out-Host
if ($LASTEXITCODE -ne 0) {
$msg = "winget could not install $Name ($Id); exit code $LASTEXITCODE."
if ($Required) { throw $msg }
Write-Warn "$msg Install it manually, then re-run this script."
return $false
}
return $true
}
# Per-user font install (no admin needed): copy into
# %LOCALAPPDATA%\Microsoft\Windows\Fonts and register under HKCU.
function Install-NerdFont {
param(
[Parameter(Mandatory = $true)][string]$Label,
[Parameter(Mandatory = $true)][string]$ZipName,
[Parameter(Mandatory = $true)][string]$SentinelFile
)
$fontDir = Join-Path $env:LOCALAPPDATA 'Microsoft\Windows\Fonts'
if (Test-Path (Join-Path $fontDir $SentinelFile)) {
Write-Host "$Label Nerd Font already installed."
return
}
$tmpZip = Join-Path $env:TEMP "$ZipName.zip"
$tmpDir = Join-Path $env:TEMP "$ZipName-extract"
try {
Invoke-WebRequest -Uri "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/$ZipName.zip" -OutFile $tmpZip -UseBasicParsing -ErrorAction Stop
if (Test-Path $tmpDir) { Remove-Item $tmpDir -Recurse -Force }
Expand-Archive -Path $tmpZip -DestinationPath $tmpDir -Force -ErrorAction Stop
New-Item -ItemType Directory -Force -Path $fontDir | Out-Null
$regPath = 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts'
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
$count = 0
Get-ChildItem -Path $tmpDir -Recurse -Include *.ttf, *.otf | ForEach-Object {
$dest = Join-Path $fontDir $_.Name
Copy-Item -LiteralPath $_.FullName -Destination $dest -Force -ErrorAction Stop
$kind = 'TrueType'
if ($_.Extension -ieq '.otf') { $kind = 'OpenType' }
New-ItemProperty -Path $regPath -Name "$($_.BaseName) ($kind)" -Value $dest -PropertyType String -Force | Out-Null
$count++
}
Write-Host "$Label Nerd Font installed ($count files)."
}
catch {
Write-Warn "$Label Nerd Font install failed: $($_.Exception.Message)"
Write-Host " Download it manually from https://www.nerdfonts.com/font-downloads"
}
finally {
Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
$IsInteractive = [Environment]::UserInteractive -and -not [Console]::IsInputRedirected
Write-Host ''
Write-Host '========================================='
Write-Host ' Starting Neovim Setup (Windows)'
Write-Host '========================================='
# ---------------------------------------------------------------------
# 1. System dependencies (winget)
# ---------------------------------------------------------------------
Write-Host '[1/4] Installing system dependencies with winget...'
if (-not (Test-Command 'winget')) {
Write-Host 'ERROR: winget was not found.' -ForegroundColor Red
Write-Host 'winget ships with Windows 10 (1809+) and Windows 11 as "App Installer".'
Write-Host 'Install or update it from the Microsoft Store, then re-run this script:'
Write-Host ' https://aka.ms/getwinget'
exit 1
}
# Which Neovim frontend(s) to set up -- same question, same meaning as in
# the Linux script. Neovide always runs the real nvim as its backend, so
# nvim gets installed either way; this only decides whether Neovide goes
# on top. Both frontends share the exact same config.
if (-not $Frontend) {
$Frontend = 'terminal'
if ($IsInteractive) {
Write-Host ''
Write-Host 'Which Neovim frontend(s) do you want installed?'
Write-Host ' 1) Terminal-based only (nvim in Windows Terminal / PowerShell)'
Write-Host ' 2) Desktop application only (Neovide GUI -- still needs the nvim'
Write-Host ' binary as its backend, so that is installed either way)'
Write-Host ' 3) Both'
$choice = Read-Host 'Enter 1, 2, or 3 [1]'
switch ($choice) {
'2' { $Frontend = 'desktop' }
'3' { $Frontend = 'both' }
default { $Frontend = 'terminal' }
}
}
else {
Write-Host 'Non-interactive shell detected -- defaulting to terminal-based Neovim only.'
Write-Host 'Run this script directly (or pass -Frontend desktop|both) to also install Neovide.'
}
}
# Required: nothing else works without these two.
[void](Install-WingetPackage -Id 'Neovim.Neovim' -Name 'Neovim' -Command 'nvim' -Required)
[void](Install-WingetPackage -Id 'Git.Git' -Name 'Git' -Command 'git' -Required)
# Build tools. WinLibs is a standalone GCC/MinGW-w64 build that also
# brings gdb and make -- the Windows stand-in for gcc/g++/make/gdb, and
# what nvim-treesitter uses to compile its parsers. Ninja is the CMake
# generator that goes with it (plain `make` isn't what you want on
# Windows). The official LLVM installer provides clangd's companions
# clang-format and clang-tidy (.clang-format / .clang-tidy, <leader>lt).
[void](Install-WingetPackage -Id 'Kitware.CMake' -Name 'CMake' -Command 'cmake')
[void](Install-WingetPackage -Id 'Ninja-build.Ninja' -Name 'Ninja' -Command 'ninja')
[void](Install-WingetPackage -Id 'BrechtSanders.WinLibs.POSIX.UCRT' -Name 'GCC / MinGW-w64 (WinLibs)' -Command 'gcc')
[void](Install-WingetPackage -Id 'LLVM.LLVM' -Name 'LLVM (clang-format, clang-tidy)' -Command 'clang-tidy')
[void](Install-WingetPackage -Id 'GoLang.Go' -Name 'Go' -Command 'go')
[void](Install-WingetPackage -Id 'Microsoft.DotNet.SDK.8' -Name '.NET SDK 8.0' -Command 'dotnet')
# Runtimes Mason needs to install some language servers.
[void](Install-WingetPackage -Id 'OpenJS.NodeJS.LTS' -Name 'Node.js (LTS)' -Command 'node')
[void](Install-WingetPackage -Id 'Python.Python.3.12' -Name 'Python 3.12')
# CLI tools. (curl, tar and unzip-equivalents ship with Windows; xxd and
# the clipboard helper come bundled with Neovim's Windows build.) PowerShell
# 7 takes fish's place for Neovim's embedded terminal -- fish has no native
# Windows build -- see config/nvim/lua/plugins/toggleterm.lua.
[void](Install-WingetPackage -Id 'BurntSushi.ripgrep.MSVC' -Name 'ripgrep' -Command 'rg')
[void](Install-WingetPackage -Id 'sharkdp.fd' -Name 'fd' -Command 'fd')
[void](Install-WingetPackage -Id 'Microsoft.PowerShell' -Name 'PowerShell 7' -Command 'pwsh')
# MariaDB *client* (vim-dadbod shells out to the `mariadb` binary). winget
# only has the full installer (server + client), so this one runs the
# installer's own wizard rather than silently installing a database
# server as a Windows service. Pick the client programs and untick the
# server/database-instance components if you only want the client.
$InstallMariaDB = $false
if (-not $SkipMariaDB) {
if ($IsInteractive) {
Write-Host ''
Write-Host 'The MariaDB client (needed for the Database features, <leader>Du) is only'
Write-Host 'available through the full MariaDB installer. Its setup wizard lets you'
Write-Host 'untick the server and keep just the client programs.'
$answer = Read-Host 'Run the MariaDB installer now? [y/N]'
if ($answer -match '^[Yy]') { $InstallMariaDB = $true }
}
else {
Write-Host 'Skipping MariaDB client (needs the interactive installer wizard).'
}
}
if ($InstallMariaDB) {
[void](Install-WingetPackage -Id 'MariaDB.Server' -Name 'MariaDB' -Interactive)
}
# Neovide, if chosen above.
if ($Frontend -eq 'desktop' -or $Frontend -eq 'both') {
Write-Host 'Installing Neovide (desktop Neovim GUI)...'
[void](Install-WingetPackage -Id 'Neovide.Neovide' -Name 'Neovide' -Command 'neovide')
}
# Newly installed tools aren't on this session's PATH yet.
Update-SessionPath
# A few installers don't add their bin folder to PATH themselves.
# WinLibs: put the real mingw64\bin first -- GCC locates its own helper
# programs relative to its executable, which a launcher shim can break.
$winlibsRoot = Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages'
if (Test-Path $winlibsRoot) {
Get-ChildItem -Path $winlibsRoot -Directory -Filter 'BrechtSanders.WinLibs.POSIX.UCRT*' -ErrorAction SilentlyContinue | ForEach-Object {
foreach ($sub in 'mingw64', 'mingw32') {
$bin = Join-Path $_.FullName "$sub\bin"
if (Test-Path (Join-Path $bin 'gcc.exe')) { Add-ToUserPath $bin -Prepend }
}
}
}
if (-not (Test-Command 'clang-tidy')) { Add-ToUserPath (Join-Path $env:ProgramFiles 'LLVM\bin') }
if (-not (Test-Command 'mariadb')) {
Get-ChildItem -Path $env:ProgramFiles -Directory -Filter 'MariaDB*' -ErrorAction SilentlyContinue | ForEach-Object {
Add-ToUserPath (Join-Path $_.FullName 'bin')
}
}
# This config relies on fairly recent Neovim APIs (vim.lsp.config/enable,
# virtual_lines diagnostics, vim.o.winborder -- all 0.11+). Warn (don't
# fail) if what got installed is too old.
if (Test-Command 'nvim') {
$verLine = (& nvim --version | Select-Object -First 1)
if ($verLine -match 'v(\d+)\.(\d+)') {
if ([int]$Matches[1] -eq 0 -and [int]$Matches[2] -lt 11) {
Write-Warn "Installed Neovim is v$($Matches[1]).$($Matches[2]), but this config needs 0.11+."
Write-Host ' Run: winget upgrade Neovim.Neovim'
}
}
# Hex view (xxd) and the system clipboard (win32yank) both come bundled
# with Neovim's Windows build, next to nvim.exe.
$nvimBin = Split-Path (Get-Command nvim).Source -Parent
foreach ($tool in 'xxd.exe', 'win32yank.exe') {
if (-not ((Test-Path (Join-Path $nvimBin $tool)) -or (Test-Command $tool))) {
Write-Warn "$tool was not found next to nvim.exe or on PATH. Hex view (xxd) / clipboard (win32yank) may not work."
}
}
}
else {
Write-Warn 'nvim is not on PATH in this session yet -- open a new terminal after the script finishes.'
}
# Global NPM packages for the JS/TS, HTML and CSS language servers.
# npm.cmd explicitly: plain `npm` resolves to npm.ps1, which the default
# PowerShell execution policy refuses to run.
Write-Host 'Installing NPM-based LSPs...'
if (Test-Command 'npm.cmd') {
& npm.cmd install -g typescript typescript-language-server vscode-langservers-extracted | Out-Host
if ($LASTEXITCODE -ne 0) { Write-Warn 'npm install -g failed -- JS/TS/HTML/CSS language servers may be missing.' }
Add-ToUserPath (Join-Path $env:APPDATA 'npm')
}
else {
Write-Warn 'npm was not found (Node.js install failed?). Skipping the NPM-based LSPs.'
}
# ---------------------------------------------------------------------
# 2. Nerd Fonts (JetBrainsMono and Cascadia Code)
# ---------------------------------------------------------------------
Write-Host '[2/4] Installing JetBrainsMono and Cascadia Code Nerd Fonts...'
Install-NerdFont -Label 'JetBrainsMono' -ZipName 'JetBrainsMono' -SentinelFile 'JetBrainsMonoNerdFont-Regular.ttf'
Install-NerdFont -Label 'Cascadia Code' -ZipName 'CascadiaCode' -SentinelFile 'CaskaydiaCoveNerdFont-Regular.ttf'
# ---------------------------------------------------------------------
# 3. Remove (or back up) the existing Neovim config and data
# ---------------------------------------------------------------------
Write-Host '[3/4] Removing existing Neovim configurations...'
# Same locations Neovim itself uses on Windows: %LOCALAPPDATA%\nvim for
# config and %LOCALAPPDATA%\nvim-data for data, unless XDG_* overrides them.
if ($env:XDG_CONFIG_HOME) { $NvimConfigDir = Join-Path $env:XDG_CONFIG_HOME 'nvim' }
else { $NvimConfigDir = Join-Path $env:LOCALAPPDATA 'nvim' }
if ($env:XDG_DATA_HOME) { $NvimDataDir = Join-Path $env:XDG_DATA_HOME 'nvim' }
else { $NvimDataDir = Join-Path $env:LOCALAPPDATA 'nvim-data' }
$existing = @(@($NvimConfigDir, $NvimDataDir) | Where-Object { Test-Path $_ })
if ($existing.Count -gt 0) {
if (-not $Backup -and -not $Force -and $IsInteractive) {
Write-Host ''
Write-Host 'This will DELETE (no backup) :' -ForegroundColor Yellow
$existing | ForEach-Object { Write-Host " $_" }
$answer = Read-Host 'Continue? [y/N] (re-run with -Backup to keep copies instead)'
if ($answer -notmatch '^[Yy]') {
Write-Host 'Aborted before touching your Neovim config.'
exit 1
}
}
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
foreach ($dir in $existing) {
try {
if ($Backup) {
Move-Item -LiteralPath $dir -Destination "$dir.bak-$stamp" -ErrorAction Stop
Write-Host " Moved $dir -> $dir.bak-$stamp"
}
else {
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction Stop
}
}
catch {
Write-Host "ERROR: could not remove $dir -- close any running Neovim/Neovide and try again." -ForegroundColor Red
Write-Host " $($_.Exception.Message)"
exit 1
}
}
}
# ---------------------------------------------------------------------
# 4. Install the Neovim configuration (copied from config\nvim, not
# embedded in this script -- edit those files directly to customize)
# ---------------------------------------------------------------------
Write-Host '[4/4] Installing new Neovim configuration...'
New-Item -ItemType Directory -Force -Path $NvimConfigDir | Out-Null
Copy-Item -Path (Join-Path $ConfigDir 'nvim\*') -Destination $NvimConfigDir -Recurse -Force -ErrorAction Stop
# --- ~\.clang-format, ~\.clang-tidy and ~\cpp-style-guide.md ---
# Personal fallback style, same as on Linux (see the comments in each
# file). clang-format/clang-tidy walk UP from the file being processed
# looking for these, so they act as the default for any project under
# your user folder that doesn't ship its own. Copied unconditionally,
# every run.
Copy-Item -LiteralPath (Join-Path $ConfigDir 'clang-format') -Destination (Join-Path $HOME '.clang-format') -Force -ErrorAction Stop
Copy-Item -LiteralPath (Join-Path $ConfigDir 'clang-tidy') -Destination (Join-Path $HOME '.clang-tidy') -Force -ErrorAction Stop
Copy-Item -LiteralPath (Join-Path $ConfigDir 'cpp-style-guide.md') -Destination (Join-Path $HOME 'cpp-style-guide.md') -Force -ErrorAction Stop
Write-Host "Wrote $HOME\.clang-format and $HOME\.clang-tidy (Allman braces, real tabs), and $HOME\cpp-style-guide.md."
Write-Host '========================================='
Write-Host ' Setup Complete!'
Write-Host " Open a NEW terminal (so PATH changes apply) and run 'nvim'."
Write-Host ' p and P are restored, use <leader>p to force inline paste!'
Write-Host ' Debugger: <leader>db to set a breakpoint, F5 / <leader>dc to start/continue,'
Write-Host ' <leader>du to toggle the debug UI. First debug session will auto-install codelldb via Mason.'
Write-Host " Project-specific CMake debug targets loaded from $NvimConfigDir\lua\dap\configurations\cmake.lua"
Write-Host ' Database (MariaDB): <leader>Du opens the DB UI; press A there to add a connection'
Write-Host ' (URL form: mariadb://user:password@127.0.0.1:3306/dbname). The sqls language server is'
Write-Host " installed on first launch; see README.md ('Database (MariaDB)') for its optional config."
Write-Host ' First launch takes a minute or two (lazy.nvim installs plugins, Mason installs servers,'
Write-Host ' treesitter compiles parsers with gcc).'
Write-Host ' Terminal: set your terminal font to "JetBrainsMono Nerd Font" (Windows Terminal:'
Write-Host ' Settings > Profiles > Defaults > Appearance > Font face), then restart it.'
if ($Frontend -eq 'desktop' -or $Frontend -eq 'both') {
Write-Host " Neovide: run 'neovide' to launch the desktop app (same config, same keymaps)."
}
Write-Host '========================================='