diff --git a/.gitignore b/.gitignore index 005b535b606..e4b332903af 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ nvim spell/ lazy-lock.json +.serena/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..f329f55b6c0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,48 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Root entrypoint: `init.lua` (Kickstart-based primary config). +- Custom Lua modules live under `lua/custom/`. +- Plugin specs are split into `lua/custom/plugins/*.lua` (one concern per file, e.g. `gitsigns.lua`, `lint.lua`, `persistence.lua`). +- Utility modules (non-plugin config) live in `lua/custom/*.lua` (for example `wrapping.lua`). +- Reference docs live in `README.md` and `doc/kickstart.txt`. +- Plugin versions are pinned in `lazy-lock.json`. + +## Build, Test, and Development Commands +- `nvim` + Starts Neovim and triggers lazy.nvim plugin loading/install. +- `nvim --headless "+qa"` + Fast startup sanity check (useful in CI-style validation). +- `nvim --headless "+checkhealth" "+qa"` + Runs health checks for Neovim, plugins, and external tools. +- `nvim --headless "+Lazy! sync" "+qa"` + Syncs plugin set to current specs. +- `luac -p init.lua lua/custom/**/*.lua` + Lua syntax validation for config files. + +## Coding Style & Naming Conventions +- Language: Lua (Neovim API style). +- Formatting: `stylua` using `.stylua.toml` settings (2-space indentation, no tabs). +- Prefer small, focused plugin spec files named by feature (`neo-tree.lua`, `markdown.lua`). +- Use descriptive keymap `desc` fields and group prefixes via which-key. +- Keep comments concise and practical; avoid repeating obvious code behavior. + +## Testing Guidelines +- No formal unit-test framework is configured in this repo. +- Required checks before PR: + - Lua parse check (`luac -p ...`) + - Headless startup (`nvim --headless "+qa"`) + - Health check (`:checkhealth`) for affected tooling (LSP, formatters, linters). +- For plugin/config changes, include manual verification steps in PR notes (keymaps, commands, expected behavior). + +## Commit & Pull Request Guidelines +- Follow existing history style: Conventional Commit-like prefixes such as: + - `feat(scope): ...` + - `fix(scope): ...` + - `chore: ...` +- Keep commits scoped to one logical change (plugin, keymap group, diagnostics, etc.). +- PRs should include: + - Summary of behavior changes + - Files touched (e.g. `init.lua`, `lua/custom/plugins/...`) + - Validation performed (commands run) + - Screenshots/GIFs only when UI behavior is materially changed. diff --git a/README.md b/README.md index 4113950550d..fa13a5afd1e 100644 --- a/README.md +++ b/README.md @@ -1,241 +1,222 @@ -# kickstart.nvim +# nvim -## Introduction +Personal Neovim config based on `kickstart.nvim`, tuned for backend-heavy work +in JavaScript/TypeScript, C/C++, Kubernetes, and server development. -A starting point for Neovim that is: +This README is the working manual for what is configured, why it exists, and +how to use it quickly. -* Small -* Single-file -* Completely Documented +## Design principles -**NOT** a Neovim distribution, but instead a starting point for your configuration. +- Additive, not disruptive: new plugins and mappings are added without replacing + existing behavior. +- Modular plugin specs: each concern lives in `lua/custom/plugins/*.lua`. +- Terminal-friendly workflow: most actions map to short leader sequences and + preserve CLI-first habits. +- Keep startup stable: major features are lazy-loaded by command, filetype, or + explicit keymaps where possible. -## Installation +## Repository layout -### Install Neovim +- `init.lua`: base options, core plugin setup, LSP, formatting, treesitter. +- `lua/custom/plugins/*.lua`: modular plugin specs and custom behavior. +- `doc/nvim.txt`: Vim help document (`:help nvim-config`). +- `lazy-lock.json`: plugin lockfile managed by lazy.nvim. -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. +## Quick validation commands -### Install External Dependencies +Run these after config changes: -External Requirements: -- Basic utils: `git`, `make`, `unzip`, C Compiler (`gcc`) -- [ripgrep](https://github.com/BurntSushi/ripgrep#installation), - [fd-find](https://github.com/sharkdp/fd#installation) -- Clipboard tool (xclip/xsel/win32yank or other depending on the platform) -- A [Nerd Font](https://www.nerdfonts.com/): optional, provides various icons - - if you have it set `vim.g.have_nerd_font` in `init.lua` to true -- Emoji fonts (Ubuntu only, and only if you want emoji!) `sudo apt install fonts-noto-color-emoji` -- Language Setup: - - If you want to write Typescript, you need `npm` - - If you want to write Golang, you will need `go` - - etc. - -> [!NOTE] -> See [Install Recipes](#Install-Recipes) for additional Windows and Linux specific notes -> and quick install snippets +```sh +nvim --headless "+qa" +nvim --headless "+checkhealth" "+qa" +luac -p init.lua lua/custom/**/*.lua +``` -### Install Kickstart +Useful maintenance commands: -> [!NOTE] -> [Backup](#FAQ) your previous configuration (if any exists) +```sh +nvim --headless "+Lazy! sync" "+qa" +nvim --headless "+MasonToolsInstallSync" "+qa" +``` + +## Keymap manual -Neovim's configurations are located under the following paths, depending on your OS: +### Git workflow -| OS | PATH | -| :- | :--- | -| Linux, MacOS | `$XDG_CONFIG_HOME/nvim`, `~/.config/nvim` | -| Windows (cmd)| `%localappdata%\nvim\` | -| Windows (powershell)| `$env:LOCALAPPDATA\nvim\` | +- `Gg`: open Neogit UI +- `Gd`: open Diffview +- `GD`: close Diffview +- `Gf`: Diffview file history (current file) +- `GF`: Diffview repo history +- `h...`: Gitsigns hunk actions (`:which-key h`) -#### Recommended Step +### Diagnostics and code navigation -[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, then install by cloning the -fork to your machine using one of the commands below, depending on your OS. +- `gd`: goto definition +- `gD`: goto declaration +- `gr`: goto references +- `gi`: goto implementation +- `gt`: goto type definition +- `xx`: Trouble diagnostics +- `xw`: Trouble workspace diagnostics +- `xd`: Trouble current buffer diagnostics +- `xq`: Trouble quickfix list +- `xl`: Trouble location list +- `tc`: toggle treesitter context header +- `jm` / `jk`: next/previous function start +- `jM` / `jK`: next/previous function end +- `jc` / `jC`: next/previous class start +- Textobject select (operator-pending/visual): `af`/`if` for function, `ac`/`ic` for class -> [!NOTE] -> Your fork's URL will be something like this: -> `https://github.com//kickstart.nvim.git` +### Yank references (AI-friendly) -You likely want to remove `lazy-lock.json` from your fork's `.gitignore` file -too - it's ignored in the kickstart repo to make maintenance easier, but it's -[recommended to track it in version control](https://lazy.folke.io/usage/lockfile). +- `ya`: yank absolute reference with location and symbol +- `yr`: yank relative reference with location and symbol +- `yf`: yank current buffer file as `@relative/path` +- `yd`: yank current buffer parent directory as `@relative/dir` -#### Clone kickstart.nvim +### Tests and debug -> [!NOTE] -> If following the recommended step above (i.e., forking the repo), replace -> `nvim-lua` with `` in the commands below +- `nr`: neotest run nearest +- `nf`: neotest run current file +- `ns`: neotest run suite (cwd) +- `nd`: neotest debug nearest via DAP +- `nn`: neotest summary toggle +- `no`: neotest output for nearest test +- `nO`: neotest output panel toggle +- `na`: attach to running neotest process +- `nS`: stop neotest run -
Linux and Mac +Existing DAP keys are unchanged: -```sh -git clone https://github.com/nvim-lua/kickstart.nvim.git "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim -``` +- `` continue/start, `` step into, `` step over, `` step out +- `` toggle dap-ui, `b` toggle breakpoint, `B` conditional bp -
+### Search, replace, and explorer -
Windows +- `sR`: project search/replace with grug-far +- `eo`: open Oil explorer view (optional, non-default explorer) -If you're using `cmd.exe`: +### CMake workflow (optional) -``` -git clone https://github.com/nvim-lua/kickstart.nvim.git "%localappdata%\nvim" -``` +- `cg`: CMake generate +- `cb`: CMake build +- `cr`: CMake run +- `ct`: CMake test +- `cc`: CMake select build type -If you're using `powershell.exe` +## Plugin stack by workflow -``` -git clone https://github.com/nvim-lua/kickstart.nvim.git "${env:LOCALAPPDATA}\nvim" -``` +### LSP and language intelligence -
+- `nvim-lspconfig` + `mason-lspconfig` + `mason-tool-installer` +- Vue integration: + - `vue_ls` enabled + - `ts_ls` scoped to `vue` with `@vue/typescript-plugin` + - keeps `typescript-tools.nvim` available for TS/JS workflows +- Kubernetes/Helm integration: + - `yamlls` with schema mappings for Kubernetes, Helm chart, Helmfile, + and Kustomize + - `helm_ls` enabled + - `vim-helm` added for Helm syntax support -### Post Installation +### Treesitter and structural editing -Start Neovim +- `nvim-treesitter` uses current API (`require('nvim-treesitter').setup()`). +- `nvim-treesitter-context` provides sticky scope context. +- `nvim-treesitter-textobjects` adds structure-aware function/class jumps. -```sh -nvim -``` +### Debugging -That's it! Lazy will install all the plugins you have. Use `:Lazy` to view -the current plugin status. Hit `q` to close the window. +- Core: `nvim-dap`, `nvim-dap-ui`, `mason-nvim-dap`, `nvim-dap-go`. +- JS/TS: `nvim-dap-vscode-js` configured with `js-debug-adapter` and + `pwa-node` launch/attach defaults. +- C/C++: `codelldb` installation via Mason and baseline launch profile + (`Launch current file (codelldb)`). -#### Read The Friendly Documentation +### Testing -Read through the `init.lua` file in your configuration folder for more -information about extending and exploring Neovim. That also includes -examples of adding popularly requested plugins. +- `neotest` core with adapters: + - `neotest-jest` + - `neotest-vitest` + - `neotest-gtest` -> [!NOTE] -> For more information about a particular plugin check its repository's documentation. +Notes for C++ tests: +- `neotest-gtest` needs executable mapping per project (use `:ConfigureGtest` + from the neotest summary window). -### Getting Started +### Formatting and linting -[The Only Video You Need to Get Started with Neovim](https://youtu.be/m8C0Cq9Uv9o) +- Formatting via `conform.nvim`: + - JS/TS/JSON/YAML: `prettierd` -> `prettier` + - C/C++: `clang_format` + - Lua: `stylua` +- Linting via `nvim-lint`: + - markdown: `markdownlint` + - dockerfile: `hadolint` + - yaml / yaml.helm-values: `yamllint` -### FAQ +Linting is executable-aware for configured linters to avoid noisy diagnostics +when a linter binary is unavailable. -* What should I do if I already have a pre-existing Neovim configuration? - * You should back it up and then delete all associated files. - * 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://lazy.folke.io/usage#-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 use to `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 - 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) +### Project workflow plugins -### Install Recipes +- `trouble.nvim`: focused diagnostics/issues panel +- `grug-far.nvim`: project-wide search/replace +- `oil.nvim`: optional file editing explorer (does not replace default explorer) +- `cmake-tools.nvim`: CMake build/run/test helpers (lazy and optional) -Below you can find OS specific install instructions for Neovim and dependencies. +## Mason-managed tools and servers -After installing all the dependencies continue with the [Install Kickstart](#Install-Kickstart) step. +This config ensures installation for key tools used by the workflows above, +including: -#### Windows Installation +- `prettierd`, `prettier`, `clang-format` +- `hadolint`, `yamllint`, `markdownlint`, `stylua` +- `js-debug-adapter`, `codelldb` +- configured LSP servers from `servers` table (including `helm_ls`) -
Windows with Microsoft C++ Build Tools and CMake -Installation may require installing build tools and updating the run command for `telescope-fzf-native` +Check with `:Mason` and install manually if needed. -See `telescope-fzf-native` documentation for [more details](https://github.com/nvim-telescope/telescope-fzf-native.nvim#installation) +## Typical workflows -This requires: +### JS/TS service workflow -- Install CMake and the Microsoft C++ Build Tools on Windows +1. Edit with LSP + treesitter context. +2. Run nearest test with `nr` or file with `nf`. +3. Debug test or code path with `nd` / ``. +4. Use `sR` for safe project refactors. -```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' } -``` -
-
Windows with gcc/make using chocolatey -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 -``` +### C/C++ workflow -2. install all requirements using choco, exit the previous cmd and -open a new one so that choco path is set, and run in cmd as **admin**: -``` -choco install -y neovim git ripgrep wget fd unzip gzip mingw make -``` -
-
WSL (Windows Subsystem for Linux) +1. Navigate symbols with `jm/jk/jc/jC`. +2. Build/test with CMake mappings if project uses CMake. +3. Debug using existing DAP keys and select `Launch current file (codelldb)`. +4. Run gtest via neotest after `:ConfigureGtest` setup. -``` -wsl --install -wsl -sudo add-apt-repository ppa:neovim-ppa/unstable -y -sudo apt update -sudo apt install make gcc ripgrep unzip git xclip neovim -``` -
+### Kubernetes/Helm workflow -#### Linux Install -
Ubuntu Install Steps +1. Edit manifests with `yamlls` schema-backed completion/validation. +2. Edit charts/templates with Helm support (`helm_ls`, `vim-helm`). +3. Use `sR` for scoped multi-file YAML refactors. -``` -sudo add-apt-repository ppa:neovim-ppa/unstable -y -sudo apt update -sudo apt install make gcc ripgrep unzip git xclip neovim -``` -
-
Debian Install Steps +## Troubleshooting -``` -sudo apt update -sudo apt install make gcc ripgrep unzip git xclip curl - -# Now we install nvim -curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz -sudo rm -rf /opt/nvim-linux-x86_64 -sudo mkdir -p /opt/nvim-linux-x86_64 -sudo chmod a+rX /opt/nvim-linux-x86_64 -sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz - -# make it available in /usr/local/bin, distro installs to /usr/bin -sudo ln -sf /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/ -``` -
-
Fedora Install Steps +- Verify startup: `nvim --headless "+qa"` +- Verify health: `nvim --headless "+checkhealth" "+qa"` +- Verify LSP clients in current buffer: `:LspInfo` +- Verify formatter mapping: `:ConformInfo` +- Verify Mason state: `:Mason` +- Re-sync plugins: `:Lazy sync` -``` -sudo dnf install -y gcc make git ripgrep fd-find unzip neovim -``` -
+If a new feature appears missing, first confirm lazy-loading trigger +(keymap/filetype/command) was actually used. -
Arch Install Steps +## Help docs -``` -sudo pacman -S --noconfirm --needed gcc make git ripgrep fd unzip neovim -``` -
+This repo ships a Vim help file: +- `:help nvim-config` +- `:help nvimn-config` diff --git a/doc/nvim.txt b/doc/nvim.txt new file mode 100644 index 00000000000..9efabeb6ef3 --- /dev/null +++ b/doc/nvim.txt @@ -0,0 +1,50 @@ +============================================================================== +NVIM CONFIG *nvim-config* + *nvimn-config* + +This is a custom Neovim configuration based on kickstart.nvim. + +QUICK CHECKS *nvim-config-quick-checks* + +Run these commands after config changes: + +> + nvim --headless "+qa" + nvim --headless "+checkhealth" "+qa" + luac -p init.lua lua/custom/**/*.lua +< + +GIT KEYMAPS *nvim-config-git* + +Core git mappings in normal mode: + +- gg: open Neogit +- gd: open Diffview +- gD: close Diffview +- gf: Diffview file history for current file +- gF: Diffview repository history +- h...: Gitsigns hunk actions + +Use |which-key| with g and h to discover more actions. + +LSP: VUE + TYPESCRIPT *nvim-config-vue* + +Vue buffers rely on both: + +- `vue_ls` for Vue language features +- `ts_ls` (filetype-scoped to `vue`) with `@vue/typescript-plugin` + +This pairing allows `vue_ls` to forward TypeScript requests for `.vue` files. + +TREESITTER *nvim-config-treesitter* + +This config uses the current nvim-treesitter API: + +> + require('nvim-treesitter').setup() +< + +Configured parsers are installed from `ensure_installed` as needed. + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: diff --git a/doc/tags b/doc/tags index 687ae7721d9..857a702e9a4 100644 --- a/doc/tags +++ b/doc/tags @@ -1,3 +1,9 @@ kickstart-is kickstart.txt /*kickstart-is* kickstart-is-not kickstart.txt /*kickstart-is-not* kickstart.nvim kickstart.txt /*kickstart.nvim* +nvim-config nvim.txt /*nvim-config* +nvim-config-git nvim.txt /*nvim-config-git* +nvim-config-quick-checks nvim.txt /*nvim-config-quick-checks* +nvim-config-treesitter nvim.txt /*nvim-config-treesitter* +nvim-config-vue nvim.txt /*nvim-config-vue* +nvimn-config nvim.txt /*nvimn-config* diff --git a/init.lua b/init.lua index b98ffc6198a..386a854032f 100644 --- a/init.lua +++ b/init.lua @@ -101,8 +101,7 @@ vim.g.have_nerd_font = false -- Make line numbers default vim.o.number = true -- You can also add relative line numbers, to help with jumping. --- Experiment for yourself to see if you like it! --- vim.o.relativenumber = true +vim.o.relativenumber = true -- Enable mouse mode, can be useful for resizing splits for example! vim.o.mouse = 'a' @@ -118,8 +117,11 @@ vim.schedule(function() vim.o.clipboard = 'unnamedplus' end) --- Enable break indent -vim.o.breakindent = true +-- Default to 2-space indentation unless overridden by filetype/plugins +vim.o.tabstop = 2 +vim.o.shiftwidth = 2 +vim.o.softtabstop = 2 +vim.o.expandtab = true -- Save undo history vim.o.undofile = true @@ -175,6 +177,13 @@ vim.keymap.set('n', '', 'nohlsearch') -- Diagnostic keymaps vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' }) +vim.keymap.set('n', '[d', function() + vim.diagnostic.jump { count = -1, float = true } +end, { desc = 'Go to previous [D]iagnostic' }) +vim.keymap.set('n', ']d', function() + vim.diagnostic.jump { count = 1, float = true } +end, { desc = 'Go to next [D]iagnostic' }) +vim.keymap.set('n', 'de', vim.diagnostic.open_float, { desc = 'Show [D]iagnostic [E]rror details' }) -- Exit terminal mode in the builtin terminal with a shortcut that is a bit easier -- for people to discover. Otherwise, you normally need to press , which @@ -191,13 +200,13 @@ vim.keymap.set('t', '', '', { desc = 'Exit terminal mode' } -- vim.keymap.set('n', '', 'echo "Use j to move!!"') -- Keybinds to make split navigation easier. --- Use CTRL+ to switch between windows +-- NOTE: are managed by vim-tmux-navigator in this config. -- -- See `:help wincmd` for a list of all window commands -vim.keymap.set('n', '', '', { desc = 'Move focus to the left window' }) -vim.keymap.set('n', '', '', { desc = 'Move focus to the right window' }) -vim.keymap.set('n', '', '', { desc = 'Move focus to the lower window' }) -vim.keymap.set('n', '', '', { desc = 'Move focus to the upper window' }) +vim.keymap.set('n', 'wh', '', { desc = 'Move focus to the left window' }) +vim.keymap.set('n', 'wl', '', { desc = 'Move focus to the right window' }) +vim.keymap.set('n', 'wj', '', { desc = 'Move focus to the lower window' }) +vim.keymap.set('n', 'wk', '', { desc = 'Move focus to the upper window' }) -- NOTE: Some terminals have colliding keymaps or are not able to send distinct keycodes -- vim.keymap.set("n", "", "H", { desc = "Move window to the left" }) @@ -219,6 +228,60 @@ vim.api.nvim_create_autocmd('TextYankPost', { end, }) +local path_on_save_group = vim.api.nvim_create_augroup('kickstart-create-path-on-save', { clear = true }) + +-- Only create paths inside these roots (defaults to current working directory). +-- Add more roots as needed, e.g. vim.fn.expand '~/notes' +local path_create_roots = { + vim.fn.getcwd(), +} + +local is_in_allowed_root = function(path) + local abs_path = vim.fn.fnamemodify(path, ':p') + for _, root in ipairs(path_create_roots) do + local abs_root = vim.fn.fnamemodify(root, ':p') + if abs_path:sub(1, #abs_root) == abs_root then + return true + end + end + return false +end + +local ensure_owner_rwx = function(path) + local perms = vim.fn.getfperm(path) + if perms ~= '' and perms:sub(1, 3) ~= 'rwx' then + vim.fn.setfperm(path, 'rwx' .. perms:sub(4)) + end +end + +vim.api.nvim_create_autocmd('BufWritePre', { + desc = 'Create missing parent directories on save', + group = path_on_save_group, + callback = function(args) + if vim.bo[args.buf].buftype ~= '' then + return + end + + local file_path = vim.api.nvim_buf_get_name(args.buf) + if file_path == '' or file_path:match '^%w+://' then + return + end + + local uv = vim.uv or vim.loop + local abs_path = vim.fn.fnamemodify(file_path, ':p') + if not is_in_allowed_root(abs_path) then + return + end + + local parent = vim.fn.fnamemodify(abs_path, ':h') + + if uv.fs_stat(parent) == nil then + vim.fn.mkdir(parent, 'p', '0700') + end + ensure_owner_rwx(parent) + end, +}) + -- [[ Install `lazy.nvim` plugin manager ]] -- See `:help lazy.nvim.txt` or https://github.com/folke/lazy.nvim for more info local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' @@ -271,19 +334,6 @@ require('lazy').setup({ -- options to `gitsigns.nvim`. -- -- See `:help gitsigns` to understand what the configuration keys do - { -- Adds git related signs to the gutter, as well as utilities for managing changes - 'lewis6991/gitsigns.nvim', - opts = { - signs = { - add = { text = '+' }, - change = { text = '~' }, - delete = { text = '_' }, - topdelete = { text = '‾' }, - changedelete = { text = '~' }, - }, - }, - }, - -- NOTE: Plugins can also be configured to run Lua code when they are loaded. -- -- This is often very useful to both group configuration, as well as handle @@ -344,8 +394,20 @@ require('lazy').setup({ -- Document existing key chains spec = { + { 'a', group = 'Harpoon [A]dd' }, + { 'c', group = '[C]Make' }, + { 'd', group = '[D]iagnostics' }, + { 'e', group = '[E]xplorer' }, + { 'g', group = '[G]oto' }, + { 'G', group = '[G]it' }, + { 'm', group = '[M]arkdown' }, + { 'n', group = '[N]eotest' }, + { 'o', group = '[O]pencode' }, { 's', group = '[S]earch' }, { 't', group = '[T]oggle' }, + { 'w', group = '[W]indow' }, + { 'x', group = 'Trouble' }, + { 'j', group = '[J]ump' }, { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, }, }, @@ -545,19 +607,23 @@ require('lazy').setup({ -- Find references for the word under your cursor. map('grr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences') + map('gr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences') -- Jump to the implementation of the word under your cursor. -- Useful when your language has ways of declaring types without an actual implementation. map('gri', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation') + map('gi', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation') -- Jump to the definition of the word under your cursor. -- This is where a variable was first declared, or where a function is defined, etc. -- To jump back, press . map('grd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition') + map('gd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition') -- WARN: This is not Goto Definition, this is Goto Declaration. -- For example, in C this would take you to the header. map('grD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') + map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') -- Fuzzy find all the symbols in your current document. -- Symbols are things like variables, functions, types, etc. @@ -571,6 +637,7 @@ require('lazy').setup({ -- Useful when you're not sure what type a variable is and you want to see -- the definition of its *type*, not where it was *defined*. map('grt', require('telescope.builtin').lsp_type_definitions, '[G]oto [T]ype Definition') + map('gt', require('telescope.builtin').lsp_type_definitions, '[G]oto [T]ype Definition') -- This function resolves a difference between neovim nightly (version 0.11) and stable (version 0.10) ---@param client vim.lsp.Client @@ -643,24 +710,19 @@ require('lazy').setup({ virtual_text = { source = 'if_many', spacing = 2, + severity = { min = vim.diagnostic.severity.WARN }, format = function(diagnostic) - local diagnostic_message = { - [vim.diagnostic.severity.ERROR] = diagnostic.message, - [vim.diagnostic.severity.WARN] = diagnostic.message, - [vim.diagnostic.severity.INFO] = diagnostic.message, - [vim.diagnostic.severity.HINT] = diagnostic.message, - } - return diagnostic_message[diagnostic.severity] + if diagnostic.severity == vim.diagnostic.severity.WARN then + return 'W: ' .. diagnostic.message + end + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return 'E: ' .. diagnostic.message + end + return nil end, }, } - -- LSP servers and clients are able to communicate to each other what features they support. - -- By default, Neovim doesn't support everything that is in the LSP specification. - -- When you add blink.cmp, luasnip, etc. Neovim now has *more* capabilities. - -- So, we create new capabilities with blink.cmp, and then broadcast that to the servers. - local capabilities = require('blink.cmp').get_lsp_capabilities() - -- Enable the following language servers -- Feel free to add/remove any LSPs that you want here. They will automatically be installed. -- @@ -670,20 +732,67 @@ require('lazy').setup({ -- - capabilities (table): Override fields in capabilities. Can be used to disable certain LSP features. -- - settings (table): Override the default settings passed when initializing the server. -- For example, to see the options for `lua_ls`, you could go to: https://luals.github.io/wiki/settings/ - local servers = { - -- clangd = {}, - -- gopls = {}, - -- pyright = {}, - -- rust_analyzer = {}, - -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs - -- - -- Some languages (like typescript) have entire language plugins that can be useful: - -- https://github.com/pmizio/typescript-tools.nvim - -- - -- But for many setups, the LSP (`ts_ls`) will work just fine - -- ts_ls = {}, - -- + local vue_language_server_path = vim.fn.stdpath 'data' .. '/mason/packages/vue-language-server/node_modules/@vue/language-server' + local vue_typescript_plugin = { + name = '@vue/typescript-plugin', + location = vue_language_server_path, + languages = { 'vue' }, + configNamespace = 'typescript', + } + local servers = { + -- Languages + clangd = {}, + gopls = { + settings = { + gopls = { + analyses = { + unusedparams = true, + shadow = true, + }, + staticcheck = true, + gofumpt = true, + }, + }, + }, + basedpyright = { + settings = { + basedpyright = { + analysis = { + typeCheckingMode = 'standard', + diagnosticMode = 'openFilesOnly', + }, + }, + }, + }, + ruff = {}, + rust_analyzer = {}, + bashls = {}, + awk_ls = {}, + cssls = {}, + html = {}, + jsonls = {}, + yamlls = { + settings = { + yaml = { + schemas = { + ['https://raw.githubusercontent.com/yannh/kubernetes-json-schema/refs/heads/master/v1.32.1-standalone-strict/all.json'] = { + '*.k8s.yaml', + 'k8s/**/*.yaml', + 'manifests/**/*.yaml', + 'kubernetes/**/*.yaml', + }, + ['https://json.schemastore.org/chart'] = 'Chart.yaml', + ['https://json.schemastore.org/helmfile.json'] = 'helmfile.yaml', + ['https://json.schemastore.org/kustomization.json'] = 'kustomization.yaml', + }, + }, + }, + }, + taplo = {}, + elixirls = {}, + gh_actions_ls = {}, + jqls = {}, lua_ls = { -- cmd = { ... }, -- filetypes = { ... }, @@ -698,6 +807,37 @@ require('lazy').setup({ }, }, }, + -- Tools + eslint = {}, + astro = {}, + vue_ls = {}, + helm_ls = {}, + ts_ls = { + filetypes = { 'vue' }, + init_options = { + plugins = { vue_typescript_plugin }, + }, + }, + tailwindcss = {}, + docker_language_server = {}, + docker_compose_language_service = {}, + marksman = {}, + postgres_lsp = {}, + neocmake = {}, + buf_ls = {}, + + -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs + -- + -- Some languages (like typescript) have entire language plugins that can be useful: + -- https://github.com/pmizio/typescript-tools.nvim + -- + -- But for many setups, the LSP (`ts_ls`) will work just fine + -- + } + ---@type MasonLspconfigSettings + ---@diagnostic disable-next-line: missing-fields + require('mason-lspconfig').setup { + automatic_enable = vim.tbl_keys(servers or {}), } -- Ensure the servers and tools above are installed @@ -716,23 +856,25 @@ require('lazy').setup({ local ensure_installed = vim.tbl_keys(servers or {}) vim.list_extend(ensure_installed, { 'stylua', -- Used to format Lua code + 'markdownlint', -- Used by nvim-lint for Markdown buffers + 'prettierd', + 'prettier', + 'clang-format', + 'hadolint', + 'yamllint', + 'js-debug-adapter', + 'codelldb', }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } - require('mason-lspconfig').setup { - ensure_installed = {}, -- explicitly set to an empty table (Kickstart populates installs via mason-tool-installer) - automatic_installation = false, - handlers = { - function(server_name) - local server = servers[server_name] or {} - -- This handles overriding only values explicitly passed - -- by the server configuration above. Useful when disabling - -- certain features of an LSP (for example, turning off formatting for ts_ls) - server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {}) - require('lspconfig')[server_name].setup(server) - end, - }, - } + -- Installed LSPs are configured and enabled automatically with mason-lspconfig + -- The loop below is for overriding the default configuration of LSPs with the ones in the servers table + for server_name, config in pairs(servers) do + vim.lsp.config(server_name, config) + end + + -- NOTE: Some servers may require an old setup until they are updated. For the full list refer here: https://github.com/neovim/nvim-lspconfig/issues/3705 + -- These servers will have to be manually set up with require("lspconfig").server_name.setup{} end, }, @@ -768,6 +910,15 @@ require('lazy').setup({ end, formatters_by_ft = { lua = { 'stylua' }, + javascript = { 'prettierd', 'prettier', stop_after_first = true }, + javascriptreact = { 'prettierd', 'prettier', stop_after_first = true }, + typescript = { 'prettierd', 'prettier', stop_after_first = true }, + typescriptreact = { 'prettierd', 'prettier', stop_after_first = true }, + json = { 'prettierd', 'prettier', stop_after_first = true }, + jsonc = { 'prettierd', 'prettier', stop_after_first = true }, + yaml = { 'prettierd', 'prettier', stop_after_first = true }, + c = { 'clang_format' }, + cpp = { 'clang_format' }, -- Conform can also run multiple formatters sequentially -- python = { "isort", "black" }, -- @@ -919,6 +1070,13 @@ require('lazy').setup({ -- - sr)' - [S]urround [R]eplace [)] ['] require('mini.surround').setup() + -- Delete buffers while preserving window layout. + local bufremove = require 'mini.bufremove' + bufremove.setup() + vim.keymap.set('n', 'bd', function() + bufremove.delete(0, false) + end, { desc = '[B]uffer [D]elete' }) + -- Simple and easy statusline. -- You could remove this setup call if you don't like it, -- and try some other statusline plugin @@ -940,22 +1098,34 @@ require('lazy').setup({ }, { -- Highlight, edit, and navigate code 'nvim-treesitter/nvim-treesitter', + branch = 'main', + lazy = false, build = ':TSUpdate', - main = 'nvim-treesitter.configs', -- Sets main module to use for opts -- [[ Configure Treesitter ]] See `:help nvim-treesitter` - opts = { - ensure_installed = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'markdown_inline', 'query', 'vim', 'vimdoc' }, - -- Autoinstall languages that are not installed - auto_install = true, - highlight = { - enable = true, - -- Some languages depend on vim's regex highlighting system (such as Ruby) for indent rules. - -- If you are experiencing weird indenting issues, add the language to - -- the list of additional_vim_regex_highlighting and disabled languages for indent. - additional_vim_regex_highlighting = { 'ruby' }, - }, - indent = { enable = true, disable = { 'ruby' } }, - }, + config = function() + local treesitter = require 'nvim-treesitter' + treesitter.setup() + require('nvim-treesitter.install').ensure_installed { + 'bash', + 'c', + 'diff', + 'html', + 'lua', + 'luadoc', + 'markdown', + 'markdown_inline', + 'query', + 'vim', + 'vimdoc', + } + + vim.api.nvim_create_autocmd('FileType', { + group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), + callback = function() + pcall(vim.treesitter.start) + end, + }) + end, -- There are additional nvim-treesitter modules that you can use to interact -- with nvim-treesitter. You should go explore a few and see what interests you: -- @@ -968,23 +1138,11 @@ require('lazy').setup({ -- init.lua. If you want these files, they are in the repository, so you can just download them and -- place them in the correct locations. - -- NOTE: Next step on your Neovim journey: Add/Configure additional plugins for Kickstart - -- - -- Here are some example plugins that I've included in the Kickstart repository. - -- Uncomment any of the lines below to enable them (you will need to restart nvim). - -- - -- require 'kickstart.plugins.debug', - -- require 'kickstart.plugins.indent_line', - -- require 'kickstart.plugins.lint', - -- require 'kickstart.plugins.autopairs', - -- require 'kickstart.plugins.neo-tree', - -- require 'kickstart.plugins.gitsigns', -- adds gitsigns recommend keymaps - -- NOTE: The import below can automatically add your own plugins, configuration, etc from `lua/custom/plugins/*.lua` -- This is the easiest way to modularize your config. -- -- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going. - -- { import = 'custom.plugins' }, + { import = 'custom.plugins' }, -- -- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec` -- Or use telescope! diff --git a/lua/kickstart/plugins/autopairs.lua b/lua/custom/plugins/autopairs.lua similarity index 100% rename from lua/kickstart/plugins/autopairs.lua rename to lua/custom/plugins/autopairs.lua diff --git a/lua/custom/plugins/bufferline.lua b/lua/custom/plugins/bufferline.lua new file mode 100644 index 00000000000..cdb68e06886 --- /dev/null +++ b/lua/custom/plugins/bufferline.lua @@ -0,0 +1,31 @@ +return { + 'akinsho/bufferline.nvim', + event = 'VeryLazy', + dependencies = { 'nvim-tree/nvim-web-devicons' }, + keys = { + { '', 'BufferLineCyclePrev', desc = 'Previous buffer' }, + { '', 'BufferLineCycleNext', desc = 'Next buffer' }, + { 'bp', 'BufferLinePick', desc = '[B]uffer [P]ick' }, + { + 'bD', + function() + local bufremove = require 'mini.bufremove' + + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted and vim.bo[bufnr].filetype ~= 'neo-tree' then + bufremove.delete(bufnr, false) + end + end + end, + desc = '[B]uffer [D]elete all', + }, + }, + opts = { + options = { + mode = 'buffers', + always_show_bufferline = true, + show_buffer_close_icons = false, + show_close_icon = false, + }, + }, +} diff --git a/lua/custom/plugins/cmake_tools.lua b/lua/custom/plugins/cmake_tools.lua new file mode 100644 index 00000000000..b532dc2dd37 --- /dev/null +++ b/lua/custom/plugins/cmake_tools.lua @@ -0,0 +1,29 @@ +return { + 'Civitasv/cmake-tools.nvim', + dependencies = { 'nvim-lua/plenary.nvim' }, + cmd = { + 'CMakeGenerate', + 'CMakeBuild', + 'CMakeRun', + 'CMakeTest', + 'CMakeSelectBuildType', + 'CMakeSelectBuildTarget', + }, + ft = { 'cmake' }, + keys = { + { 'cg', 'CMakeGenerate', desc = '[C]Make [G]enerate' }, + { 'cb', 'CMakeBuild', desc = '[C]Make [B]uild' }, + { 'cr', 'CMakeRun', desc = '[C]Make [R]un' }, + { 'ct', 'CMakeTest', desc = '[C]Make [T]est' }, + { 'cc', 'CMakeSelectBuildType', desc = '[C]Make [C]onfiguration' }, + }, + opts = { + cmake_generate_options = { '-DCMAKE_EXPORT_COMPILE_COMMANDS=1' }, + cmake_executor = { + name = 'quickfix', + }, + cmake_runner = { + name = 'terminal', + }, + }, +} diff --git a/lua/custom/plugins/dap_js_ts.lua b/lua/custom/plugins/dap_js_ts.lua new file mode 100644 index 00000000000..8dc13a726ad --- /dev/null +++ b/lua/custom/plugins/dap_js_ts.lua @@ -0,0 +1,40 @@ +return { + 'mxsdev/nvim-dap-vscode-js', + dependencies = { 'mfussenegger/nvim-dap' }, + ft = { 'javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue' }, + config = function() + require('dap-vscode-js').setup { + debugger_cmd = { 'js-debug-adapter' }, + adapters = { + 'pwa-node', + 'pwa-chrome', + 'pwa-msedge', + 'node-terminal', + 'pwa-extensionHost', + }, + } + + local dap = require 'dap' + local configs = { + { + type = 'pwa-node', + request = 'launch', + name = 'Launch current file (Node)', + program = '${file}', + cwd = '${workspaceFolder}', + sourceMaps = true, + }, + { + type = 'pwa-node', + request = 'attach', + name = 'Attach to process', + processId = require('dap.utils').pick_process, + cwd = '${workspaceFolder}', + }, + } + + for _, language in ipairs { 'javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue' } do + dap.configurations[language] = vim.list_extend(dap.configurations[language] or {}, configs) + end + end, +} diff --git a/lua/kickstart/plugins/debug.lua b/lua/custom/plugins/debug.lua similarity index 80% rename from lua/kickstart/plugins/debug.lua rename to lua/custom/plugins/debug.lua index 753cb0cedd3..ba9491db525 100644 --- a/lua/kickstart/plugins/debug.lua +++ b/lua/custom/plugins/debug.lua @@ -6,6 +6,44 @@ -- be extended to other languages as well. That's why it's called -- kickstart.nvim and not kitchen-sink.nvim ;) +local function has_configuration(configurations, name, adapter) + for _, configuration in ipairs(configurations or {}) do + if configuration.name == name and configuration.type == adapter then + return true + end + end + + return false +end + +local function setup_cpp_dap(dap) + dap.adapters.codelldb = dap.adapters.codelldb or { + type = 'executable', + command = 'codelldb', + } + + local launch_name = 'Launch current file (codelldb)' + local launch_configuration = { + name = launch_name, + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + args = {}, + } + + for _, language in ipairs { 'c', 'cpp' } do + dap.configurations[language] = dap.configurations[language] or {} + + if not has_configuration(dap.configurations[language], launch_name, 'codelldb') then + table.insert(dap.configurations[language], vim.deepcopy(launch_configuration)) + end + end +end + return { -- NOTE: Yes, you can install new plugins here! 'mfussenegger/nvim-dap', @@ -95,6 +133,7 @@ return { ensure_installed = { -- Update this to ensure that you have the debuggers for the langs you want 'delve', + 'codelldb', }, } @@ -144,5 +183,7 @@ return { detached = vim.fn.has 'win32' == 0, }, } + + setup_cpp_dap(dap) end, } diff --git a/lua/custom/plugins/diffview.lua b/lua/custom/plugins/diffview.lua new file mode 100644 index 00000000000..b893a0c8898 --- /dev/null +++ b/lua/custom/plugins/diffview.lua @@ -0,0 +1,19 @@ +return { + 'sindrets/diffview.nvim', + dependencies = { 'nvim-lua/plenary.nvim' }, + cmd = { + 'DiffviewOpen', + 'DiffviewClose', + 'DiffviewFileHistory', + 'DiffviewFocusFiles', + 'DiffviewToggleFiles', + 'DiffviewRefresh', + }, + keys = { + { 'Gd', 'DiffviewOpen', desc = '[G]it [D]iff view' }, + { 'GD', 'DiffviewClose', desc = '[G]it [D]iff close' }, + { 'Gf', 'DiffviewFileHistory %', desc = '[G]it [F]ile history' }, + { 'GF', 'DiffviewFileHistory', desc = '[G]it [F]ull history' }, + }, + opts = {}, +} diff --git a/lua/custom/plugins/folding.lua b/lua/custom/plugins/folding.lua new file mode 100644 index 00000000000..c6cf7c21908 --- /dev/null +++ b/lua/custom/plugins/folding.lua @@ -0,0 +1,27 @@ +return { + { + 'kevinhwang91/nvim-ufo', + dependencies = { + 'kevinhwang91/promise-async', + }, + event = 'VeryLazy', + init = function() + vim.o.foldcolumn = '1' + vim.o.foldlevel = 99 + vim.o.foldlevelstart = 99 + vim.o.foldenable = true + end, + opts = { + provider_selector = function(_, _, _) + return { 'treesitter', 'indent' } + end, + }, + config = function(_, opts) + local ufo = require 'ufo' + ufo.setup(opts) + + vim.keymap.set('n', 'zR', ufo.openAllFolds, { desc = 'Open all folds' }) + vim.keymap.set('n', 'zM', ufo.closeAllFolds, { desc = 'Close all folds' }) + end, + }, +} diff --git a/lua/kickstart/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua similarity index 85% rename from lua/kickstart/plugins/gitsigns.lua rename to lua/custom/plugins/gitsigns.lua index cbbd22d24fc..cf3d48931a0 100644 --- a/lua/kickstart/plugins/gitsigns.lua +++ b/lua/custom/plugins/gitsigns.lua @@ -6,6 +6,13 @@ return { { 'lewis6991/gitsigns.nvim', opts = { + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '_' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + }, on_attach = function(bufnr) local gitsigns = require 'gitsigns' @@ -44,7 +51,7 @@ return { map('n', 'hs', gitsigns.stage_hunk, { desc = 'git [s]tage hunk' }) map('n', 'hr', gitsigns.reset_hunk, { desc = 'git [r]eset hunk' }) map('n', 'hS', gitsigns.stage_buffer, { desc = 'git [S]tage buffer' }) - map('n', 'hu', gitsigns.stage_hunk, { desc = 'git [u]ndo stage hunk' }) + map('n', 'hu', gitsigns.undo_stage_hunk, { desc = 'git [u]ndo stage hunk' }) map('n', 'hR', gitsigns.reset_buffer, { desc = 'git [R]eset buffer' }) map('n', 'hp', gitsigns.preview_hunk, { desc = 'git [p]review hunk' }) map('n', 'hb', gitsigns.blame_line, { desc = 'git [b]lame line' }) @@ -54,7 +61,7 @@ return { end, { desc = 'git [D]iff against last commit' }) -- Toggles map('n', 'tb', gitsigns.toggle_current_line_blame, { desc = '[T]oggle git show [b]lame line' }) - map('n', 'tD', gitsigns.preview_hunk_inline, { desc = '[T]oggle git show [D]eleted' }) + map('n', 'tD', gitsigns.toggle_deleted, { desc = '[T]oggle git show [D]eleted' }) end, }, }, diff --git a/lua/custom/plugins/grug_far.lua b/lua/custom/plugins/grug_far.lua new file mode 100644 index 00000000000..7023842f7a0 --- /dev/null +++ b/lua/custom/plugins/grug_far.lua @@ -0,0 +1,27 @@ +return { + 'MagicDuck/grug-far.nvim', + cmd = { 'GrugFar', 'GrugFarWithin' }, + opts = {}, + keys = { + { + 'sR', + function() + require('grug-far').open { + prefills = { + search = vim.fn.expand '', + }, + } + end, + mode = 'n', + desc = '[S]earch project [R]eplace', + }, + { + 'sR', + function() + require('grug-far').with_visual_selection() + end, + mode = 'x', + desc = '[S]earch project [R]eplace selection', + }, + }, +} diff --git a/lua/custom/plugins/harpoon.lua b/lua/custom/plugins/harpoon.lua new file mode 100644 index 00000000000..21ec13d6482 --- /dev/null +++ b/lua/custom/plugins/harpoon.lua @@ -0,0 +1,72 @@ +return { + 'ThePrimeagen/harpoon', + branch = 'harpoon2', + dependencies = { 'nvim-lua/plenary.nvim' }, + config = function() + local harpoon = require 'harpoon' + harpoon:setup { + default = { + select = function(list_item, _, options) + if not list_item or not list_item.value or list_item.value == '' then + return + end + + options = options or {} + local open_cmd = 'edit' + if options.vsplit then + open_cmd = 'vsplit' + elseif options.split then + open_cmd = 'split' + elseif options.tabedit then + open_cmd = 'tabedit' + end + + vim.cmd(open_cmd .. ' ' .. vim.fn.fnameescape(list_item.value)) + + local context = list_item.context or {} + local row = math.max(1, tonumber(context.row) or 1) + local col = math.max(0, tonumber(context.col) or 0) + local line_count = vim.api.nvim_buf_line_count(0) + if line_count < 1 then + return + end + + if row > line_count then + row = line_count + end + + local row_text = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] or '' + if col > #row_text then + col = #row_text + end + + pcall(vim.api.nvim_win_set_cursor, 0, { row, col }) + end, + }, + } + + vim.keymap.set('n', 'a', function() + if vim.bo.buftype ~= '' then + vim.notify('Harpoon: current buffer is not a file', vim.log.levels.INFO) + return + end + harpoon:list():add() + end, { desc = 'Harpoon add' }) + vim.keymap.set('n', 'hm', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Harpoon menu' }) + vim.keymap.set('n', '', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Harpoon menu' }) + vim.api.nvim_create_user_command('HarpoonMenu', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Toggle Harpoon quick menu' }) + + vim.keymap.set('n', '', function() + harpoon:list():prev() + end) + vim.keymap.set('n', '', function() + harpoon:list():next() + end) + end, +} diff --git a/lua/custom/plugins/helm.lua b/lua/custom/plugins/helm.lua new file mode 100644 index 00000000000..d4b5e1b4bf4 --- /dev/null +++ b/lua/custom/plugins/helm.lua @@ -0,0 +1,3 @@ +return { + 'towolf/vim-helm', +} diff --git a/lua/kickstart/plugins/indent_line.lua b/lua/custom/plugins/indent_line.lua similarity index 100% rename from lua/kickstart/plugins/indent_line.lua rename to lua/custom/plugins/indent_line.lua diff --git a/lua/custom/plugins/lint.lua b/lua/custom/plugins/lint.lua new file mode 100644 index 00000000000..08494844582 --- /dev/null +++ b/lua/custom/plugins/lint.lua @@ -0,0 +1,126 @@ +return { + + { -- Linting + 'mfussenegger/nvim-lint', + event = { 'BufReadPre', 'BufNewFile' }, + config = function() + local lint = require 'lint' + lint.linters_by_ft = { + markdown = { 'markdownlint' }, + dockerfile = { 'hadolint' }, + yaml = { 'yamllint' }, + ['yaml.helm-values'] = { 'yamllint' }, + } + lint.linters.markdownlint = vim.tbl_deep_extend('force', lint.linters.markdownlint or {}, { + args = { + '--stdin', + '--disable', + 'MD013', -- line length + 'MD033', -- inline HTML + 'MD041', -- first line heading + }, + }) + + -- To allow other plugins to add linters to require('lint').linters_by_ft, + -- instead set linters_by_ft like this: + -- lint.linters_by_ft = lint.linters_by_ft or {} + -- lint.linters_by_ft['markdown'] = { 'markdownlint' } + -- + -- However, note that this will enable a set of default linters, + -- which will cause errors unless these tools are available: + -- { + -- clojure = { "clj-kondo" }, + -- dockerfile = { "hadolint" }, + -- inko = { "inko" }, + -- janet = { "janet" }, + -- json = { "jsonlint" }, + -- markdown = { "vale" }, + -- rst = { "vale" }, + -- ruby = { "ruby" }, + -- terraform = { "tflint" }, + -- text = { "vale" } + -- } + -- + -- You can disable the default linters by setting their filetypes to nil: + -- lint.linters_by_ft['clojure'] = nil + -- lint.linters_by_ft['dockerfile'] = nil + -- lint.linters_by_ft['inko'] = nil + -- lint.linters_by_ft['janet'] = nil + -- lint.linters_by_ft['json'] = nil + -- lint.linters_by_ft['markdown'] = nil + -- lint.linters_by_ft['rst'] = nil + -- lint.linters_by_ft['ruby'] = nil + -- lint.linters_by_ft['terraform'] = nil + -- lint.linters_by_ft['text'] = nil + + local function executable_cmd(cmd) + if type(cmd) == 'function' then + local ok, value = pcall(cmd) + if not ok then + return nil + end + return value + end + return cmd + end + + local function available_linters_for(ft) + local configured = lint.linters_by_ft[ft] or {} + local available = {} + for _, linter_name in ipairs(configured) do + local linter = lint.linters[linter_name] + local cmd = linter and executable_cmd(linter.cmd) + if cmd == nil or cmd == '' or vim.fn.executable(cmd) == 1 then + table.insert(available, linter_name) + end + end + return available + end + + local function trigger_lint() + if vim.bo.modifiable then + local linters = available_linters_for(vim.bo.filetype) + if #linters > 0 then + lint.try_lint(linters) + end + end + end + + -- Toggle lint on/off + local lint_enabled = true + vim.keymap.set('n', 'tl', function() + lint_enabled = not lint_enabled + if not lint_enabled then + local cleared = {} + for _, linters in pairs(lint.linters_by_ft) do + for _, linter_name in ipairs(linters) do + local ns = lint.get_namespace(linter_name) + if not cleared[ns] then + vim.diagnostic.reset(ns) + cleared[ns] = true + end + end + end + else + trigger_lint() + end + vim.notify('Lint ' .. (lint_enabled and 'enabled' or 'disabled')) + end, { desc = '[T]oggle [L]int' }) + + -- Create autocommand which carries out the actual linting + -- on the specified events. + local lint_augroup = vim.api.nvim_create_augroup('lint', { clear = true }) + vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { + group = lint_augroup, + -- Only run the linter in buffers that you can modify in order to + -- avoid superfluous noise, notably within the handy LSP pop-ups that + -- describe the hovered symbol using Markdown. + callback = function() + if lint_enabled then + trigger_lint() + end + end, + }) + end, + }, +} diff --git a/lua/custom/plugins/location_reference.lua b/lua/custom/plugins/location_reference.lua new file mode 100644 index 00000000000..1b44725161f --- /dev/null +++ b/lua/custom/plugins/location_reference.lua @@ -0,0 +1,274 @@ +local sk = vim.lsp.protocol.SymbolKind +local symbol_kinds = { + [sk.Class] = true, + [sk.Method] = true, + [sk.Constructor] = true, + [sk.Enum] = true, + [sk.Interface] = true, + [sk.Function] = true, + [sk.Struct] = true, +} + +local function range_contains(range, line0, col0) + if not range then + return false + end + + local start_line = range.start.line + local start_col = range.start.character + local end_line = range['end'].line + local end_col = range['end'].character + + if line0 < start_line or line0 > end_line then + return false + end + if line0 == start_line and col0 < start_col then + return false + end + if line0 == end_line and col0 > end_col then + return false + end + + return true +end + +local function range_size(range) + -- Weight lines heavily so a single-line range is always smaller than a multi-line one + return (range['end'].line - range.start.line) * 100000 + (range['end'].character - range.start.character) +end + +local function append_document_symbols(symbols, out, parent_name) + for _, symbol in ipairs(symbols or {}) do + local symbol_name = symbol.name + local full_name = parent_name and (parent_name .. '.' .. symbol_name) or symbol_name + table.insert(out, { + name = full_name, + kind = symbol.kind, + range = symbol.range, + }) + append_document_symbols(symbol.children, out, full_name) + end +end + +local function append_symbol_information(symbols, out) + for _, symbol in ipairs(symbols or {}) do + local name = symbol.name + if symbol.containerName and symbol.containerName ~= '' then + name = symbol.containerName .. '.' .. name + end + table.insert(out, { + name = name, + kind = symbol.kind, + range = symbol.location and symbol.location.range, + }) + end +end + +local function get_symbol_at_cursor() + if #vim.lsp.get_clients { bufnr = 0 } == 0 then + return nil + end + + local params = { textDocument = vim.lsp.util.make_text_document_params() } + local responses = vim.lsp.buf_request_sync(0, 'textDocument/documentSymbol', params, 500) + if not responses then + return nil + end + + local cursor = vim.api.nvim_win_get_cursor(0) + local line0 = cursor[1] - 1 + -- LSP ranges use UTF-16 offsets; convert from byte offset for correct matching + -- on files with non-ASCII characters (e.g. CJK, emoji). + local line_text = vim.api.nvim_buf_get_lines(0, line0, line0 + 1, false)[1] or '' + local col0 = vim.str_utfindex(line_text, 'utf-16', cursor[2]) + local symbols = {} + + for _, response in pairs(responses) do + if not response.error then + local result = response.result + if type(result) == 'table' and result[1] then + if result[1].selectionRange or result[1].children then + append_document_symbols(result, symbols, nil) + else + append_symbol_information(result, symbols) + end + end + end + end + + local best_symbol, best_size + for _, symbol in ipairs(symbols) do + if symbol_kinds[symbol.kind] and range_contains(symbol.range, line0, col0) then + local size = range_size(symbol.range) + if not best_size or size < best_size then + best_size = size + best_symbol = symbol.name + end + end + end + + return best_symbol +end + +local function get_relative_path_from_root(file_path) + local root = vim.fs.root(file_path, { '.git' }) or vim.fn.getcwd() + + if vim.fs.relpath then + local rel = vim.fs.relpath(root, file_path) + if rel then + return rel + end + end + + local prefix = root + if not prefix:match '/$' then + prefix = prefix .. '/' + end + if file_path:sub(1, #prefix) == prefix then + return file_path:sub(#prefix + 1) + end + + return file_path +end + +local function get_file_parts() + local file_path = vim.api.nvim_buf_get_name(0) + if file_path == '' then + vim.notify('No file path for current buffer', vim.log.levels.WARN) + return nil + end + + return { + file_path = file_path, + file_rel = get_relative_path_from_root(file_path), + } +end + +local function get_location_parts() + local parts = get_file_parts() + if not parts then + return nil + end + + local mode = vim.api.nvim_get_mode().mode + local loc + + if mode == 'v' or mode == 'V' or mode == '\22' then + -- Use 'v' (visual anchor) and '.' (cursor) for the live selection. + -- ' marks are only reliable after exiting visual mode (e.g. ':' mappings), + -- but mappings stay in visual mode so those marks are stale. + local start_pos = vim.fn.getpos 'v' + local end_pos = vim.fn.getpos '.' + local s_line, s_col = start_pos[2], start_pos[3] + local e_line, e_col = end_pos[2], end_pos[3] + + -- getpos('v') returns zeros when called outside a real visual context; fall back. + if s_line == 0 then + local cur = vim.api.nvim_win_get_cursor(0) + s_line, s_col = cur[1], cur[2] + 1 + e_line, e_col = s_line, s_col + elseif s_line > e_line or (s_line == e_line and s_col > e_col) then + s_line, e_line = e_line, s_line + s_col, e_col = e_col, s_col + end + + loc = string.format('%d:%d-%d:%d', s_line, s_col, e_line, e_col) + else + local cursor = vim.api.nvim_win_get_cursor(0) + local line_num = cursor[1] + local line_text = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1] or '' + local end_col = math.max(vim.fn.strchars(line_text), 1) + loc = string.format('%d:%d-%d:%d', line_num, 1, line_num, end_col) + end + + return { + file_path = parts.file_path, + file_rel = parts.file_rel, + loc = loc, + symbol = get_symbol_at_cursor(), + } +end + +local function yank(label, value) + vim.fn.setreg('"', value) + pcall(vim.fn.setreg, '+', value) + vim.notify('Copied ' .. label .. ': ' .. value, vim.log.levels.INFO) +end + +local function copy_absolute_location_reference() + local parts = get_location_parts() + if not parts then + return + end + + local location = string.format('%s:%s', parts.file_path, parts.loc) + if parts.symbol and parts.symbol ~= '' then + location = location .. ' symbol:' .. parts.symbol + end + + yank('absolute location', location) +end + +local function copy_relative_location_reference() + local parts = get_location_parts() + if not parts then + return + end + + local symbol = parts.symbol and parts.symbol ~= '' and parts.symbol or 'none' + yank('relative location', string.format('@%s loc:%s symbol:%s', parts.file_rel, parts.loc, symbol)) +end + +local function copy_buffer_file_reference() + local parts = get_file_parts() + if not parts then + return + end + + yank('buffer file', string.format('@%s', parts.file_rel)) +end + +local function copy_buffer_directory_reference() + local parts = get_file_parts() + if not parts then + return + end + + local abs_dir = vim.fn.fnamemodify(parts.file_path, ':h') + local rel_dir = get_relative_path_from_root(abs_dir) + yank('buffer directory', string.format('@%s', rel_dir)) +end + +return { + 'folke/which-key.nvim', + keys = { + { + 'ya', + copy_absolute_location_reference, + mode = { 'n', 'x' }, + desc = '[Y]ank [A]bsolute ref', + }, + { + 'yr', + copy_relative_location_reference, + mode = { 'n', 'x' }, + desc = '[Y]ank [R]elative ref', + }, + { + 'yf', + copy_buffer_file_reference, + mode = { 'n', 'x' }, + desc = '[Y]ank Buffer [F]ile', + }, + { + 'yd', + copy_buffer_directory_reference, + mode = { 'n', 'x' }, + desc = '[Y]ank Buffer [D]irectory', + }, + }, + opts = function(_, opts) + opts.spec = opts.spec or {} + table.insert(opts.spec, { 'y', group = '[Y]ank' }) + end, +} diff --git a/lua/custom/plugins/lsp_file_operations.lua b/lua/custom/plugins/lsp_file_operations.lua new file mode 100644 index 00000000000..3f27b8eefe9 --- /dev/null +++ b/lua/custom/plugins/lsp_file_operations.lua @@ -0,0 +1,13 @@ +return { + 'antosha417/nvim-lsp-file-operations', + dependencies = { 'nvim-neo-tree/neo-tree.nvim' }, + config = function() + require('lsp-file-operations').setup() + + -- Advertise file operation capabilities to all LSP servers + local file_ops_caps = require('lsp-file-operations').default_capabilities() + vim.lsp.config('*', { + capabilities = file_ops_caps, + }) + end, +} diff --git a/lua/custom/plugins/markdown.lua b/lua/custom/plugins/markdown.lua new file mode 100644 index 00000000000..82a8729caca --- /dev/null +++ b/lua/custom/plugins/markdown.lua @@ -0,0 +1,46 @@ +return { + { + 'MeanderingProgrammer/render-markdown.nvim', + ft = { 'markdown' }, + dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, + opts = { + file_types = { 'markdown' }, + }, + }, + { + 'iamcco/markdown-preview.nvim', + ft = { 'markdown' }, + cmd = { 'MarkdownPreviewToggle', 'MarkdownPreview', 'MarkdownPreviewStop' }, + build = function() + vim.fn['mkdp#util#install']() + end, + init = function() + local browser = '' + local is_macos = vim.fn.has('macunix') == 1 + + if is_macos then + vim.cmd([[ + function! OpenMarkdownPreview(url) abort + execute 'silent !open ' . shellescape(a:url) + endfunction + ]]) + elseif vim.fn.executable('google-chrome-stable') == 1 then + browser = 'google-chrome-stable' + elseif vim.fn.executable('google-chrome') == 1 then + browser = 'google-chrome' + elseif vim.fn.executable('chromium') == 1 then + browser = 'chromium' + end + + vim.g.mkdp_auto_start = 0 + vim.g.mkdp_auto_close = 1 + vim.g.mkdp_refresh_slow = 0 + vim.g.mkdp_browser = browser + vim.g.mkdp_browserfunc = is_macos and 'OpenMarkdownPreview' or '' + vim.g.mkdp_filetypes = { 'markdown' } + end, + keys = { + { 'mp', 'MarkdownPreviewToggle', desc = '[M]arkdown [P]review' }, + }, + }, +} diff --git a/lua/kickstart/plugins/neo-tree.lua b/lua/custom/plugins/neo-tree.lua similarity index 100% rename from lua/kickstart/plugins/neo-tree.lua rename to lua/custom/plugins/neo-tree.lua diff --git a/lua/custom/plugins/neogit.lua b/lua/custom/plugins/neogit.lua new file mode 100644 index 00000000000..c91dfedc141 --- /dev/null +++ b/lua/custom/plugins/neogit.lua @@ -0,0 +1,18 @@ +return { + 'NeogitOrg/neogit', + lazy = true, + dependencies = { + 'nvim-lua/plenary.nvim', -- required + 'sindrets/diffview.nvim', -- optional - Diff integration + + -- Only one of these is needed. + 'nvim-telescope/telescope.nvim', -- optional + 'ibhagwan/fzf-lua', -- optional + 'nvim-mini/mini.pick', -- optional + 'folke/snacks.nvim', -- optional + }, + cmd = 'Neogit', + keys = { + { 'Gg', 'Neogit', desc = 'Show Neogit UI' }, + }, +} diff --git a/lua/custom/plugins/neotest.lua b/lua/custom/plugins/neotest.lua new file mode 100644 index 00000000000..1b0d2937b6f --- /dev/null +++ b/lua/custom/plugins/neotest.lua @@ -0,0 +1,98 @@ +return { + 'nvim-neotest/neotest', + dependencies = { + 'nvim-neotest/nvim-nio', + 'nvim-lua/plenary.nvim', + 'nvim-treesitter/nvim-treesitter', + 'nvim-neotest/neotest-jest', + 'marilari88/neotest-vitest', + 'alfaix/neotest-gtest', + }, + keys = { + { + 'nr', + function() + require('neotest').run.run() + end, + desc = '[N]eotest run nea[r]est', + }, + { + 'nf', + function() + require('neotest').run.run(vim.fn.expand '%') + end, + desc = '[N]eotest run [F]ile', + }, + { + 'ns', + function() + require('neotest').run.run(vim.fn.getcwd()) + end, + desc = '[N]eotest run [S]uite', + }, + { + 'nd', + function() + require('neotest').run.run { strategy = 'dap' } + end, + desc = '[N]eotest [D]ebug nearest', + }, + { + 'nn', + function() + require('neotest').summary.toggle() + end, + desc = '[N]eotest summary', + }, + { + 'no', + function() + require('neotest').output.open { enter = true, auto_close = true } + end, + desc = '[N]eotest [O]utput', + }, + { + 'nO', + function() + require('neotest').output_panel.toggle() + end, + desc = '[N]eotest [O]utput panel', + }, + { + 'na', + function() + require('neotest').run.attach() + end, + desc = '[N]eotest [A]ttach', + }, + { + 'nS', + function() + require('neotest').run.stop() + end, + desc = '[N]eotest [S]top', + }, + }, + config = function() + local adapters = {} + + local ok_jest, jest = pcall(require, 'neotest-jest') + if ok_jest then + table.insert(adapters, jest {}) + end + + local ok_vitest, vitest = pcall(require, 'neotest-vitest') + if ok_vitest then + table.insert(adapters, vitest {}) + end + + local ok_gtest, gtest = pcall(require, 'neotest-gtest') + if ok_gtest then + table.insert(adapters, gtest.setup {}) + end + + require('neotest').setup { + adapters = adapters, + } + end, +} diff --git a/lua/custom/plugins/oil.lua b/lua/custom/plugins/oil.lua new file mode 100644 index 00000000000..c4fb1abd068 --- /dev/null +++ b/lua/custom/plugins/oil.lua @@ -0,0 +1,19 @@ +return { + 'stevearc/oil.nvim', + cmd = { 'Oil' }, + keys = { + { 'eo', 'Oil', desc = '[E]xplorer [O]il' }, + }, + opts = { + default_file_explorer = false, + columns = { 'icon' }, + view_options = { + show_hidden = true, + }, + lsp_file_methods = { + enabled = true, + timeout_ms = 2000, + autosave_changes = 'unmodified', + }, + }, +} diff --git a/lua/custom/plugins/opencode.lua b/lua/custom/plugins/opencode.lua new file mode 100644 index 00000000000..56c1346db5a --- /dev/null +++ b/lua/custom/plugins/opencode.lua @@ -0,0 +1,178 @@ +return { + 'NickvanDyke/opencode.nvim', + dependencies = { + -- Recommended for `ask()` and `select()`. + -- Required for `snacks` provider. + ---@module 'snacks' <- Loads `snacks.nvim` types for configuration intellisense. + { + 'folke/snacks.nvim', + opts = { input = {}, picker = {}, terminal = {}, rename = {} }, + keys = { + { 'cR', function() Snacks.rename.rename_file() end, desc = 'Rename File (LSP)' }, + }, + }, + }, + config = function() + local function redact_sensitive(value) + if type(value) ~= 'string' then + return value + end + + local redacted = value + redacted = redacted:gsub('([Aa]uthorization%s*:%s*[Bb]earer%s+)[^%s,;]+', '%1[REDACTED]') + redacted = redacted:gsub('([Aa][Pp][Ii][_%-%s]?[Kk][Ee][Yy]%s*[:=]%s*)[^%s,;]+', '%1[REDACTED]') + redacted = redacted:gsub('([Tt][Oo][Kk][Ee][Nn]%s*[:=]%s*)[^%s,;]+', '%1[REDACTED]') + return redacted + end + + local function truncate(value, max_len) + if #value <= max_len then + return value + end + + return value:sub(1, max_len) .. '...' + end + + local function format_opencode_error(err) + if type(err) == 'string' then + return truncate(redact_sensitive(err), 300) + end + + if type(err) == 'table' then + local message = err.message or err.msg or err.error or err.reason + if type(message) == 'string' and message ~= '' then + return truncate(redact_sensitive(message), 300) + end + + local status = err.status or err.code + if status ~= nil then + return 'opencode request failed (' .. tostring(status) .. ')' + end + + return 'opencode request failed' + end + + return truncate(redact_sensitive(tostring(err)), 300) + end + + local function notify_opencode_error(err) + if not err then + return + end + + vim.notify(format_opencode_error(err), vim.log.levels.ERROR, { title = 'opencode' }) + end + + local function opencode_ask(default, opts) + opts = opts or {} + opts.context = opts.context or require('opencode.context').new() + + return require('opencode.ui.ask') + .ask(default, opts.context) + :next(function(input) + if input:sub(-2) == '\\n' then + input = input:sub(1, -3) .. '\n' + opts.clear = false + opts.submit = false + end + + opts.context:clear() + return require('opencode.api.prompt').prompt(input, opts) + end) + :catch(notify_opencode_error) + end + + local function opencode_select(opts) + return require('opencode.ui.select').select(opts):catch(notify_opencode_error) + end + + local function opencode_command(command) + return require('opencode.api.command').command(command):catch(notify_opencode_error) + end + + ---@type opencode.Opts + vim.g.opencode_opts = { + provider = { + enabled = 'snacks', + snacks = { + auto_close = false, + win = { + position = 'right', + border = 'rounded', + width = math.floor(vim.o.columns * 0.35), + enter = false, + }, + }, + }, + } + + -- Required for `opts.events.reload`. + vim.o.autoread = true + + -- Recommended/example keymaps. + vim.keymap.set({ 'n', 'x' }, '', function() + opencode_ask('@this: ', { submit = true }) + end, { desc = 'Ask opencode…' }) + vim.keymap.set({ 'n', 'x' }, '', function() + opencode_select() + end, { desc = 'Execute opencode action…' }) + local function opencode_toggle() + local ok, err = pcall(function() + require('opencode').toggle() + end) + if not ok then + vim.notify('opencode toggle failed: ' .. tostring(err), vim.log.levels.ERROR) + end + end + + vim.keymap.set({ 'n', 't' }, '', opencode_toggle, { desc = 'Toggle opencode' }) + vim.keymap.set('n', 'oc', opencode_toggle, { desc = 'Toggle opencode chat' }) + + local opencode_term_nav_group = vim.api.nvim_create_augroup('opencode-term-nav', { clear = true }) + vim.api.nvim_create_autocmd('FileType', { + group = opencode_term_nav_group, + pattern = 'opencode_terminal', + callback = function(ev) + local function tmux_nav(cmd) + local esc = vim.api.nvim_replace_termcodes('', true, false, true) + vim.api.nvim_feedkeys(esc, 'n', false) + vim.cmd(cmd) + end + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateLeft' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate left from opencode' }) + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateDown' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate down from opencode' }) + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateUp' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate up from opencode' }) + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateRight' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate right from opencode' }) + end, + }) + + vim.keymap.set({ 'n', 'x' }, 'go', function() + return require('opencode').operator '@this ' + end, { desc = 'Add range to opencode', expr = true }) + vim.keymap.set('n', 'goo', function() + return require('opencode').operator '@this ' .. '_' + end, { desc = 'Add line to opencode', expr = true }) + + vim.keymap.set('n', '', function() + opencode_command 'session.half.page.up' + end, { desc = 'Scroll opencode up' }) + vim.keymap.set('n', '', function() + opencode_command 'session.half.page.down' + end, { desc = 'Scroll opencode down' }) + + -- You may want these if you stick with the opinionated "" and "" above — otherwise consider "o…". + vim.keymap.set('n', '+', '', { desc = 'Increment under cursor', noremap = true }) + vim.keymap.set('n', '-', '', { desc = 'Decrement under cursor', noremap = true }) + end, +} diff --git a/lua/custom/plugins/persistence.lua b/lua/custom/plugins/persistence.lua new file mode 100644 index 00000000000..5c0763d3117 --- /dev/null +++ b/lua/custom/plugins/persistence.lua @@ -0,0 +1,30 @@ +return { + 'folke/persistence.nvim', + event = 'BufReadPre', + opts = { + options = { 'buffers', 'curdir', 'tabpages', 'winsize', 'help', 'globals' }, + }, + keys = { + { + 'wr', + function() + require('persistence').load() + end, + desc = '[W]orkspace [R]estore session', + }, + { + 'wl', + function() + require('persistence').load { last = true } + end, + desc = '[W]orkspace [L]ast session', + }, + { + 'wd', + function() + require('persistence').stop() + end, + desc = '[W]orkspace [D]isable session save', + }, + }, +} diff --git a/lua/custom/plugins/supermaven.lua b/lua/custom/plugins/supermaven.lua new file mode 100644 index 00000000000..29be06968cc --- /dev/null +++ b/lua/custom/plugins/supermaven.lua @@ -0,0 +1,22 @@ +return { + 'supermaven-inc/supermaven-nvim', + event = 'InsertEnter', + config = function() + require('supermaven-nvim').setup { + keymaps = { + accept_suggestion = '', + clear_suggestion = '', + accept_word = '', + }, + ignore_filetypes = { cpp = true }, -- or { "cpp", } + color = { + suggestion_color = '#ffffff', + cterm = 244, + }, + log_level = 'off', + disable_inline_completion = false, -- disables inline completion for use with cmp + disable_keymaps = false, -- disables built in keymaps for more manual control + condition = function() return false end, -- condition to check for stopping supermaven, `true` means to stop supermaven when the condition is true. + } + end, +} diff --git a/lua/custom/plugins/tmux_navigator.lua b/lua/custom/plugins/tmux_navigator.lua new file mode 100644 index 00000000000..26fb72ba14c --- /dev/null +++ b/lua/custom/plugins/tmux_navigator.lua @@ -0,0 +1,18 @@ +return { + 'christoomey/vim-tmux-navigator', + cmd = { + 'TmuxNavigateLeft', + 'TmuxNavigateDown', + 'TmuxNavigateUp', + 'TmuxNavigateRight', + 'TmuxNavigatePrevious', + 'TmuxNavigatorProcessList', + }, + keys = { + { '', 'TmuxNavigateLeft' }, + { '', 'TmuxNavigateDown' }, + { '', 'TmuxNavigateUp' }, + { '', 'TmuxNavigateRight' }, + { '', 'TmuxNavigatePrevious' }, + }, +} diff --git a/lua/custom/plugins/treesitter_context.lua b/lua/custom/plugins/treesitter_context.lua new file mode 100644 index 00000000000..2904e80767c --- /dev/null +++ b/lua/custom/plugins/treesitter_context.lua @@ -0,0 +1,17 @@ +return { + 'nvim-treesitter/nvim-treesitter-context', + opts = { + max_lines = 4, + multiline_threshold = 20, + trim_scope = 'outer', + }, + keys = { + { + 'tc', + function() + require('treesitter-context').toggle() + end, + desc = '[T]oggle code [C]ontext', + }, + }, +} diff --git a/lua/custom/plugins/treesitter_textobjects.lua b/lua/custom/plugins/treesitter_textobjects.lua new file mode 100644 index 00000000000..f904c5b70b2 --- /dev/null +++ b/lua/custom/plugins/treesitter_textobjects.lua @@ -0,0 +1,47 @@ +return { + 'nvim-treesitter/nvim-treesitter-textobjects', + branch = 'main', + dependencies = { 'nvim-treesitter/nvim-treesitter' }, + config = function() + require('nvim-treesitter-textobjects').setup { + select = { + lookahead = true, + keymaps = { + ['af'] = '@function.outer', + ['if'] = '@function.inner', + ['ac'] = '@class.outer', + ['ic'] = '@class.inner', + }, + }, + move = { + set_jumps = true, + }, + } + + local move = require 'nvim-treesitter-textobjects.move' + + vim.keymap.set('n', 'jm', function() + move.goto_next_start '@function.outer' + end, { desc = '[J]ump next [M]ethod start' }) + + vim.keymap.set('n', 'jM', function() + move.goto_next_end '@function.outer' + end, { desc = '[J]ump next [M]ethod end' }) + + vim.keymap.set('n', 'jk', function() + move.goto_previous_start '@function.outer' + end, { desc = '[J]ump previous method start' }) + + vim.keymap.set('n', 'jK', function() + move.goto_previous_end '@function.outer' + end, { desc = '[J]ump previous method end' }) + + vim.keymap.set('n', 'jc', function() + move.goto_next_start '@class.outer' + end, { desc = '[J]ump next [C]lass start' }) + + vim.keymap.set('n', 'jC', function() + move.goto_previous_start '@class.outer' + end, { desc = '[J]ump previous class start' }) + end, +} diff --git a/lua/custom/plugins/trouble.lua b/lua/custom/plugins/trouble.lua new file mode 100644 index 00000000000..d47f80a791b --- /dev/null +++ b/lua/custom/plugins/trouble.lua @@ -0,0 +1,45 @@ +return { + 'folke/trouble.nvim', + cmd = { 'Trouble' }, + opts = {}, + keys = { + { + 'xx', + function() + require('trouble').toggle 'diagnostics' + end, + desc = 'Trouble: all diagnostics', + }, + { + 'xw', + function() + require('trouble').toggle 'diagnostics' + end, + desc = 'Trouble: [W]orkspace diagnostics', + }, + { + 'xd', + function() + require('trouble').toggle { + mode = 'diagnostics', + filter = { buf = 0 }, + } + end, + desc = 'Trouble: [D]ocument diagnostics', + }, + { + 'xq', + function() + require('trouble').toggle 'qflist' + end, + desc = 'Trouble: [Q]uickfix list', + }, + { + 'xl', + function() + require('trouble').toggle 'loclist' + end, + desc = 'Trouble: [L]ocation list', + }, + }, +} diff --git a/lua/custom/plugins/typescript_tools.lua b/lua/custom/plugins/typescript_tools.lua new file mode 100644 index 00000000000..ba7fb2046e7 --- /dev/null +++ b/lua/custom/plugins/typescript_tools.lua @@ -0,0 +1,5 @@ +return { + 'pmizio/typescript-tools.nvim', + dependencies = { 'nvim-lua/plenary.nvim', 'neovim/nvim-lspconfig' }, + opts = {}, +} diff --git a/lua/kickstart/health.lua b/lua/kickstart/health.lua deleted file mode 100644 index b59d08649af..00000000000 --- a/lua/kickstart/health.lua +++ /dev/null @@ -1,52 +0,0 @@ ---[[ --- --- This file is not required for your own configuration, --- but helps people determine if their system is setup correctly. --- ---]] - -local check_version = function() - local verstr = tostring(vim.version()) - if not vim.version.ge then - vim.health.error(string.format("Neovim out of date: '%s'. Upgrade to latest stable or nightly", verstr)) - return - end - - if vim.version.ge(vim.version(), '0.10-dev') then - vim.health.ok(string.format("Neovim version is: '%s'", verstr)) - else - vim.health.error(string.format("Neovim out of date: '%s'. Upgrade to latest stable or nightly", verstr)) - end -end - -local check_external_reqs = function() - -- Basic utils: `git`, `make`, `unzip` - for _, exe in ipairs { 'git', 'make', 'unzip', 'rg' } do - local is_executable = vim.fn.executable(exe) == 1 - if is_executable then - vim.health.ok(string.format("Found executable: '%s'", exe)) - else - vim.health.warn(string.format("Could not find executable: '%s'", exe)) - end - end - - return true -end - -return { - check = function() - vim.health.start 'kickstart.nvim' - - vim.health.info [[NOTE: Not every warning is a 'must-fix' in `:checkhealth` - - Fix only warnings for plugins and languages you intend to use. - Mason will give warnings for languages that are not installed. - You do not need to install, unless you want to use those languages!]] - - local uv = vim.uv or vim.loop - vim.health.info('System Information: ' .. vim.inspect(uv.os_uname())) - - check_version() - check_external_reqs() - end, -} diff --git a/lua/kickstart/plugins/lint.lua b/lua/kickstart/plugins/lint.lua deleted file mode 100644 index dec42f097c6..00000000000 --- a/lua/kickstart/plugins/lint.lua +++ /dev/null @@ -1,60 +0,0 @@ -return { - - { -- Linting - 'mfussenegger/nvim-lint', - event = { 'BufReadPre', 'BufNewFile' }, - config = function() - local lint = require 'lint' - lint.linters_by_ft = { - markdown = { 'markdownlint' }, - } - - -- To allow other plugins to add linters to require('lint').linters_by_ft, - -- instead set linters_by_ft like this: - -- lint.linters_by_ft = lint.linters_by_ft or {} - -- lint.linters_by_ft['markdown'] = { 'markdownlint' } - -- - -- However, note that this will enable a set of default linters, - -- which will cause errors unless these tools are available: - -- { - -- clojure = { "clj-kondo" }, - -- dockerfile = { "hadolint" }, - -- inko = { "inko" }, - -- janet = { "janet" }, - -- json = { "jsonlint" }, - -- markdown = { "vale" }, - -- rst = { "vale" }, - -- ruby = { "ruby" }, - -- terraform = { "tflint" }, - -- text = { "vale" } - -- } - -- - -- You can disable the default linters by setting their filetypes to nil: - -- lint.linters_by_ft['clojure'] = nil - -- lint.linters_by_ft['dockerfile'] = nil - -- lint.linters_by_ft['inko'] = nil - -- lint.linters_by_ft['janet'] = nil - -- lint.linters_by_ft['json'] = nil - -- lint.linters_by_ft['markdown'] = nil - -- lint.linters_by_ft['rst'] = nil - -- lint.linters_by_ft['ruby'] = nil - -- lint.linters_by_ft['terraform'] = nil - -- lint.linters_by_ft['text'] = nil - - -- Create autocommand which carries out the actual linting - -- on the specified events. - local lint_augroup = vim.api.nvim_create_augroup('lint', { clear = true }) - vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { - group = lint_augroup, - callback = function() - -- Only run the linter in buffers that you can modify in order to - -- avoid superfluous noise, notably within the handy LSP pop-ups that - -- describe the hovered symbol using Markdown. - if vim.bo.modifiable then - lint.try_lint() - end - end, - }) - end, - }, -}