nvim windows support + multicursors
This commit is contained in:
1 parent
b6968a10b3
commit
93c97e65e2
10 files changed
+778
-24
No files matched your search
+126
-2
@@ -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.
|
||||
|
||||
+20
-13
@@ -113,19 +113,26 @@ vim.api.nvim_create_autocmd("FileType", {
|
||||
})
|
||||
|
||||
-- SSH Clipboard Support (OSC 52)
|
||||
vim.g.clipboard = {
|
||||
name = 'OSC 52',
|
||||
copy = {
|
||||
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
|
||||
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
|
||||
},
|
||||
paste = {
|
||||
-- We now return BOTH the text and the register type ('v', 'V', or '^V')
|
||||
-- This restores native p and P line-wise behavior
|
||||
['+'] = function() return { vim.fn.getreg('"', 1, true), vim.fn.getregtype('"') } end,
|
||||
['*'] = function() return { vim.fn.getreg('"', 1, true), vim.fn.getregtype('"') } end,
|
||||
},
|
||||
}
|
||||
-- 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 = {
|
||||
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
|
||||
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
|
||||
},
|
||||
paste = {
|
||||
-- We now return BOTH the text and the register type ('v', 'V', or '^V')
|
||||
-- This restores native p and P line-wise behavior
|
||||
['+'] = function() return { vim.fn.getreg('"', 1, true), vim.fn.getregtype('"') } end,
|
||||
['*'] = 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')
|
||||
|
||||
@@ -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,9 +62,16 @@ 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
|
||||
data_home = vim.env.HOME .. "/.local/share"
|
||||
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"
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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 '========================================='
|
||||
Reference in new issue
Block a user