diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..4f3412e8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,42 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +A personal Neovim config forked from [kickstart.nvim](https://github.com/nvim-lua/kickstart.nvim). Loaded by Neovim from `~/.config/nvim`. There is no build/test step — changes take effect by restarting `nvim` (or `:source %` / `:Lazy reload ` for some edits). + +## Architecture + +- `init.lua` — entrypoint. Sets options, global keymaps, autocmds, bootstraps `lazy.nvim`, then loads every file under `lua/custom/plugins/` via `{ import = 'custom.plugins' }`. The kickstart single-file model has been broken out: only base options/keymaps live here; plugin specs live in their own files. +- `lua/custom/plugins/*.lua` — one file per plugin (or tightly related group). Each returns a lazy.nvim spec table (or list of specs). Adding a new plugin = create a new file here; it is picked up automatically. No central manifest to update. +- `lua/custom/zed-keymaps.lua` — mirrors Zed bindings (alt-j/k line move, F2 rename, `.` code action, `gb` blame, `x` close buffer, task spawners). Required directly from `init.lua` before lazy setup. Task spawner prefers a floating zellij pane (`$ZELLIJ` set) and falls back to an `nvim` split terminal. +- `lua/custom/health.lua` — backs `:checkhealth kickstart`. +- `lazy-lock.json` — committed; pin plugin versions. Update via `:Lazy update` then commit. +- `lua/kickstart/` does **not** exist in this fork (upstream kickstart had it). Don't add files there; everything custom lives under `lua/custom/`. + +### Key conventions baked into the config + +- **Leader = Space** (both `mapleader` and `maplocalleader`). +- **Window navigation** (``) is owned by `smart-splits.nvim` (`lua/custom/plugins/tmux.lua`) with `multiplexer_integration = 'zellij'` — at a split edge it forwards focus to the zellij pane. Don't re-bind these in `init.lua`. +- **LSP** is wired in `lua/custom/plugins/lsp.lua`. Servers configured: `clangd`, `gopls`, `pyright` (organize-imports disabled, lint suppressed — `ruff` handles both), `ruff` (hover disabled — `pyright` handles), `lua_ls`. Mason auto-installs them plus `stylua`, `jdtls`, `google-java-format`. Buffer-local LSP keymaps (`gd`, `gr`, `gI`, `rn`, `ca`, …) are set in the `LspAttach` autocmd — add new LSP keybindings there, not globally. Inlay hints are forced on for Go buffers. +- **Go imports** are organized on `BufWritePre` for `*.go` via a `source.organizeImports` code action (`lsp.lua`). +- **Formatting** is `conform.nvim` (`conform.lua`); `format_on_save` runs with `timeout_ms = 500` and LSP fallback (disabled for `c`/`cpp`). Per-ft formatters: `stylua` (lua), `ruff_organize_imports`+`ruff_format` (python), `google-java-format` (java), `terraform_fmt`, prettier (js/xml). +- **Per-filetype `colorcolumn`** is set by a `FileType` autocmd in `init.lua` (python 80, lua 100, go 120, rust 100, java 120). Other filetypes have no ruler. +- **Buffer switching**: ``..`` jump to `vim.t.bufs[i]` (populated by `mini.bufremove`/`mini.tabline` from `mini.lua`). +- **`;` is remapped to `:`** in normal mode. `` clears search highlight. + +## Formatting / lint for files in this repo + +Lua files are formatted with **stylua** using `.stylua.toml` (2-space indent, single quotes preferred, 160-col width, no call parens). Run via `f` or `:Format`, or from a shell: `stylua .`. There is no test suite. + +## Adding / modifying plugins + +- New plugin → new file in `lua/custom/plugins/`, return a lazy spec. No need to edit `init.lua`. +- After edits: `:Lazy sync` (install/update/clean) or restart Neovim. Commit `lazy-lock.json` along with spec changes so the lock stays reproducible. +- Use `:checkhealth` and `:Mason` to verify tool installation. + +## Repo-specific style notes + +- `.stylua.toml` enforces single quotes and no call parens — keep `require 'foo'` style (not `require('foo')`) in plain calls; conform/lazy spec tables of course still use parens where required. +- Existing files have heavy kickstart-original tutorial comments. When editing those files, leave the surrounding teaching comments intact unless the user asks for cleanup; for new files, follow the global comment policy (terse, only when WHY is non-obvious). diff --git a/README.md b/README.md index aa5f4fc8..bf8a3a78 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,208 @@ -# kickstart.nvim +# nvim -## Introduction +Personal Neovim config, originally forked from [kickstart.nvim](https://github.com/nvim-lua/kickstart.nvim) and broken into per-plugin files. Lives at `~/.config/nvim`. -A starting point for Neovim that is: +## Layout -* Small -* Single-file -* Completely Documented +``` +init.lua -- options, global keymaps, autocmds, lazy bootstrap +lua/custom/ +├── zed-keymaps.lua -- Zed-parity bindings + task spawners +├── health.lua -- :checkhealth kickstart +└── plugins/*.lua -- one file per plugin; auto-imported by lazy +lazy-lock.json -- committed, pinned plugin versions +``` -**NOT** a Neovim distribution, but instead a starting point for your configuration. +No build/test step. Changes take effect on `nvim` restart (or `:source %` / `:Lazy reload `). Lua is formatted by `stylua` per `.stylua.toml` (2-space, single quotes, 160-col). -## Installation +## Install -### 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 - -External Requirements: -- Basic utils: `git`, `make`, `unzip`, C Compiler (`gcc`) -- [ripgrep](https://github.com/BurntSushi/ripgrep#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 -- 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 - -### Install Kickstart - -> **NOTE** -> [Backup](#FAQ) your previous configuration (if any exists) - -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)| `%localappdata%\nvim\` | -| Windows (powershell)| `$env:LOCALAPPDATA\nvim\` | - -#### Recommended Step - -[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. - -> **NOTE** -> Your fork's URL will be something like this: -> `https://github.com//kickstart.nvim.git` - -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). - -#### Clone kickstart.nvim -> **NOTE** -> If following the recommended step above (i.e., forking the repo), replace -> `nvim-lua` with `` in the commands below - -
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 "%localappdata%\nvim" -``` - -If you're using `powershell.exe` - -``` -git clone https://github.com/nvim-lua/kickstart.nvim.git "${env:LOCALAPPDATA}\nvim" -``` - -
- -### Post Installation - -Start Neovim +Requires Neovim stable or nightly, plus: `git`, `make`, a C compiler, `ripgrep`, a clipboard tool, and a [Nerd Font](https://www.nerdfonts.com/). Optional: `npm`, `go`, `python`, `java` for the respective LSPs. ```sh +git clone "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim nvim ``` -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. +On first launch `lazy.nvim` installs every plugin under `lua/custom/plugins/`, and Mason installs the LSPs/formatters listed in `lsp.lua`. Run `:Lazy`, `:Mason`, `:checkhealth` to verify. -#### Read The Friendly Documentation +## Editor defaults (`init.lua`) -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. +- Leader and localleader are **Space**. +- `relativenumber`, `cursorline`, `signcolumn=yes`, `scrolloff=8`, `splitright`/`splitbelow`. +- `spell` on (`en_us`); `undofile` on; `inccommand=split` for live substitution preview. +- `ignorecase` + `smartcase`; `timeoutlen=300`, `updatetime=250`. +- Per-filetype `colorcolumn`: python 80, lua 100, rust 100, go 120, java 120. Off everywhere else. +- Yank highlight on `TextYankPost`; clipboard synced to `unnamedplus` after UI ready. -> [!NOTE] -> For more information about a particular plugin check its repository's documentation. +## Plugins +| File | Plugin | Purpose | +| :--- | :--- | :--- | +| `lsp.lua` | `nvim-lspconfig` + Mason + `fidget.nvim` + `lazydev` | LSP for `clangd`, `gopls`, `pyright`, `ruff`, `lua_ls`; auto-installs `stylua`, `jdtls`, `google-java-format` | +| `cmp.lua` | `blink.cmp` | Completion (capabilities fed to LSP) | +| `conform.lua` | `conform.nvim` | Format on save (500ms timeout, LSP fallback; off for c/cpp/json/xml/html) | +| `lint.lua` | `nvim-lint` | Linting | +| `treesitter.lua` | `nvim-treesitter` (`main` branch) + textobjects | Highlight, indent, function/class/param motions | +| `treesitter-context.lua` | `nvim-treesitter-context` | Sticky context header | +| `telescope.lua` | `telescope.nvim` + `fzf-native` + `ui-select` | Fuzzy finder | +| `neo-tree.lua` | `neo-tree.nvim` | File tree (auto-opens on `nvim` / `nvim `) | +| `harpoon.lua` | `harpoon2` | Quick file marks | +| `gitsigns.lua` | `gitsigns.nvim` | Gutter signs, line blame (300ms), hunk actions | +| `lazygit.lua` | `lazygit.nvim` | Lazygit popup | +| `trouble.lua` | `trouble.nvim` | Diagnostics / refs / qflist panel | +| `todo.lua` | `todo-comments` | Highlight `TODO/FIX/HACK/...` | +| `toggleterm.lua` | `toggleterm.nvim` | Float / split terminals | +| `tmux.lua` | `smart-splits.nvim` | `` nav across nvim splits ↔ zellij panes | +| `mini.lua` | `mini.ai`, `mini.move`, `mini.notify`, `mini.starter`, `mini.statusline` | Misc utilities + statusline | +| `leap.lua` | `flash.nvim` | `s`/`S` motions, remote ops | +| `persistence.lua` | `persistence.nvim` | Per-cwd auto session | +| `which-key.lua` | `which-key.nvim` | Keybind popup | +| `copilot.lua` | `copilot.vim` | Inline suggestions (accept on ``) | +| `copilot-chat.lua` | `CopilotChat.nvim` | In-editor chat | +| `render-markdown.lua` | `render-markdown.nvim` | Pretty markdown in buffer | +| `markdown-preview.lua` | `markdown-preview.nvim` | Browser preview | +| `gruvbox.lua` | gruvbox | Colorscheme | +| `neoscroll.lua` | `neoscroll.nvim` | Smooth scrolling | +| `illuminate.lua` | `vim-illuminate` | Highlight word under cursor | +| `indent_line.lua` | `indent-blankline` | Indent guides | +| `autopairs.lua` | `nvim-autopairs` | Bracket pairs | +| `nvim-surround.lua` | `nvim-surround` | Surround motions | +| `dressing.lua` | `dressing.nvim` | Better `vim.ui` prompts | +| `sleuth.lua` | `vim-sleuth` | Auto-detect indent | +| `java.lua` | `nvim-jdtls` | Java LSP wrapper | +| `debug.lua` | `nvim-dap` + UI | Debugger | -### Getting Started +## Keymaps -[The Only Video You Need to Get Started with Neovim](https://youtu.be/m8C0Cq9Uv9o) +### Movement & editing (`init.lua` / `zed-keymaps.lua`) +| Key | Mode | Action | +| :--- | :--- | :--- | +| `;` | n | `:` (command mode) | +| `` | n | Clear search highlight | +| `oo` / `OO` | n | Open blank line below/above without entering insert | +| `p` | x | Paste without clobbering register | +| `` / `` | n/v | Move line/selection down/up | +| `` | i | Arrow keys in insert | +| `` | c | Word/char nav in command line | +| `` | i | Delete previous word | +| `s` / `S` / `r` / `R` | n/x/o | flash.nvim jump / treesitter / remote | -### FAQ +### Windows, buffers, tasks +| Key | Action | +| :--- | :--- | +| `` | Focus split — falls through to zellij pane | +| ``..`` | Jump to buffer slot 1..9 (`vim.t.bufs[i]`) | +| `x` | Close current buffer | +| `t` / `T` | Floating / horizontal toggleterm | +| `` | Open mapping for toggleterm | +| `gh` | `gh dash` in floating pane | +| `kk` | `k9s` | +| `bb` | bruno tests | +| `uu` / `dd` | `make up` / `make down` in `restopay/` | +| `aa` | Refresh Athenz cert + AWS creds for `external-factory` | -* 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) +### Finder (`f…`) +| Key | Action | +| :--- | :--- | +| `ff` | Files | +| `fg` | Live grep | +| `fw` | Grep current word | +| `fd` | Diagnostics | +| `fh` | Help tags | +| `fk` | Keymaps | +| `fs` | Telescope builtins | +| `fr` | Resume last picker | +| `f.` | Recent files | +| `fc` | Commands | +| `fe` | Workspace symbols | +| `` | Buffers | +| `/` | Fuzzy find in current buffer | +| `s/` | Live grep in open buffers | +| `sn` | Find files in nvim config | -### Install Recipes +### LSP (set on `LspAttach`, plus zed-parity) +| Key | Action | +| :--- | :--- | +| `gd` / `gr` / `gI` | Definitions / refs / implementations (telescope) | +| `gi` | Implementations (lowercase zed-style) | +| `gD` | Declaration | +| `D` | Type definition | +| `ds` / `ws` | Document / workspace symbols | +| `rn` or `F2` | Rename | +| `ca` or `.` | Code action | +| `th` | Toggle inlay hints (on by default for Go) | +| `q` | Diagnostics → loclist | +| `f` | Format buffer (conform) | -Below you can find OS specific install instructions for Neovim and dependencies. +### Git +| Key | Action | +| :--- | :--- | +| `]c` / `[c` | Next / prev hunk | +| `hs` / `hr` | Stage / reset hunk (works in visual) | +| `hS` / `hR` | Stage / reset buffer | +| `hu` | Undo stage hunk | +| `hp` | Preview hunk | +| `hb` | Blame current line | +| `hd` / `hD` | Diff vs index / last commit | +| `tb` / `tD` | Toggle inline blame / deleted markers | +| `gb` | Full-buffer blame | -After installing all the dependencies continue with the [Install Kickstart](#Install-Kickstart) step. +### Harpoon +| Key | Action | +| :--- | :--- | +| `ha` | Add current file | +| `hh` | Toggle harpoon menu | +| `1`..`4` | Jump to slot 1..4 | +| `[h` / `]h` | Prev / next entry | -#### Windows Installation +### Trouble (`x…`) +| Key | Action | +| :--- | :--- | +| `xx` | Workspace diagnostics | +| `xX` | Buffer diagnostics | +| `xs` | Symbols | +| `xr` | LSP refs/defs (right) | +| `xl` / `xq` | Loclist / quickfix | -
Windows with Microsoft C++ Build Tools and CMake -Installation may require installing build tools and updating the run command for `telescope-fzf-native` +### Neo-tree +| Key | Action | +| :--- | :--- | +| `\` or `e` | Reveal file in tree | +| (inside tree) `h` / `l` / `` | Collapse / expand+open / smart-toggle | +| (inside tree) `T` | Trash file (via `trash` CLI) | -See `telescope-fzf-native` documentation for [more details](https://github.com/nvim-telescope/telescope-fzf-native.nvim#installation) +### Treesitter textobjects +`af`/`if` function, `ac`/`ic` class, `aa`/`ia` parameter, `al`/`il` loop; `]m`/`[m`/`]M`/`[M` move between functions. -This requires: +### Copilot / chat +| Key | Action | +| :--- | :--- | +| `` (insert) | Accept Copilot suggestion | +| `cc` / `ce` / `cf` / `cr` / `ct` / `cm` | Chat / explain / fix / review / tests / commit-msg | -- Install CMake and the Microsoft C++ Build Tools on Windows +### Sessions (persistence.nvim) +| Key | Action | +| :--- | :--- | +| `qs` | Load session for cwd | +| `ql` | Load last session | +| `qd` | Stop saving current session | -```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: +## LSP & formatting notes -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 -``` +- `gopls` uses `-tags=integration`, `gofumpt`, `staticcheck`, full inlay hints; `useany`/`unusedparams` on, `shadow` off. +- Go imports run as a `source.organizeImports` code action on `BufWritePre` for `*.go`. +- `pyright` lint is suppressed and its organize-imports disabled; `ruff` handles lint + organize + format. `ruff`'s hover is disabled — `pyright` provides hover. +- `lua_ls`: `callSnippet = 'Replace'`; `lazydev` loads `vim.uv` types. +- Per-ft formatters: `stylua` (lua), `ruff_organize_imports` + `ruff_format` (python), `google-java-format` (java), `terraform_fmt`, `prettier`/`xmlformatter` (xml), `prettierd`/`prettier` (js). Format-on-save off for `c`/`cpp`/`json`/`xml`/`html`. -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) +## Adding a plugin -``` -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 -``` -
+Drop a new file in `lua/custom/plugins/` returning a lazy spec. No central manifest. After edits, run `:Lazy sync` (or restart) and commit `lazy-lock.json` alongside the spec. -#### Linux Install -
Ubuntu Install Steps - -``` -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 - -``` -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-linux64.tar.gz -sudo rm -rf /opt/nvim-linux64 -sudo mkdir -p /opt/nvim-linux64 -sudo chmod a+rX /opt/nvim-linux64 -sudo tar -C /opt -xzf nvim-linux64.tar.gz - -# make it available in /usr/local/bin, distro installs to /usr/bin -sudo ln -sf /opt/nvim-linux64/bin/nvim /usr/local/bin/ -``` -
-
Fedora Install Steps - -``` -sudo dnf install -y gcc make git ripgrep fd-find unzip neovim -``` -
- -
Arch Install Steps - -``` -sudo pacman -S --noconfirm --needed gcc make git ripgrep fd unzip neovim -``` -
+## FAQ +- **Where do LSP keymaps live?** In the `LspAttach` autocmd in `lsp.lua`. Add new buffer-local LSP bindings there, not globally. +- **Why doesn't `` move splits in some configs?** They're owned by `smart-splits.nvim`; at a split edge they forward to the zellij pane (`multiplexer_integration = 'zellij'`). +- **Uninstall:** see [lazy.nvim uninstall](https://lazy.folke.io/usage#-uninstalling). +- **Run alongside another config:** `NVIM_APPNAME=nvim-other nvim`. diff --git a/init.lua b/init.lua index b83959a9..412db1d5 100644 --- a/init.lua +++ b/init.lua @@ -4,13 +4,19 @@ vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' vim.g.have_nerd_font = true +-- Skip nvim's OSC 11/DSR terminal probe (zellij sometimes delays the response, +-- producing the "Did not detect DSR response" warning + 100ms slower startup). +-- Setting background explicitly tells defaults.lua to bypass the query. +vim.opt.background = 'dark' + vim.opt.relativenumber = true vim.opt.mouse = 'a' vim.opt.showmode = false vim.opt.smartindent = true vim.opt.spelllang = 'en_us' vim.o.spell = true -vim.o.colorcolumn = '80' +-- colorcolumn set per-filetype (see autocmd below); default off. +vim.o.colorcolumn = '' -- Sync clipboard between OS and Neovim. -- Schedule the setting after `UiEnter` because it can increase startup-time. @@ -97,18 +103,22 @@ vim.keymap.set('n', '', 'nohlsearch') -- Diagnostic keymaps vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' }) --- Keybinds to make split navigation easier. --- Use CTRL+ to switch between windows --- --- 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' }) +-- Window-nav Ctrl-h/j/k/l owned by smart-splits.nvim (custom/plugins/tmux.lua). +-- Falls through to zellij when at a split edge. -- [[ Basic Autocommands ]] -- See `:help lua-guide-autocommands` +-- Per-filetype colorcolumn (Go allows long lines; markdown shouldn't have a ruler). +vim.api.nvim_create_autocmd('FileType', { + group = vim.api.nvim_create_augroup('per-ft-colorcolumn', { clear = true }), + callback = function(args) + local widths = { python = '80', lua = '100', go = '120', rust = '100', java = '120' } + vim.bo[args.buf].textwidth = 0 + vim.wo.colorcolumn = widths[vim.bo[args.buf].filetype] or '' + end, +}) + -- Highlight when yanking (copying) text -- Try it with `yap` in normal mode -- See `:help vim.highlight.on_yank()` @@ -132,39 +142,7 @@ if not (vim.uv or vim.loop).fs_stat(lazypath) then end ---@diagnostic disable-next-line: undefined-field vim.opt.rtp:prepend(lazypath) -local floating_win = nil - -function FloatingTerm() - if floating_win and vim.api.nvim_win_is_valid(floating_win) then - vim.api.nvim_win_close(floating_win, true) - floating_win = nil - return - end - - local buf = vim.api.nvim_create_buf(false, true) - local width = math.floor(vim.o.columns * 0.8) - local height = math.floor(vim.o.lines * 0.8) - local row = math.floor((vim.o.lines - height) / 2) - local col = math.floor((vim.o.columns - width) / 2) - - floating_win = vim.api.nvim_open_win(buf, true, { - relative = 'editor', - width = width, - height = height, - row = row, - col = col, - style = 'minimal', - border = 'rounded', - }) - - vim.fn.termopen(vim.o.shell) - vim.cmd 'startinsert' - - -- Map to close the floating terminal when inside it - vim.api.nvim_buf_set_keymap(buf, 't', '', ':lua FloatingTerm()', { noremap = true, silent = true }) -end - -vim.api.nvim_set_keymap('n', 't', ':lua FloatingTerm()', { noremap = true, silent = true }) +require('custom.zed-keymaps') require('lazy').setup({ { import = 'custom.plugins' } }, { ui = { diff --git a/lua/custom/plugins/cmp.lua b/lua/custom/plugins/cmp.lua index f34bac27..0a1937eb 100644 --- a/lua/custom/plugins/cmp.lua +++ b/lua/custom/plugins/cmp.lua @@ -1,115 +1,39 @@ -return { -- Autocompletion - 'hrsh7th/nvim-cmp', - event = 'InsertEnter', +-- blink.cmp: faster Rust-based completion (replaces nvim-cmp). +-- Provides LSP, path, snippet, and lazydev sources via single plugin. +return { + 'saghen/blink.cmp', + version = '*', + event = { 'InsertEnter', 'CmdlineEnter' }, dependencies = { - -- Snippet Engine & its associated nvim-cmp source - { - 'L3MON4D3/LuaSnip', - build = (function() - -- Build Step is needed for regex support in snippets. - -- This step is not supported in many windows environments. - -- Remove the below condition to re-enable on windows. - if vim.fn.has 'win32' == 1 or vim.fn.executable 'make' == 0 then - return - end - return 'make install_jsregexp' - end)(), - dependencies = { - -- `friendly-snippets` contains a variety of premade snippets. - -- See the README about individual language/framework/plugin snippets: - -- https://github.com/rafamadriz/friendly-snippets - -- { - -- 'rafamadriz/friendly-snippets', - -- config = function() - -- require('luasnip.loaders.from_vscode').lazy_load() - -- end, - -- }, + 'rafamadriz/friendly-snippets', + }, + opts = { + -- Use the 'enter' preset: confirms, / cycle items. + keymap = { + preset = 'enter', + [''] = { 'select_next', 'snippet_forward', 'fallback' }, + [''] = { 'select_prev', 'snippet_backward', 'fallback' }, + [''] = { 'show', 'show_documentation', 'hide_documentation' }, + }, + appearance = { + use_nvim_cmp_as_default = true, + nerd_font_variant = 'mono', + }, + completion = { + accept = { auto_brackets = { enabled = true } }, + documentation = { auto_show = true, auto_show_delay_ms = 200 }, + menu = { border = 'rounded' }, + }, + signature = { enabled = true, window = { border = 'rounded' } }, + sources = { + default = { 'lsp', 'path', 'snippets', 'buffer', 'lazydev' }, + providers = { + lazydev = { + name = 'LazyDev', + module = 'lazydev.integrations.blink', + score_offset = 100, + }, }, }, - 'saadparwaiz1/cmp_luasnip', - - -- Adds other completion capabilities. - -- nvim-cmp does not ship with all sources by default. They are split - -- into multiple repos for maintenance purposes. - 'hrsh7th/cmp-nvim-lsp', - 'hrsh7th/cmp-path', }, - config = function() - -- See `:help cmp` - local cmp = require 'cmp' - local luasnip = require 'luasnip' - luasnip.config.setup {} - - cmp.setup { - snippet = { - expand = function(args) - luasnip.lsp_expand(args.body) - end, - }, - completion = { completeopt = 'menu,menuone,noinsert' }, - - -- For an understanding of why these mappings were - -- chosen, you will need to read `:help ins-completion` - -- - -- No, but seriously. Please read `:help ins-completion`, it is really good! - mapping = cmp.mapping.preset.insert { - -- Select the [n]ext item - -- [''] = cmp.mapping.select_next_item(), - -- Select the [p]revious item - -- [''] = cmp.mapping.select_prev_item(), - - -- Scroll the documentation window [b]ack / [f]orward - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - - -- Accept ([y]es) the completion. - -- This will auto-import if your LSP supports it. - -- This will expand snippets if the LSP sent a snippet. - -- [''] = cmp.mapping.confirm { select = true }, - - -- If you prefer more traditional completion keymaps, - -- you can uncomment the following lines - [''] = cmp.mapping.confirm { select = true }, - [''] = cmp.mapping.select_next_item(), - [''] = cmp.mapping.select_prev_item(), - - -- Manually trigger a completion from nvim-cmp. - -- Generally you don't need this, because nvim-cmp will display - -- completions whenever it has completion options available. - [''] = cmp.mapping.complete {}, - - -- Think of as moving to the right of your snippet expansion. - -- So if you have a snippet that's like: - -- function $name($args) - -- $body - -- end - -- - -- will move you to the right of each of the expansion locations. - -- is similar, except moving you backwards. - [''] = cmp.mapping(function() - if luasnip.expand_or_locally_jumpable() then - luasnip.expand_or_jump() - end - end, { 'i', 's' }), - [''] = cmp.mapping(function() - if luasnip.locally_jumpable(-1) then - luasnip.jump(-1) - end - end, { 'i', 's' }), - - -- For more advanced Luasnip keymaps (e.g. selecting choice nodes, expansion) see: - -- https://github.com/L3MON4D3/LuaSnip?tab=readme-ov-file#keymaps - }, - sources = { - { - name = 'lazydev', - -- set group index to 0 to skip loading LuaLS completions as lazydev recommends it - group_index = 0, - }, - { name = 'nvim_lsp' }, - { name = 'luasnip' }, - { name = 'path' }, - }, - } - end, } diff --git a/lua/custom/plugins/conform.lua b/lua/custom/plugins/conform.lua index dff388b2..886a835c 100644 --- a/lua/custom/plugins/conform.lua +++ b/lua/custom/plugins/conform.lua @@ -18,7 +18,7 @@ return { -- Autoformat -- Disable "format_on_save lsp_fallback" for languages that don't -- have a well standardized coding style. You can add additional -- languages here or re-enable it for the disabled ones. - local disable_filetypes = { c = true, cpp = true } + local disable_filetypes = { c = true, cpp = true, json = true, xml = true, html = true } local lsp_format_opt if disable_filetypes[vim.bo[bufnr].filetype] then lsp_format_opt = 'never' @@ -35,7 +35,7 @@ return { -- Autoformat terraform = { 'terraform_fmt' }, xml = { 'xmlformatter', 'prettier' }, lua = { 'stylua' }, - python = { 'isort', 'black' }, + python = { 'ruff_organize_imports', 'ruff_format' }, javascript = { 'prettierd', 'prettier', stop_after_first = true }, }, }, diff --git a/lua/custom/plugins/copilot-chat.lua b/lua/custom/plugins/copilot-chat.lua new file mode 100644 index 00000000..1de49d58 --- /dev/null +++ b/lua/custom/plugins/copilot-chat.lua @@ -0,0 +1,29 @@ +-- CopilotChat: in-nvim chat using your existing GitHub Copilot auth. +return { + 'CopilotC-Nvim/CopilotChat.nvim', + dependencies = { + 'github/copilot.vim', + 'nvim-lua/plenary.nvim', + }, + build = 'make tiktoken', + cmd = { 'CopilotChat', 'CopilotChatOpen', 'CopilotChatToggle', 'CopilotChatExplain', 'CopilotChatReview', 'CopilotChatFix', 'CopilotChatTests', 'CopilotChatCommit' }, + opts = { + model = 'gpt-4o', + window = { + layout = 'float', + width = 0.8, + height = 0.8, + border = 'rounded', + }, + show_help = true, + auto_follow_cursor = false, + }, + keys = { + { 'cc', 'CopilotChatToggle', mode = { 'n', 'x' }, desc = '[C]opilot [C]hat' }, + { 'ce', 'CopilotChatExplain', mode = { 'n', 'x' }, desc = '[C]opilot [E]xplain' }, + { 'cf', 'CopilotChatFix', mode = { 'n', 'x' }, desc = '[C]opilot [F]ix' }, + { 'cr', 'CopilotChatReview', mode = { 'n', 'x' }, desc = '[C]opilot [R]eview' }, + { 'ct', 'CopilotChatTests', mode = { 'n', 'x' }, desc = '[C]opilot [T]ests' }, + { 'cm', 'CopilotChatCommit', desc = '[C]opilot co[M]mit msg' }, + }, +} diff --git a/lua/custom/plugins/dressing.lua b/lua/custom/plugins/dressing.lua new file mode 100644 index 00000000..7f4147be --- /dev/null +++ b/lua/custom/plugins/dressing.lua @@ -0,0 +1,6 @@ +-- Replace the ugly default vim.ui.input/select with a floating UI. +return { + 'stevearc/dressing.nvim', + event = 'VeryLazy', + opts = {}, +} diff --git a/lua/custom/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua index 3f46d06a..826619c2 100644 --- a/lua/custom/plugins/gitsigns.lua +++ b/lua/custom/plugins/gitsigns.lua @@ -2,6 +2,8 @@ return { { 'lewis6991/gitsigns.nvim', opts = { + current_line_blame = true, + current_line_blame_opts = { delay = 300, virt_text_pos = 'eol' }, on_attach = function(bufnr) local gitsigns = require 'gitsigns' diff --git a/lua/custom/plugins/glow.lua b/lua/custom/plugins/glow.lua deleted file mode 100644 index be576e3d..00000000 --- a/lua/custom/plugins/glow.lua +++ /dev/null @@ -1,6 +0,0 @@ -return { - 'ellisonleao/glow.nvim', - config = function() - require('glow').setup() - end, -} diff --git a/lua/custom/plugins/harpoon.lua b/lua/custom/plugins/harpoon.lua new file mode 100644 index 00000000..0c81784c --- /dev/null +++ b/lua/custom/plugins/harpoon.lua @@ -0,0 +1,21 @@ +return { + 'ThePrimeagen/harpoon', + branch = 'harpoon2', + dependencies = { 'nvim-lua/plenary.nvim' }, + config = function() + local harpoon = require 'harpoon' + harpoon:setup() + + vim.keymap.set('n', 'ha', function() harpoon:list():add() end, { desc = '[H]arpoon [A]dd file' }) + vim.keymap.set('n', 'hh', function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = '[H]arpoon toggle list' }) + + -- Jump to harpoon slots 1..4. ( reserved for smart-splits pane nav.) + for i = 1, 4 do + vim.keymap.set('n', '' .. i, function() harpoon:list():select(i) end, { desc = 'Harpoon slot ' .. i }) + end + + -- Prev/next harpoon entries. + vim.keymap.set('n', '[h', function() harpoon:list():prev() end, { desc = 'Harpoon prev' }) + vim.keymap.set('n', ']h', function() harpoon:list():next() end, { desc = 'Harpoon next' }) + end, +} diff --git a/lua/custom/plugins/java.lua b/lua/custom/plugins/java.lua new file mode 100644 index 00000000..1593a3de --- /dev/null +++ b/lua/custom/plugins/java.lua @@ -0,0 +1,23 @@ +return { + -- 'mfussenegger/nvim-jdtls', + -- ft = { 'java' }, + -- config = function() + -- local jdtls = require 'jdtls' + -- local home = os.getenv 'HOME' + -- local workspace_dir = home .. '~/.cache/jdtls/workspace/' .. vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t') + -- + -- require('jdtls').start_or_attach { + -- cmd = { 'jdtls' }, + -- root_dir = require('jdtls.setup').find_root { '.git', 'mvnw', 'gradlew' }, + -- settings = { + -- java = { + -- format = { + -- enabled = true, + -- settings = home .. '/.config/nvim/eclipse-java-style.xml', -- Use your exported formatter + -- }, + -- }, + -- }, + -- workspace_dir = workspace_dir, + -- } + -- end, +} diff --git a/lua/custom/plugins/leap.lua b/lua/custom/plugins/leap.lua index 231a596e..03051547 100644 --- a/lua/custom/plugins/leap.lua +++ b/lua/custom/plugins/leap.lua @@ -1,9 +1,13 @@ +-- flash.nvim: smarter `s`/`S` jump with treesitter-aware selection and +-- remote operations (e.g. `yr` to yank a remote region). return { - 'ggandor/leap.nvim', + 'folke/flash.nvim', event = 'VeryLazy', - config = function() - vim.keymap.set({ 'n', 'x', 'o' }, 's', '(leap-forward)', { noremap = true, silent = true }) - vim.keymap.set({ 'n', 'x', 'o' }, 'S', '(leap-backward)', { noremap = true, silent = true }) - vim.keymap.set({ 'n', 'x', 'o' }, 'gs', '(leap-from-window)', { noremap = true, silent = true }) - end, + opts = {}, + keys = { + { 's', mode = { 'n', 'x', 'o' }, function() require('flash').jump() end, desc = 'Flash' }, + { 'S', mode = { 'n', 'x', 'o' }, function() require('flash').treesitter() end, desc = 'Flash Treesitter' }, + { 'r', mode = 'o', function() require('flash').remote() end, desc = 'Remote Flash' }, + { 'R', mode = { 'o', 'x' }, function() require('flash').treesitter_search() end, desc = 'Treesitter Search' }, + }, } diff --git a/lua/custom/plugins/lsp.lua b/lua/custom/plugins/lsp.lua index 2b63356d..c9caca58 100644 --- a/lua/custom/plugins/lsp.lua +++ b/lua/custom/plugins/lsp.lua @@ -25,8 +25,8 @@ return { -- Useful status updates for LSP. { 'j-hui/fidget.nvim', opts = {} }, - -- Allows extra capabilities provided by nvim-cmp - 'hrsh7th/cmp-nvim-lsp', + -- Completion capabilities from blink.cmp. + 'saghen/blink.cmp', }, config = function() -- Brief aside: **What is LSP?** @@ -114,7 +114,7 @@ return { -- -- When you move your cursor, the highlights will be cleared (the second autocommand). local client = vim.lsp.get_client_by_id(event.data.client_id) - if client and client.supports_method(vim.lsp.protocol.Methods.textDocument_documentHighlight) then + if client and client:supports_method(vim.lsp.protocol.Methods.textDocument_documentHighlight) then local highlight_augroup = vim.api.nvim_create_augroup('kickstart-lsp-highlight', { clear = false }) vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' }, { buffer = event.buf, @@ -141,10 +141,32 @@ return { -- code, if the language server you are using supports them -- -- This may be unwanted, since they displace some of your code - if client and client.supports_method(vim.lsp.protocol.Methods.textDocument_inlayHint) then + if client and client:supports_method(vim.lsp.protocol.Methods.textDocument_inlayHint) then map('th', function() vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled { bufnr = event.buf }) end, '[T]oggle Inlay [H]ints') + -- Inlay hints on by default for Go. + if vim.bo[event.buf].filetype == 'go' then + vim.lsp.inlay_hint.enable(true, { bufnr = event.buf }) + end + end + end, + }) + + -- Go: organize imports on save via gopls code action. + vim.api.nvim_create_autocmd('BufWritePre', { + group = vim.api.nvim_create_augroup('go-organize-imports', { clear = true }), + pattern = '*.go', + callback = function() + local params = vim.lsp.util.make_range_params(0, 'utf-8') + params.context = { only = { 'source.organizeImports' } } + local result = vim.lsp.buf_request_sync(0, 'textDocument/codeAction', params, 1000) + for _, res in pairs(result or {}) do + for _, action in pairs(res.result or {}) do + if action.edit then + vim.lsp.util.apply_workspace_edit(action.edit, 'utf-8') + end + end end end, }) @@ -163,8 +185,7 @@ return { -- By default, Neovim doesn't support everything that is in the LSP specification. -- When you add nvim-cmp, luasnip, etc. Neovim now has *more* capabilities. -- So, we create new capabilities with nvim cmp, and then broadcast that to the servers. - local capabilities = vim.lsp.protocol.make_client_capabilities() - capabilities = vim.tbl_deep_extend('force', capabilities, require('cmp_nvim_lsp').default_capabilities()) + 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. @@ -181,6 +202,8 @@ return { filetypes = { 'go', 'go.mod', 'go.work' }, settings = { gopls = { + -- Resolve files behind `//go:build integration`. Matches Zed's gopls config. + buildFlags = { '-tags=integration' }, completeUnimported = true, usePlaceholders = true, analyses = { @@ -190,13 +213,38 @@ return { }, gofumpt = true, staticcheck = true, + hints = { + assignVariableTypes = true, + compositeLiteralFields = true, + compositeLiteralTypes = true, + constantValues = true, + functionTypeParameters = true, + parameterNames = true, + rangeVariableTypes = true, + }, }, }, - env = { - GOEXPERIMENT = 'rangefunc', + }, + pyright = { + settings = { + pyright = { + -- Disable pyright's import organizer; let ruff handle it. + disableOrganizeImports = true, + }, + python = { + analysis = { + -- ignore lint errors; ruff handles linting. + ignore = { '*' }, + }, + }, }, }, - pyright = {}, + ruff = { + -- Disable ruff's hover; pyright provides better hover. + on_attach = function(client, _) + client.server_capabilities.hoverProvider = false + end, + }, -- rust_analyzer = {}, -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs -- @@ -239,8 +287,10 @@ return { local ensure_installed = vim.tbl_keys(servers or {}) vim.list_extend(ensure_installed, { 'stylua', -- Used to format Lua code + 'jdtls', -- Java LSP 'gopls', 'pyright', + 'ruff', -- Python linter/formatter LSP. }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } diff --git a/lua/custom/plugins/markdown-preview.lua b/lua/custom/plugins/markdown-preview.lua index b570bb80..7baf82f6 100644 --- a/lua/custom/plugins/markdown-preview.lua +++ b/lua/custom/plugins/markdown-preview.lua @@ -1,7 +1,17 @@ +-- Live browser preview on `:MarkdownPreview`. +-- Build via the plugin's helper so it doesn't dirty yarn.lock and break Lazy updates. return { 'iamcco/markdown-preview.nvim', - build = 'cd app && npm install', - config = function() - vim.g.mkdp_auto_start = 1 + ft = { 'markdown' }, + build = function() + vim.fn['mkdp#util#install']() end, + cmd = { 'MarkdownPreview', 'MarkdownPreviewStop', 'MarkdownPreviewToggle' }, + init = function() + vim.g.mkdp_auto_start = 0 + vim.g.mkdp_filetypes = { 'markdown' } + end, + keys = { + { 'mp', 'MarkdownPreviewToggle', desc = '[M]arkdown [P]review' }, + }, } diff --git a/lua/custom/plugins/mini.lua b/lua/custom/plugins/mini.lua index f6b60e32..b5ac322f 100644 --- a/lua/custom/plugins/mini.lua +++ b/lua/custom/plugins/mini.lua @@ -5,7 +5,6 @@ return { -- Collection of various small independent plugins/modules require('mini.move').setup() require('mini.notify').setup() require('mini.starter').setup() - require('mini.tabline').setup() require('mini.statusline').setup { use_icons = vim.g.have_nerd_font, } diff --git a/lua/custom/plugins/neo-tree.lua b/lua/custom/plugins/neo-tree.lua index 7d4ed26c..d72d6412 100644 --- a/lua/custom/plugins/neo-tree.lua +++ b/lua/custom/plugins/neo-tree.lua @@ -8,56 +8,30 @@ return { 'saifulapm/neotree-file-nesting-config', }, cmd = 'Neotree', - opts = { - -- recommended config for better UI - hide_root_node = true, - retain_hidden_root_indent = true, - filesystem = { - filtered_items = { - show_hidden_count = false, - never_show = { - '.DS_Store', - }, - }, - }, - default_component_configs = { - indent = { - with_expanders = true, - expander_collapsed = '', - expander_expanded = '', - }, - }, - }, + -- Auto-open on startup when nvim is launched without a file or on a directory. + init = function() + vim.api.nvim_create_autocmd('VimEnter', { + group = vim.api.nvim_create_augroup('neo-tree-auto-open', { clear = true }), + callback = function() + local argc = vim.fn.argc() + local arg = argc > 0 and vim.fn.argv(0) or '' + if argc == 0 or vim.fn.isdirectory(arg) == 1 then + vim.schedule(function() + -- `reveal_force_cwd` roots at cwd and avoids the + -- expand_to_node recursion that `show` can hit. + vim.cmd('Neotree reveal_force_cwd') + end) + end + end, + }) + end, keys = { { '\\', ':Neotree reveal', desc = 'NeoTree reveal', silent = true }, - { - 'b', - function() - require('neo-tree.command').execute { toggle = true, source = 'buffers', position = 'left' } - end, - desc = 'Buffers (root dir)', - }, - { - 'e', - function() - require('neo-tree.command').execute { toggle = true, source = 'filesystem', position = 'left' } - end, - desc = 'Filesystem (root dir)', - }, - { - 'g', - function() - require('neo-tree.command').execute { toggle = true, source = 'git_status', position = 'left' } - end, - desc = 'Filesystem (root dir)', - }, + { 'e', ':Neotree reveal', desc = 'NeoTree reveal', silent = true }, }, - config = function(_, opts) - opts.nesting_rules = require('neotree-file-nesting-config').nesting_rules - require('neo-tree').setup(opts) - + config = function() local inputs = require 'neo-tree.ui.inputs' - -- Trash the target + local function trash(state) local node = state.tree:get_node() if node.type == 'message' then @@ -74,7 +48,6 @@ return { end) end - -- Trash the selections (visual mode) local function trash_visual(state, selected_nodes) local paths_to_trash = {} for _, node in ipairs(selected_nodes) do @@ -95,6 +68,25 @@ return { end require('neo-tree').setup { + -- hide_root_node + follow_current_file used to cause an infinite + -- expand_to_node/restore recursion at startup; both disabled here. + nesting_rules = require('neotree-file-nesting-config').nesting_rules, + filesystem = { + filtered_items = { + show_hidden_count = false, + never_show = { '.DS_Store' }, + }, + follow_current_file = { + enabled = false, + }, + }, + default_component_configs = { + indent = { + with_expanders = true, + expander_collapsed = '', + expander_expanded = '', + }, + }, event_handlers = { { event = 'neo_tree_buffer_enter', diff --git a/lua/custom/plugins/persistence.lua b/lua/custom/plugins/persistence.lua new file mode 100644 index 00000000..c5e7b641 --- /dev/null +++ b/lua/custom/plugins/persistence.lua @@ -0,0 +1,12 @@ +-- Auto session per cwd. Run `:lua require('persistence').load()` to restore +-- the most recent session, or use the keymaps below. +return { + 'folke/persistence.nvim', + event = 'BufReadPre', + opts = {}, + keys = { + { 'qs', function() require('persistence').load() end, desc = 'Restore session for cwd' }, + { 'ql', function() require('persistence').load({ last = true }) end, desc = 'Restore last session' }, + { 'qd', function() require('persistence').stop() end, desc = "Don't save current session" }, + }, +} diff --git a/lua/custom/plugins/render-markdown.lua b/lua/custom/plugins/render-markdown.lua new file mode 100644 index 00000000..07616818 --- /dev/null +++ b/lua/custom/plugins/render-markdown.lua @@ -0,0 +1,11 @@ +-- In-buffer markdown rendering (headings, lists, code blocks, tables). +-- Applies to markdown buffers, LSP hover docs, and noice popups. +return { + 'MeanderingProgrammer/render-markdown.nvim', + dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, + ft = { 'markdown', 'codecompanion' }, + opts = { + file_types = { 'markdown', 'codecompanion' }, + completions = { lsp = { enabled = true } }, + }, +} diff --git a/lua/custom/plugins/tmux.lua b/lua/custom/plugins/tmux.lua index 0b0db10c..129abd1d 100644 --- a/lua/custom/plugins/tmux.lua +++ b/lua/custom/plugins/tmux.lua @@ -1,18 +1,16 @@ +-- Seamless nvim-split + zellij-pane navigation. +-- Replaces vim-tmux-navigator. smart-splits owns Ctrl-h/j/k/l and falls +-- through to `zellij action move-focus` when at a vim split edge. return { - 'christoomey/vim-tmux-navigator', - event = 'VeryLazy', - cmd = { - 'TmuxNavigateLeft', - 'TmuxNavigateDown', - 'TmuxNavigateUp', - 'TmuxNavigateRight', - 'TmuxNavigatePrevious', + 'mrjones2014/smart-splits.nvim', + lazy = false, + opts = { + multiplexer_integration = 'zellij', }, keys = { - { '', 'TmuxNavigateLeft' }, - { '', 'TmuxNavigateDown' }, - { '', 'TmuxNavigateUp' }, - { '', 'TmuxNavigateRight' }, - { '', 'TmuxNavigatePrevious' }, + { '', function() require('smart-splits').move_cursor_left() end, desc = 'Focus left split/pane' }, + { '', function() require('smart-splits').move_cursor_down() end, desc = 'Focus down split/pane' }, + { '', function() require('smart-splits').move_cursor_up() end, desc = 'Focus up split/pane' }, + { '', function() require('smart-splits').move_cursor_right() end, desc = 'Focus right split/pane' }, }, } diff --git a/lua/custom/plugins/toggleterm.lua b/lua/custom/plugins/toggleterm.lua new file mode 100644 index 00000000..201435c4 --- /dev/null +++ b/lua/custom/plugins/toggleterm.lua @@ -0,0 +1,28 @@ +-- Persistent floating/split terminals. Replaces the hand-rolled FloatingTerm. +return { + 'akinsho/toggleterm.nvim', + version = '*', + cmd = { 'ToggleTerm', 'TermExec', 'ToggleTermSendCurrentLine', 'ToggleTermSendVisualLines' }, + keys = { + { 't', 'ToggleTerm direction=float', desc = 'Toggle floating terminal' }, + { 'T', 'ToggleTerm direction=horizontal', desc = 'Toggle horizontal terminal' }, + }, + opts = { + size = function(term) + if term.direction == 'horizontal' then + return math.floor(vim.o.lines * 0.3) + else + return math.floor(vim.o.columns * 0.4) + end + end, + open_mapping = [[]], + direction = 'float', + float_opts = { border = 'rounded' }, + start_in_insert = true, + insert_mappings = true, + terminal_mappings = true, + persist_size = true, + persist_mode = true, + shade_terminals = true, + }, +} diff --git a/lua/custom/plugins/treesitter-context.lua b/lua/custom/plugins/treesitter-context.lua new file mode 100644 index 00000000..492766bd --- /dev/null +++ b/lua/custom/plugins/treesitter-context.lua @@ -0,0 +1,22 @@ +-- Sticky-scroll style context shown at the top of the buffer. +-- Mirrors Zed's `sticky_scroll` feature. +return { + 'nvim-treesitter/nvim-treesitter-context', + event = 'BufReadPost', + opts = { + max_lines = 3, + min_window_height = 20, + mode = 'cursor', + trim_scope = 'outer', + }, + keys = { + { + '[c', + function() + require('treesitter-context').go_to_context(vim.v.count1) + end, + desc = 'Jump to context', + silent = true, + }, + }, +} diff --git a/lua/custom/plugins/treesitter.lua b/lua/custom/plugins/treesitter.lua index 1c2ba53a..d5861c10 100644 --- a/lua/custom/plugins/treesitter.lua +++ b/lua/custom/plugins/treesitter.lua @@ -1,50 +1,70 @@ -return { -- Highlight, edit, and navigate code +-- nvim-treesitter `main` branch (required for nvim 0.11+). +-- No more `require('nvim-treesitter.configs').setup{}`. Highlighting is +-- enabled per buffer via vim.treesitter.start() in a FileType autocmd. +return { '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 = { - 'go', - 'gowork', - 'gomod', - 'gosum', - 'terraform', - 'bash', - 'c', - 'diff', - 'html', - 'lua', - 'luadoc', - 'markdown', - 'markdown_inline', - 'query', - 'vim', - 'vimdoc', - 'java', - 'json', - 'markdown', - 'nginx', - 'sql', - 'tmux', - 'typescript', - 'yaml', - }, - -- 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' } }, + dependencies = { + { 'nvim-treesitter/nvim-treesitter-textobjects', branch = 'main' }, }, - -- 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: - -- - -- - Incremental selection: Included, see `:help nvim-treesitter-incremental-selection-mod` - -- - Show your current context: https://github.com/nvim-treesitter/nvim-treesitter-context - -- - Treesitter + textobjects: https://github.com/nvim-treesitter/nvim-treesitter-textobjects + config = function() + -- Parsers we want installed. + local parsers = { + 'go', 'gowork', 'gomod', 'gosum', + 'terraform', 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', + 'markdown', 'markdown_inline', 'query', 'vim', 'vimdoc', + 'java', 'json', 'nginx', 'sql', 'tmux', 'typescript', 'yaml', + 'python', 'rust', 'toml', + } + require('nvim-treesitter').install(parsers) + + -- Start treesitter highlighting + indent for any filetype with a parser. + vim.api.nvim_create_autocmd('FileType', { + group = vim.api.nvim_create_augroup('ts-start', { clear = true }), + callback = function(ev) + local ft = vim.bo[ev.buf].filetype + local lang = vim.treesitter.language.get_lang(ft) + if not lang then return end + local ok = pcall(vim.treesitter.start, ev.buf, lang) + if ok then + vim.bo[ev.buf].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" + end + end, + }) + + -- Treesitter-aware textobjects + motion. + local move = require('nvim-treesitter-textobjects.move') + local select = require('nvim-treesitter-textobjects.select') + + -- Select: vif/vaf (function inner/outer), vac/vic (class), vaa/via (parameter). + local function map_select(key, query) + vim.keymap.set({ 'x', 'o' }, key, function() + select.select_textobject(query, 'textobjects') + end, { desc = 'TS select ' .. query }) + end + map_select('af', '@function.outer') + map_select('if', '@function.inner') + map_select('ac', '@class.outer') + map_select('ic', '@class.inner') + map_select('aa', '@parameter.outer') + map_select('ia', '@parameter.inner') + map_select('al', '@loop.outer') + map_select('il', '@loop.inner') + + -- Move: ]m / [m / ]M / [M between functions. + vim.keymap.set({ 'n', 'x', 'o' }, ']m', + function() move.goto_next_start('@function.outer', 'textobjects') end, + { desc = 'TS next function start' }) + vim.keymap.set({ 'n', 'x', 'o' }, ']M', + function() move.goto_next_end('@function.outer', 'textobjects') end, + { desc = 'TS next function end' }) + vim.keymap.set({ 'n', 'x', 'o' }, '[m', + function() move.goto_previous_start('@function.outer', 'textobjects') end, + { desc = 'TS prev function start' }) + vim.keymap.set({ 'n', 'x', 'o' }, '[M', + function() move.goto_previous_end('@function.outer', 'textobjects') end, + { desc = 'TS prev function end' }) + end, } diff --git a/lua/custom/plugins/trouble.lua b/lua/custom/plugins/trouble.lua new file mode 100644 index 00000000..c9ccbb89 --- /dev/null +++ b/lua/custom/plugins/trouble.lua @@ -0,0 +1,14 @@ +-- Pretty diagnostics / LSP refs / TODO / quickfix lists. +return { + 'folke/trouble.nvim', + cmd = 'Trouble', + opts = {}, + keys = { + { 'xx', 'Trouble diagnostics toggle', desc = '[T]rouble diagnostics' }, + { 'xX', 'Trouble diagnostics toggle filter.buf=0', desc = '[T]rouble buffer diagnostics' }, + { 'xs', 'Trouble symbols toggle focus=false', desc = '[T]rouble symbols' }, + { 'xr', 'Trouble lsp toggle focus=false win.position=right', desc = '[T]rouble LSP refs/defs' }, + { 'xl', 'Trouble loclist toggle', desc = '[T]rouble loclist' }, + { 'xq', 'Trouble qflist toggle', desc = '[T]rouble quickfix' }, + }, +} diff --git a/lua/custom/zed-keymaps.lua b/lua/custom/zed-keymaps.lua new file mode 100644 index 00000000..2b2bab75 --- /dev/null +++ b/lua/custom/zed-keymaps.lua @@ -0,0 +1,57 @@ +-- Mirrors common Zed bindings for muscle-memory continuity. +-- Source from init.lua: require('custom.zed-keymaps'). + +-- Move lines (Zed: alt-j / alt-k). +vim.keymap.set('n', '', ':m .+1==', { desc = 'Move line down', silent = true }) +vim.keymap.set('n', '', ':m .-2==', { desc = 'Move line up', silent = true }) +vim.keymap.set('v', '', ":m '>+1gv=gv", { desc = 'Move selection down', silent = true }) +vim.keymap.set('v', '', ":m '<-2gv=gv", { desc = 'Move selection up', silent = true }) + +-- Lowercase gi for implementation (Zed: g i; existing gI still works). +vim.keymap.set('n', 'gi', function() + require('telescope.builtin').lsp_implementations() +end, { desc = '[G]oto [I]mplementation' }) + +-- Zed-parity: F2 to rename, . for code action (mirrors cmd-.), +-- fc for command palette. +vim.keymap.set('n', '', vim.lsp.buf.rename, { desc = 'LSP rename' }) +vim.keymap.set('n', '.', vim.lsp.buf.code_action, { desc = 'Code action' }) +vim.keymap.set('n', 'fc', function() require('telescope.builtin').commands() end, { desc = '[F]ind [C]ommand' }) + +-- Full-buffer git blame (Zed: g b). +vim.keymap.set('n', 'gb', function() + require('gitsigns').blame() +end, { desc = '[G]it [B]lame buffer' }) + +-- Close current buffer (Zed: space x). +vim.keymap.set('n', 'x', 'bd', { desc = 'Close buffer' }) + +-- Workspace symbols (Zed: space f e). +vim.keymap.set('n', 'fe', function() + require('telescope.builtin').lsp_dynamic_workspace_symbols() +end, { desc = '[F]ind workspace symbols' }) + +-- Task spawner: floating zellij pane when in zellij, fallback to nvim split term. +local function task(cmd) + return function() + if vim.env.ZELLIJ ~= nil and vim.env.ZELLIJ ~= '' then + vim.fn.jobstart({ + 'zellij', 'action', 'new-pane', '--floating', + '--close-on-exit', '--', '/bin/zsh', '-lc', cmd, + }, { detach = true }) + else + vim.cmd('botright 15split | terminal ' .. cmd) + vim.cmd('startinsert') + end + end +end + +-- Task bindings (mirror Zed's tasks.json). +vim.keymap.set('n', 'gh', task('gh dash'), { desc = '[G]itHub das[H]board' }) +vim.keymap.set('n', 'kk', task('k9s'), { desc = '[K]9s' }) +vim.keymap.set('n', 'bb', task('cd bruno && bru run --env Local --insecure --tests-only'), { desc = '[B]runo tests' }) +vim.keymap.set('n', 'uu', task('cd restopay && make up'), { desc = 'Make [U]p' }) +vim.keymap.set('n', 'dd', task('cd restopay && make down'), { desc = 'Make Do[w]n' }) +vim.keymap.set('n', 'aa', task( + 'hv athenz user-cert --system=public && awscreds -d vespa.external.factory -r admin -p external-factory -z public' +), { desc = 'Update certs for External-Factory' })