Files
LinuxInstalls/nvim/setup_neovim.ps1
T

446 lines
21 KiB
PowerShell

<#
.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 '========================================='