diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..dc8f9dc9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,24 @@
+name: Neovim config
+
+on:
+ push:
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ smoke-test:
+ runs-on: ubuntu-24.04
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y build-essential curl git ripgrep
+ - name: Install Neovim 0.12.2
+ run: |
+ curl -fL https://github.com/neovim/neovim/releases/download/v0.12.2/nvim-linux-x86_64.tar.gz -o /tmp/nvim.tar.gz
+ tar -xzf /tmp/nvim.tar.gz -C /tmp
+ echo "/tmp/nvim-linux-x86_64/bin" >> "$GITHUB_PATH"
+ - name: Restore locked plugins and start Neovim
+ run: scripts/nvim-test.sh restore
diff --git a/.github/workflows/update-plugins.yml b/.github/workflows/update-plugins.yml
new file mode 100644
index 00000000..a54f2fd8
--- /dev/null
+++ b/.github/workflows/update-plugins.yml
@@ -0,0 +1,52 @@
+name: Update locked plugins
+
+on:
+ schedule:
+ - cron: '17 7 * * 1'
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ update:
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y build-essential curl git ripgrep
+ - name: Install Neovim 0.12.2
+ run: |
+ curl -fL https://github.com/neovim/neovim/releases/download/v0.12.2/nvim-linux-x86_64.tar.gz -o /tmp/nvim.tar.gz
+ tar -xzf /tmp/nvim.tar.gz -C /tmp
+ echo "/tmp/nvim-linux-x86_64/bin" >> "$GITHUB_PATH"
+ - name: Update plugins and smoke test the result
+ run: scripts/nvim-test.sh update
+ - name: Open or refresh the update pull request
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ if git diff --quiet -- lazy-lock.json; then
+ echo "Plugin lockfile is already current."
+ exit 0
+ fi
+
+ branch=automation/plugin-updates
+ git config user.name github-actions[bot]
+ git config user.email 41898282+github-actions[bot]@users.noreply.github.com
+ git fetch origin "$branch" || true
+ git switch -C "$branch"
+ git add lazy-lock.json
+ git commit -m "chore: update locked plugins"
+ git push --force-with-lease origin "$branch"
+
+ gh pr create \
+ --base "${GITHUB_REF_NAME}" \
+ --head "$branch" \
+ --title "chore: update locked Neovim plugins" \
+ --body "Automated weekly plugin update. Merge only after the Neovim config check passes." \
+ || gh pr edit "$branch" --body "Automated weekly plugin update. Merge only after the Neovim config check passes."
diff --git a/.gitignore b/.gitignore
index 005b535b..f7ba1915 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ test.sh
nvim
spell/
-lazy-lock.json
+# The plugin lockfile is intentionally committed. It is the rollback point for
+# safe updates and must not be ignored.
diff --git a/.nvim-version b/.nvim-version
new file mode 100644
index 00000000..60d68b23
--- /dev/null
+++ b/.nvim-version
@@ -0,0 +1 @@
+v0.12.2
diff --git a/README.md b/README.md
index bb20f57b..de239a73 100644
--- a/README.md
+++ b/README.md
@@ -1,222 +1,50 @@
-# kickstart.nvim
+# River's Neovim config
-# install:
-`git clone https://github.com/RiverMatsumoto/kickstart.nvim.git "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim`
+This configuration targets **Neovim 0.12.x** and pins the complete plugin graph
+in `lazy-lock.json`. Normal editor startup never updates plugins. A weekly GitHub
+Actions job proposes lockfile updates in a pull request, and the same isolated
+smoke test runs on every push and pull request.
-## Introduction
+That gives updates a rollback point and keeps upstream breaking changes out of
+the working editor until they pass CI. It cannot make arbitrary upstream
+changes risk-free, but it turns them into reviewed, reversible changes instead
+of surprise startup failures.
-A starting point for Neovim that is:
+## Install
-* Small
-* Single-file
-* Completely Documented
-
-**NOT** a Neovim distribution, but instead a starting point for your configuration.
-
-## Installation
-
-### Install Neovim
-
-Kickstart.nvim targets *only* the latest
-['stable'](https://github.com/neovim/neovim/releases/tag/stable) and latest
-['nightly'](https://github.com/neovim/neovim/releases/tag/nightly) of Neovim.
-If you are experiencing issues, please make sure you have the latest versions.
-
-### Install External Dependencies
-
-> **NOTE**
-> [Backup](#FAQ) your previous configuration (if any exists)
-
-External Requirements:
-- Basic utils: `git`, `make`, `unzip`, C Compiler (`gcc`)
-- [ripgrep](https://github.com/BurntSushi/ripgrep#installation)
-- Language Setup:
- - If want to write Typescript, you need `npm`
- - If want to write Golang, you will need `go`
- - etc.
-
-> **NOTE**
-> See [Windows Installation](#Windows-Installation) to double check any additional Windows notes
-
-Neovim's configurations are located under the following paths, depending on your OS:
-
-| OS | PATH |
-| :- | :--- |
-| Linux, MacOS | `$XDG_CONFIG_HOME/nvim`, `~/.config/nvim` |
-| Windows (cmd)| `%userprofile%\AppData\Local\nvim\` |
-| Windows (powershell)| `$env:USERPROFILE\AppData\Local\nvim\` |
-
-### Install Kickstart
-
-Clone kickstart.nvim:
-
- Linux and Mac
-
-```sh
-git clone https://github.com/nvim-lua/kickstart.nvim.git "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim
-```
-
-
-
- Windows
-
-If you're using `cmd.exe`:
-
-```
-git clone https://github.com/nvim-lua/kickstart.nvim.git %userprofile%\AppData\Local\nvim\
-```
-
-If you're using `powershell.exe`
-
-```
-git clone https://github.com/nvim-lua/kickstart.nvim.git $env:USERPROFILE\AppData\Local\nvim\
-```
-
-
-
-### Post Installation
-
-Start Neovim
+Required: Neovim 0.12.x, Git, a C compiler, `make`, `unzip`, and `ripgrep`. A Nerd
+Font is recommended. Language servers, formatters, and debuggers are installed
+through Mason on first interactive startup.
```sh
+git clone https://github.com/RiverMatsumoto/kickstart.nvim.git \
+ "${XDG_CONFIG_HOME:-$HOME/.config}/nvim"
nvim
```
-That's it! Lazy will install all the plugins you have. Use `:Lazy` to view
-current plugin status.
+To try it without replacing another config:
-Read through the `init.lua` file in your configuration folder for more
-information about extending and exploring Neovim.
-
-### Getting Started
-
-See [Effective Neovim: Instant IDE](https://youtu.be/stqUbv-5u2s), covering the
-previous version. Note: The install via init.lua is outdated, please follow the
-install instructions in this file instead. An updated video is coming soon.
-
-### Recommended Steps
-
-[Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) this repo
-(so that you have your own copy that you can modify) and then installing you
-can install to your machine using the methods above.
-
-> **NOTE**
-> Your fork's url will be something like this: `https://github.com//kickstart.nvim.git`
-
-#### Examples of adding popularly requested plugins
-
-NOTE: You'll need to uncomment the line in the init.lua that turns on loading custom plugins.
-
-
- Adding autopairs
-
-This will automatically install [windwp/nvim-autopairs](https://github.com/windwp/nvim-autopairs) and enable it on startup. For more information, see documentation for [lazy.nvim](https://github.com/folke/lazy.nvim).
-
-In the file: `lua/custom/plugins/autopairs.lua`, add:
-
-```lua
--- File: lua/custom/plugins/autopairs.lua
-
-return {
- "windwp/nvim-autopairs",
- -- Optional dependency
- dependencies = { 'hrsh7th/nvim-cmp' },
- config = function()
- require("nvim-autopairs").setup {}
- -- If you want to automatically add `(` after selecting a function or method
- local cmp_autopairs = require('nvim-autopairs.completion.cmp')
- local cmp = require('cmp')
- cmp.event:on(
- 'confirm_done',
- cmp_autopairs.on_confirm_done()
- )
- end,
-}
+```sh
+git clone https://github.com/RiverMatsumoto/kickstart.nvim.git ~/.config/nvim-river
+NVIM_APPNAME=nvim-river nvim
```
-
-
- Adding a file tree plugin
+## Updates
-This will install the tree plugin and add the command `:Neotree` for you. You can explore the documentation at [neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim) for more information.
+- Do not use `:Lazy update` on the main branch for routine updates.
+- Merge the automated `chore: update locked Neovim plugins` pull request after
+ its checks pass.
+- To test an update locally, run `scripts/nvim-test.sh update`. This uses
+ isolated data/cache directories and changes only `lazy-lock.json`.
+- Restore the committed versions at any time with `:Lazy restore`.
-In the file: `lua/custom/plugins/filetree.lua`, add:
+The Neovim version is pinned in `.nvim-version`. Upgrade Neovim separately from
+plugin updates so failures have one clear cause.
-```lua
--- Unless you are still migrating, remove the deprecated commands from v1.x
-vim.cmd([[ let g:neo_tree_remove_legacy_commands = 1 ]])
+## Design choices
-return {
- "nvim-neo-tree/neo-tree.nvim",
- version = "*",
- dependencies = {
- "nvim-lua/plenary.nvim",
- "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
- "MunifTanjim/nui.nvim",
- },
- config = function ()
- require('neo-tree').setup {}
- end,
-}
-```
-
-
-
-### FAQ
-
-* What should I do if I already have a pre-existing neovim configuration?
- * You should back it up, then delete all files associated with it.
- * This includes your existing init.lua and the neovim files in `~/.local` which can be deleted with `rm -rf ~/.local/share/nvim/`
-* Can I keep my existing configuration in parallel to kickstart?
- * Yes! You can use [NVIM_APPNAME](https://neovim.io/doc/user/starting.html#%24NVIM_APPNAME)`=nvim-NAME` to maintain multiple configurations. For example you can install the kickstart configuration in `~/.config/nvim-kickstart` and create an alias:
- ```
- alias nvim-kickstart='NVIM_APPNAME="nvim-kickstart" nvim'
- ```
- When you run Neovim using `nvim-kickstart` alias it will use the alternative config directory and the matching local directory `~/.local/share/nvim-kickstart`. You can apply this approach to any Neovim distribution that you would like to try out.
-* What if I want to "uninstall" this configuration:
- * See [lazy.nvim uninstall](https://github.com/folke/lazy.nvim#-uninstalling) information
-* Why is the kickstart `init.lua` a single file? Wouldn't it make sense to split it into multiple files?
- * The main purpose of kickstart is to serve as a teaching tool and a reference
- configuration that someone can easily `git clone` as a basis for their own.
- As you progress in learning Neovim and Lua, you might consider splitting `init.lua`
- into smaller parts. A fork of kickstart that does this while maintaining the exact
- same functionality is available here:
- * [kickstart-modular.nvim](https://github.com/dam9000/kickstart-modular.nvim)
- * Discussions on this topic can be found here:
- * [Restructure the configuration](https://github.com/nvim-lua/kickstart.nvim/issues/218)
- * [Reorganize init.lua into a multi-file setup](https://github.com/nvim-lua/kickstart.nvim/pull/473)
-
-### Windows Installation
-
-Installation may require installing build tools, and updating the run command for `telescope-fzf-native`
-
-See `telescope-fzf-native` documentation for [more details](https://github.com/nvim-telescope/telescope-fzf-native.nvim#installation)
-
-This requires:
-
-- Install CMake, and the Microsoft C++ Build Tools on Windows
-
-```lua
-{'nvim-telescope/telescope-fzf-native.nvim', build = 'cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build' }
-```
-
-Alternatively one can install gcc and make which don't require changing the config,
-the easiest way is to use choco:
-
-1. install [chocolatey](https://chocolatey.org/install)
-either follow the instructions on the page or use winget,
-run in cmd as **admin**:
-```
-winget install --accept-source-agreements chocolatey.chocolatey
-```
-
-2. install all requirements using choco, exit previous cmd and
-open a new one so that choco path is set, run in cmd as **admin**:
-```
-choco install -y neovim git ripgrep wget fd unzip gzip mingw make
-```
-
-Then continue with the [Install Kickstart](#Install-Kickstart) step.
-
-
->>>>>>> afed39f595301147188dac2c5453bce8515d5325
+The version guard, modular setup conventions, native `vim.lsp.config` API, and
+current Treesitter `main` API follow the useful compatibility patterns in
+[jdhao/nvim-config](https://github.com/jdhao/nvim-config). The system package
+installer is intentionally not run from Neovim; editor startup should never ask
+for administrator privileges or mutate the operating system.
diff --git a/init.lua b/init.lua
index d19f6cb9..81872dc7 100644
--- a/init.lua
+++ b/init.lua
@@ -1,22 +1,29 @@
vim.g.maplocalleader = ' '
vim.g.mapleader = ' '
--- install deps if need to
-require 'bootstrap.pacman'
+vim.loader.enable()
+
+local version = vim.version()
+if version.major ~= 0 or version.minor ~= 12 then
+ error(('This config supports Neovim 0.12.x (found %s)'):format(tostring(version)))
+end
-- =========================
-- Lazy.nvim bootstrap
-- =========================
local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim'
-if not vim.loop.fs_stat(lazypath) then
- vim.fn.system {
+if not vim.uv.fs_stat(lazypath) then
+ local result = vim.system({
'git',
'clone',
'--filter=blob:none',
'https://github.com/folke/lazy.nvim.git',
'--branch=stable',
lazypath,
- }
+ }, { text = true }):wait()
+ if result.code ~= 0 then
+ error(('Unable to install lazy.nvim:\n%s'):format(result.stderr or 'unknown error'))
+ end
end
vim.opt.rtp:prepend(lazypath)
@@ -91,43 +98,46 @@ require('lazy').setup({
{
'nvim-treesitter/nvim-treesitter',
+ branch = 'main',
lazy = false,
build = ':TSUpdate',
- opts = {
- ensure_installed = {
- 'lua',
- 'rust',
- 'python',
- 'javascript',
- 'typescript',
- 'c',
- 'c_sharp',
- 'cpp',
- 'bash',
- 'json',
- 'yaml',
- 'toml',
- },
- highlight = {
- enable = true,
- },
- },
},
{
'MeanderingProgrammer/treesitter-modules.nvim',
dependencies = { 'nvim-treesitter/nvim-treesitter' },
- opts = {
- incremental_selection = {
- enable = true,
- keymaps = {
- init_selection = '', -- Alt+Space
- node_incremental = '', -- Alt+Space (expand)
- node_decremental = '', -- Alt+Backspace (shrink)
- scope_incremental = '', -- Alt+Shift+Space (scope expand)
+ config = function()
+ local parsers = {
+ 'bash',
+ 'c',
+ 'c_sharp',
+ 'cpp',
+ 'gdscript',
+ 'javascript',
+ 'json',
+ 'lua',
+ 'markdown',
+ 'python',
+ 'rust',
+ 'toml',
+ 'typescript',
+ 'yaml',
+ }
+
+ require('treesitter-modules').setup {
+ ensure_installed = vim.env.NVIM_SKIP_TOOL_INSTALL == '1' and {} or parsers,
+ highlight = { enable = true },
+ incremental_selection = {
+ enable = true,
+ keymaps = {
+ init_selection = '',
+ node_incremental = '',
+ node_decremental = '',
+ scope_incremental = '',
+ },
},
- },
- },
+ }
+ end,
},
'rhysd/git-messenger.vim',
@@ -404,34 +414,6 @@ require('lazy').setup({
end,
},
- -- Tooling installer
- {
- 'WhoIsSethDaniel/mason-tool-installer.nvim',
- dependencies = { 'williamboman/mason.nvim' },
- opts = {
- ensure_installed = {
- 'clangd',
- 'clang-format',
- 'codelldb',
- 'cpplint',
- 'cpptools',
- 'csharpier',
- 'csharp-language-server',
- 'lua-language-server',
- 'netcoredbg',
- 'omnisharp',
- 'omnisharp-mono',
- 'prettier',
- 'ruff',
- 'rust-analyzer',
- 'sonarlint-language-server',
- 'stylua',
- },
- auto_update = false,
- run_on_start = true,
- },
- },
-
-- Terminal UX
{
'akinsho/toggleterm.nvim',
@@ -459,7 +441,7 @@ require('lazy').setup({
function _G.set_terminal_keymaps()
local opts = { noremap = true }
- vim.diagnostic.disable(0)
+ vim.diagnostic.enable(false, { bufnr = 0 })
vim.api.nvim_buf_set_keymap(0, 't', '', [[]], opts)
end
vim.cmd 'autocmd! TermOpen term://* lua set_terminal_keymaps()'
@@ -473,7 +455,7 @@ require('lazy').setup({
float_opts = { width = vim.o.columns, height = vim.o.lines },
on_open = function(term)
vim.cmd 'startinsert!'
- vim.diagnostic.disable(0)
+ vim.diagnostic.enable(false, { bufnr = 0 })
vim.api.nvim_buf_set_keymap(0, 't', '', 'close', { silent = false, noremap = true })
if vim.fn.mapcheck('', 't') ~= '' then
vim.api.nvim_buf_del_keymap(term.bufnr, 't', '')
@@ -592,8 +574,8 @@ require('lazy').setup({
{
'neovim/nvim-lspconfig',
dependencies = {
- { 'williamboman/mason.nvim', config = true },
- 'williamboman/mason-lspconfig.nvim',
+ { 'mason-org/mason.nvim', config = true },
+ 'mason-org/mason-lspconfig.nvim',
'WhoIsSethDaniel/mason-tool-installer.nvim',
{
'j-hui/fidget.nvim',
@@ -660,12 +642,14 @@ require('lazy').setup({
},
},
pyright = {
- python = {
- analysis = {
- autoSearchPaths = true,
- diagnosticMode = 'openFilesOnly',
- useLibraryCodeForTypes = true,
- reportDuplicateImport = true,
+ settings = {
+ python = {
+ analysis = {
+ autoSearchPaths = true,
+ diagnosticMode = 'openFilesOnly',
+ useLibraryCodeForTypes = true,
+ reportDuplicateImport = true,
+ },
},
},
},
@@ -692,30 +676,38 @@ require('lazy').setup({
capabilities.textDocument.foldingRange = { dynamicRegistration = false, lineFoldingOnly = true }
require('mason').setup { ui = { border = 'rounded' } }
+ for server_name, server in pairs(servers) do
+ server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {})
+ vim.lsp.config(server_name, server)
+ end
+
require('mason-lspconfig').setup {
- handlers = {
- function(server_name)
- local server = servers[server_name] or {}
- server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {})
- require('lspconfig')[server_name].setup(server)
- end,
- },
+ ensure_installed = vim.env.NVIM_SKIP_TOOL_INSTALL == '1' and {} or vim.tbl_keys(servers),
+ automatic_enable = true,
}
require('mason-tool-installer').setup {
ensure_installed = {
'clangd',
+ 'clang-format',
'lua_ls',
'pyright',
'ruff',
'codelldb',
'cpptools',
'cpplint',
+ 'csharpier',
+ 'csharp-language-server',
+ 'netcoredbg',
+ 'omnisharp',
+ 'omnisharp-mono',
+ 'rust-analyzer',
+ 'sonarlint-language-server',
'stylua',
'prettier',
},
auto_update = false,
- run_on_start = true,
+ run_on_start = vim.env.NVIM_SKIP_TOOL_INSTALL ~= '1',
start_delay = 3000,
debounce_hours = 5,
}
@@ -801,25 +793,18 @@ require('lazy').setup({
}),
})
- require('lspconfig.ui.windows').default_options.border = 'rounded'
- vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(vim.lsp.handlers.hover, { border = 'rounded' })
- vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(vim.lsp.handlers.signature_help, { border = 'rounded' })
-
- local diagnostic_signs = {
- { name = 'DiagnosticSignError', text = ' ' },
- { name = 'DiagnosticSignWarn', text = ' ' },
- { name = 'DiagnosticSignHint', text = ' ' },
- { name = 'DiagnosticSignInfo', text = ' ' },
- }
- for _, sign in ipairs(diagnostic_signs) do
- vim.fn.sign_define(sign.name, { texthl = sign.name, text = sign.text, numhl = sign.name })
- end
-
vim.diagnostic.config {
virtual_text = { prefix = '●' },
severity_sort = true,
float = { source = 'always' },
- signs = true,
+ signs = {
+ text = {
+ [vim.diagnostic.severity.ERROR] = ' ',
+ [vim.diagnostic.severity.WARN] = ' ',
+ [vim.diagnostic.severity.HINT] = ' ',
+ [vim.diagnostic.severity.INFO] = ' ',
+ },
+ },
}
end,
},
@@ -862,11 +847,11 @@ require('lazy').setup({
['end'] = { args.line2, end_line:len() },
}
end
- require('conform').format { async = true, lsp_fallback = true, range = range }
+ require('conform').format { async = true, lsp_format = 'fallback', range = range }
end, { range = true })
vim.keymap.set('', 'fa', function()
- require('conform').format { async = true, lsp_fallback = true }
+ require('conform').format { async = true, lsp_format = 'fallback' }
end, { desc = '[F]ormat [a]ll' })
end,
},
@@ -1032,18 +1017,7 @@ require('lazy').setup({
{
'MeanderingProgrammer/render-markdown.nvim',
dependencies = {
- {
- 'nvim-treesitter/nvim-treesitter',
- branch = 'main',
- config = function()
- vim.api.nvim_create_autocmd('FileType', {
- pattern = { 'llm', 'markdown' },
- callback = function()
- vim.treesitter.start(0, 'markdown')
- end,
- })
- end,
- },
+ 'nvim-treesitter/nvim-treesitter',
'nvim-mini/mini.icons',
}, -- if you use standalone mini plugins
ft = { 'markdown', 'llm' },
@@ -1170,7 +1144,12 @@ require('lazy').setup({
vim.api.nvim_create_user_command('DapResetUI', ":lua require('dapui').open({reset = true})", { desc = 'Reset DAP UI Layout' })
end,
},
-}, {})
+}, {
+ lockfile = vim.fn.stdpath 'config' .. '/lazy-lock.json',
+ checker = { enabled = false },
+ change_detection = { enabled = false, notify = false },
+ install = { colorscheme = { 'gruvbox-material', 'habamax' } },
+})
-- =========================================
-- ============ START SMEAR PROFILE ========
@@ -1533,12 +1512,20 @@ vim.keymap.set('x', 'p', [["_dP]], { desc = 'Paste without overwriting r
vim.keymap.set('n', 'ya', ':%y+', { desc = 'Yank entire buffer to clipboard' })
-- diagnostics
-vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, { desc = 'Previous diagnostic' })
-vim.keymap.set('n', ']d', vim.diagnostic.goto_next, { desc = 'Next diagnostic' })
+vim.keymap.set('n', '[d', function()
+ vim.diagnostic.jump { count = -1, float = true }
+end, { desc = 'Previous diagnostic' })
+vim.keymap.set('n', ']d', function()
+ vim.diagnostic.jump { count = 1, float = true }
+end, { desc = 'Next diagnostic' })
vim.keymap.set('n', 'e', vim.diagnostic.open_float, { desc = 'Show diagnostic under cursor' })
vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Diagnostics to loclist' })
-vim.keymap.set('n', 'dd', vim.diagnostic.disable, { desc = 'Disable diagnostics' })
-vim.keymap.set('n', 'de', vim.diagnostic.enable, { desc = 'Enable diagnostics' })
+vim.keymap.set('n', 'dd', function()
+ vim.diagnostic.enable(false)
+end, { desc = 'Disable diagnostics' })
+vim.keymap.set('n', 'de', function()
+ vim.diagnostic.enable(true)
+end, { desc = 'Enable diagnostics' })
-- misc
vim.keymap.set('n', 'cw', ':cd %:p:h:pwd', { desc = 'cd to current file directory' })
@@ -1586,9 +1573,9 @@ vim.keymap.set('n', 'me', ':!chmod +x %:p', { desc = 'Make file exec
vim.keymap.set('n', 'P', require('spectre').open, { desc = 'Open Spectre search/replace' })
-- Trouble
-vim.keymap.set('n', 'xx', 'TroubleToggle', { silent = true, noremap = true, desc = 'Toggle Trouble' })
-vim.keymap.set('n', 'xw', 'TroubleToggle workspace_diagnostics', { silent = true, noremap = true, desc = 'Workspace diagnostics (Trouble)' })
-vim.keymap.set('n', 'xd', 'TroubleToggle document_diagnostics', { silent = true, noremap = true, desc = 'Document diagnostics (Trouble)' })
+vim.keymap.set('n', 'xx', 'Trouble diagnostics toggle', { silent = true, desc = 'Toggle Trouble diagnostics' })
+vim.keymap.set('n', 'xw', 'Trouble diagnostics toggle', { silent = true, desc = 'Workspace diagnostics (Trouble)' })
+vim.keymap.set('n', 'xd', 'Trouble diagnostics toggle filter.buf=0', { silent = true, desc = 'Document diagnostics (Trouble)' })
-- DAP
vim.keymap.set('n', 'dap', ":lua require('dapui').toggle()", { desc = 'Toggle DAP UI' })
@@ -1693,9 +1680,6 @@ vim.api.nvim_set_keymap('i', '', '', { noremap = true })
-- git coauthors
vim.keymap.set('n', 'ga', ':Telescope coauthors')
--- install treesitter parsers
-require('nvim-treesitter').install { 'c', 'rust', 'gdscript', 'c_sharp' }
-
-- ======== LSP settings ===========
-- signature help
vim.api.nvim_set_keymap('i', '', 'lua vim.lsp.buf.signature_help()', { noremap = true, silent = true })
diff --git a/lazy-lock.json b/lazy-lock.json
new file mode 100644
index 00000000..0902ef67
--- /dev/null
+++ b/lazy-lock.json
@@ -0,0 +1,62 @@
+{
+ "Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" },
+ "LuaSnip": { "branch": "master", "commit": "0abc8f390b278c3b4aabc4c004ac8a088b65cf24" },
+ "barbar.nvim": { "branch": "master", "commit": "337ecfadb8bf005050990bf2f624dc4fc828dabd" },
+ "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
+ "cmp-cmdline": { "branch": "main", "commit": "d126061b624e0af6c3a556428712dd4d4194ec6d" },
+ "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
+ "cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
+ "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
+ "conflict-marker.vim": { "branch": "master", "commit": "62742b2ffe7a433988759c67b5c5a22eff74a14b" },
+ "conform.nvim": { "branch": "master", "commit": "619363c30309d29ffa631e67c8183f2a72caa373" },
+ "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
+ "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
+ "fidget.nvim": { "branch": "main", "commit": "6f793b2bcd2d35e201c09520f698bb763220908a" },
+ "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
+ "friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
+ "git-coauthors.nvim": { "branch": "main", "commit": "a31352a63a99e0aff613aa7961d4f5fa955b3d87" },
+ "git-messenger.vim": { "branch": "master", "commit": "fd124457378a295a5d1036af4954b35d6b807385" },
+ "gitsigns.nvim": { "branch": "main", "commit": "2038c666bd9d8a0b7349a0b6ee00dc83104b9ecf" },
+ "gruvbox-material": { "branch": "master", "commit": "5b45305389cac6db2dba0eff338fcae19b867703" },
+ "indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
+ "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
+ "llm.nvim": { "branch": "main", "commit": "a36ef8065fd97e774c90dfde63ff5cfb732958ec" },
+ "lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
+ "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
+ "mason-lspconfig.nvim": { "branch": "main", "commit": "47059d71b42d74b0a1e9f61c1d99d301039c3b5b" },
+ "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" },
+ "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
+ "mini.icons": { "branch": "main", "commit": "e56797f90192d81f1fda02e662fc3e8e3d775027" },
+ "neo-tree.nvim": { "branch": "v3.x", "commit": "ebd66767191714e008ce73b769518a763ff31bdc" },
+ "neodev.nvim": { "branch": "main", "commit": "46aa467dca16cf3dfe27098042402066d2ae242d" },
+ "neoscroll.nvim": { "branch": "master", "commit": "c8d29979cb0cb3a2437a8e0ae683fd82f340d3b8" },
+ "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
+ "nvim-autopairs": { "branch": "master", "commit": "7b9923abad60b903ece7c52940e1321d39eccc79" },
+ "nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
+ "nvim-dap": { "branch": "master", "commit": "9e848e09a697ee95302a3ef2dd43fd6eb709e570" },
+ "nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" },
+ "nvim-dap-ui": { "branch": "master", "commit": "1a66cabaa4a4da0be107d5eda6d57242f0fe7e49" },
+ "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
+ "nvim-lspconfig": { "branch": "master", "commit": "3371bf298c1f56efc26771ee961f461176958fb5" },
+ "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
+ "nvim-spectre": { "branch": "master", "commit": "72f56f7585903cd7bf92c665351aa585e150af0f" },
+ "nvim-surround": { "branch": "main", "commit": "8b47db616ef658b8fc27e61db2896aa2f40134de" },
+ "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
+ "nvim-web-devicons": { "branch": "master", "commit": "dfbfaa967a6f7ec50789bead7ef87e336c1fa63c" },
+ "onedark.nvim": { "branch": "master", "commit": "df4792accde9db0043121f32628bcf8e645d9aea" },
+ "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
+ "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" },
+ "smear-cursor.nvim": { "branch": "main", "commit": "9e9378d6ee34bb3782e0e8c63d9ec8ca618b479b" },
+ "telescope-fzf-native.nvim": { "branch": "main", "commit": "b25b749b9db64d375d782094e2b9dce53ad53a40" },
+ "telescope-live-grep-args.nvim": { "branch": "master", "commit": "53e9df55b3651dd7cf77e172f1e8c9a17407acca" },
+ "telescope-luasnip.nvim": { "branch": "master", "commit": "07a2a2936a7557404c782dba021ac0a03165b343" },
+ "telescope.nvim": { "branch": "master", "commit": "427b576c16792edad01a92b89721d923c19ad60f" },
+ "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
+ "toggleterm.nvim": { "branch": "main", "commit": "9a88eae817ef395952e08650b3283726786fb5fb" },
+ "treesitter-modules.nvim": { "branch": "main", "commit": "290eec96bfc43ed28264661dd30e894a60c4b99c" },
+ "trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
+ "vim-floaterm": { "branch": "master", "commit": "bb4ba7952e906408e1f83b215f55ffe57efcade6" },
+ "vim-fugitive": { "branch": "master", "commit": "3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0" },
+ "vim-visual-multi": { "branch": "master", "commit": "a6975e7c1ee157615bbc80fc25e4392f71c344d4" },
+ "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
+}
diff --git a/lua/bootstrap/pacman.lua b/lua/bootstrap/pacman.lua
deleted file mode 100644
index 2dbbf123..00000000
--- a/lua/bootstrap/pacman.lua
+++ /dev/null
@@ -1,111 +0,0 @@
--- ~/.config/nvim/lua/bootstrap/pacman.lua
--- Fix1: run pacman inside a Neovim terminal so sudo has a real TTY.
-
-local M = {}
-
-local function has_exe(exe)
- return vim.fn.executable(exe) == 1
-end
-
-local function pacman_has(pkg)
- local out = vim.fn.system({ "pacman", "-Qi", pkg })
- return vim.v.shell_error == 0 and out ~= nil and out ~= ""
-end
-
-local function missing_pkgs(pkgs)
- local missing = {}
- for _, p in ipairs(pkgs) do
- if not pacman_has(p) then
- table.insert(missing, p)
- end
- end
- return missing
-end
-
-local function shell_escape_arg(s)
- -- minimal quoting for a shell command string
- return "'" .. tostring(s):gsub("'", [['"'"']]) .. "'"
-end
-
-local function open_install_terminal(pkgs)
- if #pkgs == 0 then
- return true
- end
-
- if not has_exe("sudo") then
- vim.notify("Missing `sudo`; cannot auto-install pacman deps.", vim.log.levels.ERROR)
- return false
- end
-
- -- Build a single shell command so sudo prompts normally.
- local parts = { "sudo", "pacman", "-S", "--needed" }
- for _, p in ipairs(pkgs) do
- table.insert(parts, shell_escape_arg(p))
- end
- local cmd = table.concat(parts, " ")
-
- vim.notify("Opening terminal to install:\n" .. table.concat(pkgs, ", "), vim.log.levels.WARN)
-
- -- Open a terminal split and run the command. User can enter sudo password normally.
- vim.cmd("botright 15split")
- vim.cmd("terminal " .. cmd)
- vim.cmd("startinsert")
-
- return true
-end
-
-function M.check()
- if not has_exe("pacman") then
- vim.notify("pacman not found. This bootstrap is Arch-specific.", vim.log.levels.WARN)
- return {}
- end
- local deps = require("bootstrap.deps")
- return missing_pkgs(deps)
-end
-
-function M.install(opts)
- opts = opts or {}
- local miss = M.check()
- if #miss == 0 then
- if not opts.quiet then
- vim.notify("All system deps already installed.", vim.log.levels.INFO)
- end
- return true
- end
-
- local prompt = "Missing system deps:\n\n"
- .. table.concat(miss, "\n")
- .. "\n\nOpen a terminal and run sudo pacman to install them?"
-
- local ok = true
- if not opts.force then
- ok = (vim.fn.confirm(prompt, "&Yes\n&No", 1) == 1)
- end
- if not ok then
- return false
- end
-
- return open_install_terminal(miss)
-end
-
--- Commands
-vim.api.nvim_create_user_command("DepsCheck", function()
- local miss = M.check()
- if #miss == 0 then
- vim.notify("DepsCheck: OK (no missing packages).", vim.log.levels.INFO)
- else
- vim.notify("DepsCheck missing:\n" .. table.concat(miss, ", "), vim.log.levels.WARN)
- end
-end, {})
-
-vim.api.nvim_create_user_command("DepsInstall", function()
- M.install({ force = true })
-end, {})
-
--- Run once per session (prompt-based)
-if vim.g.__deps_bootstrap_ran ~= true then
- vim.g.__deps_bootstrap_ran = true
- M.install({ force = false, quiet = true })
-end
-
-return M
diff --git a/scripts/nvim-test.sh b/scripts/nvim-test.sh
new file mode 100755
index 00000000..7f7b95dc
--- /dev/null
+++ b/scripts/nvim-test.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+mode="${1:-restore}"
+if [[ "$mode" != "restore" && "$mode" != "update" ]]; then
+ echo "usage: $0 [restore|update]" >&2
+ exit 2
+fi
+
+root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+nvim="${NVIM_BIN:-nvim}"
+tmp="$(mktemp -d)"
+trap 'rm -rf "$tmp"' EXIT
+
+mkdir -p "$tmp/config"
+ln -s "$root" "$tmp/config/nvim"
+
+export XDG_CONFIG_HOME="$tmp/config"
+export XDG_DATA_HOME="$tmp/data"
+export XDG_STATE_HOME="$tmp/state"
+export XDG_CACHE_HOME="$tmp/cache"
+export NVIM_SKIP_TOOL_INSTALL=1
+
+"$nvim" --headless "+Lazy! $mode" +qa
+"$nvim" --headless \
+ "+Lazy load all" \
+ "+lua assert(vim.fn.exists(':Lazy') == 2, 'lazy.nvim did not load')" \
+ "+lua if vim.v.errmsg ~= '' then print(vim.v.errmsg); vim.cmd('cquit 1') end" \
+ +qa
+
+"$nvim" --headless \
+ "+checkhealth vim.deprecated" \
+ "+silent write! $tmp/deprecated.txt" \
+ +qa
+
+if grep -q 'WARNING\|ERROR' "$tmp/deprecated.txt"; then
+ cat "$tmp/deprecated.txt"
+ exit 1
+fi