Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f12297d8c | ||
|
|
e68fd278cc | ||
|
|
c85b2870a7 | ||
|
|
9a30131d6c | ||
|
|
ca1eaee186 | ||
|
|
e00586dd10 | ||
|
|
5f57ab9009 | ||
|
|
74c901f261 | ||
|
|
e4b1da80e3 | ||
|
|
258b1c2c3c | ||
|
|
8931dc4589 | ||
|
|
5e3461c5f3 | ||
|
|
c1666fe7a3 | ||
|
|
2df7760de0 | ||
|
|
c298e5598e | ||
|
|
9a37a2db0f | ||
|
|
992cf65a50 | ||
|
|
4521c4ed12 | ||
|
|
e291d41585 | ||
|
|
209e405260 | ||
|
|
d54e2968b0 | ||
|
|
cea8ce8882 | ||
|
|
4a74f60fd7 | ||
|
|
f90c967872 | ||
|
|
9ef384af74 | ||
|
|
2eb6ca7c37 | ||
|
|
d047b4905f | ||
|
|
55f479eb86 | ||
|
|
09623f8590 | ||
|
|
ff89015b9c | ||
|
|
795137d2af | ||
|
|
f60bb4ab01 | ||
|
|
d86d1f7fcb | ||
|
|
918bd8ce0c | ||
|
|
1f798f0da1 | ||
|
|
d26830bf71 | ||
|
|
4524944dae | ||
|
|
49364f362d | ||
|
|
a1907c44c8 | ||
|
|
e4c3e21d9b | ||
|
|
74c28612ae | ||
|
|
9040e3a62f | ||
|
|
7b51fef715 | ||
|
|
e782476e93 | ||
|
|
77802f4692 | ||
|
|
fa2a5f66e5 | ||
|
|
98adcdc503 | ||
|
|
928a33a0de | ||
|
|
7caa4e9e01 | ||
|
|
96bf8dd300 | ||
|
|
4ca80e1cb3 | ||
|
|
cc18eff3f5 | ||
|
|
98afa7639a | ||
|
|
3b87993d60 | ||
|
|
2def250609 | ||
|
|
924a4c5888 | ||
|
|
a807da6f07 | ||
|
|
b46c53bc11 | ||
|
|
c455df4a90 | ||
|
|
192e2e9e9b | ||
|
|
032b6018a7 | ||
|
|
550f9eb89c | ||
|
|
5a27f3751b | ||
|
|
7140ddaf8b | ||
|
|
8379c929dc | ||
|
|
758c0eea5e | ||
|
|
f7954b0d38 | ||
|
|
d90d567af3 | ||
|
|
adbb9f1af3 | ||
|
|
3b3ebd4524 | ||
|
|
5cfb307909 | ||
|
|
3692eecdd7 | ||
|
|
9cadee2e32 | ||
|
|
0116171278 | ||
|
|
dfd0382852 | ||
|
|
4c524c0d00 | ||
|
|
81a691dce6 | ||
|
|
a3860b33da | ||
|
|
abbe69e9ee | ||
|
|
0fd2c7067a | ||
|
|
54444c9f33 | ||
|
|
b4b165fb96 | ||
|
|
393011a521 | ||
|
|
96f0f9791c | ||
|
|
f17686a991 | ||
|
|
265e728a9f | ||
|
|
e296ae8fa5 | ||
|
|
82a1c14a1e | ||
|
|
3832508b40 | ||
|
|
bdf8ad1bb0 | ||
|
|
06e0bab7dd | ||
|
|
4bd926e7ed | ||
|
|
f0851b44de | ||
|
|
be6d52c586 | ||
|
|
eb30446aeb | ||
|
|
1e7a713aaa | ||
|
|
0a0d094e3c | ||
|
|
c832c27c1e | ||
|
|
049334330d | ||
|
|
963c35e67f | ||
|
|
e5283a89ed | ||
|
|
2c1e70cfdc | ||
|
|
8c7f943e64 | ||
|
|
233cb7c4d8 | ||
|
|
2f18e2c031 | ||
|
|
858dbe14ea | ||
|
|
827cc0f118 | ||
|
|
2068e2afd4 | ||
|
|
65b4d2bbc7 | ||
|
|
e7da050111 | ||
|
|
9859b36075 | ||
|
|
3c02ba5c9d | ||
|
|
98605f84ab | ||
|
|
f0800ef8d7 | ||
|
|
1c70bbda10 | ||
|
|
7cbc0c9a7f | ||
|
|
56485842a4 | ||
|
|
0b9ee59ab7 | ||
|
|
c5e964ba16 | ||
|
|
f117ced1dd | ||
|
|
9c3613ce9c | ||
|
|
e34d8595a9 | ||
|
|
d0d2067980 | ||
|
|
4831cfc27c | ||
|
|
f711722233 | ||
|
|
16efb1c9e5 | ||
|
|
c5133f1a4d | ||
|
|
0b801006f1 | ||
|
|
60c1f88cfb | ||
|
|
6a6c49b78e | ||
|
|
a1f8d8c465 | ||
|
|
ec7bec004f | ||
|
|
288831e280 | ||
|
|
eb56100a73 | ||
|
|
553796304c | ||
|
|
a5e7e118b3 | ||
|
|
07f21fd441 | ||
|
|
dd0fc8a1d6 | ||
|
|
0773383ad4 | ||
|
|
584049ad51 | ||
|
|
192031b559 | ||
|
|
f00117de45 | ||
|
|
e16915f0e3 | ||
|
|
ec61cbe072 | ||
|
|
d2b7cf6e62 | ||
|
|
a89862a187 | ||
|
|
087a24541f | ||
|
|
cee08ee5ec | ||
|
|
e1c47171e4 | ||
|
|
2cd17abf76 | ||
|
|
2cc12e341a | ||
|
|
08d5780f92 |
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
[scopes]
|
||||||
|
config = [".github", ".neoconf.json", ".luarc.json", ".stylua.toml", "selene.toml", "lazy-lock.json", "neovim.yml"]
|
||||||
|
docs = ["README.md"]
|
||||||
|
core = ["init.lua", "lua/config", "lua/plugins", "lua/lazy_setup.lua", "lua/polish.lua", "lua/community.lua"]
|
||||||
|
claude = ["claude/agents", "claude/commands", "claude/skills"]
|
||||||
|
docker = ["Dockerfile"]
|
||||||
+31
-13
@@ -1,17 +1,35 @@
|
|||||||
# AI agent / tooling state written into the config dir
|
# Neovim
|
||||||
/.omc/
|
.cache
|
||||||
/.claude/
|
.local
|
||||||
/.crush/
|
.session
|
||||||
/.beads/
|
.swap
|
||||||
/.codegraph/
|
*lock.json
|
||||||
/.understand-anything/
|
|
||||||
/.ai/
|
|
||||||
|
|
||||||
# Neovim scratch
|
|
||||||
*.log
|
|
||||||
*.swp
|
|
||||||
*~
|
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
Session.vim
|
||||||
|
Sessionx.vim
|
||||||
|
tags
|
||||||
|
tags.*
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
tmp
|
||||||
|
temp
|
||||||
|
!lua/plugins
|
||||||
|
lua/plugins/local.lua
|
||||||
|
|
||||||
|
# Claude Code
|
||||||
|
.claude/
|
||||||
|
claude
|
||||||
|
!claude/agents
|
||||||
|
!claude/commands
|
||||||
|
!claude/skills
|
||||||
|
!claude/settings.json
|
||||||
|
|
||||||
|
lazy-lock.json
|
||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
{
|
{
|
||||||
"format.enable": false
|
"diagnostics.disable": [
|
||||||
|
"deprecated"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
+18
-18
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"neodev": {
|
"neodev": {
|
||||||
"library": {
|
"library": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"plugins": true
|
"plugins": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"neoconf": {
|
"neoconf": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"lua_ls": {
|
"lua_ls": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lspconfig": {
|
"lspconfig": {
|
||||||
"lua_ls": {
|
"lua_ls": {
|
||||||
"Lua.format.enable": false
|
"Lua.format.enable": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Whitespace-only changes.
@@ -1,28 +1,12 @@
|
|||||||
# prdlk/nvim
|
# AstroNvim Template
|
||||||
|
|
||||||
Personal Neovim config built on [AstroNvim](https://github.com/AstroNvim/AstroNvim) v6 (`version = "^6"` in `lua/lazy_setup.lua`), targeting Neovim 0.12.
|
**NOTE:** This is for AstroNvim v5+
|
||||||
|
|
||||||
It started from the AstroNvim template and now adds git-aware sessions, `fff`-backed file search, multiplexer-aware split navigation, mermaid diagnostics, MDX templates, and an Oh My Pi side terminal. One plugin per job: where AstroNvim or a community pack ships a second implementation of something already covered, it is disabled rather than configured.
|
A template for getting started with [AstroNvim](https://github.com/AstroNvim/AstroNvim)
|
||||||
|
|
||||||
## Requirements
|
## 🛠️ Installation
|
||||||
|
|
||||||
| Tool | Used by |
|
#### Make a backup of your current nvim and shared folder
|
||||||
| --- | --- |
|
|
||||||
| Neovim 0.12+, git | everything |
|
|
||||||
| Nerd Font | icons (`icons_enabled = true`) |
|
|
||||||
| `fd`, `ripgrep` | `<C-f>e` / `<C-f>m` pickers, grug-far |
|
|
||||||
| `cargo` (fallback only) | `fff` builds from source when no prebuilt binary downloads |
|
|
||||||
| `node` + `npm` | mermaid validator (installs `mermaid` + `jsdom` under `stdpath("data")` on first use) |
|
|
||||||
| `lazygit`, `lazydocker`, `gh` (+ `gh-dash`), `ghq` | git/GitHub/project binds |
|
|
||||||
| `kitty`, `yazi` | neo-tree image preview (`kitten icat`) and yazi launchers |
|
|
||||||
| `omp` + `omp plugin install omp.nvim` | Oh My Pi terminal and `omp.nvim` integration |
|
|
||||||
| `uv` / a project venv | basedpyright third-party imports (`uv sync`) |
|
|
||||||
|
|
||||||
Language servers, formatters, and linters come from the AstroCommunity packs through Mason.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Back up any existing config:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mv ~/.config/nvim ~/.config/nvim.bak
|
mv ~/.config/nvim ~/.config/nvim.bak
|
||||||
@@ -31,151 +15,20 @@ mv ~/.local/state/nvim ~/.local/state/nvim.bak
|
|||||||
mv ~/.cache/nvim ~/.cache/nvim.bak
|
mv ~/.cache/nvim ~/.cache/nvim.bak
|
||||||
```
|
```
|
||||||
|
|
||||||
Clone and start:
|
#### Create a new user repository from this template
|
||||||
|
|
||||||
|
Press the "Use this template" button above to create a new repository to store your user configuration.
|
||||||
|
|
||||||
|
You can also just clone this repository directly if you do not want to track your user configuration in GitHub.
|
||||||
|
|
||||||
|
#### Clone the repository
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/<your_user>/<your_repository> ~/.config/nvim
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Start Neovim
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/prdlk/nvim ~/.config/nvim
|
|
||||||
nvim
|
nvim
|
||||||
```
|
```
|
||||||
|
|
||||||
`init.lua` bootstraps lazy.nvim on first launch and installs the plugins pinned in `lazy-lock.json`.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
init.lua lazy.nvim bootstrap, terminal title, cursor shape
|
|
||||||
lua/lazy_setup.lua AstroNvim import, leader keys (<Space>, localleader ","), lazy options
|
|
||||||
lua/community.lua AstroCommunity imports (language packs, colorscheme, trouble, supermaven)
|
|
||||||
lua/plugins/ plugin specs and AstroNvim overrides, one file per concern
|
|
||||||
lua/config/ plain Lua modules used by the plugin specs
|
|
||||||
lua/polish.lua runs last; registers the <Leader>u toggles
|
|
||||||
templates/mdx/ template.nvim snippets for MDX docs
|
|
||||||
scripts/ mermaid-validator.mjs (node process used by config/mermaid.lua)
|
|
||||||
```
|
|
||||||
|
|
||||||
## What's included
|
|
||||||
|
|
||||||
**Languages** (`lua/community.lua`): Go (+ golangci-lint), Rust, TypeScript, Biome, Python (basedpyright, no black/isort), HTML/CSS, Tailwind, JSON, YAML, TOML, Proto, Pkl, MDX.
|
|
||||||
|
|
||||||
**Python**: `plugins/python.lua` points basedpyright at `$VIRTUAL_ENV`/`$CONDA_PREFIX`, then `.venv`/`venv`/`env` in the project root, then system `python3`. A project with a `uv.lock` or `poetry.lock` and no venv gets a one-time warning. Ruff organizes imports and formats.
|
|
||||||
|
|
||||||
**Format on save** (`plugins/conform.lua`, `config/lsp_code_actions.lua`): on save, `source.organizeImports` and `source.fixAll` code actions run first, then conform formats. JavaScript buffers get the code actions but are not formatted. `<Leader>uf` turns the whole pipeline on or off.
|
|
||||||
|
|
||||||
**Sessions** (`config/session.lua`): sessions are keyed by `<worktree root>@<branch>`, so each worktree and branch keeps its own layout. On `VimEnter`, neo-tree opens and the saved session is restored; with no saved session, the git files picker opens instead. None of this happens when nvim is started with file arguments. Sessions save on exit.
|
|
||||||
|
|
||||||
**Search**: `fff.nvim` handles files and grep (frecency, respects `.gitignore` and `.ignore`). snacks.picker handles everything else. `config/ignore_patterns.lua` holds the shared ignore list used by neo-tree and the snacks pickers.
|
|
||||||
|
|
||||||
**Pane navigation** (`config/pane_nav.lua`): `<C-h/j/k/l>` moves between Neovim splits first. At the outermost split, the move goes to herdr, tmux, or kitty, whichever is the nearest ancestor process. Under tmux the move goes through smart-splits.nvim, loaded at startup, which flags the pane `@pane-is-vim` for `tmux.conf`. There, `<C-Up/Down/Left/Right>` and `<M-h/j/k/l>` are smart-splits resize and cross into tmux panes at the edge. Elsewhere smart-splits stays unloaded and resize is plain `:resize` on `<C-Up/Down/Left/Right>`.
|
|
||||||
|
|
||||||
**Mermaid** (`config/mermaid.lua`): tree-sitter highlights ```` ```mermaid ```` fences in markdown/MDX and `.mmd` files. Diagnostics come from mermaid's own parser running in a node process, which exits after 5 idle minutes. Run `:MermaidCheck` to re-validate.
|
|
||||||
|
|
||||||
**Markdown**: `:Vellum` opens a live GitHub-style preview (prdlk fork, `mdx` branch). In markdown and MDX buffers, visual `<Leader>e…` applies formatting (see below).
|
|
||||||
|
|
||||||
**AI**: Supermaven provides inline ghost-text completions (`<C-l>` accepts). It is disabled for Python and inside the LeetCode work directory. `<C-a><C-a>` toggles an Oh My Pi terminal on the right.
|
|
||||||
|
|
||||||
**UI**: cyberdream colorscheme (muted variant), bufferline, neo-tree replacing mini.files, smear-cursor (loaded at `VeryLazy`). No statusline (`laststatus = 0`); heirline stays for the winbar breadcrumbs. Snacks dashboard is disabled.
|
|
||||||
|
|
||||||
### Disabled on purpose
|
|
||||||
|
|
||||||
Core or community plugins turned off in `plugins/user.lua` because something else already owns the job:
|
|
||||||
|
|
||||||
| Plugin | Replaced by |
|
|
||||||
| --- | --- |
|
|
||||||
| `none-ls.nvim`, `mason-null-ls.nvim` | conform (`mason-null-ls` would auto-register every Mason tool as a second formatter/linter client) |
|
|
||||||
| `toggleterm.nvim` | kitty / snacks terminals |
|
|
||||||
| `lsp_signature.nvim` (not imported) | astrolsp's built-in signature help (`features.signature_help = true`) |
|
|
||||||
| `refactoring.nvim`, `dev-tools.nvim` (not imported) | LSP code actions (`<C-a>a`) |
|
|
||||||
| `tv.nvim` (removed) | fff + snacks pickers |
|
|
||||||
|
|
||||||
Startup on a TTY is ~80 ms with 41 of 65 plugins loaded eagerly (`:Lazy profile`); the rest load on their command, key, filetype, or `User AstroFile`.
|
|
||||||
|
|
||||||
## Keymaps
|
|
||||||
|
|
||||||
Leader is `<Space>`; localleader is `,`. Prefixes: `<C-f>` find, `<C-g>` git/GitHub, `<C-b>` buffers, `<C-a>` assist, `<Leader>u` UI toggles.
|
|
||||||
|
|
||||||
### Files and search
|
|
||||||
|
|
||||||
| Key | Action |
|
|
||||||
| --- | --- |
|
|
||||||
| `<Leader><Leader>` | Smart picker (scoped to the git root) |
|
|
||||||
| `<Leader>e` | Git files |
|
|
||||||
| `<C-f>f` / `<C-f>.` | Find files in cwd / in the buffer's directory |
|
|
||||||
| `<C-f>w` / `<C-f>z` / `<C-f><C-w>` | Live grep / fuzzy grep / grep word or selection |
|
|
||||||
| `<C-f>r` | Resume last find/grep |
|
|
||||||
| `<C-f>e` | Pick an extension, then find files with that extension |
|
|
||||||
| `<C-f>m` | Files modified today |
|
|
||||||
| `<C-f>x` | URLs in the buffer (opens in browser) |
|
|
||||||
| `<C-f>o` / `<C-f>p` / `<C-f>P` | Recent files / projects / ghq repos |
|
|
||||||
| `<C-f>d` / `<C-f>l` | Diagnostics / lines |
|
|
||||||
| `<C-f>gw` `gd` `gm` `gp` `gc` | Workflows, Devbox/Docker, Makefiles, package.json, `.ai/` files |
|
|
||||||
| `<C-f><C-f>` | LSP symbols |
|
|
||||||
| `<C-i>` | Template picker (also takes over `<Tab>` in normal mode) |
|
|
||||||
| `<C-r>` | Find & replace (grug-far; linewise visual selection limits the range) |
|
|
||||||
| `<C-e>` | Toggle neo-tree |
|
|
||||||
|
|
||||||
File pickers open the selection with `:tab drop` on `<CR>` and in a vsplit on `<S-CR>`.
|
|
||||||
|
|
||||||
### Editing and navigation
|
|
||||||
|
|
||||||
| Key | Action |
|
|
||||||
| --- | --- |
|
|
||||||
| `f` / `F` | Flash word-start labels / toggle fold |
|
|
||||||
| `g/` | Flash search: type a pattern, labels on every match |
|
|
||||||
| `s` / `S` | Flash jump / Flash treesitter |
|
|
||||||
| `J` / `K` | Next / previous diagnostic |
|
|
||||||
| `H` / `L` | Previous / next buffer |
|
|
||||||
| `]]` / `[[` | Next / previous LSP reference |
|
|
||||||
| `<C-h/j/k/l>` | Move between splits, then tmux/herdr/kitty panes |
|
|
||||||
| `<C-Up/Down/Left/Right>` | Resize split |
|
|
||||||
| `vv` / `T` | Select all / go to top |
|
|
||||||
| visual `D` / `R` | Delete / replace every occurrence of the selection |
|
|
||||||
| `<C-p>` | Paste from system clipboard |
|
|
||||||
|
|
||||||
### Buffers, windows, saving
|
|
||||||
|
|
||||||
| Key | Action |
|
|
||||||
| --- | --- |
|
|
||||||
| `<C-s>` | Save if modified, otherwise buffer picker |
|
|
||||||
| `<C-w>` | Save buffer |
|
|
||||||
| `<C-c>` | Save if modified, then close buffer |
|
|
||||||
| `<C-q>` | Close window; save all and quit if it's the last one |
|
|
||||||
| `ZZ` / `<A-q>` | Save all and quit |
|
|
||||||
| `<C-b>b` / `<C-b>f` / `<C-b><C-c>` | Pick buffer / find buffers / close all other buffers |
|
|
||||||
|
|
||||||
`<C-w>` is remapped to save, so the built-in window prefix is not available on it.
|
|
||||||
|
|
||||||
### Git and GitHub
|
|
||||||
|
|
||||||
| Key | Action |
|
|
||||||
| --- | --- |
|
|
||||||
| `<C-g><C-g>` | lazygit |
|
|
||||||
| `<C-g><C-d>` | CodeDiff review workspace |
|
|
||||||
| `<C-g>d` / `<C-g><C-b>` | Git diff / branches picker |
|
|
||||||
| `<C-g><C-i>` / `<C-g>I` | GitHub issues (open / all) |
|
|
||||||
| `<C-g><C-p>` / `<C-g>P` | GitHub PRs (open / all) |
|
|
||||||
| `<C-g>o` | Open the repo on the web |
|
|
||||||
| `<C-d><C-d>` | lazydocker |
|
|
||||||
|
|
||||||
### Assist
|
|
||||||
|
|
||||||
| Key | Action |
|
|
||||||
| --- | --- |
|
|
||||||
| `<C-a>a` | Code actions |
|
|
||||||
| `<C-a>s` / `<C-a>S` | Document / workspace symbols (`<C-t>` in the picker sends results to Trouble) |
|
|
||||||
| `<C-a>d` | Diagnostics (`<C-t>` sends results to Trouble) |
|
|
||||||
| `<C-a><C-s>` | Toggle Supermaven |
|
|
||||||
| `<C-a><C-a>` | Toggle Oh My Pi terminal |
|
|
||||||
|
|
||||||
LSP binds are created only when the attached server supports the method (`plugins/astrolsp.lua`).
|
|
||||||
|
|
||||||
### Markdown (visual mode, markdown/MDX buffers)
|
|
||||||
|
|
||||||
`<Leader>ei` italic, `eb` bold, `ec` inline code, `ex` link, `el` / `eL` unordered / ordered list, `eC` code block.
|
|
||||||
|
|
||||||
### UI toggles
|
|
||||||
|
|
||||||
`<Leader>u` + `d` diagnostics, `l` diagnostic virtual lines, `f` format on save, `n` line numbers, `r` relative numbers, `w` wrap, `s` spell, `c` conceal, `i` inlay hints, `t` treesitter, `I` indent guides, `x` dim, `z` zen, `Z` zoom, `a` animations, `S` smooth scroll, `p` / `h` profiler.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Lua is formatted with StyLua (`.stylua.toml`) and linted with Selene (`selene.toml`, `neovim.yml`). Run `:Lazy update` to update plugins, then commit `lazy-lock.json`.
|
|
||||||
@@ -1,38 +1,54 @@
|
|||||||
-- This file simply bootstraps the installation of Lazy.nvim and then calls other files for execution
|
-- Bootstrap Lazy.nvim and load config safely across environments
|
||||||
-- This file doesn't necessarily need to be touched, BE CAUTIOUS editing this file and proceed at your own risk.
|
local uv = vim.uv or vim.loop
|
||||||
local lazypath = vim.env.LAZY or vim.fn.stdpath "data" .. "/lazy/lazy.nvim"
|
pcall(function()
|
||||||
|
if vim.loader then vim.loader.enable() end
|
||||||
|
end)
|
||||||
|
|
||||||
if not (vim.env.LAZY or (vim.uv or vim.loop).fs_stat(lazypath)) then
|
-- Set leaders early for consistency
|
||||||
-- stylua: ignore
|
vim.g.mapleader = " "
|
||||||
local result = vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath })
|
vim.g.maplocalleader = ","
|
||||||
if vim.v.shell_error ~= 0 then
|
|
||||||
-- stylua: ignore
|
-- Allow overriding lazy path via LAZY env
|
||||||
vim.api.nvim_echo({ { ("Error cloning lazy.nvim:\n%s\n"):format(result), "ErrorMsg" }, { "Press any key to exit...", "MoreMsg" } }, true, {})
|
local lazypath = vim.env.LAZY or (vim.fn.stdpath "data" .. "/lazy/lazy.nvim")
|
||||||
vim.fn.getchar()
|
|
||||||
vim.cmd.quit()
|
-- Bootstrap lazy.nvim only if not provided via env and not installed
|
||||||
|
if not vim.env.LAZY and not (uv and uv.fs_stat(lazypath)) then
|
||||||
|
if vim.fn.executable "git" == 1 then
|
||||||
|
local result = vim.fn.system {
|
||||||
|
"git",
|
||||||
|
"clone",
|
||||||
|
"--filter=blob:none",
|
||||||
|
"https://github.com/folke/lazy.nvim.git",
|
||||||
|
"--branch=stable",
|
||||||
|
lazypath,
|
||||||
|
}
|
||||||
|
if vim.v.shell_error ~= 0 then vim.notify(("Error cloning lazy.nvim:\n%s"):format(result), vim.log.levels.ERROR) end
|
||||||
|
else
|
||||||
|
vim.notify("git not found; skipping lazy.nvim bootstrap", vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.opt.rtp:prepend(lazypath)
|
-- Add to runtimepath if available, else exit quietly (headless-safe)
|
||||||
|
if uv and uv.fs_stat(lazypath) then
|
||||||
-- validate that lazy is available
|
vim.opt.rtp:prepend(lazypath)
|
||||||
if not pcall(require, "lazy") then
|
else
|
||||||
-- stylua: ignore
|
vim.notify(("lazy.nvim not available at %s"):format(lazypath), vim.log.levels.WARN)
|
||||||
vim.api.nvim_echo({ { ("Unable to load lazy from: %s\n"):format(lazypath), "ErrorMsg" }, { "Press any key to exit...", "MoreMsg" } }, true, {})
|
return
|
||||||
vim.fn.getchar()
|
|
||||||
vim.cmd.quit()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require "lazy_setup"
|
-- validate that lazy is available
|
||||||
require "polish"
|
local ok_lazy = pcall(require, "lazy")
|
||||||
|
if not ok_lazy then
|
||||||
|
vim.notify(("Unable to load lazy from: %s"):format(lazypath), vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- Enable terminal title setting in Nvim
|
-- Load specs and final polish with guards
|
||||||
vim.opt.title = true
|
local ok_setup = pcall(require, "lazy_setup")
|
||||||
-- Set the format of the title
|
if not ok_setup then
|
||||||
vim.opt.titlestring = "%t - Nvim"
|
vim.notify("Failed to load lazy_setup", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- Ensure Nvim doesn't slow down on exit by resetting shell
|
-- Polish is optional; don't fail startup if it's missing
|
||||||
vim.cmd('set shell=sh') -- Prevents fish-incompatible lag when exiting nvim in fish/tmux
|
pcall(require, "polish")
|
||||||
|
|
||||||
vim.opt.guicursor:append("v:hor20")
|
|
||||||
vim.opt.guicursor:append("a:blinkon0")
|
|
||||||
+64
-51
@@ -1,71 +1,84 @@
|
|||||||
{
|
{
|
||||||
"AstroNvim": { "branch": "main", "commit": "fc95d5d331b897b1c6b46cbfa4e0251d8d9be6b7" },
|
"AstroNvim": { "branch": "main", "commit": "c5e610f614e74c9dd9bf11760c4d0ad2c98c0abe" },
|
||||||
"LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
|
"Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" },
|
||||||
"aerial.nvim": { "branch": "master", "commit": "645d108a5242ec7b378cbe643eb6d04d4223f034" },
|
"LuaSnip": { "branch": "master", "commit": "5a1e39223db9a0498024a77b8441169d260c8c25" },
|
||||||
"astrocommunity": { "branch": "main", "commit": "d79a4b5d88bc38cf4f5926c0f32655c8e3d12b2b" },
|
"aerial.nvim": { "branch": "master", "commit": "5c4e2da4486da5f9b798ea9a0f1fc5c6bcd3d9cf" },
|
||||||
"astrocore": { "branch": "main", "commit": "f1ebf1d5b0bf4d92ab8c276a222fb39e02aad63e" },
|
"alpha-nvim": { "branch": "main", "commit": "de72250e054e5e691b9736ee30db72c65d560771" },
|
||||||
"astrolsp": { "branch": "main", "commit": "41810aa99789c5fcd2480a0e4ba27d3038afdd61" },
|
"astrocommunity": { "branch": "main", "commit": "94bc6b34df039e919b380c15ee6b5a3f79df8186" },
|
||||||
"astrotheme": { "branch": "main", "commit": "83c077c29195882f9583b45ea41154b2fb8935fb" },
|
"astrocore": { "branch": "main", "commit": "44a3dc0bf1591022b2a6bc89dccdfac1be17bec9" },
|
||||||
"astroui": { "branch": "main", "commit": "9a797715484a6e68fc32bdd4c81cb1246f72139f" },
|
"astrolsp": { "branch": "main", "commit": "909fbe64f3f87d089ff3777751261544557117cc" },
|
||||||
|
"astrotheme": { "branch": "main", "commit": "cf0e65a7b3ce2f830b052f4da937729eee1e7a7d" },
|
||||||
|
"astroui": { "branch": "main", "commit": "e923a84c488d879a260fc9cfb2dc27dd870fb6ac" },
|
||||||
|
"auto-save.nvim": { "branch": "main", "commit": "61892d67acd5b7c845f5da4d780b26fcd7f63e34" },
|
||||||
"better-escape.nvim": { "branch": "master", "commit": "199dcc2643dec5d8dbdab4ec672cf405224dcb3b" },
|
"better-escape.nvim": { "branch": "master", "commit": "199dcc2643dec5d8dbdab4ec672cf405224dcb3b" },
|
||||||
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
"blink.cmp": { "branch": "main", "commit": "451168851e8e2466bc97ee3e026c3dcb9141ce07" },
|
||||||
"blink.compat": { "branch": "main", "commit": "2ed6d9a28b07fa6f3bface818470605f8896408c" },
|
|
||||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||||
"cmp-dap": { "branch": "master", "commit": "ea92773e84c0ad3288c3bc5e452ac91559669087" },
|
"chezmoi.nvim": { "branch": "main", "commit": "ad26a6c4f5be0c348612518fcb1b7116649e026f" },
|
||||||
"codediff.nvim": { "branch": "main", "commit": "09d9ebef2cc5a5c04db7a349cd6c61bdf84ecc8e" },
|
"chezmoi.vim": { "branch": "main", "commit": "73b30df35c6b645ebd2e6a440eea8463ef3c3f47" },
|
||||||
"conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
|
"conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" },
|
||||||
"crates.nvim": { "branch": "main", "commit": "7039bc1d130e424232a7aa64ceddb170a154ff5b" },
|
"crates.nvim": { "branch": "main", "commit": "ac9fa498a9edb96dc3056724ff69d5f40b898453" },
|
||||||
"cyberdream.nvim": { "branch": "main", "commit": "8ed987565d081a8ebeda0bac939c22a0f451d074" },
|
"cyberdream.nvim": { "branch": "main", "commit": "a68ffa6af98bf1fc88f33a071c51a188ec85c725" },
|
||||||
"fff": { "branch": "main", "commit": "d1c7dd47130ca670ab71e7466a1a3b4a57d5348d" },
|
"dressing.nvim": { "branch": "master", "commit": "3a45525bb182730fe462325c99395529308f431e" },
|
||||||
"flash.nvim": { "branch": "main", "commit": "5f0f270fdc7c5b0c21d903ee85b9cb06f2ac636a" },
|
"editorconfig-vim": { "branch": "master", "commit": "13b86c5c691785ffbf2d6508c621dabb08e93df0" },
|
||||||
"friendly-snippets": { "branch": "main", "commit": "b4d01b0fdf3c9a549961c2f9ffe8dc09be166219" },
|
"friendly-snippets": { "branch": "main", "commit": "efff286dd74c22f731cdec26a70b46e5b203c619" },
|
||||||
"gitsigns.nvim": { "branch": "main", "commit": "a462f416e2ce4744531c6256252dee99a7d34a83" },
|
"gitsigns.nvim": { "branch": "main", "commit": "7010000889bfb6c26065e0b0f7f1e6aa9163edd9" },
|
||||||
"gopher.nvim": { "branch": "main", "commit": "f397bed1c35f29f4f85d60a210650e677a8c5cb4" },
|
"gopher.nvim": { "branch": "main", "commit": "27ba078f14dd39fcd5e8a57d7009cdd77450ebbb" },
|
||||||
"grug-far.nvim": { "branch": "main", "commit": "91b7e58b6ffc6ddad5acb7a7c1df21257724449e" },
|
"guess-indent.nvim": { "branch": "main", "commit": "6cd61f7a600bb756e558627cd2e740302c58e32d" },
|
||||||
"guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" },
|
|
||||||
"heirline.nvim": { "branch": "master", "commit": "fae936abb5e0345b85c3a03ecf38525b0828b992" },
|
"heirline.nvim": { "branch": "master", "commit": "fae936abb5e0345b85c3a03ecf38525b0828b992" },
|
||||||
|
"inc-rename.nvim": { "branch": "main", "commit": "0074b551a17338ccdcd299bd86687cc651bcb33d" },
|
||||||
|
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
|
||||||
|
"kitty-navigator.nvim": { "branch": "main", "commit": "abaaa37fe14cf762a8957a64ff50d2233aff7fe6" },
|
||||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||||
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
"lazydev.nvim": { "branch": "main", "commit": "01bc2aacd51cf9021eb19d048e70ce3dd09f7f93" },
|
||||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "00b5473cca4db5b410ff4bae724e3b6e21f2687f" },
|
"lsp_lines.nvim": { "branch": "main", "commit": "a92c755f182b89ea91bd8a6a2227208026f27b4d" },
|
||||||
|
"lspkind.nvim": { "branch": "master", "commit": "d79a1c3299ad0ef94e255d045bed9fa26025dab6" },
|
||||||
|
"mason-lspconfig.nvim": { "branch": "main", "commit": "1a31f824b9cd5bc6f342fc29e9a53b60d74af245" },
|
||||||
"mason-null-ls.nvim": { "branch": "main", "commit": "2b8433f76598397fcc97318d410e0c4f7a4bea6a" },
|
"mason-null-ls.nvim": { "branch": "main", "commit": "2b8433f76598397fcc97318d410e0c4f7a4bea6a" },
|
||||||
"mason-nvim-dap.nvim": { "branch": "main", "commit": "e51f9b259f066c4347f9a79ffde54c29a0619384" },
|
"mason-nvim-dap.nvim": { "branch": "main", "commit": "e51f9b259f066c4347f9a79ffde54c29a0619384" },
|
||||||
"mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" },
|
"mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" },
|
||||||
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
"mason.nvim": { "branch": "main", "commit": "fc98833b6da5de5a9c5b1446ac541577059555be" },
|
||||||
"mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" },
|
"mini.bufremove": { "branch": "main", "commit": "285bdac9596ee7375db50c0f76ed04336dcd2685" },
|
||||||
"neo-tree.nvim": { "branch": "main", "commit": "f3f3bf73414e400cf9fc13fda50f00404a8f8ab1" },
|
"mini.icons": { "branch": "main", "commit": "5b9076dae1bfbe47ba4a14bc8b967cde0ab5d77e" },
|
||||||
"none-ls.nvim": { "branch": "main", "commit": "01f8e62ea11603e59ad9ff7afcfa94fd183f76d6" },
|
"neo-tree.nvim": { "branch": "main", "commit": "ba6871d15528e36657d19a3112cc1697c59da19a" },
|
||||||
"nui.nvim": { "branch": "main", "commit": "f535005e6ad1016383f24e39559833759453564e" },
|
"neoconf.nvim": { "branch": "main", "commit": "f630568a4d04154803886f21ca60923f12709f0f" },
|
||||||
"nvim-autopairs": { "branch": "master", "commit": "3172f40c16563db871a58f20d205c710ad782311" },
|
"none-ls.nvim": { "branch": "main", "commit": "a117163db44c256d53c3be8717f3e1a2a28e6299" },
|
||||||
"nvim-dap": { "branch": "master", "commit": "cfa2d58f4537aca6ca83e2de1a0d9f1491121264" },
|
"nui.nvim": { "branch": "main", "commit": "a0fd35fcbb4cb479366f1dc5f20145fd718a3733" },
|
||||||
|
"nvim-autopairs": { "branch": "master", "commit": "68f0e5c3dab23261a945272032ee6700af86227a" },
|
||||||
|
"nvim-bqf": { "branch": "main", "commit": "f65fba733268ffcf9c5b8ac381287eca7c223422" },
|
||||||
|
"nvim-colorizer.lua": { "branch": "master", "commit": "517df88cf2afb36652830df2c655df2da416a0ae" },
|
||||||
|
"nvim-dap": { "branch": "master", "commit": "6a5bba0ddea5d419a783e170c20988046376090d" },
|
||||||
"nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
|
"nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
|
||||||
"nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" },
|
"nvim-dap-ui": { "branch": "master", "commit": "bc81f8d3440aede116f821114547a476b082b319" },
|
||||||
"nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" },
|
"nvim-dev-container": { "branch": "main", "commit": "87ea57f420b3460d0c45e239057ffc38fa32f886" },
|
||||||
"nvim-highlight-colors": { "branch": "main", "commit": "e4c7af0211866162d999ce0bdd6a029302e19139" },
|
"nvim-lint": { "branch": "master", "commit": "606b823a57b027502a9ae00978ebf4f5d5158098" },
|
||||||
"nvim-lsp-file-operations": { "branch": "master", "commit": "a76cdd2c4f0ad3ce6219506aad1208c81d600e20" },
|
"nvim-lsp-file-operations": { "branch": "master", "commit": "b9c795d3973e8eec22706af14959bc60c579e771" },
|
||||||
"nvim-lspconfig": { "branch": "master", "commit": "4d363f93c3581b9212a24f7a830d7590b3f050af" },
|
"nvim-lspconfig": { "branch": "master", "commit": "185b2af444b27d6541c02d662b5b68190e5cf0c4" },
|
||||||
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
|
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
|
||||||
"nvim-treesitter": { "branch": "main", "commit": "ca581c3436fc2b236847f5b739ebc263739e5ca6" },
|
"nvim-notify": { "branch": "master", "commit": "a3020c2cf4dfc4c4f390c4a21e84e35e46cf5d17" },
|
||||||
"nvim-treesitter-textobjects": { "branch": "main", "commit": "5c7b0263797dfd1bd6202f2b219f3b53a80b2187" },
|
"nvim-treesitter": { "branch": "main", "commit": "f8aaf5ce4e27cd20de917946b2ae5c968a2c2858" },
|
||||||
"nvim-ts-autotag": { "branch": "main", "commit": "d7220b55e16ed3c04e88086256def5d282429493" },
|
"nvim-treesitter-context": { "branch": "master", "commit": "9a8e39993e3b895601bf8227124a48ea8268149e" },
|
||||||
|
"nvim-treesitter-textobjects": { "branch": "main", "commit": "9937e5e356e5b227ec56d83d0a9d0a0f6bc9cad4" },
|
||||||
|
"nvim-ts-autotag": { "branch": "main", "commit": "a1d526af391f6aebb25a8795cbc05351ed3620b5" },
|
||||||
|
"nvim-ts-context-commentstring": { "branch": "main", "commit": "1b212c2eee76d787bbea6aa5e92a2b534e7b4f8f" },
|
||||||
|
"nvim-ufo": { "branch": "main", "commit": "61463090a4f55f5d080236ea62f09d1cd8976ff3" },
|
||||||
"nvim-vtsls": { "branch": "main", "commit": "0b5f73c9e50ce95842ea07bb3f05c7d66d87d14a" },
|
"nvim-vtsls": { "branch": "main", "commit": "0b5f73c9e50ce95842ea07bb3f05c7d66d87d14a" },
|
||||||
"nvim-window-picker": { "branch": "main", "commit": "6382540b2ae5de6c793d4aa2e3fe6dbb518505ec" },
|
"nvim-window-picker": { "branch": "main", "commit": "6382540b2ae5de6c793d4aa2e3fe6dbb518505ec" },
|
||||||
"omp.nvim": { "branch": "main", "commit": "a538777ecdd76f9632550672f998706221459adf" },
|
"overseer.nvim": { "branch": "master", "commit": "a2194447f4c5a1baf95139c5c7b539fa7b0d012f" },
|
||||||
"package-info.nvim": { "branch": "master", "commit": "b86cf49efc3855dd94afb33304b278f6f122bf1a" },
|
"package-info.nvim": { "branch": "master", "commit": "52e407af634cd5d3add0dc916c517865850113a4" },
|
||||||
"pkl-neovim": { "branch": "main", "commit": "20bfa7232262f36da45470170e7a954e487f4ac6" },
|
"plenary.nvim": { "branch": "master", "commit": "857c5ac632080dba10aae49dba902ce3abf91b35" },
|
||||||
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
"promise-async": { "branch": "main", "commit": "38a4575da9497326badd3995e768b4ccf0bb153e" },
|
||||||
"resession.nvim": { "branch": "master", "commit": "cc819b0489938d03e4f3532a583354f0287c015b" },
|
"resession.nvim": { "branch": "master", "commit": "cc819b0489938d03e4f3532a583354f0287c015b" },
|
||||||
"rustaceanvim": { "branch": "main", "commit": "b7ca983c6ec88c94cf25e5deaad96df9a68b9a98" },
|
"schemastore.nvim": { "branch": "main", "commit": "157fa3f37a3be55ceb375274e2f3ea2d7d2f9ef6" },
|
||||||
"schemastore.nvim": { "branch": "main", "commit": "cc6302a0957806dfc0f2687f1f35ee9652686101" },
|
"smart-splits.nvim": { "branch": "master", "commit": "ddb23c1a1cf1507bda487cda7f6e4690965ef9f5" },
|
||||||
"smart-splits.nvim": { "branch": "master", "commit": "32f67888ac71942442fdab3819864a10449dfc41" },
|
"snacks.nvim": { "branch": "main", "commit": "a049339328e2599ad6e85a69fa034ac501e921b2" },
|
||||||
"smear-cursor.nvim": { "branch": "main", "commit": "f6cb57c342268177f719acc3968b9d8de3ecc969" },
|
|
||||||
"snacks.nvim": { "branch": "main", "commit": "e6fd58c82f2f3fcddd3fe81703d47d6d48fc7b9f" },
|
|
||||||
"supermaven-nvim": { "branch": "main", "commit": "07d20fce48a5629686aefb0a7cd4b25e33947d50" },
|
"supermaven-nvim": { "branch": "main", "commit": "07d20fce48a5629686aefb0a7cd4b25e33947d50" },
|
||||||
"template.nvim": { "branch": "main", "commit": "308f6f8f0bf98cb7c71855ffa8a3019a5642d1cd" },
|
"telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" },
|
||||||
|
"telescope.nvim": { "branch": "master", "commit": "a17d611a0e111836a1db5295f04945df407c5135" },
|
||||||
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||||
"toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" },
|
"toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" },
|
||||||
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||||
"tsc.nvim": { "branch": "main", "commit": "e083bcf1e54bc3af7df92b33235efb334e8c782c" },
|
"tsc.nvim": { "branch": "main", "commit": "e083bcf1e54bc3af7df92b33235efb334e8c782c" },
|
||||||
"vellum.nvim": { "branch": "mdx", "commit": "c9e79e6c8f12047562760df7faa2d6762251fad7" },
|
"typescript-tools.nvim": { "branch": "master", "commit": "c2f5910074103705661e9651aa841e0d7eea9932" },
|
||||||
"venv-selector.nvim": { "branch": "main", "commit": "cc4bb3975de8835291f9bb45889e96c6b2795fc4" },
|
"vim-illuminate": { "branch": "master", "commit": "19cb21f513fc2b02f0c66be70107741e837516a1" },
|
||||||
"which-key.nvim": { "branch": "main", "commit": "fcbf4eea17cb299c02557d576f0d568878e354a4" }
|
"which-key.nvim": { "branch": "main", "commit": "fcbf4eea17cb299c02557d576f0d568878e354a4" }
|
||||||
}
|
}
|
||||||
+14
-24
@@ -6,34 +6,24 @@
|
|||||||
return {
|
return {
|
||||||
"AstroNvim/astrocommunity",
|
"AstroNvim/astrocommunity",
|
||||||
version = false, -- Use latest astrocommunity
|
version = false, -- Use latest astrocommunity
|
||||||
{ import = "astrocommunity.bars-and-lines.bufferline-nvim" },
|
{ import = "astrocommunity.pack.bash" },
|
||||||
{ import = "astrocommunity.editing-support.conform-nvim" },
|
|
||||||
{ import = "astrocommunity.diagnostics.trouble-nvim" },
|
|
||||||
{ import = "astrocommunity.pack.mdx" },
|
|
||||||
{ import = "astrocommunity.pack.biome" },
|
|
||||||
{ import = "astrocommunity.pack.go" },
|
|
||||||
{ import = "astrocommunity.pack.lua" },
|
{ import = "astrocommunity.pack.lua" },
|
||||||
|
{ import = "astrocommunity.pack.markdown" },
|
||||||
|
{ import = "astrocommunity.pack.mdx" },
|
||||||
|
{ import = "astrocommunity.pack.json" },
|
||||||
|
{ import = "astrocommunity.pack.chezmoi" },
|
||||||
|
{ import = "astrocommunity.pack.go" },
|
||||||
{ import = "astrocommunity.pack.golangci-lint" },
|
{ import = "astrocommunity.pack.golangci-lint" },
|
||||||
{ import = "astrocommunity.pack.html-css" },
|
{ import = "astrocommunity.pack.html-css" },
|
||||||
{ import = "astrocommunity.pack.json" },
|
|
||||||
{ import = "astrocommunity.pack.nix" },
|
|
||||||
{ import = "astrocommunity.pack.proto" },
|
|
||||||
{ import = "astrocommunity.pack.pkl" },
|
|
||||||
{ import = "astrocommunity.pack.rust" },
|
|
||||||
{ import = "astrocommunity.pack.yaml" },
|
|
||||||
{ import = "astrocommunity.pack.toml" },
|
{ import = "astrocommunity.pack.toml" },
|
||||||
{ import = "astrocommunity.pack.typescript" },
|
{ import = "astrocommunity.pack.typescript" },
|
||||||
{ import = "astrocommunity.pack.tailwindcss" },
|
{ import = "astrocommunity.pack.yaml" },
|
||||||
-- python: treesitter/debugpy/venv-selector + basedpyright language server.
|
{ import = "astrocommunity.icon.mini-icons" },
|
||||||
-- The full `pack.python` also pulls black + isort; formatting stays on ruff
|
{ import = "astrocommunity.bars-and-lines.bufferline-nvim" },
|
||||||
-- (see plugins/conform.lua), so only the base and basedpyright subpacks are
|
{ import = "astrocommunity.completion.blink-cmp" },
|
||||||
-- imported. Interpreter resolution is overridden in plugins/python.lua.
|
|
||||||
|
|
||||||
{ import = "astrocommunity.pack.python.base" },
|
|
||||||
{ import = "astrocommunity.pack.python.basedpyright" },
|
|
||||||
{ import = "astrocommunity.pack.python.black" },
|
|
||||||
{ import = "astrocommunity.pack.python.isort" },
|
|
||||||
{ import = "astrocommunity.completion.supermaven-nvim" },
|
|
||||||
{ import = "astrocommunity.lsp.nvim-lsp-file-operations" },
|
|
||||||
{ import = "astrocommunity.colorscheme.cyberdream-nvim" },
|
{ import = "astrocommunity.colorscheme.cyberdream-nvim" },
|
||||||
|
{ import = "astrocommunity.editing-support.auto-save-nvim" },
|
||||||
|
{ import = "astrocommunity.editing-support.conform-nvim" },
|
||||||
|
{ import = "astrocommunity.lsp.inc-rename-nvim" },
|
||||||
|
{ import = "astrocommunity.lsp.nvim-lsp-file-operations" },
|
||||||
}
|
}
|
||||||
+52
-111
@@ -1,31 +1,15 @@
|
|||||||
--- Shared ignore patterns for file explorers and pickers
|
--- Shared ignore patterns for file explorers and pickers
|
||||||
--- This module provides a centralized list of patterns to ignore across
|
--- This module provides a centralized list of patterns to ignore across
|
||||||
--- snacks.picker and other file browsing tools. (`patterns` and the
|
--- neotree, telescope, snacks.picker, and other file browsing tools.
|
||||||
--- `never_show` lists are also handed to neo-tree's `filtered_items`, see
|
|
||||||
--- plugins/neotree.lua.)
|
|
||||||
---
|
|
||||||
--- Everything here is fast-event-context safe (pure Lua + libuv only):
|
|
||||||
--- snacks picker transforms run inside async/uv callbacks where vimL
|
|
||||||
--- functions (vim.fn.*) raise E5560.
|
|
||||||
--- @module config.ignore_patterns
|
--- @module config.ignore_patterns
|
||||||
|
|
||||||
local uv = vim.uv or vim.loop
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- Core ignore patterns used across the configuration
|
--- Core ignore patterns used across the configuration
|
||||||
--- These patterns use glob syntax (* and ? wildcards), matched against
|
--- These patterns use glob syntax (* for wildcards)
|
||||||
--- file basenames and individual path segments.
|
|
||||||
M.patterns = {
|
M.patterns = {
|
||||||
-- Build artifacts and generated files
|
-- Build artifacts and generated files
|
||||||
".beads",
|
|
||||||
"chain_*.json",
|
"chain_*.json",
|
||||||
".npmrc",
|
|
||||||
".epiq",
|
|
||||||
".omc",
|
|
||||||
".claude",
|
|
||||||
".changeset",
|
|
||||||
".codegraph",
|
|
||||||
".gitignore",
|
".gitignore",
|
||||||
"turbo.json",
|
"turbo.json",
|
||||||
"vite.config.ts",
|
"vite.config.ts",
|
||||||
@@ -39,14 +23,13 @@ M.patterns = {
|
|||||||
"*_mock.go",
|
"*_mock.go",
|
||||||
"*_integration.go",
|
"*_integration.go",
|
||||||
"*_test.go",
|
"*_test.go",
|
||||||
"AGENTS.md",
|
|
||||||
"test",
|
|
||||||
"tests",
|
|
||||||
|
|
||||||
-- Configuration files
|
-- Configuration files
|
||||||
".editorconfig",
|
".editorconfig",
|
||||||
".gitpod.*",
|
".gitpod.*",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Lock files and dependencies
|
-- Lock files and dependencies
|
||||||
"*.lock",
|
"*.lock",
|
||||||
"*.lockb",
|
"*.lockb",
|
||||||
@@ -86,7 +69,6 @@ M.patterns = {
|
|||||||
"Icon?",
|
"Icon?",
|
||||||
"iCloud~",
|
"iCloud~",
|
||||||
"com~",
|
"com~",
|
||||||
".githooks",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Patterns that should never be shown in any file browser
|
--- Patterns that should never be shown in any file browser
|
||||||
@@ -94,7 +76,6 @@ M.never_show = {
|
|||||||
-- Version control
|
-- Version control
|
||||||
".git",
|
".git",
|
||||||
".ai",
|
".ai",
|
||||||
".omc",
|
|
||||||
|
|
||||||
-- Dependencies
|
-- Dependencies
|
||||||
"node_modules",
|
"node_modules",
|
||||||
@@ -152,7 +133,6 @@ M.never_show = {
|
|||||||
|
|
||||||
--- Patterns that should never be shown (pattern matching)
|
--- Patterns that should never be shown (pattern matching)
|
||||||
M.never_show_patterns = {
|
M.never_show_patterns = {
|
||||||
".crush",
|
|
||||||
"*DS_Store",
|
"*DS_Store",
|
||||||
".obsidian",
|
".obsidian",
|
||||||
".pkl-lsp",
|
".pkl-lsp",
|
||||||
@@ -184,6 +164,7 @@ M.never_show_patterns = {
|
|||||||
"Icon?",
|
"Icon?",
|
||||||
"iCloud~",
|
"iCloud~",
|
||||||
"com~",
|
"com~",
|
||||||
|
"*.icns",
|
||||||
".conform*",
|
".conform*",
|
||||||
".null-ls_*",
|
".null-ls_*",
|
||||||
}
|
}
|
||||||
@@ -202,108 +183,68 @@ M.ignored_directories = {
|
|||||||
"translations",
|
"translations",
|
||||||
"env",
|
"env",
|
||||||
".husky",
|
".husky",
|
||||||
".worktrees",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local EMPTY = {}
|
--- Read additional ignore patterns from .rgignore file in git root
|
||||||
|
--- @return table patterns List of patterns from .rgignore
|
||||||
--- Convert a glob (* and ? wildcards) to an anchored Lua pattern
|
function M.read_rgignore()
|
||||||
--- @param glob string
|
|
||||||
--- @return string
|
|
||||||
local function glob_to_lua(glob)
|
|
||||||
return "^" .. glob:gsub("[%%%.%(%)%+%-%^%$%[%]]", "%%%0"):gsub("%*", ".*"):gsub("%?", ".") .. "$"
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param globs string[]
|
|
||||||
--- @return string[]
|
|
||||||
local function compile_globs(globs)
|
|
||||||
local out = {}
|
|
||||||
for _, g in ipairs(globs) do
|
|
||||||
out[#out + 1] = glob_to_lua(g)
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Find the git root for a directory by walking up (pure libuv, cached)
|
|
||||||
local root_cache = {} ---@type table<string, string|false>
|
|
||||||
--- @param dir string
|
|
||||||
--- @return string?
|
|
||||||
local function git_root(dir)
|
|
||||||
local cached = root_cache[dir]
|
|
||||||
if cached ~= nil then return cached or nil end
|
|
||||||
local cur = dir
|
|
||||||
while cur and cur ~= "" do
|
|
||||||
if uv.fs_stat(cur .. "/.git") then
|
|
||||||
root_cache[dir] = cur
|
|
||||||
return cur
|
|
||||||
end
|
|
||||||
local parent = cur:match "(.+)/[^/]+$"
|
|
||||||
if not parent or parent == cur then break end
|
|
||||||
cur = parent
|
|
||||||
end
|
|
||||||
root_cache[dir] = false
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Read additional ignore patterns from the .rgignore file in the git root.
|
|
||||||
--- Cached per file and invalidated on mtime change. Safe in fast event contexts.
|
|
||||||
--- @param dir? string directory to resolve the git root from (defaults to cwd)
|
|
||||||
--- @return string[] patterns List of glob patterns from .rgignore
|
|
||||||
local rgignore_cache = {} ---@type table<string, {mtime: number, patterns: string[]}>
|
|
||||||
function M.read_rgignore(dir)
|
|
||||||
local root = git_root(dir or uv.cwd())
|
|
||||||
if not root then return EMPTY end
|
|
||||||
local path = root .. "/.rgignore"
|
|
||||||
local stat = uv.fs_stat(path)
|
|
||||||
if not stat then return EMPTY end
|
|
||||||
local cached = rgignore_cache[path]
|
|
||||||
if cached and cached.mtime == stat.mtime.sec then return cached.patterns end
|
|
||||||
local patterns = {}
|
local patterns = {}
|
||||||
local file = io.open(path, "r")
|
local current_file = vim.fn.expand "%:p"
|
||||||
if file then
|
local current_dir = vim.fn.fnamemodify(current_file, ":h")
|
||||||
for line in file:lines() do
|
|
||||||
if line ~= "" and not line:match "^#" then
|
if current_dir == "" then current_dir = vim.fn.getcwd() end
|
||||||
patterns[#patterns + 1] = (line:gsub("^%./", ""))
|
|
||||||
|
local git_root =
|
||||||
|
vim.fn.systemlist("cd " .. vim.fn.shellescape(current_dir) .. " && git rev-parse --show-toplevel 2>/dev/null")[1]
|
||||||
|
|
||||||
|
if git_root and git_root ~= "" and not git_root:match "^fatal:" then
|
||||||
|
local rgignore_path = git_root .. "/.rgignore"
|
||||||
|
local file = io.open(rgignore_path, "r")
|
||||||
|
|
||||||
|
if file then
|
||||||
|
for line in file:lines() do
|
||||||
|
if line ~= "" and not line:match "^#" then
|
||||||
|
line = line:gsub("^%./", "")
|
||||||
|
table.insert(patterns, line)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
file:close()
|
||||||
end
|
end
|
||||||
file:close()
|
|
||||||
end
|
end
|
||||||
rgignore_cache[path] = { mtime = stat.mtime.sec, patterns = patterns }
|
|
||||||
return patterns
|
return patterns
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Create a filter for snacks picker `transform` options.
|
--- Get all hide patterns including rgignore
|
||||||
--- Snacks contract: return `false` to DROP an item, `true`/`nil` to keep it
|
--- @return table patterns Combined list of all patterns to hide
|
||||||
--- (returning nil keeps the item!). Runs per item in a fast event context,
|
function M.get_all_patterns()
|
||||||
--- so only pure Lua and libuv are used here.
|
local rgignore_patterns = M.read_rgignore()
|
||||||
--- @return fun(item: snacks.picker.finder.Item): boolean
|
return vim.list_extend(vim.list_extend({}, M.patterns), rgignore_patterns)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a file filter function for pickers
|
||||||
|
--- @return function filter Function that filters files based on ignore patterns
|
||||||
function M.create_picker_filter()
|
function M.create_picker_filter()
|
||||||
local static ---@type string[]? compiled static globs (lazy)
|
|
||||||
local last_raw, last_rg = nil, EMPTY ---@type string[]?, string[]
|
|
||||||
return function(item)
|
return function(item)
|
||||||
local file = item and item.file
|
if not item or not item.file then return item end
|
||||||
if not file then return true end
|
|
||||||
|
|
||||||
static = static or compile_globs(M.patterns)
|
local file = item.file
|
||||||
local raw = M.read_rgignore(item.cwd)
|
local basename = vim.fn.fnamemodify(file, ":t")
|
||||||
if raw ~= last_raw then
|
local dir = vim.fn.fnamemodify(file, ":h:t")
|
||||||
last_raw, last_rg = raw, compile_globs(raw)
|
|
||||||
|
-- Check against all patterns
|
||||||
|
local all_patterns = M.get_all_patterns()
|
||||||
|
for _, pattern in ipairs(all_patterns) do
|
||||||
|
local lua_pattern = pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("%?", ".")
|
||||||
|
if basename:match("^" .. lua_pattern .. "$") or file:match(lua_pattern) then return nil end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Match every path segment (directories and basename) anchored,
|
-- Check ignored directories
|
||||||
-- so `test` drops `test/foo.lua` but never `latest.md`.
|
for _, ignored in ipairs(M.ignored_directories) do
|
||||||
for seg in file:gmatch "[^/\\]+" do
|
if dir == ignored or file:match("/" .. ignored .. "/") then return nil end
|
||||||
for _, pat in ipairs(static) do
|
|
||||||
if seg:match(pat) then return false end
|
|
||||||
end
|
|
||||||
for _, pat in ipairs(last_rg) do
|
|
||||||
if seg:match(pat) then return false end
|
|
||||||
end
|
|
||||||
for _, dirname in ipairs(M.ignored_directories) do
|
|
||||||
if seg == dirname then return false end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
|
return item
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
--- Pre-save LSP code action pass: apply every action an attached server
|
|
||||||
--- advertises that is safe to run unattended (import organizing, fix-alls,
|
|
||||||
--- quickfixes), before conform formats the buffer.
|
|
||||||
---
|
|
||||||
--- Driven from conform's `format_on_save` hook (plugins/conform.lua) rather
|
|
||||||
--- than its own BufWritePre autocmd: that hook is the one place where the order
|
|
||||||
--- relative to formatting is guaranteed, and it is already gated by the
|
|
||||||
--- `<Leader>uf` / `<Leader>uF` autoformat toggles.
|
|
||||||
--- @module config.lsp_code_actions
|
|
||||||
|
|
||||||
-- Action kinds we apply unattended. Matches exact kinds and any subkinds via
|
|
||||||
-- "<kind>." prefix (LSP CodeActionKind is hierarchical).
|
|
||||||
--
|
|
||||||
-- Deliberately NOT the bare "source" umbrella: it covers non-idempotent
|
|
||||||
-- document rewrites (e.g. marksman's "Create Table of Contents").
|
|
||||||
-- Deliberately NOT "quickfix" either: those are diagnostic-scoped choices that
|
|
||||||
-- need a human. ruff, for one, offers "Disable rule for this line" as a
|
|
||||||
-- quickfix, so a save would silently paste `# noqa: I001` into the source.
|
|
||||||
local SAFE_KINDS = {
|
|
||||||
"source.organizeImports",
|
|
||||||
"source.fixAll",
|
|
||||||
}
|
|
||||||
|
|
||||||
local function is_safe_kind(kind)
|
|
||||||
if not kind or kind == "" then return false end
|
|
||||||
for _, safe in ipairs(SAFE_KINDS) do
|
|
||||||
if kind == safe or vim.startswith(kind, safe .. ".") then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Pull the kinds a given client advertises support for, filtered to safe ones.
|
|
||||||
-- If the server returns no kinds list, fall back to the top-level SAFE_KINDS so
|
|
||||||
-- it gets the chance to satisfy a broad "only" filter.
|
|
||||||
local function safe_kinds_for(client)
|
|
||||||
local provider = (client.server_capabilities or {}).codeActionProvider
|
|
||||||
if not provider then return {} end
|
|
||||||
local advertised = type(provider) == "table" and provider.codeActionKinds or nil
|
|
||||||
if not advertised or #advertised == 0 then return vim.deepcopy(SAFE_KINDS) end
|
|
||||||
local kinds = {}
|
|
||||||
for _, k in ipairs(advertised) do
|
|
||||||
if is_safe_kind(k) then table.insert(kinds, k) end
|
|
||||||
end
|
|
||||||
return kinds
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Collect LSP-shaped diagnostics so quickfix actions have something to bind to.
|
|
||||||
local function lsp_diagnostics(bufnr)
|
|
||||||
local out = {}
|
|
||||||
for _, d in ipairs(vim.diagnostic.get(bufnr)) do
|
|
||||||
if d.user_data and d.user_data.lsp then table.insert(out, d.user_data.lsp) end
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Servers are allowed to return an action with neither `edit` nor `command`,
|
|
||||||
-- carrying only `data` to be exchanged for the real edit via codeAction/resolve
|
|
||||||
-- (vtsls does exactly this for source.organizeImports). Without this step the
|
|
||||||
-- whole pass silently does nothing on those servers.
|
|
||||||
local function resolve(client, bufnr, action)
|
|
||||||
if action.edit or type(action.command) == "table" then return action end
|
|
||||||
if not vim.tbl_get(client.server_capabilities or {}, "codeActionProvider", "resolveProvider") then return action end
|
|
||||||
local resp = client:request_sync("codeAction/resolve", action, 1000, bufnr)
|
|
||||||
if resp and not resp.err and resp.result then return resp.result end
|
|
||||||
return action
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Servers may ignore context.only (LSP allows it), so re-filter the returned
|
|
||||||
-- actions by kind before applying. Kind-less actions are skipped: we asked for
|
|
||||||
-- specific kinds, so anything untagged is not provably safe.
|
|
||||||
local function apply_result(client, bufnr, result)
|
|
||||||
if not result then return end
|
|
||||||
for _, action in ipairs(result) do
|
|
||||||
if is_safe_kind(action.kind) then
|
|
||||||
action = resolve(client, bufnr, action)
|
|
||||||
if action.edit then vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding or "utf-8") end
|
|
||||||
if type(action.command) == "table" then client:exec_cmd(action.command, { bufnr = bufnr }) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
--- Apply every safe code action advertised for `bufnr`, synchronously.
|
|
||||||
--- No-op for scratch/special buffers and for buffers no server serves.
|
|
||||||
--- @param bufnr integer
|
|
||||||
function M.apply(bufnr)
|
|
||||||
if vim.bo[bufnr].buftype ~= "" then return end
|
|
||||||
local clients = vim.lsp.get_clients { bufnr = bufnr, method = "textDocument/codeAction" }
|
|
||||||
if #clients == 0 then return end
|
|
||||||
local diagnostics = lsp_diagnostics(bufnr)
|
|
||||||
for _, client in ipairs(clients) do
|
|
||||||
local kinds = safe_kinds_for(client)
|
|
||||||
if #kinds > 0 then
|
|
||||||
local enc = client.offset_encoding or "utf-8"
|
|
||||||
local params = vim.lsp.util.make_range_params(0, enc)
|
|
||||||
params.context = { only = kinds, diagnostics = diagnostics, triggerKind = 2 }
|
|
||||||
local resp = client:request_sync("textDocument/codeAction", params, 1000, bufnr)
|
|
||||||
if resp and not resp.err then apply_result(client, bufnr, resp.result) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
--- Visual-mode markdown formatting for the `<Leader>e` group in markdown/mdx
|
|
||||||
--- buffers: wrap the selection in inline markers (italic, bold, code, link)
|
|
||||||
--- or rewrite its lines as a list / fenced code block.
|
|
||||||
---
|
|
||||||
--- Inline ops work on the exact charwise selection, or on the whole lines of
|
|
||||||
--- a linewise one. Line ops always act on whole lines, whichever way the
|
|
||||||
--- selection was made. Blockwise selections are refused.
|
|
||||||
---
|
|
||||||
--- Mapped buffer-locally from plugins/markdown-edit.lua.
|
|
||||||
--- @module config.markdown_edit
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local ESC = vim.keycode "<Esc>"
|
|
||||||
|
|
||||||
--- Leave visual mode and return the selection as a 0-based, end-exclusive
|
|
||||||
--- `nvim_buf_set_text` rectangle.
|
|
||||||
--- @return integer? srow, integer scol, integer erow, integer ecol
|
|
||||||
local function selection()
|
|
||||||
-- `'<`/`'>` are only set once visual mode ends. `:normal!` rather than
|
|
||||||
-- feedkeys(..., "x"): the latter also flushes any typed-ahead keys, which
|
|
||||||
-- would run before the insert mode some of the binds end in.
|
|
||||||
vim.cmd.normal { ESC, bang = true }
|
|
||||||
local mode = vim.fn.visualmode()
|
|
||||||
local s, e = vim.fn.getpos "'<", vim.fn.getpos "'>"
|
|
||||||
local srow, erow = s[2] - 1, e[2] - 1
|
|
||||||
if mode == "V" then
|
|
||||||
local last = vim.api.nvim_buf_get_lines(0, erow, erow + 1, true)[1]
|
|
||||||
return srow, 0, erow, #last
|
|
||||||
elseif mode == "v" then
|
|
||||||
-- getregionpos clamps a `$` end column (v:maxcol) to the line length and
|
|
||||||
-- moves the inclusive end onto the last byte of a multibyte character
|
|
||||||
local pos = vim.fn.getregionpos(s, e, { type = mode })
|
|
||||||
return srow, pos[1][1][3] - 1, erow, pos[#pos][2][3]
|
|
||||||
end
|
|
||||||
vim.notify("Blockwise selections are not supported", vim.log.levels.WARN)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Insert `before` at the start and `after` at the end of the selection.
|
|
||||||
--- @param before string
|
|
||||||
--- @param after string
|
|
||||||
--- @return integer? row, integer? col 0-based position of `after`
|
|
||||||
local function wrap(before, after)
|
|
||||||
local srow, scol, erow, ecol = selection()
|
|
||||||
if not srow then return end
|
|
||||||
-- suffix first so the start position stays valid
|
|
||||||
vim.api.nvim_buf_set_text(0, erow, ecol, erow, ecol, { after })
|
|
||||||
vim.api.nvim_buf_set_text(0, srow, scol, srow, scol, { before })
|
|
||||||
if srow == erow then ecol = ecol + #before end
|
|
||||||
return erow, ecol
|
|
||||||
end
|
|
||||||
|
|
||||||
--- `[text](|)` with insert mode started at the cursor
|
|
||||||
local function link()
|
|
||||||
local row, col = wrap("[", "]()")
|
|
||||||
if not row then return end
|
|
||||||
vim.api.nvim_win_set_cursor(0, { row + 1, col + 2 })
|
|
||||||
vim.cmd "startinsert"
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Prefix every non-blank selected line, after its indentation, with
|
|
||||||
--- `marker(n)` for the n-th such line. Blank lines separate items and are
|
|
||||||
--- left alone.
|
|
||||||
--- @param marker fun(n: integer): string
|
|
||||||
local function list(marker)
|
|
||||||
local srow, _, erow = selection()
|
|
||||||
if not srow then return end
|
|
||||||
local lines = vim.api.nvim_buf_get_lines(0, srow, erow + 1, true)
|
|
||||||
local n = 0
|
|
||||||
for i, line in ipairs(lines) do
|
|
||||||
if line:find "%S" then
|
|
||||||
n = n + 1
|
|
||||||
local indent = line:match "^%s*"
|
|
||||||
lines[i] = indent .. marker(n) .. line:sub(#indent + 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vim.api.nvim_buf_set_lines(0, srow, erow + 1, true, lines)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Fence the selected lines, indented like the first one, with insert mode
|
|
||||||
--- started after the opening ``` for the language
|
|
||||||
local function code_block()
|
|
||||||
local srow, _, erow = selection()
|
|
||||||
if not srow then return end
|
|
||||||
local fence = vim.api.nvim_buf_get_lines(0, srow, srow + 1, true)[1]:match "^%s*" .. "```"
|
|
||||||
vim.api.nvim_buf_set_lines(0, erow + 1, erow + 1, true, { fence })
|
|
||||||
vim.api.nvim_buf_set_lines(0, srow, srow, true, { fence })
|
|
||||||
vim.api.nvim_win_set_cursor(0, { srow + 1, 0 })
|
|
||||||
vim.cmd "startinsert!"
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @type table<string, table<string, AstroCoreMapping>>
|
|
||||||
M.mappings = {
|
|
||||||
x = {
|
|
||||||
["<Leader>e"] = { desc = "Markdown" },
|
|
||||||
["<Leader>ei"] = { function() wrap("*", "*") end, desc = "Italic" },
|
|
||||||
["<Leader>eb"] = { function() wrap("**", "**") end, desc = "Bold" },
|
|
||||||
["<Leader>ec"] = { function() wrap("`", "`") end, desc = "Inline code" },
|
|
||||||
["<Leader>ex"] = { link, desc = "Link" },
|
|
||||||
["<Leader>el"] = {
|
|
||||||
function()
|
|
||||||
list(function() return "- " end)
|
|
||||||
end,
|
|
||||||
desc = "Unordered list",
|
|
||||||
},
|
|
||||||
["<Leader>eL"] = {
|
|
||||||
function()
|
|
||||||
list(function(n) return n .. ". " end)
|
|
||||||
end,
|
|
||||||
desc = "Ordered list",
|
|
||||||
},
|
|
||||||
["<Leader>eC"] = { code_block, desc = "Code block" },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,420 +0,0 @@
|
|||||||
--- Mermaid syntax diagnostics for `mermaid` buffers and ```mermaid fences in
|
|
||||||
--- markdown/mdx.
|
|
||||||
---
|
|
||||||
--- Why not a language server: there is no packaged mermaid LSP (nothing on npm,
|
|
||||||
--- nothing in mason), and no client-side parser is trustworthy. The tree-sitter
|
|
||||||
--- mermaid grammar is stale enough to reject valid mermaid — `graph`, `style`,
|
|
||||||
--- `classDef`, `click`, `linkStyle`, `box`, `create`, `critical`, frontmatter
|
|
||||||
--- and `note for` all produce ERROR nodes (measured: 12 of 40 valid samples) —
|
|
||||||
--- so it is used for highlighting only, where a mis-parse costs colors instead
|
|
||||||
--- of a false error.
|
|
||||||
---
|
|
||||||
--- Instead mermaid's own parsers decide, hosted in a persistent node process
|
|
||||||
--- (`scripts/mermaid-validator.mjs`, protocol documented there). That process
|
|
||||||
--- needs `mermaid` + `jsdom` in a private workspace under `stdpath("data")`;
|
|
||||||
--- the first validation installs them with `npm` and reports progress. It exits
|
|
||||||
--- itself after five idle minutes so its ~300MB heap is not resident for the
|
|
||||||
--- whole session, and is respawned on demand.
|
|
||||||
--- @module config.mermaid
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local NS = vim.api.nvim_create_namespace "mermaid"
|
|
||||||
local ROOT = vim.fs.joinpath(vim.fn.stdpath "data", "mermaid-validator")
|
|
||||||
local SCRIPT = vim.fs.joinpath(ROOT, "validator.mjs")
|
|
||||||
local SOURCE = vim.fs.joinpath(vim.fn.stdpath "config", "scripts", "mermaid-validator.mjs")
|
|
||||||
local IDLE_MS = 300000
|
|
||||||
local DEBOUNCE_MS = 400
|
|
||||||
|
|
||||||
-- pinned so a mermaid major bump cannot silently change what counts as valid
|
|
||||||
local PACKAGE_JSON = table.concat({
|
|
||||||
"{",
|
|
||||||
' "name": "nvim-mermaid-validator",',
|
|
||||||
' "private": true,',
|
|
||||||
' "type": "module",',
|
|
||||||
' "dependencies": {',
|
|
||||||
' "jsdom": "^30.0.0",',
|
|
||||||
' "mermaid": "^11.12.0"',
|
|
||||||
" }",
|
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
}, "\n")
|
|
||||||
|
|
||||||
--- Fenced blocks whose info string names mermaid. `@_lang` is not consumed by
|
|
||||||
--- the match, it only feeds the predicate.
|
|
||||||
local FENCE_QUERY = [[
|
|
||||||
(fenced_code_block
|
|
||||||
(info_string (language) @_lang)
|
|
||||||
(code_fence_content) @content
|
|
||||||
(#any-of? @_lang "mermaid" "mmd"))
|
|
||||||
]]
|
|
||||||
|
|
||||||
--- @class MermaidBlock
|
|
||||||
--- @field row integer 0-based buffer row where the block's first line lives
|
|
||||||
--- @field text string block source, as sent to the validator
|
|
||||||
|
|
||||||
--- @class MermaidResult
|
|
||||||
--- @field ok boolean
|
|
||||||
--- @field line? integer 1-based line within the block
|
|
||||||
--- @field col? integer 0-based UTF-16 column
|
|
||||||
--- @field message? string
|
|
||||||
|
|
||||||
--- @class MermaidBufState
|
|
||||||
--- @field timer uv.uv_timer_t debounce timer
|
|
||||||
--- @field seq integer validation pass counter, guards out-of-order responses
|
|
||||||
--- @field results table<string, MermaidResult> validated blocks, keyed by text
|
|
||||||
|
|
||||||
--- @type table<integer, MermaidBufState>
|
|
||||||
local buffers = {}
|
|
||||||
|
|
||||||
--- @type table<integer, fun(result: MermaidResult)> in-flight requests by id
|
|
||||||
local pending = {}
|
|
||||||
local next_id = 0
|
|
||||||
|
|
||||||
local job --- @type vim.SystemObj?
|
|
||||||
local tail = "" --- @type string incomplete stdout line
|
|
||||||
local deps = "unknown" --- @type "unknown"|"installing"|"ready"|"failed"
|
|
||||||
local deps_waiters = {} --- @type fun(ok: boolean)[]
|
|
||||||
|
|
||||||
--- @param msg string
|
|
||||||
--- @param level integer
|
|
||||||
local function notify(msg, level) vim.notify(msg, level, { title = "mermaid" }) end
|
|
||||||
|
|
||||||
--- @param path string
|
|
||||||
--- @return string?
|
|
||||||
local function read_file(path)
|
|
||||||
local file = io.open(path, "r")
|
|
||||||
if not file then return nil end
|
|
||||||
local content = file:read "a"
|
|
||||||
file:close()
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Write the workspace files, skipping writes that would change nothing. The
|
|
||||||
--- validator is copied out of the config rather than run in place so node
|
|
||||||
--- resolves `mermaid`/`jsdom` from the workspace's own `node_modules`.
|
|
||||||
--- @return boolean ok
|
|
||||||
local function write_workspace()
|
|
||||||
local script = read_file(SOURCE)
|
|
||||||
if not script then
|
|
||||||
notify("validator script missing: " .. SOURCE, vim.log.levels.ERROR)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
vim.fn.mkdir(ROOT, "p")
|
|
||||||
for path, content in pairs { [SCRIPT] = script, [vim.fs.joinpath(ROOT, "package.json")] = PACKAGE_JSON } do
|
|
||||||
if read_file(path) ~= content then
|
|
||||||
local out = io.open(path, "w")
|
|
||||||
if not out then
|
|
||||||
notify("cannot write " .. path, vim.log.levels.ERROR)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
out:write(content)
|
|
||||||
out:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @return boolean
|
|
||||||
local function deps_installed()
|
|
||||||
for _, dep in ipairs { "mermaid", "jsdom" } do
|
|
||||||
if not vim.uv.fs_stat(vim.fs.joinpath(ROOT, "node_modules", dep, "package.json")) then return false end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param ok boolean
|
|
||||||
local function resolve_deps(ok)
|
|
||||||
deps = ok and "ready" or "failed"
|
|
||||||
local waiters = deps_waiters
|
|
||||||
deps_waiters = {}
|
|
||||||
for _, waiter in ipairs(waiters) do
|
|
||||||
waiter(ok)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Make sure the workspace exists with its dependencies installed, then call
|
|
||||||
--- `cb`. A failure is remembered: the install is not retried this session.
|
|
||||||
--- @param cb fun(ok: boolean)
|
|
||||||
local function ensure_deps(cb)
|
|
||||||
if deps == "ready" then return cb(true) end
|
|
||||||
if deps == "failed" then return cb(false) end
|
|
||||||
deps_waiters[#deps_waiters + 1] = cb
|
|
||||||
if deps == "installing" then return end
|
|
||||||
deps = "installing"
|
|
||||||
|
|
||||||
if vim.fn.executable "node" == 0 or vim.fn.executable "npm" == 0 then
|
|
||||||
notify("node and npm are required for mermaid diagnostics", vim.log.levels.WARN)
|
|
||||||
return resolve_deps(false)
|
|
||||||
end
|
|
||||||
if not write_workspace() then return resolve_deps(false) end
|
|
||||||
if deps_installed() then return resolve_deps(true) end
|
|
||||||
|
|
||||||
notify("installing mermaid validator dependencies...", vim.log.levels.INFO)
|
|
||||||
vim.system(
|
|
||||||
{ "npm", "install", "--omit=dev", "--no-audit", "--no-fund", "--loglevel=error" },
|
|
||||||
{ cwd = ROOT, text = true },
|
|
||||||
vim.schedule_wrap(function(result)
|
|
||||||
if result.code == 0 and deps_installed() then
|
|
||||||
notify("mermaid validator ready", vim.log.levels.INFO)
|
|
||||||
return resolve_deps(true)
|
|
||||||
end
|
|
||||||
notify("npm install failed: " .. vim.trim(result.stderr or ""), vim.log.levels.ERROR)
|
|
||||||
resolve_deps(false)
|
|
||||||
end)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param line string one JSON response from the validator
|
|
||||||
local function handle_response(line)
|
|
||||||
local ok, response = pcall(vim.json.decode, line)
|
|
||||||
if not ok or type(response) ~= "table" or not response.id then return end
|
|
||||||
local cb = pending[response.id]
|
|
||||||
pending[response.id] = nil
|
|
||||||
if cb then cb(response) end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param err string?
|
|
||||||
--- @param data string?
|
|
||||||
local function on_stdout(err, data)
|
|
||||||
if err or not data then return end
|
|
||||||
tail = tail .. data
|
|
||||||
while true do
|
|
||||||
local newline = tail:find "\n"
|
|
||||||
if not newline then break end
|
|
||||||
local line = tail:sub(1, newline - 1)
|
|
||||||
tail = tail:sub(newline + 1)
|
|
||||||
if line ~= "" then vim.schedule(function() handle_response(line) end) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- The validator writes to stderr only when it cannot run: a broken install, an
|
|
||||||
--- incompatible node, or a crash mid-session.
|
|
||||||
--- @param err string?
|
|
||||||
--- @param data string?
|
|
||||||
local function on_stderr(err, data)
|
|
||||||
if err or not data or vim.trim(data) == "" then return end
|
|
||||||
vim.schedule(function() notify(vim.trim(data), vim.log.levels.ERROR) end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Responses for in-flight requests will never arrive; the affected blocks are
|
|
||||||
--- absent from the result cache, so the next pass re-queues them.
|
|
||||||
local function on_exit()
|
|
||||||
job, tail, pending = nil, "", {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Start the validator unless it is already running (it exits itself on idle).
|
|
||||||
--- @param cb fun(ok: boolean)
|
|
||||||
local function ensure_job(cb)
|
|
||||||
if job then return cb(true) end
|
|
||||||
ensure_deps(function(ok)
|
|
||||||
if not ok then return cb(false) end
|
|
||||||
if job then return cb(true) end
|
|
||||||
local started, handle = pcall(
|
|
||||||
vim.system,
|
|
||||||
{ "node", SCRIPT, "--idle-ms", tostring(IDLE_MS) },
|
|
||||||
{ cwd = ROOT, text = true, stdin = true, stdout = on_stdout, stderr = on_stderr },
|
|
||||||
vim.schedule_wrap(on_exit)
|
|
||||||
)
|
|
||||||
if not started then
|
|
||||||
notify("cannot start validator: " .. tostring(handle), vim.log.levels.ERROR)
|
|
||||||
return cb(false)
|
|
||||||
end
|
|
||||||
job = handle
|
|
||||||
cb(true)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param text string
|
|
||||||
--- @param cb fun(result: MermaidResult)
|
|
||||||
local function request(text, cb)
|
|
||||||
if not job then return end
|
|
||||||
next_id = next_id + 1
|
|
||||||
pending[next_id] = cb
|
|
||||||
local id = next_id
|
|
||||||
if not pcall(job.write, job, vim.json.encode { id = id, text = text } .. "\n") then pending[id] = nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Mermaid sources in `buf`: the whole buffer for a mermaid file, every
|
|
||||||
--- ```mermaid fence for markdown/mdx.
|
|
||||||
--- @param buf integer
|
|
||||||
--- @return MermaidBlock[]
|
|
||||||
local function blocks(buf)
|
|
||||||
if vim.bo[buf].filetype == "mermaid" then
|
|
||||||
return { { row = 0, text = table.concat(vim.api.nvim_buf_get_lines(buf, 0, -1, false), "\n") } }
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, parser = pcall(vim.treesitter.get_parser, buf)
|
|
||||||
-- mdx buffers (`markdown.mdx`) parse as markdown; anything else has no fences
|
|
||||||
if not ok or not parser or parser:lang() ~= "markdown" then return {} end
|
|
||||||
|
|
||||||
local query = vim.treesitter.query.parse("markdown", FENCE_QUERY)
|
|
||||||
local found = {}
|
|
||||||
for _, tree in ipairs(parser:parse(true)) do
|
|
||||||
for id, node in query:iter_captures(tree:root(), buf) do
|
|
||||||
if query.captures[id] == "content" then
|
|
||||||
found[#found + 1] = { row = node:start(), text = vim.treesitter.get_node_text(node, buf) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return found
|
|
||||||
end
|
|
||||||
|
|
||||||
--- The validator reports UTF-16 columns (javascript string indices).
|
|
||||||
--- @param line string
|
|
||||||
--- @param col integer
|
|
||||||
--- @return integer
|
|
||||||
local function byte_col(line, col)
|
|
||||||
local ok, byte = pcall(vim.str_byteindex, line, "utf-16", col, false)
|
|
||||||
if ok then return byte end
|
|
||||||
return math.min(col, #line)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param buf integer
|
|
||||||
--- @param block MermaidBlock
|
|
||||||
--- @param result MermaidResult
|
|
||||||
--- @return vim.Diagnostic
|
|
||||||
local function diagnostic(buf, block, result)
|
|
||||||
local lnum = math.max(0, math.min(block.row + (result.line or 1) - 1, vim.api.nvim_buf_line_count(buf) - 1))
|
|
||||||
local line = vim.api.nvim_buf_get_lines(buf, lnum, lnum + 1, false)[1] or ""
|
|
||||||
local col = byte_col(line, result.col or 0)
|
|
||||||
return {
|
|
||||||
lnum = lnum,
|
|
||||||
col = col,
|
|
||||||
-- mermaid points at a single position; underlining the rest of the line
|
|
||||||
-- shows which statement it choked on
|
|
||||||
end_lnum = lnum,
|
|
||||||
end_col = math.max(col, #line),
|
|
||||||
severity = vim.diagnostic.severity.ERROR,
|
|
||||||
source = "mermaid",
|
|
||||||
message = result.message or "parse error",
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param buf integer
|
|
||||||
--- @param found MermaidBlock[]
|
|
||||||
--- @param results table<string, MermaidResult>
|
|
||||||
local function publish(buf, found, results)
|
|
||||||
if not vim.api.nvim_buf_is_valid(buf) then return end
|
|
||||||
local diagnostics = {}
|
|
||||||
for _, block in ipairs(found) do
|
|
||||||
local result = results[block.text]
|
|
||||||
if result and not result.ok then diagnostics[#diagnostics + 1] = diagnostic(buf, block, result) end
|
|
||||||
end
|
|
||||||
vim.diagnostic.set(NS, buf, diagnostics)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Validate every mermaid block in `buf` and replace its diagnostics.
|
|
||||||
---
|
|
||||||
--- Results are cached by block text, so editing one diagram in a document full
|
|
||||||
--- of them only re-validates that diagram. Passes are sequenced: a response
|
|
||||||
--- that arrives after a newer pass started is dropped instead of publishing
|
|
||||||
--- positions for text that has since moved.
|
|
||||||
--- @param buf? integer defaults to the current buffer
|
|
||||||
--- @param force? boolean ignore cached results
|
|
||||||
function M.validate(buf, force)
|
|
||||||
buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf
|
|
||||||
local state = buffers[buf]
|
|
||||||
if not state or not vim.api.nvim_buf_is_valid(buf) then return end
|
|
||||||
-- astrocore marks oversized buffers; re-parsing those on every keystroke
|
|
||||||
-- costs more than the diagnostics are worth
|
|
||||||
if vim.b[buf].large_buf then return end
|
|
||||||
|
|
||||||
local found = blocks(buf)
|
|
||||||
local previous = force and {} or state.results
|
|
||||||
local results, queue, queued = {}, {}, {}
|
|
||||||
for _, block in ipairs(found) do
|
|
||||||
if vim.trim(block.text) ~= "" then
|
|
||||||
local cached = previous[block.text]
|
|
||||||
if cached then
|
|
||||||
results[block.text] = cached
|
|
||||||
elseif not queued[block.text] then
|
|
||||||
queued[block.text] = true
|
|
||||||
queue[#queue + 1] = block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
state.seq = state.seq + 1
|
|
||||||
state.results = results
|
|
||||||
local seq = state.seq
|
|
||||||
|
|
||||||
if #queue == 0 then return publish(buf, found, results) end
|
|
||||||
|
|
||||||
ensure_job(function(ok)
|
|
||||||
if not ok or buffers[buf] ~= state or state.seq ~= seq then return end
|
|
||||||
local remaining = #queue
|
|
||||||
for _, block in ipairs(queue) do
|
|
||||||
request(block.text, function(result)
|
|
||||||
if buffers[buf] ~= state or state.seq ~= seq then return end
|
|
||||||
results[block.text] = result
|
|
||||||
remaining = remaining - 1
|
|
||||||
if remaining == 0 then publish(buf, found, results) end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param buf integer
|
|
||||||
local function detach(buf)
|
|
||||||
local state = buffers[buf]
|
|
||||||
if not state then return end
|
|
||||||
buffers[buf] = nil
|
|
||||||
state.timer:stop()
|
|
||||||
state.timer:close()
|
|
||||||
pcall(vim.api.nvim_del_augroup_by_name, "mermaid_buf_" .. buf)
|
|
||||||
if vim.api.nvim_buf_is_valid(buf) then vim.diagnostic.reset(NS, buf) end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Start validating `buf` on changes.
|
|
||||||
---
|
|
||||||
--- Re-entrant: the driving FileType autocmd fires again on every `setfiletype`
|
|
||||||
--- and on `:edit` reloads, and an already-attached buffer just gets re-checked
|
|
||||||
--- (its content may have been re-read from disk).
|
|
||||||
--- @param buf integer
|
|
||||||
function M.attach(buf)
|
|
||||||
if buffers[buf] then return M.validate(buf) end
|
|
||||||
local state = { timer = assert(vim.uv.new_timer()), seq = 0, results = {} }
|
|
||||||
buffers[buf] = state
|
|
||||||
|
|
||||||
local group = vim.api.nvim_create_augroup("mermaid_buf_" .. buf, { clear = true })
|
|
||||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "InsertLeave" }, {
|
|
||||||
group = group,
|
|
||||||
buffer = buf,
|
|
||||||
desc = "Re-validate mermaid diagrams after edits",
|
|
||||||
callback = function()
|
|
||||||
state.timer:stop()
|
|
||||||
state.timer:start(DEBOUNCE_MS, 0, vim.schedule_wrap(function() M.validate(buf) end))
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost" }, {
|
|
||||||
group = group,
|
|
||||||
buffer = buf,
|
|
||||||
desc = "Re-validate mermaid diagrams on read and write",
|
|
||||||
callback = function() M.validate(buf) end,
|
|
||||||
})
|
|
||||||
-- `nvim_buf_attach`'s `on_detach` is not usable here: it also fires for a
|
|
||||||
-- `:edit` reload, and its scheduled callback then tears down state that the
|
|
||||||
-- reload's FileType has already re-attached
|
|
||||||
vim.api.nvim_create_autocmd({ "BufDelete", "BufWipeout" }, {
|
|
||||||
group = group,
|
|
||||||
buffer = buf,
|
|
||||||
desc = "Stop validating mermaid diagrams in a discarded buffer",
|
|
||||||
callback = function() detach(buf) end,
|
|
||||||
})
|
|
||||||
|
|
||||||
M.validate(buf)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- `:MermaidCheck` — re-validate the current buffer, ignoring cached results.
|
|
||||||
function M.check()
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
if buffers[buf] then
|
|
||||||
M.validate(buf, true)
|
|
||||||
else
|
|
||||||
M.attach(buf)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
--- Single Oh My Pi side terminal, toggled with <C-a><C-a>.
|
|
||||||
---
|
|
||||||
--- `Snacks.terminal.toggle` keys terminals by cmd + window-local cwd +
|
|
||||||
--- v:count1, so toggling from a buffer whose window has another cwd spawns a
|
|
||||||
--- second omp. Holding the handle here keeps exactly one session; it is
|
|
||||||
--- recreated only after the omp process exits (auto_close wipes the buffer).
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local term ---@type snacks.win?
|
|
||||||
|
|
||||||
function M.toggle()
|
|
||||||
if term and term:buf_valid() then
|
|
||||||
term:toggle()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
term = require("snacks").terminal.open("omp", {
|
|
||||||
win = {
|
|
||||||
position = "right",
|
|
||||||
width = 0.4,
|
|
||||||
-- buffer-local, so <C-a> keeps reaching other terminals unchanged
|
|
||||||
keys = { omp_hide = { "<C-a><C-a>", "hide", mode = { "n", "t" }, desc = "Hide Oh My Pi" } },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
--- Multiplexer-aware <C-h/j/k/l> split navigation.
|
|
||||||
---
|
|
||||||
--- Neovim owns the key first: move between Neovim splits, and only once the
|
|
||||||
--- cursor is already at the outermost split in that direction hand the move to
|
|
||||||
--- whatever owns the surrounding panes:
|
|
||||||
---
|
|
||||||
--- herdr -> herdr pane focus --direction <dir> --pane $HERDR_PANE_ID
|
|
||||||
--- tmux -> tmux select-pane -t $TMUX_PANE -L/-D/-U/-R
|
|
||||||
--- kitty -> kitty @ kitten navigate_kitty.py <edge> (needs `listen_on`)
|
|
||||||
--- none -> stay put
|
|
||||||
---
|
|
||||||
--- The outer half of each pair already forwards the chord into Neovim when the
|
|
||||||
--- pane runs (n)vim (herdr: vim-herdr-navigation's navigate.sh, tmux: the
|
|
||||||
--- `is_vim` if-shell in tmux.conf, kitty: pass_keys.py), so this side only has
|
|
||||||
--- to answer "who do I hand the edge to".
|
|
||||||
---
|
|
||||||
--- Which one owns the panes is answered by the process tree, not by env vars:
|
|
||||||
--- $TMUX / $HERDR_PANE_ID / $KITTY_LISTEN_ON are inherited, so they survive into
|
|
||||||
--- unrelated grandchildren (a kitty window launched from a herdr pane still
|
|
||||||
--- advertises $HERDR_PANE_ID, and a tmux server started inside a herdr pane
|
|
||||||
--- leaves both set). The nearest ancestor process that is a terminal or
|
|
||||||
--- multiplexer is the one actually drawing the surrounding panes; the env var is
|
|
||||||
--- then required as proof that we can drive it (a socket, a pane id) and demoted
|
|
||||||
--- to the only signal on platforms without /proc.
|
|
||||||
--- @module config.pane_nav
|
|
||||||
|
|
||||||
local uv = vim.uv or vim.loop
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
--- Per-direction handoff arguments: Neovim wincmd, tmux select-pane flag and
|
|
||||||
--- kitty edge name (kitty calls the vertical edges "top"/"bottom").
|
|
||||||
local directions = {
|
|
||||||
left = { wincmd = "h", tmux = "-L", kitty = "left" },
|
|
||||||
down = { wincmd = "j", tmux = "-D", kitty = "bottom" },
|
|
||||||
up = { wincmd = "k", tmux = "-U", kitty = "top" },
|
|
||||||
right = { wincmd = "l", tmux = "-R", kitty = "right" },
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Read a single /proc file, trimmed. Returns nil when unreadable (non-Linux,
|
|
||||||
--- process already gone).
|
|
||||||
--- @param path string
|
|
||||||
--- @return string|nil
|
|
||||||
local function read_proc(path)
|
|
||||||
local fd = uv.fs_open(path, "r", 292) -- 0444
|
|
||||||
if not fd then return nil end
|
|
||||||
local data = uv.fs_read(fd, 4096, 0)
|
|
||||||
uv.fs_close(fd)
|
|
||||||
if not data then return nil end
|
|
||||||
return (data:gsub("%s+$", ""))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Nearest terminal/multiplexer ancestor of this Neovim. comm is "tmux: server"
|
|
||||||
--- for tmux, "herdr" for the herdr server, "kitty" for kitty.
|
|
||||||
--- @return "tmux"|"herdr"|"kitty"|nil
|
|
||||||
local function ancestor_mux()
|
|
||||||
local pid = uv.os_getppid()
|
|
||||||
for _ = 1, 32 do
|
|
||||||
if not pid or pid <= 1 then return nil end
|
|
||||||
local comm = read_proc(("/proc/%d/comm"):format(pid))
|
|
||||||
if not comm then return nil end
|
|
||||||
if comm:match "^tmux" then return "tmux" end
|
|
||||||
if comm == "herdr" then return "herdr" end
|
|
||||||
if comm == "kitty" then return "kitty" end
|
|
||||||
local status = read_proc(("/proc/%d/status"):format(pid))
|
|
||||||
pid = status and tonumber(status:match "PPid:%s*(%d+)")
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Whether this Neovim was handed what it needs to drive `mux`: herdr and tmux
|
|
||||||
--- name the pane to move from, kitty a remote-control socket.
|
|
||||||
--- @param mux "tmux"|"herdr"|"kitty"
|
|
||||||
--- @return boolean
|
|
||||||
local function drivable(mux)
|
|
||||||
if mux == "herdr" then return (vim.env.HERDR_PANE_ID or "") ~= "" end
|
|
||||||
if mux == "tmux" then return (vim.env.TMUX or "") ~= "" end
|
|
||||||
return (vim.env.KITTY_LISTEN_ON or "") ~= ""
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @return "herdr"|"tmux"|"kitty"|"none"
|
|
||||||
local function detect()
|
|
||||||
local owner = ancestor_mux()
|
|
||||||
-- Known owner: either we can drive it, or nothing else may be driven on its
|
|
||||||
-- behalf (e.g. kitty without `listen_on`, where the edge simply stops).
|
|
||||||
if owner then return drivable(owner) and owner or "none" end
|
|
||||||
-- No ancestry (no /proc): fall back to env vars, innermost-looking first.
|
|
||||||
for _, mux in ipairs { "herdr", "tmux", "kitty" } do
|
|
||||||
if drivable(mux) then return mux end
|
|
||||||
end
|
|
||||||
return "none"
|
|
||||||
end
|
|
||||||
|
|
||||||
local env
|
|
||||||
|
|
||||||
--- The multiplexer that owns the panes around this Neovim. Cached: a process
|
|
||||||
--- cannot change multiplexers mid-life.
|
|
||||||
--- @return "herdr"|"tmux"|"kitty"|"none"
|
|
||||||
function M.env()
|
|
||||||
if not env then env = detect() end
|
|
||||||
return env
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Fire off a handoff command, warning if it fails (e.g. `kitty @` without a
|
|
||||||
--- remote-control socket) instead of swallowing a dead keypress.
|
|
||||||
--- @param cmd string[]
|
|
||||||
local function run(cmd)
|
|
||||||
vim.system(cmd, { text = true }, function(result)
|
|
||||||
if result.code == 0 then return end
|
|
||||||
local message = ("pane_nav: %s exited %d%s"):format(cmd[1], result.code, result.stderr ~= "" and (": " .. vim.trim(result.stderr)) or "")
|
|
||||||
vim.schedule(function() vim.notify(message, vim.log.levels.WARN) end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Hand the move to the surrounding multiplexer. Both herdr and tmux resolve a
|
|
||||||
--- bare command against the *focused* pane; target this Neovim's own pane
|
|
||||||
--- instead, so a handoff is still correct if focus drifted (e.g. a click, or a
|
|
||||||
--- second keypress arriving while the first is still in flight).
|
|
||||||
--- @param dir "left"|"down"|"up"|"right"
|
|
||||||
local function handoff(dir)
|
|
||||||
local mux = M.env()
|
|
||||||
if mux == "herdr" then
|
|
||||||
local herdr = vim.env.HERDR_BIN_PATH
|
|
||||||
if herdr == nil or herdr == "" then herdr = "herdr" end
|
|
||||||
run { herdr, "pane", "focus", "--direction", dir, "--pane", vim.env.HERDR_PANE_ID }
|
|
||||||
elseif mux == "tmux" then
|
|
||||||
local pane = vim.env.TMUX_PANE
|
|
||||||
if pane == nil or pane == "" then
|
|
||||||
run { "tmux", "select-pane", directions[dir].tmux }
|
|
||||||
else
|
|
||||||
run { "tmux", "select-pane", "-t", pane, directions[dir].tmux }
|
|
||||||
end
|
|
||||||
elseif mux == "kitty" then
|
|
||||||
run { "kitty", "@", "kitten", "navigate_kitty.py", directions[dir].kitty }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Move to the split in `dir`, crossing into the surrounding multiplexer when
|
|
||||||
--- already at that edge. Floating windows never hand off: they are overlays, so
|
|
||||||
--- jumping the whole pane out from under one is never what was meant.
|
|
||||||
--- @param dir "left"|"down"|"up"|"right"
|
|
||||||
function M.navigate(dir)
|
|
||||||
local from = vim.api.nvim_get_current_win()
|
|
||||||
-- Under tmux smart-splits owns the edge handoff (plugins/user.lua).
|
|
||||||
if M.env() == "tmux" and vim.api.nvim_win_get_config(from).relative == "" then
|
|
||||||
require("smart-splits")["move_cursor_" .. dir]()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
vim.cmd.wincmd(directions[dir].wincmd)
|
|
||||||
if vim.api.nvim_get_current_win() ~= from then return end
|
|
||||||
if vim.api.nvim_win_get_config(0).relative ~= "" then return end
|
|
||||||
handoff(dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Terminal-mode variant. Inside a floating terminal the chord belongs to the
|
|
||||||
--- child process (AstroNvim's `term_nav` behaviour), everywhere else it
|
|
||||||
--- navigates as usual.
|
|
||||||
--- @param dir "left"|"down"|"up"|"right"
|
|
||||||
--- @return function
|
|
||||||
function M.terminal(dir)
|
|
||||||
local key = ("<C-%s>"):format(directions[dir].wincmd)
|
|
||||||
return function()
|
|
||||||
if vim.api.nvim_win_get_config(0).zindex then
|
|
||||||
vim.api.nvim_feedkeys(vim.keycode(key), "n", false)
|
|
||||||
else
|
|
||||||
M.navigate(dir)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
--- Shared snacks.picker helpers.
|
|
||||||
--- @module config.picker
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
--- Open a snacks picker with trouble.nvim's send-to-list action bound to <C-t>,
|
|
||||||
--- so a picker result set can be promoted to a persistent Trouble list.
|
|
||||||
--- @param source string snacks.picker source name (e.g. "diagnostics")
|
|
||||||
--- @param opts? snacks.picker.Config extra picker options
|
|
||||||
function M.with_trouble(source, opts)
|
|
||||||
opts = opts or {}
|
|
||||||
opts.actions = require("trouble.sources.snacks").actions
|
|
||||||
opts.win = opts.win or {}
|
|
||||||
opts.win.input = opts.win.input or {}
|
|
||||||
opts.win.input.keys = vim.tbl_extend("force", opts.win.input.keys or {}, {
|
|
||||||
["<c-t>"] = { "trouble_open", mode = { "n", "i" } },
|
|
||||||
})
|
|
||||||
require("snacks").picker[source](opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
--- Git-aware directory sessions on top of resession's `dirsession` store.
|
|
||||||
---
|
|
||||||
--- Session identity is `<worktree root>@<ref>`, so every git worktree gets its
|
|
||||||
--- own session (worktrees live in distinct directories) and every branch
|
|
||||||
--- inside a worktree gets its own layout. Detached HEAD pins to the short
|
|
||||||
--- commit; outside a repository the plain cwd is used, matching AstroNvim's
|
|
||||||
--- default dirsession naming.
|
|
||||||
---
|
|
||||||
--- resession sanitizes `/` and `:` in session names, so slashes in worktree
|
|
||||||
--- paths and branch names (`feat/foo`) are safe.
|
|
||||||
--- @module config.session
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local DIR = "dirsession"
|
|
||||||
|
|
||||||
--- Run git in `cwd`; nil on failure or empty output.
|
|
||||||
--- @param cwd string
|
|
||||||
--- @param ... string git arguments
|
|
||||||
--- @return string?
|
|
||||||
local function git(cwd, ...)
|
|
||||||
local cmd = { "git", "-C", cwd }
|
|
||||||
for _, arg in ipairs { ... } do
|
|
||||||
cmd[#cmd + 1] = arg
|
|
||||||
end
|
|
||||||
local out = vim.trim(vim.fn.system(cmd))
|
|
||||||
if vim.v.shell_error ~= 0 or out == "" then return nil end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Session name for the current working directory.
|
|
||||||
--- @return string
|
|
||||||
function M.name()
|
|
||||||
local cwd = vim.fn.getcwd()
|
|
||||||
local root = git(cwd, "rev-parse", "--show-toplevel")
|
|
||||||
if not root then return cwd end
|
|
||||||
-- `branch --show-current` is empty on detached HEAD; an unborn branch in a
|
|
||||||
-- fresh repo has no commit to fall back to.
|
|
||||||
local ref = git(cwd, "branch", "--show-current")
|
|
||||||
or git(cwd, "rev-parse", "--short", "HEAD")
|
|
||||||
or "HEAD"
|
|
||||||
return root .. "@" .. ref
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Whether a saved session file exists for `name`.
|
|
||||||
--- @param name string
|
|
||||||
--- @return boolean
|
|
||||||
function M.exists(name)
|
|
||||||
return vim.uv.fs_stat(require("resession.util").get_session_file(name, DIR)) ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Save the session for the current worktree/branch.
|
|
||||||
--- @param opts? table extra resession save options
|
|
||||||
function M.save(opts)
|
|
||||||
require("resession").save(M.name(), vim.tbl_extend("force", { dir = DIR }, opts or {}))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Load the session for the current worktree/branch.
|
|
||||||
--- @param opts? table extra resession load options
|
|
||||||
function M.load(opts)
|
|
||||||
require("resession").load(M.name(), vim.tbl_extend("force", { dir = DIR }, opts or {}))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Open the picker used when there is nothing to restore: git files inside a
|
|
||||||
--- repository, plain file search outside one.
|
|
||||||
local function pick_files()
|
|
||||||
local snacks = require "snacks"
|
|
||||||
local transform = require("config.ignore_patterns").create_picker_filter()
|
|
||||||
if snacks.git.get_root() then
|
|
||||||
snacks.picker.git_files { transform = transform }
|
|
||||||
else
|
|
||||||
snacks.picker.files { transform = transform }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- VimEnter entry point: show neo-tree, then restore this worktree/branch
|
|
||||||
--- session, or fall back to the file picker when none has been saved yet.
|
|
||||||
---
|
|
||||||
--- Exception: when nvim was started with file arguments (`nvim foo.lua`, a
|
|
||||||
--- directory, or `-`) the requested buffer is what the user asked for, so
|
|
||||||
--- neither the tree, the session nor the picker may take the screen.
|
|
||||||
function M.restore()
|
|
||||||
if vim.fn.argc(-1) > 0 then return end
|
|
||||||
local restored = M.exists(M.name())
|
|
||||||
if restored then M.load { silence_errors = true } end
|
|
||||||
-- defer so the tree and picker open after startup finished drawing the UI
|
|
||||||
vim.schedule(function()
|
|
||||||
vim.cmd "Neotree show" -- no focus: the restored buffer / picker keeps it
|
|
||||||
if not restored then pick_files() end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
--- Snacks picker frontend for template.nvim (<C-f>t).
|
|
||||||
---
|
|
||||||
--- <CR>: insert the template at the cursor when the current buffer is a
|
|
||||||
--- named file whose filetype matches the template's; otherwise fall through
|
|
||||||
--- to the new-file flow. <C-n>: always prompt (snacks.input) for a new file
|
|
||||||
--- to create in the cwd and fill with the template.
|
|
||||||
---
|
|
||||||
--- template.nvim resolves templates by filetype + basename-without-extension
|
|
||||||
--- using Lua-pattern substring matching, so template file names must use
|
|
||||||
--- underscores only and never be substrings of one another.
|
|
||||||
--- @module config.templates
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function temp_dir() return vim.fs.normalize(require("template").temp_dir) end
|
|
||||||
|
|
||||||
local function tpl_name(item) return vim.fn.fnamemodify(item.file, ":t:r") end
|
|
||||||
|
|
||||||
--- Insert the template at the cursor of the current buffer.
|
|
||||||
local function insert(item) require("template"):generate_template { tpl_name(item) } end
|
|
||||||
|
|
||||||
--- Prompt for a filename, create it under cwd, and fill it with the template.
|
|
||||||
local function create(item)
|
|
||||||
local ext = vim.fn.fnamemodify(item.file, ":e")
|
|
||||||
require("snacks").input({
|
|
||||||
prompt = ("New .%s file (created in %s)"):format(ext, vim.fn.fnamemodify(vim.fn.getcwd(), ":~")),
|
|
||||||
}, function(value)
|
|
||||||
if not value or value == "" then return end
|
|
||||||
-- template.nvim detects the file argument by its extension
|
|
||||||
if not value:find("%.%w+$") then value = value .. "." .. ext end
|
|
||||||
-- template.nvim opens the file with bare uv.fs_open; make parents first
|
|
||||||
local parent = vim.fn.fnamemodify(vim.fn.getcwd() .. "/" .. value, ":h")
|
|
||||||
if vim.fn.isdirectory(parent) == 0 then vim.fn.mkdir(parent, "p") end
|
|
||||||
require("template"):generate_template { value, tpl_name(item) }
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Open the template picker.
|
|
||||||
function M.pick()
|
|
||||||
require "template" -- lazy-loads the plugin so temp_dir is configured
|
|
||||||
require("snacks").picker.files {
|
|
||||||
title = "Templates",
|
|
||||||
cwd = temp_dir(),
|
|
||||||
actions = {
|
|
||||||
template_new_file = function(picker, item)
|
|
||||||
picker:close()
|
|
||||||
if item then vim.schedule(function() create(item) end) end
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
win = {
|
|
||||||
input = {
|
|
||||||
keys = { ["<c-n>"] = { "template_new_file", mode = { "n", "i" } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
confirm = function(picker, item)
|
|
||||||
picker:close()
|
|
||||||
if not item then return end
|
|
||||||
-- defer past the picker teardown so the target window is current again
|
|
||||||
vim.schedule(function()
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
local named = vim.api.nvim_buf_get_name(buf) ~= "" and vim.bo[buf].buftype == ""
|
|
||||||
local tpl_ft = vim.filetype.match { filename = vim.fs.joinpath(temp_dir(), item.file) }
|
|
||||||
-- template.nvim silently no-ops on filetype mismatch, so route
|
|
||||||
-- non-matching buffers to the new-file flow instead
|
|
||||||
if named and vim.bo[buf].filetype == tpl_ft then
|
|
||||||
insert(item)
|
|
||||||
else
|
|
||||||
create(item)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
--- Terminal configurations using toggleterm
|
||||||
|
--- This module provides terminal toggle functions for various CLI tools
|
||||||
|
--- @module config.terminals
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Check if toggleterm is available
|
||||||
|
--- @return boolean available Whether toggleterm is loaded
|
||||||
|
local function is_toggleterm_available()
|
||||||
|
local ok, _ = pcall(require, "toggleterm.terminal")
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Initialize all terminal configurations
|
||||||
|
--- Only runs if toggleterm is available
|
||||||
|
function M.setup()
|
||||||
|
if not is_toggleterm_available() then
|
||||||
|
vim.notify("toggleterm not available, skipping terminal setup", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local Terminal = require("toggleterm.terminal").Terminal
|
||||||
|
-- Scooter find and replace terminal
|
||||||
|
local smartCommit = Terminal:new {
|
||||||
|
cmd = "smartcommit",
|
||||||
|
hidden = true,
|
||||||
|
direction = "vertical",
|
||||||
|
close_on_exit = true,
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>close<CR>", { noremap = true, silent = true })
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Scooter find and replace terminal
|
||||||
|
local scooter = Terminal:new {
|
||||||
|
cmd = "scooter",
|
||||||
|
hidden = true,
|
||||||
|
direction = "vertical",
|
||||||
|
close_on_exit = true,
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>close<CR>", { noremap = true, silent = true })
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Mk (make) terminal
|
||||||
|
local mk = Terminal:new {
|
||||||
|
cmd = "task",
|
||||||
|
hidden = true,
|
||||||
|
direction = "horizontal",
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>close<CR>", { noremap = true, silent = true })
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Mk (make) terminal
|
||||||
|
local lazygit = Terminal:new {
|
||||||
|
cmd = "lazygit",
|
||||||
|
hidden = true,
|
||||||
|
direction = "float",
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>close<CR>", { noremap = true, silent = true })
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
-- Mk (make) terminal
|
||||||
|
local lazydocker = Terminal:new {
|
||||||
|
cmd = "lazydocker",
|
||||||
|
hidden = true,
|
||||||
|
direction = "float",
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>close<CR>", { noremap = true, silent = true })
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Opencode Terminal Tab
|
||||||
|
|
||||||
|
-- Opencode Terminal Tab (persistent)
|
||||||
|
local opencode = Terminal:new {
|
||||||
|
cmd = "opencode",
|
||||||
|
hidden = true,
|
||||||
|
direction = "tab",
|
||||||
|
close_on_exit = false,
|
||||||
|
count = 99, -- fixed ID so toggleterm never creates a duplicate
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>tabprevious<CR>", { noremap = true, silent = true })
|
||||||
|
vim.api.nvim_buf_set_keymap(
|
||||||
|
term.bufnr,
|
||||||
|
"t",
|
||||||
|
"<C-a><C-a>",
|
||||||
|
"<cmd>tabprevious<CR>",
|
||||||
|
{ noremap = true, silent = true }
|
||||||
|
)
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "t", "<C-p>", "", {
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
callback = function()
|
||||||
|
vim.fn.chansend(term.job_id, "\x10") -- raw Ctrl-P
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "t", "<C-j>", "", {
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
callback = function()
|
||||||
|
vim.fn.chansend(term.job_id, "\x0a") -- Ctrl+J / LF — always works in opencode
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
vim.api.nvim_buf_set_keymap(
|
||||||
|
term.bufnr,
|
||||||
|
"n",
|
||||||
|
"<C-a><C-a>",
|
||||||
|
"<cmd>tabprevious<CR>",
|
||||||
|
{ noremap = true, silent = true }
|
||||||
|
)
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "t", "<C-o>", "<cmd>tabprevious<CR>", { noremap = true, silent = true })
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "<C-o>", "<cmd>tabprevious<CR>", { noremap = true, silent = true })
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "t", "<C-c>", "", {
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
callback = function()
|
||||||
|
term:shutdown()
|
||||||
|
vim.cmd "tabprevious"
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "<C-c>", "", {
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
callback = function()
|
||||||
|
term:shutdown()
|
||||||
|
vim.cmd "tabprevious"
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
-- Scratch terminal
|
||||||
|
local scratch = Terminal:new {
|
||||||
|
cmd = "zsh",
|
||||||
|
hidden = true,
|
||||||
|
direction = "horizontal",
|
||||||
|
on_open = function(term)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
vim.api.nvim_buf_set_keymap(term.bufnr, "n", "q", "<cmd>close<CR>", { noremap = true, silent = true })
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
-- Export toggle functions to module instead of global namespace
|
||||||
|
M.scooter_toggle = function() scooter:toggle() end
|
||||||
|
M.mk_toggle = function() mk:toggle() end
|
||||||
|
M.lazygit_toggle = function() lazygit:toggle() end
|
||||||
|
M.lazydocker_toggle = function() lazydocker:toggle() end
|
||||||
|
M.smartCommit_toggle = function() smartCommit:toggle() end
|
||||||
|
M.scratch_toggle = function() scratch:toggle() end
|
||||||
|
M.opencode_toggle = function()
|
||||||
|
if not opencode:is_open() then
|
||||||
|
opencode:open()
|
||||||
|
elseif opencode:is_focused() then
|
||||||
|
vim.cmd "tabprevious"
|
||||||
|
else
|
||||||
|
-- Find and switch to the opencode tab
|
||||||
|
local bufnr = opencode.bufnr
|
||||||
|
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||||
|
if vim.api.nvim_win_get_buf(win) == bufnr then
|
||||||
|
vim.api.nvim_set_current_win(win)
|
||||||
|
vim.cmd "startinsert!"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
--- UI toggles for the `<Leader>u` group, registered through
|
|
||||||
--- `Snacks.toggle():map()` instead of plain astrocore mappings.
|
|
||||||
---
|
|
||||||
--- That is what buys the snacks default which-key integration: each toggle
|
|
||||||
--- adds its own which-key entry with the enabled/disabled state icon, the
|
|
||||||
--- green/yellow state colour, and an "Enable X" / "Disable X" label that
|
|
||||||
--- flips with the current value. Plain `vim.keymap.set` mappings only ever
|
|
||||||
--- show a static description.
|
|
||||||
---
|
|
||||||
--- Called from polish.lua, i.e. after astrocore has installed its own
|
|
||||||
--- `<Leader>u` defaults, so these win on the keys they share. Toggles
|
|
||||||
--- AstroNvim provides and snacks does not (`<Leader>ub` background,
|
|
||||||
--- `<Leader>uA` autochdir, ...) are left alone.
|
|
||||||
--- @module config.toggles
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.setup()
|
|
||||||
local ok, Snacks = pcall(require, "snacks")
|
|
||||||
if not ok then return end
|
|
||||||
local toggle = Snacks.toggle
|
|
||||||
|
|
||||||
toggle.diagnostics():map "<Leader>ud"
|
|
||||||
toggle.line_number():map "<Leader>un"
|
|
||||||
toggle.option("relativenumber", { name = "Relative Numbers" }):map "<Leader>ur"
|
|
||||||
toggle.option("wrap", { name = "Wrap" }):map "<Leader>uw"
|
|
||||||
toggle.option("spell", { name = "Spelling" }):map "<Leader>us"
|
|
||||||
toggle.option("conceallevel", { off = 0, on = 2, name = "Conceal" }):map "<Leader>uc"
|
|
||||||
toggle.inlay_hints():map "<Leader>ui"
|
|
||||||
toggle.treesitter():map "<Leader>ut"
|
|
||||||
toggle.indent():map "<Leader>uI"
|
|
||||||
toggle.dim():map "<Leader>ux"
|
|
||||||
toggle.zen():map "<Leader>uz"
|
|
||||||
toggle.zoom():map "<Leader>uZ"
|
|
||||||
toggle.animate():map "<Leader>ua"
|
|
||||||
toggle.scroll():map "<Leader>uS"
|
|
||||||
toggle.profiler():map "<Leader>up"
|
|
||||||
toggle.profiler_highlights():map "<Leader>uh"
|
|
||||||
|
|
||||||
-- conform owns format-on-save (astrocommunity conform pack) and gates it on
|
|
||||||
-- vim.b.autoformat / vim.g.autoformat; the pre-save LSP code action pass
|
|
||||||
-- rides the same hook, so this one flag governs the whole on-save pipeline.
|
|
||||||
-- Mirrors the pack's own <Leader>uF, with which-key state on top.
|
|
||||||
toggle({
|
|
||||||
name = "Format on Save",
|
|
||||||
get = function() return vim.F.if_nil(vim.b.autoformat, vim.g.autoformat, true) end,
|
|
||||||
set = function(state) vim.g.autoformat, vim.b.autoformat = state, nil end,
|
|
||||||
}):map "<Leader>uf"
|
|
||||||
|
|
||||||
-- virtual_lines is native since 0.11; showing both at once is unreadable,
|
|
||||||
-- so the two diagnostic renderers trade places
|
|
||||||
toggle({
|
|
||||||
name = "Diagnostic Virtual Lines",
|
|
||||||
get = function() return ((vim.diagnostic.config() or {}).virtual_lines and true) or false end,
|
|
||||||
set = function(state) vim.diagnostic.config { virtual_lines = state, virtual_text = not state } end,
|
|
||||||
}):map "<Leader>ul"
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
+16
-9
@@ -1,21 +1,28 @@
|
|||||||
require("lazy").setup({
|
require("lazy").setup({
|
||||||
{
|
{
|
||||||
"AstroNvim/AstroNvim",
|
"AstroNvim/AstroNvim",
|
||||||
version = "^6", -- Remove version tracking to elect for nightly AstroNvim
|
version = "^4", -- Ensure we use AstroNvim v4+
|
||||||
import = "astronvim.plugins",
|
import = "astronvim.plugins",
|
||||||
opts = { -- AstroNvim options must be set here with the `import` key
|
opts = {
|
||||||
mapleader = " ", -- This ensures the leader key must be configured before Lazy is set up
|
-- Leaders are set early in init.lua to ensure consistency
|
||||||
maplocalleader = ",", -- This ensures the localleader key must be configured before Lazy is set up
|
icons_enabled = (vim.g.have_nerd_font ~= false),
|
||||||
icons_enabled = true, -- Set to false to disable icons (if no Nerd Font is available)
|
pin_plugins = nil,
|
||||||
pin_plugins = nil, -- Default will pin plugins when tracking `version` of AstroNvim, set to true/false to override
|
update_notifications = false,
|
||||||
update_notifications = true, -- Enable/disable notification about running `:Lazy update` twice to update pinned plugins
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ import = "community" },
|
{ import = "community" },
|
||||||
|
{
|
||||||
|
"MunsMan/kitty-navigator.nvim",
|
||||||
|
keys = {
|
||||||
|
{ "<C-h>", function() require("kitty-navigator").navigateLeft() end, desc = "Move left a Split", mode = { "n" } },
|
||||||
|
{ "<C-j>", function() require("kitty-navigator").navigateDown() end, desc = "Move down a Split", mode = { "n" } },
|
||||||
|
{ "<C-k>", function() require("kitty-navigator").navigateUp() end, desc = "Move up a Split", mode = { "n" } },
|
||||||
|
{ "<C-l>", function() require("kitty-navigator").navigateRight() end, desc = "Move right a Split", mode = { "n" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
{ import = "plugins" },
|
{ import = "plugins" },
|
||||||
} --[[@as LazySpec]], {
|
} --[[@as LazySpec]], {
|
||||||
-- Configure any other `lazy.nvim` configuration options here
|
install = { colorscheme = { "cyberdream" } },
|
||||||
install = { colorscheme = { "cyberdream", "habamax" } },
|
|
||||||
ui = { backdrop = 100 },
|
ui = { backdrop = 100 },
|
||||||
performance = {
|
performance = {
|
||||||
rtp = {
|
rtp = {
|
||||||
|
|||||||
+362
-350
@@ -1,185 +1,124 @@
|
|||||||
--- AstroCore configuration (AstroNvim v6 / Neovim 0.12)
|
--- AstroCore configuration
|
||||||
--- Core settings, keymappings, and autocmds
|
--- Core settings, keymappings, and autocmds for AstroNvim
|
||||||
--- @module plugins.astrocore
|
--- @module plugins.astrocore
|
||||||
|
|
||||||
|
-- Import shared modules
|
||||||
local ignore_patterns = require "config.ignore_patterns"
|
local ignore_patterns = require "config.ignore_patterns"
|
||||||
|
local terminals = require "config.terminals"
|
||||||
|
|
||||||
|
-- Initialize terminals
|
||||||
|
terminals.setup()
|
||||||
|
|
||||||
|
-- Get file filter for pickers
|
||||||
local file_filter = ignore_patterns.create_picker_filter()
|
local file_filter = ignore_patterns.create_picker_filter()
|
||||||
|
|
||||||
-- <C-q>: close the window; only quit (saving all) when it's the last one
|
|
||||||
local function close_win()
|
|
||||||
local ok, err = pcall(vim.cmd.close)
|
|
||||||
if ok then return end
|
|
||||||
-- E444: last window; E5601: only floating windows would remain
|
|
||||||
if not tostring(err):find "E444" and not tostring(err):find "E5601" then error(err, 0) end
|
|
||||||
vim.cmd "wqa"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- <C-c>: save the current buffer only if it has unsaved changes, then close it
|
|
||||||
local function save_close_buf()
|
|
||||||
if vim.bo.buftype == "" then vim.cmd "update" end
|
|
||||||
require("snacks").bufdelete()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- <C-f>e: two-stage find-by-extension. Stage 1 scans the repo with fd
|
|
||||||
-- (respects .gitignore) and shows every file extension ranked by count;
|
|
||||||
-- stage 2 opens fff prefilled with a `*.<ext> ` glob constraint so the
|
|
||||||
-- rest of the query fuzzy-matches within that extension.
|
|
||||||
local function find_files_by_extension()
|
|
||||||
local snacks = require "snacks"
|
|
||||||
local root = snacks.git.get_root() or vim.uv.cwd()
|
|
||||||
local files = vim.fn.systemlist { "fd", "--type", "f", "--base-directory", root }
|
|
||||||
if vim.v.shell_error ~= 0 then
|
|
||||||
vim.notify("fd failed to list files in " .. root, vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local counts = {}
|
|
||||||
for _, path in ipairs(files) do
|
|
||||||
-- last dot of the basename; skips dotfiles (.gitignore) and bare names
|
|
||||||
local ext = path:match "[^/]%.([%w_%-]+)$"
|
|
||||||
if ext then counts[ext] = (counts[ext] or 0) + 1 end
|
|
||||||
end
|
|
||||||
local items = {}
|
|
||||||
for ext, count in pairs(counts) do
|
|
||||||
items[#items + 1] = { text = ext, ext = ext, count = count }
|
|
||||||
end
|
|
||||||
if #items == 0 then
|
|
||||||
vim.notify("No file extensions found in " .. root, vim.log.levels.WARN)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
table.sort(items, function(a, b)
|
|
||||||
if a.count ~= b.count then return a.count > b.count end
|
|
||||||
return a.ext < b.ext
|
|
||||||
end)
|
|
||||||
snacks.picker {
|
|
||||||
title = "File Extensions",
|
|
||||||
layout = { preset = "select" },
|
|
||||||
items = items,
|
|
||||||
format = function(item)
|
|
||||||
return {
|
|
||||||
{ ("%-12s"):format(item.ext), "SnacksPickerLabel" },
|
|
||||||
{ ("%d file%s"):format(item.count, item.count == 1 and "" or "s"), "SnacksPickerComment" },
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
confirm = function(picker, item)
|
|
||||||
picker:close()
|
|
||||||
if not item then return end
|
|
||||||
require("fff").find_files { cwd = root, title = "*." .. item.ext .. " files", query = "*." .. item.ext .. " " }
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- <C-f>x: every URL in the current buffer, in file order; confirm hands the
|
|
||||||
-- URL to vim.ui.open (xdg-open). Reuses astrocore's url_matcher so the
|
|
||||||
-- picker agrees with the highlighturl feature about what counts as a URL.
|
|
||||||
local function find_buffer_urls()
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
local re = vim.regex(require("astrocore").url_matcher)
|
|
||||||
local items = {}
|
|
||||||
for lnum, line in ipairs(vim.api.nvim_buf_get_lines(buf, 0, -1, false)) do
|
|
||||||
local offset = 0
|
|
||||||
while offset < #line do
|
|
||||||
-- match_str returns 0-indexed byte offsets relative to the substring
|
|
||||||
local s, e = re:match_str(line:sub(offset + 1))
|
|
||||||
if not s then break end
|
|
||||||
local url = line:sub(offset + s + 1, offset + e)
|
|
||||||
items[#items + 1] = { text = url, url = url, buf = buf, pos = { lnum, offset + s }, lnum = lnum }
|
|
||||||
offset = offset + e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #items == 0 then
|
|
||||||
vim.notify("No URLs found in buffer", vim.log.levels.WARN)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
require("snacks").picker {
|
|
||||||
title = "Buffer URLs",
|
|
||||||
items = items,
|
|
||||||
format = function(item)
|
|
||||||
return {
|
|
||||||
{ ("%4d "):format(item.lnum), "SnacksPickerRow" },
|
|
||||||
{ item.url, "SnacksPickerLink" },
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
confirm = function(picker, item)
|
|
||||||
picker:close()
|
|
||||||
if not item then return end
|
|
||||||
vim.ui.open(item.url)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- <C-f>m: files under the git root whose mtime is today (local midnight
|
|
||||||
-- onward). fd's --changed-within accepts a YYYY-MM-DD date as "since".
|
|
||||||
local function find_files_modified_today()
|
|
||||||
local snacks = require "snacks"
|
|
||||||
snacks.picker.files {
|
|
||||||
title = "Modified today",
|
|
||||||
cwd = snacks.git.get_root() or vim.uv.cwd(),
|
|
||||||
cmd = "fd",
|
|
||||||
args = { "--changed-within", os.date "%Y-%m-%d" },
|
|
||||||
transform = file_filter,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"AstroNvim/astrocore",
|
"AstroNvim/astrocore",
|
||||||
---@type AstroCoreOpts
|
---@type AstroCoreOpts
|
||||||
opts = {
|
opts = {
|
||||||
sessions = {
|
|
||||||
-- cwd autosave is handled by the git_branch_sessions autocmd below so
|
|
||||||
-- the session name carries the worktree/branch, see config.session
|
|
||||||
autosave = {
|
|
||||||
last = true,
|
|
||||||
cwd = false,
|
|
||||||
},
|
|
||||||
ignore = {
|
|
||||||
dirs = {},
|
|
||||||
filetypes = { "gitcommit", "gitrebase" },
|
|
||||||
buftypes = {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
|
sessions = {
|
||||||
|
-- Configure auto saving
|
||||||
|
autosave = {
|
||||||
|
last = true, -- auto save last session
|
||||||
|
cwd = true, -- auto save session for each working directory
|
||||||
|
},
|
||||||
|
-- Patterns to ignore when saving sessions
|
||||||
|
ignore = {
|
||||||
|
dirs = {}, -- working directories to ignore sessions in
|
||||||
|
filetypes = { "gitcommit", "gitrebase" }, -- filetypes to ignore sessions
|
||||||
|
buftypes = {}, -- buffer types to ignore sessions
|
||||||
|
},
|
||||||
|
-- Store pinned buffers in session data
|
||||||
|
before_save = function()
|
||||||
|
local pinned = {}
|
||||||
|
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
||||||
|
if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_option(buf, "buflisted") then
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(buf)
|
||||||
|
if bufname ~= "" then
|
||||||
|
-- Check if buffer is pinned using BufferLine's API
|
||||||
|
local ok, bufferline = pcall(require, "bufferline")
|
||||||
|
if ok and bufferline.get_elements then
|
||||||
|
local elements = bufferline.get_elements()
|
||||||
|
for _, element in ipairs(elements) do
|
||||||
|
if element.id == buf and element.pinned then
|
||||||
|
table.insert(pinned, bufname)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vim.g.session_pinned_buffers = pinned
|
||||||
|
end,
|
||||||
|
-- Restore pinned buffers after session load
|
||||||
|
after_load = function()
|
||||||
|
if vim.g.session_pinned_buffers then
|
||||||
|
vim.defer_fn(function()
|
||||||
|
for _, bufname in ipairs(vim.g.session_pinned_buffers) do
|
||||||
|
-- Find the buffer by name
|
||||||
|
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
||||||
|
if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_get_name(buf) == bufname then
|
||||||
|
-- Pin the buffer using BufferLine command
|
||||||
|
vim.cmd("BufferLineTogglePin " .. buf)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vim.g.session_pinned_buffers = nil
|
||||||
|
end, 100)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
rooter = {
|
rooter = {
|
||||||
|
-- list of detectors in order of prevalence, elements can be:
|
||||||
|
-- "lsp" : lsp detection
|
||||||
|
-- string[] : a list of directory patterns to look for
|
||||||
|
-- fun(bufnr: integer): string|string[] : a function that takes a buffer number and outputs detected roots
|
||||||
detector = {
|
detector = {
|
||||||
"lsp",
|
"lsp", -- highest priority is getting workspace from running language servers
|
||||||
{ ".git", "_darcs", ".hg", ".bzr", ".svn" },
|
{ ".git", "_darcs", ".hg", ".bzr", ".svn" }, -- next check for a version controlled parent directory
|
||||||
{
|
{
|
||||||
"package.json",
|
"package.json",
|
||||||
"lua",
|
"lua",
|
||||||
"Cargo.toml",
|
"Cargo.toml",
|
||||||
"Pipfile",
|
"Pipfile",
|
||||||
"docs.json",
|
"docs.json",
|
||||||
},
|
}, -- lastly check for a file named `main`
|
||||||
},
|
},
|
||||||
|
-- ignore things from root detection
|
||||||
ignore = {
|
ignore = {
|
||||||
-- golangci_lint_ls roots at the nearest go.mod, not the repo
|
servers = {
|
||||||
servers = { "golangci_lint_ls" },
|
"golangci_lint_ls",
|
||||||
dirs = {},
|
}, -- list of language server names to ignore (Ex. { "efm" })
|
||||||
|
dirs = {}, -- list of directory patterns (Ex. { "~/.cargo/*" })
|
||||||
},
|
},
|
||||||
|
-- automatically update working directory (update manually with `:AstroRoot`)
|
||||||
autochdir = true,
|
autochdir = true,
|
||||||
scope = "global",
|
-- scope of working directory to change ("global"|"tab"|"win")
|
||||||
|
scope = "global", -- Changed from "win" to "global" to make directory changes apply globally
|
||||||
|
-- show notification on every working directory change
|
||||||
notify = false,
|
notify = false,
|
||||||
},
|
},
|
||||||
|
|
||||||
-- v6: features schema changed
|
-- Configure core features of AstroNvim
|
||||||
-- diagnostics_mode (0-3) -> diagnostics = { virtual_text, virtual_lines }
|
|
||||||
-- large_buf and cmp are new keys
|
|
||||||
features = {
|
features = {
|
||||||
large_buf = { size = 1024 * 256, lines = 10000 },
|
autopairs = true, -- enable autopairs at start
|
||||||
autopairs = true,
|
diagnostics_mode = 1, -- diagnostic mode on start (0 = off, 1 = no signs/virtual text, 2 = no virtual text, 3 = on)
|
||||||
cmp = true,
|
highlighturl = true, -- highlight URLs at start
|
||||||
diagnostics = { virtual_text = false, virtual_lines = false },
|
notifications = false, -- enable notifications at start
|
||||||
highlighturl = true,
|
|
||||||
notifications = false,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
-- vim.diagnostic.config() values applied when diagnostics are toggled on
|
-- Diagnostics configuration (for vim.diagnostics.config({...})) when diagnostics are on
|
||||||
diagnostics = {
|
diagnostics = {
|
||||||
virtual_text = true,
|
virtual_text = true,
|
||||||
underline = true,
|
underline = true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
-- Autocmds can be configured here
|
||||||
autocmds = {
|
autocmds = {
|
||||||
|
-- Custom title formatting for ghq repos
|
||||||
custom_title = {
|
custom_title = {
|
||||||
{
|
{
|
||||||
event = { "BufEnter", "DirChanged" },
|
event = { "BufEnter", "DirChanged" },
|
||||||
@@ -188,6 +127,7 @@ return {
|
|||||||
local filepath = vim.fn.expand "%:p"
|
local filepath = vim.fn.expand "%:p"
|
||||||
local filename = vim.fn.expand "%:t"
|
local filename = vim.fn.expand "%:t"
|
||||||
|
|
||||||
|
-- If no file is open, try to show just the repo name
|
||||||
if filename == "" then
|
if filename == "" then
|
||||||
local cwd = vim.fn.getcwd()
|
local cwd = vim.fn.getcwd()
|
||||||
local github_match = cwd:match "github%.com/([^/]+/[^/]+)"
|
local github_match = cwd:match "github%.com/([^/]+/[^/]+)"
|
||||||
@@ -199,83 +139,78 @@ return {
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check if path contains github.com (ghq structure)
|
||||||
local github_match = filepath:match "github%.com/([^/]+/[^/]+)"
|
local github_match = filepath:match "github%.com/([^/]+/[^/]+)"
|
||||||
|
|
||||||
if github_match then
|
if github_match then
|
||||||
|
-- Format: filename | org/repo
|
||||||
vim.opt.titlestring = string.format("%s | %s", filename, github_match)
|
vim.opt.titlestring = string.format("%s | %s", filename, github_match)
|
||||||
else
|
else
|
||||||
|
-- Fallback: filename | Nvim
|
||||||
vim.opt.titlestring = string.format("%s | Nvim", filename)
|
vim.opt.titlestring = string.format("%s | Nvim", filename)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
git_branch_sessions = {
|
-- Show smart picker on startup
|
||||||
{
|
startup_picker = {
|
||||||
event = "VimLeavePre",
|
|
||||||
desc = "Save the worktree/branch directory session on close",
|
|
||||||
callback = vim.schedule_wrap(function()
|
|
||||||
if require("astrocore.buffer").is_valid_session() then
|
|
||||||
require("config.session").save { notify = false }
|
|
||||||
end
|
|
||||||
end),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
event = "VimEnter",
|
event = "VimEnter",
|
||||||
desc = "Show neo-tree; restore worktree/branch session, else open the git files picker",
|
desc = "Show smart picker on startup",
|
||||||
nested = true, -- trigger other autocommands as buffers open
|
callback = function()
|
||||||
-- no-op when nvim was started with file arguments, see config.session
|
-- Skip if opening with arguments
|
||||||
callback = function() require("config.session").restore() end,
|
if vim.fn.argc() > 0 then return end
|
||||||
|
|
||||||
|
-- Skip if current buffer has content
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||||
|
if #lines > 1 or (#lines == 1 and lines[1] ~= "") then return end
|
||||||
|
|
||||||
|
-- Delay to ensure everything is loaded
|
||||||
|
vim.defer_fn(function() require("snacks").picker.smart() end, 50)
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
-- vim options can be configured here
|
||||||
options = {
|
options = {
|
||||||
opt = {
|
opt = { -- vim.opt.<key>
|
||||||
relativenumber = false,
|
relativenumber = false, -- sets vim.opt.relativenumber
|
||||||
number = true,
|
number = true, -- sets vim.opt.number
|
||||||
spell = false,
|
spell = false, -- sets vim.opt.spell
|
||||||
wrap = true,
|
wrap = true, -- sets vim.opt.wrap
|
||||||
clipboard = "unnamedplus",
|
clipboard = "unnamedplus", -- use system clipboard for all operations
|
||||||
title = true,
|
title = true, -- enable title string
|
||||||
titlestring = "",
|
titlestring = "", -- will be set dynamically by autocommand
|
||||||
laststatus = 0, -- no statusline under the last window
|
laststatus = 0, -- disable statusline (0 = never show)
|
||||||
|
},
|
||||||
|
g = { -- vim.g.<key>
|
||||||
|
-- configure global vim variables (vim.g)
|
||||||
|
-- NOTE: `mapleader` and `maplocalleader` must be set in the AstroNvim opts or before `lazy.setup`
|
||||||
|
-- This can be found in the `lua/lazy_setup.lua` file
|
||||||
},
|
},
|
||||||
g = {},
|
|
||||||
},
|
},
|
||||||
|
-- Mappings can be configured through AstroCore as well.
|
||||||
|
-- NOTE: keycodes follow the casing in the vimdocs. For example, `<Leader>` must be capitalized
|
||||||
mappings = {
|
mappings = {
|
||||||
|
-- first key is the mode
|
||||||
n = {
|
n = {
|
||||||
["<C-q>"] = { close_win, desc = "Close window (quit if last)" },
|
|
||||||
["<C-!>"] = { "<Cmd>suspend<CR>", desc = "Suspend nvim (return with 'fg')" },
|
["<C-!>"] = { "<Cmd>suspend<CR>", desc = "Suspend nvim (return with 'fg')" },
|
||||||
["ZZ"] = { "<Cmd>wqa<CR>", desc = "Save all buffers and quit Neovim" },
|
["<C-q>Q"] = { "<Cmd>wqa<CR>", desc = "Save all buffers and quit Neovim" },
|
||||||
["<A-q>"] = { "<Cmd>wqa<CR>", desc = "Save all buffers and quit Neovim" },
|
-- navigate buffer tabs
|
||||||
["<C-c>"] = { save_close_buf, desc = "Save (if modified) and close buffer" },
|
["<C-c>"] = { "<Cmd>wa<CR><Cmd>bd<CR>", desc = "Save and close buffer" }, -- Added C-x to save and close buffer
|
||||||
["F"] = { "za", desc = "Toggle fold under cursor" },
|
["F"] = { "za", desc = "Toggle fold under cursor" },
|
||||||
-- v6: vim.diagnostic.goto_next/goto_prev deprecated in 0.11, use jump()
|
["J"] = { function() vim.diagnostic.goto_next() end, desc = "Next diagnostic" },
|
||||||
["J"] = {
|
["K"] = { function() vim.diagnostic.goto_prev() end, desc = "Previous diagnostic" },
|
||||||
function() vim.diagnostic.jump { count = 1, float = true } end,
|
|
||||||
desc = "Next diagnostic",
|
|
||||||
},
|
|
||||||
["K"] = {
|
|
||||||
function() vim.diagnostic.jump { count = -1, float = true } end,
|
|
||||||
desc = "Previous diagnostic",
|
|
||||||
},
|
|
||||||
["L"] = { "<Cmd>BufferLineCycleNext<CR>", desc = "Next buffer" },
|
["L"] = { "<Cmd>BufferLineCycleNext<CR>", desc = "Next buffer" },
|
||||||
["H"] = { "<Cmd>BufferLineCyclePrev<CR>", desc = "Previous buffer" },
|
["H"] = { "<Cmd>BufferLineCyclePrev<CR>", desc = "Previous buffer" },
|
||||||
["<Leader>e"] = {
|
-- Window navigation
|
||||||
function() require("snacks").picker.git_files { transform = file_filter } end,
|
["<C-h>"] = { function() require("kitty-navigator").navigateLeft() end, desc = "Navigate to left window" },
|
||||||
desc = "Find git files",
|
["<C-j>"] = { function() require("kitty-navigator").navigateDown() end, desc = "Navigate to bottom window" },
|
||||||
},
|
["<C-k>"] = { function() require("kitty-navigator").navigateUp() end, desc = "Navigate to top window" },
|
||||||
-- AstroNvim's dirsession maps key off the bare cwd; use the
|
["<C-l>"] = { function() require("kitty-navigator").navigateRight() end, desc = "Navigate to right window" },
|
||||||
-- worktree/branch-aware name instead
|
["<C-e>"] = { "<Cmd>Neotree toggle<CR>", desc = "Open Explorer" },
|
||||||
["<Leader>SS"] = {
|
["<C-m>"] = { "<Cmd>OverseerRun<CR>", desc = "Run Overseer" },
|
||||||
function() require("config.session").save() end,
|
|
||||||
desc = "Save this dirsession",
|
|
||||||
},
|
|
||||||
["<Leader>S."] = {
|
|
||||||
function() require("config.session").load() end,
|
|
||||||
desc = "Load current dirsession",
|
|
||||||
},
|
|
||||||
["<leader><leader>"] = {
|
["<leader><leader>"] = {
|
||||||
function() require("snacks").picker.smart() end,
|
function() require("snacks").picker.smart() end,
|
||||||
desc = "Find files",
|
desc = "Find files",
|
||||||
@@ -285,8 +220,14 @@ return {
|
|||||||
function() require("snacks").picker.buffers() end,
|
function() require("snacks").picker.buffers() end,
|
||||||
desc = "Find buffers",
|
desc = "Find buffers",
|
||||||
},
|
},
|
||||||
|
|
||||||
["<C-b><C-c>"] = {
|
["<C-b><C-c>"] = {
|
||||||
function() require("snacks").bufdelete.other() end,
|
function()
|
||||||
|
local current_buffer = vim.api.nvim_get_current_buf()
|
||||||
|
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
||||||
|
if buf ~= current_buffer then vim.api.nvim_buf_delete(buf, { force = true }) end
|
||||||
|
end
|
||||||
|
end,
|
||||||
desc = "Close all buffers except the current one",
|
desc = "Close all buffers except the current one",
|
||||||
},
|
},
|
||||||
["<C-b>g"] = {
|
["<C-b>g"] = {
|
||||||
@@ -298,68 +239,45 @@ return {
|
|||||||
end,
|
end,
|
||||||
desc = "Toggle all buffer groups",
|
desc = "Toggle all buffer groups",
|
||||||
},
|
},
|
||||||
|
-- Quick group toggles
|
||||||
["<C-b>l"] = {
|
["<C-b>l"] = {
|
||||||
function() require("snacks").picker.lines() end,
|
function() require("snacks").picker.lines() end,
|
||||||
desc = "Find in line",
|
desc = "Find in line",
|
||||||
},
|
},
|
||||||
["<C-g>o"] = { function() require("snacks").gitbrowse() end, desc = "Open Repo on Web" },
|
["<C-b>1"] = { "<Cmd>BufferLineGroupToggle DEX<CR>", desc = "Toggle DEX group" },
|
||||||
["<C-s>"] = {
|
["<C-b>2"] = { "<Cmd>BufferLineGroupToggle DID<CR>", desc = "Toggle DID group" },
|
||||||
|
["<C-b>3"] = { "<Cmd>BufferLineGroupToggle DWN<CR>", desc = "Toggle DWN group" },
|
||||||
|
["<C-b>4"] = { "<Cmd>BufferLineGroupToggle SVC<CR>", desc = "Toggle SVC group" },
|
||||||
|
["<C-b>t"] = { "<Cmd>BufferLineGroupToggle Tests<CR>", desc = "Toggle Tests group" },
|
||||||
|
["<C-b>k"] = { "<Cmd>BufferLinePickClose<CR>", desc = "Pick buffer to close" },
|
||||||
|
["<C-g>o"] = { "<cmd>!gh repo view --web<CR>", desc = "Open Repo on Web" },
|
||||||
|
["<C-g><C-d>"] = {
|
||||||
function()
|
function()
|
||||||
if vim.bo.modified and vim.bo.buftype == "" then
|
require("snacks").terminal("gh dash", {
|
||||||
vim.cmd "write"
|
hidden = true,
|
||||||
else
|
auto_close = true,
|
||||||
require("snacks").picker.buffers()
|
interactive = true,
|
||||||
end
|
})
|
||||||
end,
|
end,
|
||||||
desc = "Save if modified, else find buffers",
|
desc = "GH Dash",
|
||||||
|
},
|
||||||
|
["<C-g><C-c>"] = {
|
||||||
|
terminals.smartCommit_toggle,
|
||||||
|
desc = "SmartCommit Toggle",
|
||||||
},
|
},
|
||||||
["<C-w>"] = { "<Cmd>w<CR>", desc = "Save buffer" },
|
|
||||||
["<C-Tab>"] = { "<cmd>tabnext<CR>", desc = "Next Tab" },
|
["<C-Tab>"] = { "<cmd>tabnext<CR>", desc = "Next Tab" },
|
||||||
["<C-g><C-i>"] = {
|
["<C-d><C-d>"] = {
|
||||||
function() require("snacks").picker.gh_issue() end,
|
terminals.lazydocker_toggle,
|
||||||
desc = "GitHub issues (open)",
|
desc = "Lazydocker Toggle",
|
||||||
},
|
|
||||||
["<C-g>I"] = {
|
|
||||||
function() require("snacks").picker.gh_issue { state = "all" } end,
|
|
||||||
desc = "GitHub issues (all)",
|
|
||||||
},
|
|
||||||
["<C-g><C-p>"] = {
|
|
||||||
function() require("snacks").picker.gh_pr() end,
|
|
||||||
desc = "GitHub PRs (open)",
|
|
||||||
},
|
|
||||||
["<C-g>P"] = {
|
|
||||||
function() require("snacks").picker.gh_pr { state = "all" } end,
|
|
||||||
desc = "GitHub PRs (all)",
|
|
||||||
},
|
},
|
||||||
["<C-g><C-g>"] = {
|
["<C-g><C-g>"] = {
|
||||||
function() require("snacks").lazygit() end,
|
terminals.lazygit_toggle,
|
||||||
desc = "Toggle lazygit (float)",
|
desc = "Lazygit Toggle",
|
||||||
},
|
|
||||||
["<C-d><C-d>"] = {
|
|
||||||
function() require("snacks").terminal.toggle("lazydocker", { win = { style = "float" } }) end,
|
|
||||||
desc = "Toggle lazydocker (float)",
|
|
||||||
},
|
|
||||||
-- grug-far.nvim: buffer-based find & replace (plugins/grug-far.lua).
|
|
||||||
-- `open` reads the visual selection itself, so the same callback
|
|
||||||
-- serves both modes.
|
|
||||||
["<C-r>"] = { function() require("grug-far").open() end, desc = "Find & replace (grug-far)" },
|
|
||||||
-- which-key prefix group labels
|
|
||||||
["<C-g>"] = { desc = "Git + GitHub" },
|
|
||||||
["<C-f>"] = { desc = "Find" },
|
|
||||||
["<C-b>"] = { desc = "Buffers" },
|
|
||||||
["<C-a>"] = { desc = "Assist" },
|
|
||||||
-- ghq project switcher
|
|
||||||
["<C-f>P"] = {
|
|
||||||
function()
|
|
||||||
local repos = vim.fn.systemlist "ghq list -p"
|
|
||||||
vim.ui.select(repos, { prompt = "Select repository" }, function(dir)
|
|
||||||
if not dir then return end
|
|
||||||
vim.cmd.cd(dir)
|
|
||||||
require("snacks").picker.files()
|
|
||||||
end)
|
|
||||||
end,
|
|
||||||
desc = "Find ghq projects",
|
|
||||||
},
|
},
|
||||||
|
["<C-g><C-i>"] = { "<cmd>Octo issue list<CR>", desc = "List Issues" },
|
||||||
|
["<C-g><C-l>"] = { "<cmd>Octo label<CR>", desc = "Manage Labels" },
|
||||||
|
["<C-g><C-p>"] = { "<cmd>Octo pr list<CR>", desc = "List PRs" },
|
||||||
|
["<C-g>s"] = { "<cmd>Octo search<CR>", desc = "Octo Search" },
|
||||||
["<C-g><C-r>"] = { "<cmd>!gh release list<CR>", desc = "List Releases" },
|
["<C-g><C-r>"] = { "<cmd>!gh release list<CR>", desc = "List Releases" },
|
||||||
["<C-g>d"] = {
|
["<C-g>d"] = {
|
||||||
function() require("snacks").picker.git_diff() end,
|
function() require("snacks").picker.git_diff() end,
|
||||||
@@ -371,6 +289,10 @@ return {
|
|||||||
},
|
},
|
||||||
["vv"] = { "gg0VG$", desc = "Select all contents in buffer" },
|
["vv"] = { "gg0VG$", desc = "Select all contents in buffer" },
|
||||||
["T"] = { "gg", desc = "Go to top of file" },
|
["T"] = { "gg", desc = "Go to top of file" },
|
||||||
|
["<C-f>r"] = {
|
||||||
|
terminals.scooter_toggle,
|
||||||
|
desc = "Find and replace",
|
||||||
|
},
|
||||||
["<C-f>d"] = {
|
["<C-f>d"] = {
|
||||||
function() require("snacks").picker.diagnostics() end,
|
function() require("snacks").picker.diagnostics() end,
|
||||||
desc = "Find diagnostics",
|
desc = "Find diagnostics",
|
||||||
@@ -379,182 +301,241 @@ return {
|
|||||||
function() require("snacks").picker.lines() end,
|
function() require("snacks").picker.lines() end,
|
||||||
desc = "Find in line",
|
desc = "Find in line",
|
||||||
},
|
},
|
||||||
-- NOTE: <C-i> and <Tab> are the same key at the mapping level, so
|
["<C-f>t"] = {
|
||||||
-- this also shadows <Tab> (jumplist-forward) in normal mode
|
function() require("snacks").picker.lines() end,
|
||||||
["<C-i>"] = {
|
desc = "Find in line",
|
||||||
function() require("config.templates").pick() end,
|
|
||||||
desc = "Find templates",
|
|
||||||
},
|
},
|
||||||
["<C-f>w"] = {
|
["<C-f>w"] = {
|
||||||
function() require("fff").live_grep() end,
|
function() require("snacks").picker.grep() end,
|
||||||
desc = "Live grep",
|
desc = "Find word",
|
||||||
},
|
|
||||||
["<C-f>z"] = {
|
|
||||||
function() require("fff").live_grep { grep = { modes = { "fuzzy", "plain" } } } end,
|
|
||||||
desc = "Live fuzzy grep",
|
|
||||||
},
|
|
||||||
["<C-f><C-w>"] = {
|
|
||||||
function() require("fff").live_grep_under_cursor() end,
|
|
||||||
desc = "Grep word under cursor",
|
|
||||||
},
|
},
|
||||||
["<C-f>f"] = {
|
["<C-f>f"] = {
|
||||||
function() require("fff").find_files() end,
|
function() require("snacks").picker.git_files { transform = file_filter } end,
|
||||||
desc = "Find files (cwd)",
|
desc = "Find files",
|
||||||
},
|
|
||||||
["<C-f>m"] = {
|
|
||||||
find_files_modified_today,
|
|
||||||
desc = "Find files modified today",
|
|
||||||
},
|
|
||||||
["<C-f>x"] = {
|
|
||||||
find_buffer_urls,
|
|
||||||
desc = "Find URLs in buffer (open in browser)",
|
|
||||||
},
|
|
||||||
["<C-f>r"] = {
|
|
||||||
function() require("fff").resume() end,
|
|
||||||
desc = "Resume last find/grep",
|
|
||||||
},
|
|
||||||
["<C-f>."] = {
|
|
||||||
function() require("fff").find_files_in_dir(vim.fn.expand "%:p:h") end,
|
|
||||||
desc = "Find files in buffer directory",
|
|
||||||
},
|
|
||||||
["<C-f>e"] = {
|
|
||||||
find_files_by_extension,
|
|
||||||
desc = "Find files by extension",
|
|
||||||
},
|
},
|
||||||
["<C-f>o"] = {
|
["<C-f>o"] = {
|
||||||
function() require("snacks").picker.recent() end,
|
function() require("snacks").picker.recent() end,
|
||||||
desc = "Find recent files",
|
desc = "Find recent files",
|
||||||
},
|
},
|
||||||
["<C-f>p"] = {
|
|
||||||
function() require("snacks").picker.projects() end,
|
|
||||||
desc = "Find projects",
|
|
||||||
},
|
|
||||||
["<C-f>gc"] = {
|
["<C-f>gc"] = {
|
||||||
function()
|
function()
|
||||||
local ai_dir = vim.fn.getcwd() .. "/.ai"
|
local ai_dir = vim.fn.getcwd() .. "/.ai"
|
||||||
|
-- Check if directory exists
|
||||||
if vim.fn.isdirectory(ai_dir) == 0 then
|
if vim.fn.isdirectory(ai_dir) == 0 then
|
||||||
vim.notify("No .ai directory found in current project", vim.log.levels.WARN)
|
vim.notify("No .ai directory found in current project", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
require("fff").find_files_in_dir(ai_dir)
|
-- Use files picker to find AI files
|
||||||
|
require("snacks").picker.files {
|
||||||
|
cwd = ai_dir,
|
||||||
|
transform = file_filter,
|
||||||
|
}
|
||||||
end,
|
end,
|
||||||
desc = "Find AI files",
|
desc = "Find AI files",
|
||||||
},
|
},
|
||||||
["<C-f>g"] = { desc = "Find by Group" },
|
["<C-f>p"] = { desc = "Find Package files" },
|
||||||
|
["<C-f>m"] = { desc = "Find Module files" },
|
||||||
|
["<C-f>g"] = {
|
||||||
|
desc = "Find by Group",
|
||||||
|
},
|
||||||
["<C-f>gw"] = {
|
["<C-f>gw"] = {
|
||||||
function()
|
function()
|
||||||
local workflows_dir = vim.fn.getcwd() .. "/.github/workflows"
|
local workflows_dir = vim.fn.getcwd() .. "/.github/workflows"
|
||||||
|
-- Check if directory exists
|
||||||
if vim.fn.isdirectory(workflows_dir) == 0 then
|
if vim.fn.isdirectory(workflows_dir) == 0 then
|
||||||
vim.notify("No .github/workflows directory found in current project", vim.log.levels.WARN)
|
vim.notify("No .github/workflows directory found in current project", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
require("fff").find_files_in_dir(workflows_dir)
|
-- Use files picker to find workflow files
|
||||||
|
require("snacks").picker.files {
|
||||||
|
cwd = workflows_dir,
|
||||||
|
transform = file_filter,
|
||||||
|
}
|
||||||
end,
|
end,
|
||||||
desc = "Find GitHub Workflow files",
|
desc = "Find GitHub Workflow files",
|
||||||
},
|
},
|
||||||
["<C-f>gd"] = {
|
["<C-f>gd"] = {
|
||||||
function()
|
function()
|
||||||
require("fff").find_files {
|
require("snacks").picker.files {
|
||||||
title = "Devbox/Docker files",
|
cmd = "fd",
|
||||||
query = "{devbox.json,process-compose.yaml,Dockerfile,docker-compose.yaml,compose.yaml} ",
|
args = {
|
||||||
|
"devbox.json|process-compose.yaml|Dockerfile|docker-compose.yaml|compose.yaml",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
end,
|
end,
|
||||||
desc = "Find Devbox/Docker files",
|
desc = "Find Devbox/Docker files",
|
||||||
},
|
},
|
||||||
["<C-f>gm"] = {
|
["<C-f>gm"] = {
|
||||||
function() require("fff").find_files { title = "Makefiles", query = "*Makefile* " } end,
|
function()
|
||||||
|
require("snacks").picker.files {
|
||||||
|
cmd = "fd",
|
||||||
|
args = {
|
||||||
|
"Makefile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end,
|
||||||
desc = "Find Makefile files",
|
desc = "Find Makefile files",
|
||||||
},
|
},
|
||||||
["<C-f>gp"] = {
|
["<C-f>gp"] = {
|
||||||
function() require("fff").find_files { title = "package.json files", query = "*package.json " } end,
|
function()
|
||||||
|
require("snacks").picker.files {
|
||||||
|
cmd = "fd",
|
||||||
|
args = {
|
||||||
|
"package.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end,
|
||||||
desc = "Find Package.json files",
|
desc = "Find Package.json files",
|
||||||
},
|
},
|
||||||
["<C-p>"] = {
|
["<C-p>"] = {
|
||||||
function()
|
function()
|
||||||
|
-- Pass through to TUI if in opencode
|
||||||
if vim.b.toggle_number == 99 then
|
if vim.b.toggle_number == 99 then
|
||||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-p>", true, false, true), "t", false)
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-p>", true, false, true), "t", false)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- Paste from clipboard in other terminals
|
||||||
local content = vim.fn.getreg "+"
|
local content = vim.fn.getreg "+"
|
||||||
vim.api.nvim_paste(content, true, -1)
|
vim.api.nvim_paste(content, true, -1)
|
||||||
end,
|
end,
|
||||||
desc = "Paste from system clipboard",
|
desc = "Paste from system clipboard",
|
||||||
},
|
},
|
||||||
-- LSP-scoped assist binds (<C-a>a/s/S) are capability-gated in
|
["<C-j><C-j>"] = { function() terminals.scratch_toggle() end, desc = "Open Scratch Terminal" },
|
||||||
-- plugins/astrolsp.lua; diagnostics exist without a server, so this
|
-- AI/Assistant mappings (Avante)
|
||||||
-- one stays global
|
["<C-a><C-a>"] = { terminals.opencode_toggle, desc = "Toggle OpenCode" },
|
||||||
["<C-a>d"] = {
|
-- UI Toggles with Snacks
|
||||||
function() require("config.picker").with_trouble "diagnostics" end,
|
|
||||||
desc = "Search diagnostics (<C-t> sends to Trouble)",
|
|
||||||
},
|
|
||||||
["<C-a><C-s>"] = { "<Cmd>SupermavenToggle<CR>", desc = "Toggle Supermaven" },
|
|
||||||
["<C-a><C-a>"] = { function() require("config.omp_term").toggle() end, desc = "Toggle Oh My Pi" },
|
|
||||||
|
|
||||||
-- <Leader>u toggles are mapped through Snacks.toggle in
|
|
||||||
-- config.toggles (called from polish.lua) so which-key renders their
|
|
||||||
-- enabled/disabled state
|
|
||||||
["<leader>u"] = { desc = "UI Toggles" },
|
["<leader>u"] = { desc = "UI Toggles" },
|
||||||
|
["<leader>ud"] = {
|
||||||
|
function() require("snacks").toggle.diagnostics():toggle() end,
|
||||||
|
desc = "Toggle Diagnostics",
|
||||||
|
},
|
||||||
|
["<leader>un"] = {
|
||||||
|
function() require("snacks").toggle.line_number():toggle() end,
|
||||||
|
desc = "Toggle Line Numbers",
|
||||||
|
},
|
||||||
|
["<leader>ur"] = {
|
||||||
|
function() require("snacks").toggle.option("relativenumber"):toggle() end,
|
||||||
|
desc = "Toggle Relative Numbers",
|
||||||
|
},
|
||||||
|
["<leader>uw"] = {
|
||||||
|
function() require("snacks").toggle.option("wrap"):toggle() end,
|
||||||
|
desc = "Toggle Line Wrap",
|
||||||
|
},
|
||||||
|
["<leader>us"] = {
|
||||||
|
function() require("snacks").toggle.option("spell"):toggle() end,
|
||||||
|
desc = "Toggle Spelling",
|
||||||
|
},
|
||||||
|
["<leader>ui"] = {
|
||||||
|
function() require("snacks").toggle.inlay_hints():toggle() end,
|
||||||
|
desc = "Toggle Inlay Hints",
|
||||||
|
},
|
||||||
|
["<leader>ut"] = {
|
||||||
|
function() require("snacks").toggle.treesitter():toggle() end,
|
||||||
|
desc = "Toggle Treesitter",
|
||||||
|
},
|
||||||
|
["<leader>uc"] = {
|
||||||
|
function() require("snacks").toggle.option("conceallevel", { off = 0, on = 2 }):toggle() end,
|
||||||
|
desc = "Toggle Conceal",
|
||||||
|
},
|
||||||
|
["<leader>uI"] = {
|
||||||
|
function() require("snacks").toggle.indent():toggle() end,
|
||||||
|
desc = "Toggle Indent Guides",
|
||||||
|
},
|
||||||
|
["<leader>uz"] = {
|
||||||
|
function() require("snacks").toggle.zen():toggle() end,
|
||||||
|
desc = "Toggle Zen Mode",
|
||||||
|
},
|
||||||
|
["<leader>uZ"] = {
|
||||||
|
function() require("snacks").toggle.zoom():toggle() end,
|
||||||
|
desc = "Toggle Zoom",
|
||||||
|
},
|
||||||
|
["<leader>ua"] = {
|
||||||
|
function() require("snacks").toggle.animate():toggle() end,
|
||||||
|
desc = "Toggle Animations",
|
||||||
|
},
|
||||||
|
["<leader>uS"] = {
|
||||||
|
function() require("snacks").toggle.scroll():toggle() end,
|
||||||
|
desc = "Toggle Smooth Scroll",
|
||||||
|
},
|
||||||
|
["<leader>up"] = {
|
||||||
|
function() require("snacks").toggle.profiler():toggle() end,
|
||||||
|
desc = "Toggle Profiler",
|
||||||
|
},
|
||||||
|
["<leader>uh"] = {
|
||||||
|
function() require("snacks").toggle.profiler_highlights():toggle() end,
|
||||||
|
desc = "Toggle Profiler Highlights",
|
||||||
|
},
|
||||||
|
["<leader>ux"] = {
|
||||||
|
function() require("snacks").toggle.dim():toggle() end,
|
||||||
|
desc = "Toggle Dim Inactive",
|
||||||
|
},
|
||||||
|
["<leader>uf"] = {
|
||||||
|
function()
|
||||||
|
vim.g.disable_autoformat = not vim.g.disable_autoformat
|
||||||
|
local status = vim.g.disable_autoformat and "disabled" or "enabled"
|
||||||
|
vim.notify("Format on save " .. status, vim.log.levels.INFO)
|
||||||
|
end,
|
||||||
|
desc = "Toggle Format on Save",
|
||||||
|
},
|
||||||
|
["<leader>ul"] = {
|
||||||
|
function()
|
||||||
|
local new_value = not require("lsp_lines").toggle()
|
||||||
|
vim.diagnostic.config { virtual_text = new_value }
|
||||||
|
end,
|
||||||
|
desc = "Toggle LSP Lines",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
i = {
|
i = {
|
||||||
["<C-s>"] = { "<Cmd>wa<CR><Esc>", desc = "Save buffer, and return to normal mode" },
|
["<C-c>"] = { "<Cmd>wa<CR><Cmd>bd<CR><Esc>", desc = "Save, close buffer, and return to normal mode" },
|
||||||
["<C-c>"] = {
|
["<C-x>"] = { "<Cmd>wa<CR><Cmd>bd<CR><Esc>", desc = "Save, close buffer, and return to normal mode" }, -- Added C-x for insert mode
|
||||||
function()
|
|
||||||
vim.cmd "stopinsert"
|
|
||||||
save_close_buf()
|
|
||||||
end,
|
|
||||||
desc = "Save (if modified), close buffer, and return to normal mode",
|
|
||||||
},
|
|
||||||
["<C-x>"] = {
|
|
||||||
function()
|
|
||||||
vim.cmd "stopinsert"
|
|
||||||
vim.cmd "wa"
|
|
||||||
require("snacks").bufdelete()
|
|
||||||
end,
|
|
||||||
desc = "Save, close buffer, and return to normal mode",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
v = {
|
v = {
|
||||||
["<C-c>"] = { save_close_buf, desc = "Save (if modified) and close buffer" },
|
["<C-e>"] = { "<Cmd>Neotree toggle<CR>", desc = "Open Explorer" },
|
||||||
["<C-x>"] = {
|
["<C-c>"] = { "<Cmd>w<CR><Cmd>bd<CR>", desc = "Save and close buffer" },
|
||||||
function()
|
["<C-x>"] = { "<Cmd>w<CR><Cmd>bd<CR>", desc = "Save and close buffer" },
|
||||||
vim.cmd "w"
|
-- Avante visual mode mappings
|
||||||
require("snacks").bufdelete()
|
["<C-a>"] = { "<cmd>AvanteAsk<cr>", desc = "Ask Avante with selection" },
|
||||||
end,
|
["<C-a>e"] = { "<cmd>AvanteEdit<cr>", desc = "Edit selection with Avante" },
|
||||||
desc = "Save and close buffer",
|
["<C-a>r"] = { "<cmd>AvanteReplace<cr>", desc = "Replace with Avante" },
|
||||||
},
|
|
||||||
["<C-f><C-w>"] = {
|
|
||||||
function() require("fff").live_grep_under_cursor() end,
|
|
||||||
desc = "Grep selection",
|
|
||||||
},
|
|
||||||
-- charwise (v) selection prefills the search input, linewise (V)
|
|
||||||
-- limits search & replace to the selected range (visualSelectionUsage
|
|
||||||
-- = "auto-detect" in plugins/grug-far.lua)
|
|
||||||
["<C-r>"] = {
|
|
||||||
function() require("grug-far").open() end,
|
|
||||||
desc = "Find & replace selection (grug-far)",
|
|
||||||
},
|
|
||||||
["D"] = {
|
["D"] = {
|
||||||
function()
|
function()
|
||||||
|
-- Yank the visual selection to register v
|
||||||
vim.cmd 'normal! "vy'
|
vim.cmd 'normal! "vy'
|
||||||
local selection = vim.fn.getreg "v"
|
local selection = vim.fn.getreg "v"
|
||||||
|
|
||||||
|
-- Escape special regex characters
|
||||||
local escaped = vim.fn.escape(selection, [[/\.*$^~[]])
|
local escaped = vim.fn.escape(selection, [[/\.*$^~[]])
|
||||||
|
|
||||||
|
-- Delete all occurrences in the buffer (e flag suppresses errors if no match)
|
||||||
vim.cmd(string.format([[%%s/%s//ge]], escaped))
|
vim.cmd(string.format([[%%s/%s//ge]], escaped))
|
||||||
|
|
||||||
|
-- Show notification of what was deleted
|
||||||
vim.notify(string.format("Deleted all occurrences of: %s", selection), vim.log.levels.INFO)
|
vim.notify(string.format("Deleted all occurrences of: %s", selection), vim.log.levels.INFO)
|
||||||
end,
|
end,
|
||||||
desc = "Delete all occurrences of selected text",
|
desc = "Delete all occurrences of selected text",
|
||||||
},
|
},
|
||||||
["R"] = {
|
["R"] = {
|
||||||
function()
|
function()
|
||||||
|
-- Yank the visual selection to register v
|
||||||
vim.cmd 'normal! "vy'
|
vim.cmd 'normal! "vy'
|
||||||
local selection = vim.fn.getreg "v"
|
local selection = vim.fn.getreg "v"
|
||||||
|
|
||||||
|
-- Escape special regex characters for search pattern
|
||||||
local escaped = vim.fn.escape(selection, [[/\.*$^~[]])
|
local escaped = vim.fn.escape(selection, [[/\.*$^~[]])
|
||||||
|
|
||||||
|
-- Show input prompt using snacks.input
|
||||||
require("snacks").input({
|
require("snacks").input({
|
||||||
prompt = string.format("Replace '%s' with:", selection),
|
prompt = string.format("Replace '%s' with:", selection),
|
||||||
default = "",
|
default = "",
|
||||||
}, function(replacement)
|
}, function(replacement)
|
||||||
if replacement then
|
if replacement then
|
||||||
|
-- Escape special characters for replacement string
|
||||||
local escaped_replacement = vim.fn.escape(replacement, [[/\&~]])
|
local escaped_replacement = vim.fn.escape(replacement, [[/\&~]])
|
||||||
|
|
||||||
|
-- Replace all occurrences in the buffer
|
||||||
vim.cmd(string.format([[%%s/%s/%s/ge]], escaped, escaped_replacement))
|
vim.cmd(string.format([[%%s/%s/%s/ge]], escaped, escaped_replacement))
|
||||||
|
|
||||||
|
-- Show notification
|
||||||
vim.notify(string.format("Replaced '%s' with '%s'", selection, replacement), vim.log.levels.INFO)
|
vim.notify(string.format("Replaced '%s' with '%s'", selection, replacement), vim.log.levels.INFO)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -563,17 +544,48 @@ return {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
t = {
|
t = {
|
||||||
|
["<C-a><C-a>"] = { terminals.opencode_toggle, desc = "Toggle OpenCode" },
|
||||||
|
["<C-t><C-t>"] = { terminals.mk_toggle, desc = "Taskfile Toggle" },
|
||||||
|
["<C-e>"] = { "<Cmd>Neotree toggle<CR>", desc = "Open Explorer" },
|
||||||
|
-- Paste from system clipboard
|
||||||
["<C-p>"] = { function() vim.cmd 'normal! "+p' end, desc = "Paste from system clipboard" },
|
["<C-p>"] = { function() vim.cmd 'normal! "+p' end, desc = "Paste from system clipboard" },
|
||||||
["<C-b>f"] = {
|
["<C-b>f"] = {
|
||||||
function() require("snacks").picker.buffers() end,
|
function() require("snacks").picker.buffers() end,
|
||||||
desc = "Find buffers",
|
desc = "Find buffers",
|
||||||
},
|
},
|
||||||
|
["<C-j><C-j>"] = { function() terminals.scratch_toggle() end, desc = "Open Scratch Terminal" },
|
||||||
|
-- Exit terminal mode and close window
|
||||||
["<C-q>"] = {
|
["<C-q>"] = {
|
||||||
function()
|
function()
|
||||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[<C-\><C-n>]], true, false, true), "n", false)
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[<C-\><C-n>]], true, false, true), "n", false)
|
||||||
close_win()
|
vim.cmd "close"
|
||||||
end,
|
end,
|
||||||
desc = "Exit terminal and close window (quit if last)",
|
desc = "Exit terminal and close window",
|
||||||
|
},
|
||||||
|
-- Exit terminal mode and close window
|
||||||
|
["<C-h>"] = {
|
||||||
|
function()
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[<C-\><C-n><C-w>h]], true, false, true), "n", false)
|
||||||
|
end,
|
||||||
|
desc = "Navigate to left window",
|
||||||
|
},
|
||||||
|
["<C-j>"] = {
|
||||||
|
function()
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[<C-\><C-n><C-w>j]], true, false, true), "n", false)
|
||||||
|
end,
|
||||||
|
desc = "Navigate to bottom window",
|
||||||
|
},
|
||||||
|
["<C-k>"] = {
|
||||||
|
function()
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[<C-\><C-n><C-w>k]], true, false, true), "n", false)
|
||||||
|
end,
|
||||||
|
desc = "Navigate to top window",
|
||||||
|
},
|
||||||
|
["<C-l>"] = {
|
||||||
|
function()
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[<C-\><C-n><C-w>l]], true, false, true), "n", false)
|
||||||
|
end,
|
||||||
|
desc = "Navigate to right window",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+695
-56
@@ -1,68 +1,707 @@
|
|||||||
--- AstroLSP: the configuration that should only exist while a language server
|
--- Comprehensive LSP, formatting, and linting configuration
|
||||||
--- is attached (`:h astrolsp`). Every mapping carries a `cond` — a server
|
--- Optimized for tech stack:
|
||||||
--- method name — so it is created buffer-locally on attach and is simply
|
--- - Cloudflare Workers/Pages
|
||||||
--- absent (including in which-key) wherever no server advertises it, instead
|
--- - Golang (Cosmos SDK)
|
||||||
--- of being a global bind that errors or silently does nothing.
|
--- - TypeScript/React/Vite
|
||||||
---
|
--- - Protobufs
|
||||||
--- Deliberately configured elsewhere:
|
--- - Bun/ES Modules/WASM
|
||||||
--- * per-server settings: basedpyright interpreter resolution lives in
|
|
||||||
--- plugins/python.lua, the language packs in community.lua own the rest
|
|
||||||
--- * formatting: conform owns it end to end. The astrocommunity conform
|
|
||||||
--- pack sets `formatting.disabled = true` here, and the pre-save code
|
|
||||||
--- action pass hangs off conform's `format_on_save` hook so its order
|
|
||||||
--- relative to formatting is deterministic (plugins/conform.lua)
|
|
||||||
--- * diagnostics jumps/pickers (`J`, `K`, `<C-a>d`) and everything else that
|
|
||||||
--- works without a server: plugins/astrocore.lua
|
|
||||||
--- @module plugins.astrolsp
|
--- @module plugins.astrolsp
|
||||||
|
|
||||||
--- @param source string snacks.picker source
|
|
||||||
--- @return function
|
|
||||||
local function trouble_picker(source)
|
|
||||||
return function() require("config.picker").with_trouble(source) end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
return {
|
||||||
"AstroNvim/astrolsp",
|
-- ============================================================================
|
||||||
---@type AstroLSPOpts
|
-- AstroLSP - Core LSP Configuration
|
||||||
opts = {
|
-- ============================================================================
|
||||||
mappings = {
|
{
|
||||||
n = {
|
"AstroNvim/astrolsp",
|
||||||
-- snacks.words auto-highlights the references of the symbol under the
|
---@type AstroLSPOpts
|
||||||
-- cursor (`words` in plugins/user.lua); these cycle through them,
|
opts = {
|
||||||
-- complementing the core astrolsp `gd` / `gr` binds
|
-- Enable LSP features
|
||||||
["]]"] = {
|
features = {
|
||||||
function() require("snacks").words.jump(vim.v.count1, true) end,
|
autoformat = true,
|
||||||
desc = "Next reference",
|
codelens = true,
|
||||||
cond = "textDocument/documentHighlight",
|
inlay_hints = true, -- Enable inlay hints for TS/Go
|
||||||
|
lsp_handlers = true,
|
||||||
|
semantic_tokens = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP UI defaults
|
||||||
|
defaults = {
|
||||||
|
hover = { border = "rounded", silent = true }, -- Rounded border for hover windows
|
||||||
|
signature_help = { border = "rounded", silent = true }, -- Enable signature help
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP client flags
|
||||||
|
flags = {
|
||||||
|
exit_timeout = 5000, -- Time to wait for LSP server to exit before force kill
|
||||||
|
debounce_text_changes = 150, -- Debounce didChange notifications
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP Formatting Configuration
|
||||||
|
formatting = {
|
||||||
|
format_on_save = {
|
||||||
|
enabled = true,
|
||||||
|
-- Allow specific filetypes (prefer conform.nvim for JS/TS)
|
||||||
|
allow_filetypes = {
|
||||||
|
"go",
|
||||||
|
"proto",
|
||||||
|
"toml",
|
||||||
|
"lua",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["[["] = {
|
-- Disable LSP formatting for these (use conform.nvim/oxfmt instead)
|
||||||
function() require("snacks").words.jump(-vim.v.count1, true) end,
|
disabled = {
|
||||||
desc = "Previous reference",
|
"ts_ls", -- Use oxfmt via conform
|
||||||
cond = "textDocument/documentHighlight",
|
"tsserver", -- Old name, keeping for compatibility
|
||||||
|
"vtsls", -- Use oxfmt via conform
|
||||||
|
"html", -- Use conform
|
||||||
|
"cssls", -- Use conform
|
||||||
|
"tailwindcss", -- Doesn't format
|
||||||
|
},
|
||||||
|
timeout_ms = 5000, -- Increase timeout for large Go files
|
||||||
|
filter = function(client)
|
||||||
|
-- Prefer conform.nvim for JS/TS formatting
|
||||||
|
if
|
||||||
|
vim.bo.filetype == "typescript"
|
||||||
|
or vim.bo.filetype == "javascript"
|
||||||
|
or vim.bo.filetype == "typescriptreact"
|
||||||
|
or vim.bo.filetype == "javascriptreact"
|
||||||
|
then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- Use gopls for Go formatting
|
||||||
|
if vim.bo.filetype == "go" then return client.name == "gopls" end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Enable file operations for better refactoring
|
||||||
|
file_operations = {
|
||||||
|
timeout = 10000,
|
||||||
|
operations = {
|
||||||
|
willCreate = true,
|
||||||
|
didCreate = true,
|
||||||
|
willRename = true,
|
||||||
|
didRename = true,
|
||||||
|
willDelete = true,
|
||||||
|
didDelete = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Language server configurations
|
||||||
|
config = {
|
||||||
|
-- Golang configuration optimized for Cosmos SDK
|
||||||
|
gopls = {
|
||||||
|
settings = {
|
||||||
|
gopls = {
|
||||||
|
-- Code analysis
|
||||||
|
analyses = {
|
||||||
|
unusedparams = true,
|
||||||
|
shadow = true,
|
||||||
|
nilness = true,
|
||||||
|
unusedwrite = true,
|
||||||
|
useany = true,
|
||||||
|
unusedvariable = true,
|
||||||
|
},
|
||||||
|
-- Staticcheck integration
|
||||||
|
staticcheck = true,
|
||||||
|
-- Inlay hints
|
||||||
|
hints = {
|
||||||
|
assignVariableTypes = true,
|
||||||
|
compositeLiteralFields = true,
|
||||||
|
compositeLiteralTypes = true,
|
||||||
|
constantValues = true,
|
||||||
|
functionTypeParameters = true,
|
||||||
|
parameterNames = true,
|
||||||
|
rangeVariableTypes = true,
|
||||||
|
},
|
||||||
|
-- Code completion
|
||||||
|
usePlaceholders = true,
|
||||||
|
completeUnimported = true,
|
||||||
|
-- Build flags for Cosmos SDK
|
||||||
|
buildFlags = { "-tags=ledger,test" },
|
||||||
|
-- Workspace settings
|
||||||
|
directoryFilters = {
|
||||||
|
"-**/node_modules",
|
||||||
|
"-**/.git",
|
||||||
|
"-**/.vscode",
|
||||||
|
"-**/.idea",
|
||||||
|
"-**/.devbox",
|
||||||
|
"-**/dist",
|
||||||
|
"-**/build",
|
||||||
|
},
|
||||||
|
-- Semantic tokens
|
||||||
|
semanticTokens = true,
|
||||||
|
-- Code lenses
|
||||||
|
codelenses = {
|
||||||
|
gc_details = true,
|
||||||
|
generate = true,
|
||||||
|
regenerate_cgo = true,
|
||||||
|
test = true,
|
||||||
|
tidy = true,
|
||||||
|
upgrade_dependency = true,
|
||||||
|
vendor = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
-- <C-a>: assist. Code actions and symbol search, each with the
|
-- TypeScript configuration for Vite/React/Cloudflare
|
||||||
-- trouble.nvim hand-off on <C-t> where a picker is involved.
|
ts_ls = {
|
||||||
["<C-a>a"] = {
|
settings = {
|
||||||
function() vim.lsp.buf.code_action() end,
|
typescript = {
|
||||||
desc = "Show code actions",
|
inlayHints = {
|
||||||
cond = "textDocument/codeAction",
|
includeInlayParameterNameHints = "all",
|
||||||
|
includeInlayParameterNameHintsWhenArgumentMatchesName = true,
|
||||||
|
includeInlayFunctionParameterTypeHints = true,
|
||||||
|
includeInlayVariableTypeHints = true,
|
||||||
|
includeInlayVariableTypeHintsWhenTypeMatchesName = true,
|
||||||
|
includeInlayPropertyDeclarationTypeHints = true,
|
||||||
|
includeInlayFunctionLikeReturnTypeHints = true,
|
||||||
|
includeInlayEnumMemberValueHints = true,
|
||||||
|
},
|
||||||
|
suggest = {
|
||||||
|
completeFunctionCalls = true,
|
||||||
|
},
|
||||||
|
-- Cloudflare Workers types
|
||||||
|
preferences = {
|
||||||
|
importModuleSpecifier = "relative",
|
||||||
|
importModuleSpecifierEnding = "minimal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
javascript = {
|
||||||
|
inlayHints = {
|
||||||
|
includeInlayParameterNameHints = "all",
|
||||||
|
includeInlayParameterNameHintsWhenArgumentMatchesName = true,
|
||||||
|
includeInlayFunctionParameterTypeHints = true,
|
||||||
|
includeInlayVariableTypeHints = true,
|
||||||
|
includeInlayPropertyDeclarationTypeHints = true,
|
||||||
|
includeInlayFunctionLikeReturnTypeHints = true,
|
||||||
|
includeInlayEnumMemberValueHints = true,
|
||||||
|
},
|
||||||
|
suggest = {
|
||||||
|
completeFunctionCalls = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["<C-a>s"] = {
|
|
||||||
trouble_picker "lsp_symbols",
|
-- Volar for Vue (also helpful for some React scenarios)
|
||||||
desc = "Search LSP symbols (<C-t> sends to Trouble)",
|
volar = {
|
||||||
cond = "textDocument/documentSymbol",
|
filetypes = { "vue", "typescript", "javascript", "typescriptreact", "javascriptreact" },
|
||||||
},
|
},
|
||||||
["<C-a>S"] = {
|
|
||||||
trouble_picker "lsp_workspace_symbols",
|
-- Tailwind CSS for React/shadcn components
|
||||||
desc = "Search LSP workspace symbols (<C-t> sends to Trouble)",
|
tailwindcss = {
|
||||||
cond = "workspace/symbol",
|
root_dir = function(fname)
|
||||||
|
local util = require "lspconfig.util"
|
||||||
|
return util.root_pattern("tailwind.config.js", "tailwind.config.ts", "postcss.config.js")(fname)
|
||||||
|
end,
|
||||||
|
settings = {
|
||||||
|
tailwindCSS = {
|
||||||
|
experimental = {
|
||||||
|
classRegex = {
|
||||||
|
-- Support for cn() and clsx() utilities
|
||||||
|
{ "cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" },
|
||||||
|
{ "clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" },
|
||||||
|
{ "cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validate = true,
|
||||||
|
lint = {
|
||||||
|
cssConflict = "warning",
|
||||||
|
invalidApply = "error",
|
||||||
|
invalidScreen = "error",
|
||||||
|
invalidVariant = "error",
|
||||||
|
invalidConfigPath = "error",
|
||||||
|
invalidTailwindDirective = "error",
|
||||||
|
recommendedVariantOrder = "warning",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["<C-f><C-f>"] = {
|
|
||||||
function() require("snacks").picker.lsp_symbols { layout = { preset = "vscode", preview = "main" } } end,
|
-- CSS LSP
|
||||||
desc = "Find LSP symbols",
|
cssls = {},
|
||||||
cond = "textDocument/documentSymbol",
|
|
||||||
|
-- HTML for React/templates
|
||||||
|
html = {
|
||||||
|
filetypes = { "html", "typescriptreact", "javascriptreact" },
|
||||||
|
settings = {
|
||||||
|
html = {
|
||||||
|
format = {
|
||||||
|
templating = true,
|
||||||
|
wrapLineLength = 100,
|
||||||
|
wrapAttributes = "auto",
|
||||||
|
},
|
||||||
|
hover = {
|
||||||
|
documentation = true,
|
||||||
|
references = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- JSON/JSONC for config files
|
||||||
|
jsonls = {
|
||||||
|
settings = {
|
||||||
|
json = {
|
||||||
|
schemas = {
|
||||||
|
-- Cloudflare wrangler.toml
|
||||||
|
{
|
||||||
|
fileMatch = { "wrangler.toml", "wrangler.json" },
|
||||||
|
url = "https://raw.githubusercontent.com/cloudflare/wrangler/main/wrangler-schema.json",
|
||||||
|
},
|
||||||
|
-- Package.json
|
||||||
|
{
|
||||||
|
fileMatch = { "package.json" },
|
||||||
|
url = "https://json.schemastore.org/package.json",
|
||||||
|
},
|
||||||
|
-- tsconfig.json
|
||||||
|
{
|
||||||
|
fileMatch = { "tsconfig.json", "tsconfig.*.json" },
|
||||||
|
url = "https://json.schemastore.org/tsconfig.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validate = { enable = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- YAML for configs
|
||||||
|
yamlls = {
|
||||||
|
settings = {
|
||||||
|
yaml = {
|
||||||
|
schemaStore = {
|
||||||
|
enable = true,
|
||||||
|
url = "https://www.schemastore.org/api/json/catalog.json",
|
||||||
|
},
|
||||||
|
schemas = {
|
||||||
|
["https://json.schemastore.org/github-workflow.json"] = ".github/workflows/*.yml",
|
||||||
|
["https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json"] =
|
||||||
|
"docker-compose*.yml",
|
||||||
|
},
|
||||||
|
validate = true,
|
||||||
|
hover = true,
|
||||||
|
completion = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Lua for Neovim config
|
||||||
|
lua_ls = {
|
||||||
|
settings = {
|
||||||
|
Lua = {
|
||||||
|
hint = {
|
||||||
|
enable = true,
|
||||||
|
arrayIndex = "Auto",
|
||||||
|
setType = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Capabilities configuration
|
||||||
|
capabilities = {
|
||||||
|
textDocument = {
|
||||||
|
completion = {
|
||||||
|
completionItem = {
|
||||||
|
documentationFormat = { "markdown", "plaintext" },
|
||||||
|
snippetSupport = true,
|
||||||
|
preselectSupport = true,
|
||||||
|
insertReplaceSupport = true,
|
||||||
|
labelDetailsSupport = true,
|
||||||
|
deprecatedSupport = true,
|
||||||
|
commitCharactersSupport = true,
|
||||||
|
tagSupport = { valueSet = { 1 } },
|
||||||
|
resolveSupport = {
|
||||||
|
properties = {
|
||||||
|
"documentation",
|
||||||
|
"detail",
|
||||||
|
"additionalTextEdits",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP-specific keymappings (keeping existing bindings)
|
||||||
|
mappings = {
|
||||||
|
n = {
|
||||||
|
-- LSP Code Actions
|
||||||
|
["<C-a>a"] = {
|
||||||
|
function() vim.lsp.buf.code_action() end,
|
||||||
|
desc = "LSP Code Action",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP Hover
|
||||||
|
["<C-a>h"] = {
|
||||||
|
function() vim.lsp.buf.hover() end,
|
||||||
|
desc = "LSP Hover",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP Rename (using inc-rename plugin)
|
||||||
|
["<C-a>r"] = {
|
||||||
|
function() return ":IncRename " .. vim.fn.expand "<cword>" end,
|
||||||
|
expr = true,
|
||||||
|
desc = "Rename current symbol",
|
||||||
|
cond = "textDocument/rename",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP Diagnostics (using snacks.picker)
|
||||||
|
["<C-a>d"] = {
|
||||||
|
function() require("snacks").picker.diagnostics() end,
|
||||||
|
desc = "Find LSP diagnostics",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- LSP Symbols (using snacks.picker)
|
||||||
|
["<C-a>s"] = {
|
||||||
|
function() require("snacks").picker.lsp_symbols() end,
|
||||||
|
desc = "Find LSP symbols",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Navigate diagnostics
|
||||||
|
K = {
|
||||||
|
function() vim.diagnostic.goto_prev() end,
|
||||||
|
desc = "Previous Diagnostic",
|
||||||
|
},
|
||||||
|
J = {
|
||||||
|
function() vim.diagnostic.goto_next() end,
|
||||||
|
desc = "Next Diagnostic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Autocmds for LSP-specific behavior
|
||||||
|
autocmds = {
|
||||||
|
-- Disable inlay hints in insert mode for cleaner editing
|
||||||
|
no_insert_inlay_hints = {
|
||||||
|
cond = vim.lsp.inlay_hint and "textDocument/inlayHint" or false,
|
||||||
|
{
|
||||||
|
event = "InsertEnter",
|
||||||
|
desc = "Disable inlay hints on insert",
|
||||||
|
callback = function(args)
|
||||||
|
local filter = { bufnr = args.buf }
|
||||||
|
if vim.lsp.inlay_hint.is_enabled(filter) then
|
||||||
|
vim.lsp.inlay_hint.enable(false, filter)
|
||||||
|
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||||
|
buffer = args.buf,
|
||||||
|
once = true,
|
||||||
|
callback = function() vim.lsp.inlay_hint.enable(true, filter) end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Formatting - Conform.nvim
|
||||||
|
-- ============================================================================
|
||||||
|
{
|
||||||
|
"stevearc/conform.nvim",
|
||||||
|
event = "BufWritePre",
|
||||||
|
cmd = { "ConformInfo" },
|
||||||
|
opts = {
|
||||||
|
formatters_by_ft = {
|
||||||
|
typescript = { "oxfmt" },
|
||||||
|
typescriptreact = { "oxfmt" },
|
||||||
|
javascript = { "oxfmt" },
|
||||||
|
javascriptreact = { "oxfmt" },
|
||||||
|
json = { "oxfmt" },
|
||||||
|
jsonc = { "oxfmt" },
|
||||||
|
css = { "oxfmt" },
|
||||||
|
scss = { "oxfmt" },
|
||||||
|
html = { "oxfmt" },
|
||||||
|
markdown = { "oxfmt" },
|
||||||
|
yaml = { "oxfmt" },
|
||||||
|
lua = { "stylua" },
|
||||||
|
rust = { "rustfmt" },
|
||||||
|
toml = { "taplo" },
|
||||||
|
},
|
||||||
|
format_on_save = function(bufnr)
|
||||||
|
-- Disable with a global or buffer-local variable
|
||||||
|
if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then return end
|
||||||
|
return {
|
||||||
|
timeout_ms = 500,
|
||||||
|
lsp_fallback = true,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
formatters = {
|
||||||
|
-- oxfmt uses default configuration
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config = function(_, opts)
|
||||||
|
require("conform").setup(opts)
|
||||||
|
|
||||||
|
-- Command to toggle format on save
|
||||||
|
vim.api.nvim_create_user_command("FormatToggle", function(args)
|
||||||
|
if args.bang then
|
||||||
|
-- Toggle buffer-local setting
|
||||||
|
vim.b.disable_autoformat = not vim.b.disable_autoformat
|
||||||
|
else
|
||||||
|
-- Toggle global setting
|
||||||
|
vim.g.disable_autoformat = not vim.g.disable_autoformat
|
||||||
|
end
|
||||||
|
local status = vim.g.disable_autoformat and "disabled" or "enabled"
|
||||||
|
vim.notify("Format on save " .. status, vim.log.levels.INFO)
|
||||||
|
end, {
|
||||||
|
desc = "Toggle format on save",
|
||||||
|
bang = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Linting - nvim-lint
|
||||||
|
-- ============================================================================
|
||||||
|
{
|
||||||
|
"mfussenegger/nvim-lint",
|
||||||
|
event = { "BufReadPre", "BufNewFile" },
|
||||||
|
config = function()
|
||||||
|
local lint = require "lint"
|
||||||
|
|
||||||
|
-- Configure linters per filetype
|
||||||
|
lint.linters_by_ft = {
|
||||||
|
typescript = { "oxlint" },
|
||||||
|
typescriptreact = { "oxlint" },
|
||||||
|
javascript = { "oxlint" },
|
||||||
|
javascriptreact = { "oxlint" },
|
||||||
|
css = { "stylelint" },
|
||||||
|
scss = { "stylelint" },
|
||||||
|
markdown = { "markdownlint" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Auto-lint on save and text changed
|
||||||
|
local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true })
|
||||||
|
vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave", "TextChanged" }, {
|
||||||
|
group = lint_augroup,
|
||||||
|
callback = function()
|
||||||
|
-- Only lint if file exists on disk
|
||||||
|
if vim.fn.filereadable(vim.api.nvim_buf_get_name(0)) == 1 then lint.try_lint() end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Treesitter Configuration
|
||||||
|
-- ============================================================================
|
||||||
|
{
|
||||||
|
"nvim-treesitter/nvim-treesitter",
|
||||||
|
optional = true,
|
||||||
|
opts = function(_, opts)
|
||||||
|
if opts.ensure_installed ~= "all" then
|
||||||
|
opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, {
|
||||||
|
"typescript",
|
||||||
|
"tsx",
|
||||||
|
"javascript",
|
||||||
|
"jsdoc",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"css",
|
||||||
|
"scss",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"markdown_inline",
|
||||||
|
"regex",
|
||||||
|
"bash",
|
||||||
|
"vim",
|
||||||
|
"lua",
|
||||||
|
"proto", -- Protobuf syntax highlighting
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Treesitter context for React component awareness
|
||||||
|
{
|
||||||
|
"nvim-treesitter/nvim-treesitter-context",
|
||||||
|
event = { "BufReadPost", "BufNewFile" },
|
||||||
|
opts = {
|
||||||
|
enable = true,
|
||||||
|
max_lines = 3,
|
||||||
|
patterns = {
|
||||||
|
default = {
|
||||||
|
"class",
|
||||||
|
"function",
|
||||||
|
"method",
|
||||||
|
"for",
|
||||||
|
"while",
|
||||||
|
"if",
|
||||||
|
"switch",
|
||||||
|
"case",
|
||||||
|
"interface",
|
||||||
|
"struct",
|
||||||
|
"enum",
|
||||||
|
},
|
||||||
|
typescript = {
|
||||||
|
"class_declaration",
|
||||||
|
"abstract_class_declaration",
|
||||||
|
"function_declaration",
|
||||||
|
"method_definition",
|
||||||
|
"arrow_function",
|
||||||
|
"function_expression",
|
||||||
|
"lexical_declaration",
|
||||||
|
"variable_declaration",
|
||||||
|
},
|
||||||
|
typescriptreact = {
|
||||||
|
"class_declaration",
|
||||||
|
"function_declaration",
|
||||||
|
"method_definition",
|
||||||
|
"arrow_function",
|
||||||
|
"function_expression",
|
||||||
|
"jsx_element",
|
||||||
|
"jsx_self_closing_element",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- TypeScript Tooling
|
||||||
|
-- ============================================================================
|
||||||
|
{
|
||||||
|
"pmizio/typescript-tools.nvim",
|
||||||
|
ft = { "typescript", "typescriptreact", "javascript", "javascriptreact" },
|
||||||
|
dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" },
|
||||||
|
opts = {
|
||||||
|
settings = {
|
||||||
|
separate_diagnostic_server = true,
|
||||||
|
publish_diagnostic_on = "insert_leave",
|
||||||
|
expose_as_code_action = "all",
|
||||||
|
tsserver_file_preferences = {
|
||||||
|
includeInlayParameterNameHints = "all",
|
||||||
|
includeInlayParameterNameHintsWhenArgumentMatchesName = true,
|
||||||
|
includeInlayFunctionParameterTypeHints = true,
|
||||||
|
includeInlayVariableTypeHints = true,
|
||||||
|
includeInlayVariableTypeHintsWhenTypeMatchesName = true,
|
||||||
|
includeInlayPropertyDeclarationTypeHints = true,
|
||||||
|
includeInlayFunctionLikeReturnTypeHints = true,
|
||||||
|
includeInlayEnumMemberValueHints = true,
|
||||||
|
},
|
||||||
|
tsserver_format_options = {
|
||||||
|
allowIncompleteCompletions = false,
|
||||||
|
allowRenameOfImportPath = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Auto-close and auto-rename JSX tags
|
||||||
|
{
|
||||||
|
"windwp/nvim-ts-autotag",
|
||||||
|
event = { "BufReadPost", "BufNewFile" },
|
||||||
|
ft = {
|
||||||
|
"html",
|
||||||
|
"javascript",
|
||||||
|
"typescript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescriptreact",
|
||||||
|
"xml",
|
||||||
|
"vue",
|
||||||
|
"svelte",
|
||||||
|
},
|
||||||
|
opts = {
|
||||||
|
autotag = {
|
||||||
|
enable = true,
|
||||||
|
enable_rename = true,
|
||||||
|
enable_close = true,
|
||||||
|
enable_close_on_slash = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Mason Configuration
|
||||||
|
-- ============================================================================
|
||||||
|
{
|
||||||
|
"williamboman/mason-lspconfig.nvim",
|
||||||
|
optional = true,
|
||||||
|
opts = function(_, opts)
|
||||||
|
opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, {
|
||||||
|
-- TypeScript/JavaScript
|
||||||
|
"ts_ls", -- tsserver was renamed to ts_ls
|
||||||
|
"tailwindcss",
|
||||||
|
"html",
|
||||||
|
|
||||||
|
-- Styling
|
||||||
|
"cssls",
|
||||||
|
|
||||||
|
-- Config files
|
||||||
|
"jsonls",
|
||||||
|
"yamlls",
|
||||||
|
|
||||||
|
-- Lua
|
||||||
|
"lua_ls",
|
||||||
|
|
||||||
|
-- Go (Cosmos SDK)
|
||||||
|
"gopls",
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Note: mason-tool-installer is configured in lua/plugins/mason.lua
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Package Management Tools
|
||||||
|
-- ============================================================================
|
||||||
|
{
|
||||||
|
"vuki656/package-info.nvim",
|
||||||
|
event = "BufRead package.json",
|
||||||
|
dependencies = { "MunifTanjim/nui.nvim" },
|
||||||
|
opts = {},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Crates.io integration (useful for Rust tooling in Workers)
|
||||||
|
{
|
||||||
|
"saecki/crates.nvim",
|
||||||
|
event = "BufRead Cargo.toml",
|
||||||
|
dependencies = { "nvim-lua/plenary.nvim" },
|
||||||
|
opts = {
|
||||||
|
lsp = {
|
||||||
|
enabled = true,
|
||||||
|
actions = true,
|
||||||
|
completion = true,
|
||||||
|
hover = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Inline linting hints
|
||||||
|
{
|
||||||
|
"https://git.sr.ht/~whynothugo/lsp_lines.nvim",
|
||||||
|
event = { "BufReadPost", "BufNewFile" },
|
||||||
|
config = function()
|
||||||
|
require("lsp_lines").setup()
|
||||||
|
-- Disable virtual_text since lsp_lines will show diagnostics
|
||||||
|
vim.diagnostic.config { virtual_text = false }
|
||||||
|
|
||||||
|
-- Toggle command
|
||||||
|
vim.api.nvim_create_user_command("LspLinesToggle", function()
|
||||||
|
local new_value = not require("lsp_lines").toggle()
|
||||||
|
vim.diagnostic.config { virtual_text = new_value }
|
||||||
|
end, { desc = "Toggle LSP lines" })
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Better quickfix for showing lint errors
|
||||||
|
{
|
||||||
|
"kevinhwang91/nvim-bqf",
|
||||||
|
ft = "qf",
|
||||||
|
opts = {
|
||||||
|
auto_resize_height = true,
|
||||||
|
func_map = {
|
||||||
|
vsplit = "v",
|
||||||
|
split = "s",
|
||||||
|
tab = "t",
|
||||||
|
tabb = "T",
|
||||||
|
tabc = "<C-t>",
|
||||||
|
ptogglemode = "z,",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+14
-33
@@ -1,43 +1,24 @@
|
|||||||
-- AstroUI provides the basis for configuring the AstroNvim User Interface
|
|
||||||
-- Configuration documentation can be found with `:h astroui`
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
"AstroNvim/astroui",
|
"AstroNvim/astroui",
|
||||||
---@type AstroUIOpts
|
---@type AstroUIOpts
|
||||||
opts = {
|
opts = {
|
||||||
-- plain `cyberdream` honours `variant` below, so :CyberdreamToggleMode
|
|
||||||
-- flips muted <-> light (`cyberdream-muted` would hard-code the palette)
|
|
||||||
colorscheme = "cyberdream",
|
colorscheme = "cyberdream",
|
||||||
},
|
-- add new user interface icon
|
||||||
},
|
icons = {
|
||||||
{
|
VimIcon = "",
|
||||||
-- layered on the astrocommunity spec (transparent, italic_comments,
|
ScrollText = "",
|
||||||
-- hide_fillchars, terminal_colors stay as it sets them)
|
GitBranch = "",
|
||||||
"scottmckendry/cyberdream.nvim",
|
GitAdd = "",
|
||||||
---@type cyberdream.Config
|
GitChange = "",
|
||||||
opts = {
|
GitDelete = "",
|
||||||
variant = "muted",
|
|
||||||
-- astrocommunity still passes the pre-rename `borderless_telescope`,
|
|
||||||
-- which cyberdream ignores; this is the current key (snacks picker)
|
|
||||||
borderless_pickers = true,
|
|
||||||
-- only the installed plugins; base highlights always load. A newly
|
|
||||||
-- added plugin with a cyberdream extension must be listed here.
|
|
||||||
extensions = {
|
|
||||||
default = false,
|
|
||||||
blinkcmp = true,
|
|
||||||
dapui = true,
|
|
||||||
gitsigns = true,
|
|
||||||
grugfar = true,
|
|
||||||
heirline = true, -- AstroNvim's statusline reads the Heirline<Mode> groups
|
|
||||||
lazy = true,
|
|
||||||
mini = true, -- mini.icons
|
|
||||||
snacks = true,
|
|
||||||
treesitter = true,
|
|
||||||
trouble = true,
|
|
||||||
whichkey = true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rebelot/heirline.nvim",
|
||||||
|
opts = {
|
||||||
|
statusline = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,55 @@
|
|||||||
--- bufferline.nvim overrides on top of the astrocommunity spec
|
--- Bufferline configuration with AstroNvim integration
|
||||||
|
--- Overrides and customizations for bufferline.nvim
|
||||||
|
--- Relies on astrocommunity.bars-and-lines.bufferline-nvim for base setup
|
||||||
--- @module plugins.bufferline
|
--- @module plugins.bufferline
|
||||||
|
|
||||||
---@type LazySpec
|
---@type LazySpec
|
||||||
return {
|
return {
|
||||||
"akinsho/bufferline.nvim",
|
"akinsho/bufferline.nvim",
|
||||||
opts = function(_, opts)
|
opts = {
|
||||||
-- the community spec reserves a "Neo-tree" titled offset over the sidebar;
|
options = {
|
||||||
-- drop it so tabs start at the far left
|
mode = "buffers",
|
||||||
opts.options = opts.options or {}
|
numbers = "none",
|
||||||
opts.options.offsets = nil
|
-- Use AstroCore buffer close handler for proper cleanup
|
||||||
end,
|
close_command = function(n) require("astrocore.buffer").close(n) end,
|
||||||
|
right_mouse_command = function(n) require("astrocore.buffer").close(n) end,
|
||||||
|
separator_style = "thick",
|
||||||
|
indicator = { style = "none", icon = "" },
|
||||||
|
diagnostics = "nvim_lsp",
|
||||||
|
diagnostics_indicator = function(_, _, diagnostics_dict)
|
||||||
|
local s = " "
|
||||||
|
for e, _ in pairs(diagnostics_dict) do
|
||||||
|
local sym = e == "error" and " " or (e == "warning" and " " or " ")
|
||||||
|
s = s .. sym
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end,
|
||||||
|
always_show_bufferline = true,
|
||||||
|
show_buffer_close_icons = true,
|
||||||
|
show_close_icon = true,
|
||||||
|
modified_icon = "●",
|
||||||
|
buffer_close_icon = "",
|
||||||
|
close_icon = "",
|
||||||
|
left_trunc_marker = "",
|
||||||
|
right_trunc_marker = "",
|
||||||
|
-- Offset for snacks picker to prevent overlap
|
||||||
|
offsets = {
|
||||||
|
{ filetype = "snacks_picker", text = "", padding = 0 },
|
||||||
|
{ filetype = "neo-tree", text = "", padding = 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
highlights = {
|
||||||
|
group_label = { fg = "#ffffff", bg = "#423f5a", bold = true },
|
||||||
|
group_separator = { fg = "#585b70" },
|
||||||
|
buffer_selected = { fg = "#89dceb", bold = true },
|
||||||
|
buffer_visible = { fg = "#585b70" },
|
||||||
|
background = { fg = "#585b70" },
|
||||||
|
hint = { fg = "#94e2d5" },
|
||||||
|
hint_selected = { fg = "#94e2d5", bold = true },
|
||||||
|
warning = { fg = "#f9e2af" },
|
||||||
|
warning_selected = { fg = "#f9e2af", bold = true },
|
||||||
|
error = { fg = "#f38ba8" },
|
||||||
|
error_selected = { fg = "#f38ba8", bold = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
--- Build tools and development environment optimizations
|
||||||
|
--- Includes support for bun, vite, and modern JS tooling
|
||||||
|
--- @module plugins.buildtools
|
||||||
|
|
||||||
|
---@type LazySpec
|
||||||
|
return {
|
||||||
|
-- Vim support for various config files
|
||||||
|
{
|
||||||
|
"nvim-treesitter/nvim-treesitter",
|
||||||
|
optional = true,
|
||||||
|
opts = function(_, opts)
|
||||||
|
if opts.ensure_installed ~= "all" then
|
||||||
|
opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, {
|
||||||
|
"toml",
|
||||||
|
"yaml",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Enhanced JSON support with schema validation
|
||||||
|
{
|
||||||
|
"b0o/schemastore.nvim",
|
||||||
|
lazy = true,
|
||||||
|
dependencies = {
|
||||||
|
{
|
||||||
|
"AstroNvim/astrolsp",
|
||||||
|
optional = true,
|
||||||
|
---@type AstroLSPOpts
|
||||||
|
opts = {
|
||||||
|
---@diagnostic disable: missing-fields
|
||||||
|
config = {
|
||||||
|
jsonls = {
|
||||||
|
on_new_config = function(config)
|
||||||
|
if not config.settings.json then config.settings.json = {} end
|
||||||
|
if not config.settings.json.schemas then config.settings.json.schemas = {} end
|
||||||
|
vim.list_extend(config.settings.json.schemas, require("schemastore").json.schemas())
|
||||||
|
end,
|
||||||
|
settings = {
|
||||||
|
json = {
|
||||||
|
format = { enable = true },
|
||||||
|
validate = { enable = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- YAML schema support
|
||||||
|
{
|
||||||
|
"AstroNvim/astrolsp",
|
||||||
|
optional = true,
|
||||||
|
---@type AstroLSPOpts
|
||||||
|
opts = {
|
||||||
|
---@diagnostic disable: missing-fields
|
||||||
|
config = {
|
||||||
|
yamlls = {
|
||||||
|
on_new_config = function(config)
|
||||||
|
config.settings.yaml.schemas =
|
||||||
|
vim.tbl_deep_extend("force", config.settings.yaml.schemas or {}, require("schemastore").yaml.schemas())
|
||||||
|
end,
|
||||||
|
settings = {
|
||||||
|
yaml = {
|
||||||
|
schemaStore = {
|
||||||
|
enable = false,
|
||||||
|
url = "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Task runner integration (for vite, bun scripts)
|
||||||
|
{
|
||||||
|
"stevearc/overseer.nvim",
|
||||||
|
cmd = {
|
||||||
|
"OverseerRun",
|
||||||
|
"OverseerToggle",
|
||||||
|
"OverseerOpen",
|
||||||
|
"OverseerClose",
|
||||||
|
"OverseerLoadBundle",
|
||||||
|
"OverseerSaveBundle",
|
||||||
|
"OverseerDeleteBundle",
|
||||||
|
"OverseerRunCmd",
|
||||||
|
"OverseerQuickAction",
|
||||||
|
"OverseerTaskAction",
|
||||||
|
},
|
||||||
|
opts = {
|
||||||
|
strategy = {
|
||||||
|
"toggleterm",
|
||||||
|
use_shell = false,
|
||||||
|
auto_scroll = true,
|
||||||
|
close_on_exit = false,
|
||||||
|
direction = "tab",
|
||||||
|
quit_on_exit = "success",
|
||||||
|
open_on_start = true,
|
||||||
|
hidden = true,
|
||||||
|
},
|
||||||
|
templates = {
|
||||||
|
"builtin",
|
||||||
|
"bun.run",
|
||||||
|
"npm.run",
|
||||||
|
"vite.dev",
|
||||||
|
"vite.build",
|
||||||
|
},
|
||||||
|
task_list = {
|
||||||
|
bindings = {
|
||||||
|
["<C-l>"] = false,
|
||||||
|
["<C-h>"] = false,
|
||||||
|
["<C-k>"] = false,
|
||||||
|
["<C-j>"] = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config = function(_, opts)
|
||||||
|
local overseer = require "overseer"
|
||||||
|
overseer.setup(opts)
|
||||||
|
|
||||||
|
-- Custom task templates for bun
|
||||||
|
overseer.register_template {
|
||||||
|
name = "bun.run",
|
||||||
|
builder = function(params)
|
||||||
|
return {
|
||||||
|
cmd = { "bun" },
|
||||||
|
args = { "run", params.script },
|
||||||
|
name = "bun run " .. params.script,
|
||||||
|
components = { "default" },
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
params = {
|
||||||
|
script = {
|
||||||
|
type = "string",
|
||||||
|
default = "dev",
|
||||||
|
description = "Script to run from package.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
condition = {
|
||||||
|
filetype = { "typescript", "javascript", "typescriptreact", "javascriptreact" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Custom task templates for vite
|
||||||
|
overseer.register_template {
|
||||||
|
name = "vite.dev",
|
||||||
|
builder = function()
|
||||||
|
return {
|
||||||
|
cmd = { "bun" },
|
||||||
|
args = { "run", "dev" },
|
||||||
|
name = "vite dev server",
|
||||||
|
components = {
|
||||||
|
{ "on_output_quickfix", open = false },
|
||||||
|
"default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
condition = {
|
||||||
|
callback = function()
|
||||||
|
return vim.fn.filereadable "vite.config.ts" == 1 or vim.fn.filereadable "vite.config.js" == 1
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
overseer.register_template {
|
||||||
|
name = "vite.build",
|
||||||
|
builder = function()
|
||||||
|
return {
|
||||||
|
cmd = { "bun" },
|
||||||
|
args = { "run", "build" },
|
||||||
|
name = "vite build",
|
||||||
|
components = {
|
||||||
|
{ "on_output_quickfix", open = true },
|
||||||
|
"default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
condition = {
|
||||||
|
callback = function()
|
||||||
|
return vim.fn.filereadable "vite.config.ts" == 1 or vim.fn.filereadable "vite.config.js" == 1
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Register Devbox template for Overseer
|
||||||
|
overseer.register_template {
|
||||||
|
name = "devbox",
|
||||||
|
params = {
|
||||||
|
script = {
|
||||||
|
type = "string",
|
||||||
|
optional = false,
|
||||||
|
desc = "The devbox script to run",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
strategy = {
|
||||||
|
"toggleterm",
|
||||||
|
use_shell = true,
|
||||||
|
auto_scroll = true,
|
||||||
|
close_on_exit = true,
|
||||||
|
direction = "tab",
|
||||||
|
quit_on_exit = "success",
|
||||||
|
open_on_start = true,
|
||||||
|
hidden = true,
|
||||||
|
},
|
||||||
|
condition = {
|
||||||
|
callback = function()
|
||||||
|
if vim.fn.executable "git" == 0 then return false, "git not found" end
|
||||||
|
local git_root = vim.fn.systemlist("git rev-parse --show-toplevel")[1]
|
||||||
|
if vim.v.shell_error ~= 0 or not git_root or git_root == "" then return false, "Not in a git repository" end
|
||||||
|
local devbox_file = git_root .. "/devbox.json"
|
||||||
|
if vim.fn.filereadable(devbox_file) == 0 then return false, "No devbox.json found in git root" end
|
||||||
|
if vim.fn.executable "devbox" == 0 then return false, "devbox command not found" end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
generator = function(_, cb)
|
||||||
|
local git_root = vim.fn.systemlist("git rev-parse --show-toplevel")[1]
|
||||||
|
if vim.v.shell_error ~= 0 or not git_root or git_root == "" then
|
||||||
|
cb {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local devbox_file = git_root .. "/devbox.json"
|
||||||
|
|
||||||
|
local file = io.open(devbox_file, "r")
|
||||||
|
if not file then
|
||||||
|
cb {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local content = file:read "*all"
|
||||||
|
file:close()
|
||||||
|
local ok, data = pcall(vim.json.decode, content)
|
||||||
|
if not ok or not data then
|
||||||
|
cb {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local tasks = {}
|
||||||
|
if data.shell and data.shell.scripts then
|
||||||
|
for script_name, _ in pairs(data.shell.scripts) do
|
||||||
|
table.insert(tasks, {
|
||||||
|
name = "devbox run " .. script_name,
|
||||||
|
builder = function() return { cmd = { "devbox" }, args = { "run", script_name }, cwd = git_root } end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(tasks, {
|
||||||
|
name = "devbox shell",
|
||||||
|
builder = function() return { cmd = { "devbox" }, args = { "shell" }, cwd = git_root } end,
|
||||||
|
})
|
||||||
|
cb(tasks)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
-- Seamless navigation between build errors
|
||||||
|
{
|
||||||
|
"folke/trouble.nvim",
|
||||||
|
cmd = { "Trouble", "TroubleToggle" },
|
||||||
|
opts = {
|
||||||
|
use_diagnostic_signs = true,
|
||||||
|
action_keys = {
|
||||||
|
close = "q",
|
||||||
|
cancel = "<esc>",
|
||||||
|
refresh = "r",
|
||||||
|
jump = { "<cr>", "<tab>" },
|
||||||
|
open_split = { "<c-x>" },
|
||||||
|
open_vsplit = { "<c-v>" },
|
||||||
|
open_tab = { "<c-t>" },
|
||||||
|
jump_close = { "o" },
|
||||||
|
toggle_mode = "m",
|
||||||
|
toggle_preview = "P",
|
||||||
|
hover = "K",
|
||||||
|
preview = "p",
|
||||||
|
close_folds = { "zM", "zm" },
|
||||||
|
open_folds = { "zR", "zr" },
|
||||||
|
toggle_fold = { "zA", "za" },
|
||||||
|
previous = "k",
|
||||||
|
next = "j",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Git integration for collaborative development
|
||||||
|
{
|
||||||
|
"lewis6991/gitsigns.nvim",
|
||||||
|
event = "User AstroGitFile",
|
||||||
|
opts = {
|
||||||
|
signs = {
|
||||||
|
add = { text = "▎" },
|
||||||
|
change = { text = "▎" },
|
||||||
|
delete = { text = "" },
|
||||||
|
topdelete = { text = "" },
|
||||||
|
changedelete = { text = "▎" },
|
||||||
|
untracked = { text = "▎" },
|
||||||
|
},
|
||||||
|
current_line_blame = false,
|
||||||
|
current_line_blame_opts = {
|
||||||
|
virt_text = true,
|
||||||
|
virt_text_pos = "eol",
|
||||||
|
delay = 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- EditorConfig support
|
||||||
|
{
|
||||||
|
"editorconfig/editorconfig-vim",
|
||||||
|
event = { "BufReadPre", "BufNewFile" },
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Development container support
|
||||||
|
{
|
||||||
|
"esensar/nvim-dev-container",
|
||||||
|
dependencies = { "nvim-treesitter/nvim-treesitter" },
|
||||||
|
cmd = {
|
||||||
|
"DevcontainerStart",
|
||||||
|
"DevcontainerAttach",
|
||||||
|
"DevcontainerExec",
|
||||||
|
"DevcontainerStop",
|
||||||
|
"DevcontainerLogs",
|
||||||
|
},
|
||||||
|
opts = {},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
--- codediff.nvim: VSCode-style diff review workspace (explorer + side-by-side)
|
|
||||||
--- `:CodeDiff` reviews the working tree; `q` closes the review tab.
|
|
||||||
--- @module plugins.codediff
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
"esmuellert/codediff.nvim",
|
|
||||||
cmd = "CodeDiff",
|
|
||||||
keys = {
|
|
||||||
{ "<C-g><C-d>", "<Cmd>CodeDiff<CR>", desc = "Review changes (CodeDiff)" },
|
|
||||||
},
|
|
||||||
opts = {},
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
-- Python/JS formatting rules, plus the on-save pipeline: every safe LSP code
|
|
||||||
-- action (import organizing, fix-alls, quickfixes) runs first, then conform
|
|
||||||
-- formats. Both hang off conform's `format_on_save` hook, which is the only
|
|
||||||
-- place the relative order is guaranteed; the action pass itself lives in
|
|
||||||
-- config.lsp_code_actions.
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
-- ruff is both the import sorter and the formatter used for python below
|
|
||||||
{
|
|
||||||
"WhoIsSethDaniel/mason-tool-installer.nvim",
|
|
||||||
optional = true,
|
|
||||||
opts = function(_, opts)
|
|
||||||
opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, { "ruff" })
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"stevearc/conform.nvim",
|
|
||||||
opts = function(_, opts)
|
|
||||||
-- Python: ruff sorts imports (I001), then reformats. ruff always emits
|
|
||||||
-- 4-space indentation, so mixed tab/space indentation is normalized on
|
|
||||||
-- save instead of surviving to raise TabError at runtime.
|
|
||||||
if not opts.formatters_by_ft then opts.formatters_by_ft = {} end
|
|
||||||
opts.formatters_by_ft.python = { "ruff_organize_imports", "ruff_format" }
|
|
||||||
|
|
||||||
-- The pack's function returns nil when autoformat is toggled off
|
|
||||||
-- (<Leader>uf buffer / <Leader>uF global), so one toggle governs the
|
|
||||||
-- whole pipeline: no code actions and no formatting.
|
|
||||||
local pack_format_on_save = opts.format_on_save
|
|
||||||
opts.format_on_save = function(bufnr)
|
|
||||||
-- NOT `f(bufnr) or f`: that resolves to the function itself when the
|
|
||||||
-- call returns nil, and conform then gets a function as format opts
|
|
||||||
local format = pack_format_on_save
|
|
||||||
if type(format) == "function" then format = format(bufnr) end
|
|
||||||
if not format then return end
|
|
||||||
|
|
||||||
require("config.lsp_code_actions").apply(bufnr)
|
|
||||||
|
|
||||||
-- Skip formatting JavaScript buffers (.js/.mjs/.cjs); the code actions
|
|
||||||
-- above still run, and manual :Format / <Leader>lf still works.
|
|
||||||
if vim.bo[bufnr].filetype == "javascript" then return end
|
|
||||||
return format
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
--- fff.nvim: rust-backed fuzzy file finder and live grep with frecency
|
|
||||||
--- Backs the <C-f> file/grep bindings in plugins/astrocore.lua. Honours
|
|
||||||
--- .gitignore plus per-repo .ignore files; the config.ignore_patterns
|
|
||||||
--- snacks filter does not apply here (snacks remains for non-file sources:
|
|
||||||
--- diagnostics, lines, LSP symbols, recent, projects, templates).
|
|
||||||
--- @module plugins.fff
|
|
||||||
|
|
||||||
return {
|
|
||||||
"dmtrKovalenko/fff",
|
|
||||||
build = function()
|
|
||||||
-- downloads a prebuilt binary or falls back to cargo build
|
|
||||||
require("fff.download").download_or_build_binary()
|
|
||||||
end,
|
|
||||||
lazy = false, -- the plugin lazy-initialises itself
|
|
||||||
opts = {
|
|
||||||
lazy_sync = true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
--- flash.nvim: labeled jumps and enhanced motions
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
"folke/flash.nvim",
|
|
||||||
event = "VeryLazy",
|
|
||||||
---@type Flash.Config
|
|
||||||
opts = {
|
|
||||||
modes = {
|
|
||||||
char = {
|
|
||||||
-- normal-mode `f` is the Vimium-style word jump and `F` toggles a
|
|
||||||
-- fold (plugins/astrocore.lua), so flash's char mode must not rebind
|
|
||||||
-- them; t/T/;/, stay enhanced
|
|
||||||
keys = { "t", "T", ";", "," },
|
|
||||||
-- show jump labels on the char-motion matches (t/T)
|
|
||||||
jump_labels = true,
|
|
||||||
},
|
|
||||||
-- label-enhanced regular `/` and `?` search; <c-s> in cmdline
|
|
||||||
-- toggles it off for a plain search
|
|
||||||
search = { enabled = true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys = {
|
|
||||||
{
|
|
||||||
"f",
|
|
||||||
mode = "n",
|
|
||||||
function()
|
|
||||||
-- Vimium-c style: label every visible word start immediately,
|
|
||||||
-- press a label to jump (no search pattern typed)
|
|
||||||
require("flash").jump {
|
|
||||||
search = { mode = "search", max_length = 0 },
|
|
||||||
label = { after = false, before = { 0, 0 } },
|
|
||||||
pattern = [[\<]],
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
desc = "Jump to word (Vimium-style)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"g/",
|
|
||||||
mode = "n",
|
|
||||||
function()
|
|
||||||
-- Search-style flash: type a regex pattern and labels appear on
|
|
||||||
-- every match. While selecting, `<C-n>`/`<C-b>` cycle to the
|
|
||||||
-- next/previous result, <CR> confirms the selection, a label jumps
|
|
||||||
-- directly, <Esc> cancels back to the origin. The pattern is pushed
|
|
||||||
-- to search history and the `/` register, so `n`/`N` keep working
|
|
||||||
-- after the jump.
|
|
||||||
local current ---@type Flash.Match?
|
|
||||||
require("flash").jump {
|
|
||||||
search = { mode = "search" },
|
|
||||||
jump = { history = true, register = true, nohlsearch = true },
|
|
||||||
actions = {
|
|
||||||
["<c-n>"] = function(state)
|
|
||||||
-- first press selects the current target, then cycle forward
|
|
||||||
current = current and state:find { match = current, forward = true, wrap = true } or state.target
|
|
||||||
if current then state:jump(current) end
|
|
||||||
end,
|
|
||||||
["<c-b>"] = function(state)
|
|
||||||
current = state:find { match = current or state.target, forward = false, wrap = true }
|
|
||||||
if current then state:jump(current) end
|
|
||||||
end,
|
|
||||||
["<cr>"] = function(state)
|
|
||||||
state:jump(current) -- nil falls back to state.target
|
|
||||||
return false -- end the loop
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
desc = "Flash search",
|
|
||||||
},
|
|
||||||
{ "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" },
|
|
||||||
{ "<c-s>", mode = { "c" }, function() require("flash").toggle() end, desc = "Toggle Flash Search" },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
--- grug-far.nvim: buffer-based find & replace, backing the <C-r> binds in
|
|
||||||
--- plugins/astrocore.lua. Replaces the scooter TUI (nvim-external-tui, gone).
|
|
||||||
--- Buffer-local actions hang off <localleader> (`,`): `,r` replace,
|
|
||||||
--- `,s` sync all lines, `,l` sync line, `,q` quickfix, `,c` close, `g?` help.
|
|
||||||
--- @module plugins.grug-far
|
|
||||||
|
|
||||||
return {
|
|
||||||
"MagicDuck/grug-far.nvim",
|
|
||||||
cmd = { "GrugFar", "GrugFarWithin" },
|
|
||||||
opts = {
|
|
||||||
-- ripgrep only: no ast-grep binary on this machine, so keep the
|
|
||||||
-- astgrep engines out of the Swap Engine (`,e`) cycle
|
|
||||||
enabledEngines = { "ripgrep" },
|
|
||||||
|
|
||||||
-- right-hand split, matching where scooter used to open
|
|
||||||
windowCreationCommand = "botright vsplit",
|
|
||||||
|
|
||||||
-- linewise selection (V) limits search/replace to that range,
|
|
||||||
-- charwise selection (v) prefills the search input instead
|
|
||||||
visualSelectionUsage = "auto-detect",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
--- `<Leader>e` visual-mode formatting binds for markdown/mdx buffers; the
|
|
||||||
--- editing itself lives in config.markdown_edit.
|
|
||||||
---
|
|
||||||
--- Buffer-local so `<Leader>e` stays the git files picker everywhere else.
|
|
||||||
--- @module plugins.markdown-edit
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
"AstroNvim/astrocore",
|
|
||||||
---@type AstroCoreOpts
|
|
||||||
opts = {
|
|
||||||
autocmds = {
|
|
||||||
markdown_edit = {
|
|
||||||
{
|
|
||||||
event = "FileType",
|
|
||||||
-- `markdown.*` catches the `markdown.mdx` buffers the mdx pack sets
|
|
||||||
-- up; a plain `markdown` pattern does not match a dotted filetype
|
|
||||||
pattern = { "markdown", "markdown.*" },
|
|
||||||
desc = "Markdown formatting binds for the visual selection",
|
|
||||||
callback = function(args)
|
|
||||||
require("astrocore").set_mappings(require("config.markdown_edit").mappings, { buffer = args.buf })
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
-- Customize Mason
|
||||||
|
-- This file configures non-LSP tools (formatters, linters, debuggers, etc.)
|
||||||
|
-- LSP servers are configured in astrolsp.lua via mason-lspconfig
|
||||||
|
|
||||||
|
---@type LazySpec
|
||||||
|
return {
|
||||||
|
-- use mason-tool-installer for automatically installing Mason packages
|
||||||
|
{
|
||||||
|
"WhoIsSethDaniel/mason-tool-installer.nvim",
|
||||||
|
-- overrides `require("mason-tool-installer").setup(...)`
|
||||||
|
opts = {
|
||||||
|
-- Make sure to use the names found in `:Mason`
|
||||||
|
ensure_installed = {
|
||||||
|
-- Formatters (non-LSP)
|
||||||
|
"stylua",
|
||||||
|
"rustfmt",
|
||||||
|
"taplo",
|
||||||
|
|
||||||
|
-- Linters
|
||||||
|
"stylelint",
|
||||||
|
"markdownlint",
|
||||||
|
|
||||||
|
-- Debuggers
|
||||||
|
"debugpy",
|
||||||
|
|
||||||
|
-- Other tools
|
||||||
|
"tree-sitter-cli",
|
||||||
|
|
||||||
|
-- Note: oxlint and oxfmt should be installed separately via cargo/npm
|
||||||
|
-- Note: LSP servers are configured in astrolsp.lua
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
--- Mermaid diagrams inside markdown/mdx (and standalone `.mmd`/`.mermaid`
|
|
||||||
--- files): tree-sitter highlighting for the fence contents, plus syntax
|
|
||||||
--- diagnostics from mermaid's own parsers.
|
|
||||||
---
|
|
||||||
--- The `mermaid` parser is all the highlighting needs — markdown's stock
|
|
||||||
--- injections query hands ```mermaid fence contents to whatever parser the info
|
|
||||||
--- string names, and `markdown.mdx` buffers parse as markdown, so mdx is
|
|
||||||
--- covered by the same parser.
|
|
||||||
---
|
|
||||||
--- Diagnostics are not a language server (none is packaged for mermaid) and are
|
|
||||||
--- deliberately not derived from tree-sitter, which rejects too much valid
|
|
||||||
--- mermaid to be used for errors; see config.mermaid for the measurements and
|
|
||||||
--- the validator it drives.
|
|
||||||
--- @module plugins.mermaid
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
"AstroNvim/astrocore",
|
|
||||||
---@type AstroCoreOpts
|
|
||||||
opts = {
|
|
||||||
treesitter = { ensure_installed = { "mermaid" } },
|
|
||||||
|
|
||||||
autocmds = {
|
|
||||||
mermaid_diagnostics = {
|
|
||||||
{
|
|
||||||
event = "FileType",
|
|
||||||
-- a dotted filetype only matches a pattern that spells it out, so
|
|
||||||
-- `markdown.*` is what catches the `markdown.mdx` buffers the mdx
|
|
||||||
-- pack sets up (plain `markdown` does not)
|
|
||||||
pattern = { "markdown", "markdown.*", "mermaid" },
|
|
||||||
desc = "Validate mermaid diagrams in this buffer as it changes",
|
|
||||||
callback = function(args) require("config.mermaid").attach(args.buf) end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
commands = {
|
|
||||||
MermaidCheck = {
|
|
||||||
function() require("config.mermaid").check() end,
|
|
||||||
desc = "Re-validate the mermaid diagrams in the current buffer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
+288
-133
@@ -1,95 +1,154 @@
|
|||||||
--- Neo-tree file explorer (replaces the mini.files explorer)
|
--- Neo-tree configuration with dynamic pattern filtering
|
||||||
--- @module plugins.neotree
|
--- @module plugins.neotree
|
||||||
---
|
|
||||||
--- This layers on top of AstroNvim's own neo-tree spec instead of replacing it:
|
|
||||||
--- `opts` takes the table AstroNvim built and only overrides what differs, so
|
|
||||||
--- `close_if_last_window`, `auto_clean_after_session_restore` (needed by the
|
|
||||||
--- resession dirsessions), `hijack_netrw_behavior` (`nvim <dir>`), the AstroUI
|
|
||||||
--- icon set and the `system_open`/`copy_selector`/`parent_or_close` commands
|
|
||||||
--- all stay in place.
|
|
||||||
---
|
|
||||||
--- Filtering comes from two sources:
|
|
||||||
--- * static globs in `config.ignore_patterns` -> `hide_by_pattern` /
|
|
||||||
--- `never_show*` (neo-tree compiles globs to Lua patterns in `setup()`)
|
|
||||||
--- * neo-tree's own gitignore-style `ignore_files` reader for per-repo
|
|
||||||
--- `.rgignore` files, which replaces the old DirChanged / `set_root`
|
|
||||||
--- "pattern refresh" hooks. Those injected raw globs *after* setup() had
|
|
||||||
--- already compiled the list, so they silently matched nothing.
|
|
||||||
|
|
||||||
|
-- Import shared modules
|
||||||
local ignore_patterns = require "config.ignore_patterns"
|
local ignore_patterns = require "config.ignore_patterns"
|
||||||
|
local terminals = require "config.terminals"
|
||||||
--- neo-tree rewrites these lists in place at `setup()` time (glob -> Lua
|
|
||||||
--- pattern), so hand it copies: the shared module keeps serving raw globs to
|
|
||||||
--- the snacks picker filter in astrocore.lua.
|
|
||||||
--- @param list string[]
|
|
||||||
--- @return string[]
|
|
||||||
local function copy(list) return vim.list_extend({}, list) end
|
|
||||||
|
|
||||||
--- Open a hidden snacks terminal running `cmd`.
|
|
||||||
--- @param cmd string
|
|
||||||
--- @param opts? table extra snacks.terminal options
|
|
||||||
local function term(cmd, opts)
|
|
||||||
return function()
|
|
||||||
require("snacks").terminal(cmd, vim.tbl_extend("force", { hidden = true, interactive = true }, opts or {}))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type LazySpec
|
---@type LazySpec
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
"nvim-neo-tree/neo-tree.nvim",
|
"neo-tree.nvim",
|
||||||
keys = {
|
init = function()
|
||||||
-- kept from the mini.files era; AstroNvim's <Leader>e is overridden by
|
-- Autocmd to refresh neo-tree patterns when switching between repos
|
||||||
-- the git files picker in astrocore.lua, <Leader>o still focuses the tree
|
vim.api.nvim_create_autocmd({ "BufEnter", "DirChanged" }, {
|
||||||
{ "<C-e>", "<Cmd>Neotree toggle<CR>", mode = { "n", "v" }, desc = "Toggle Explorer" },
|
callback = function()
|
||||||
},
|
-- Defer to let buffer settle
|
||||||
opts = function(_, opts)
|
vim.defer_fn(function()
|
||||||
opts = require("astrocore").extend_tbl(opts, {
|
local ok, manager = pcall(require, "neo-tree.sources.manager")
|
||||||
|
if ok then
|
||||||
|
-- Silently refresh if neo-tree is loaded
|
||||||
|
pcall(manager.refresh, "filesystem")
|
||||||
|
end
|
||||||
|
end, 100)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
opts = function()
|
||||||
|
-- Get all hide patterns from shared module
|
||||||
|
local hide_patterns = ignore_patterns.get_all_patterns()
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
popup_border_style = "rounded",
|
popup_border_style = "rounded",
|
||||||
|
enable_git_status = true,
|
||||||
enable_diagnostics = true,
|
enable_diagnostics = true,
|
||||||
git_status_async = true, -- asynchronous git status, improves performance
|
|
||||||
hide_root_node = true,
|
|
||||||
retain_hidden_root_indent = true, -- keep the indent so expanders line up
|
|
||||||
sort_case_insensitive = false,
|
sort_case_insensitive = false,
|
||||||
source_selector = { winbar = false, statusline = false },
|
auto_expand_width = true,
|
||||||
|
git_status_async = true, -- Asynchronous git status. Improves performance.
|
||||||
|
hide_root_node = true, -- Hide the root node.
|
||||||
|
retain_hidden_root_indent = true, -- IF the root node is hidden, keep the indentation anyhow.
|
||||||
|
use_libuv_file_watcher = false,
|
||||||
|
sort = {
|
||||||
|
sorter = "name",
|
||||||
|
},
|
||||||
window = {
|
window = {
|
||||||
position = "left",
|
position = "left",
|
||||||
width = 32,
|
width = 34,
|
||||||
-- window option, not a top level one (it used to be set top level,
|
|
||||||
-- where neo-tree ignored it)
|
|
||||||
auto_expand_width = false,
|
|
||||||
-- keys must be spelled exactly like neo-tree's own defaults:
|
|
||||||
-- setup() does not case-normalize them, so `<CR>` would sit next to
|
|
||||||
-- the default `<cr>` and whichever `pairs()` reached last would win
|
|
||||||
mappings = {
|
mappings = {
|
||||||
["H"] = "navigate_up",
|
["H"] = "navigate_up",
|
||||||
["L"] = "set_root",
|
["L"] = function(state)
|
||||||
|
require("neo-tree.sources.filesystem.commands").set_root(state)
|
||||||
|
-- Refresh patterns when changing root
|
||||||
|
vim.defer_fn(function()
|
||||||
|
local new_patterns = ignore_patterns.get_all_patterns()
|
||||||
|
require("neo-tree").config.filesystem.filtered_items.hide_by_pattern = new_patterns
|
||||||
|
require("neo-tree.sources.manager").refresh "filesystem"
|
||||||
|
end, 50)
|
||||||
|
end,
|
||||||
["<bs>"] = "toggle_hidden",
|
["<bs>"] = "toggle_hidden",
|
||||||
["."] = "toggle_hidden",
|
["."] = "toggle_hidden",
|
||||||
["/"] = "fuzzy_finder",
|
["/"] = "fuzzy_finder",
|
||||||
["f"] = "filter_on_submit",
|
["f"] = "filter_on_submit",
|
||||||
["<C-x>"] = "clear_filter",
|
["<c-x>"] = "clear_filter",
|
||||||
["<cr>"] = "open_and_close_neotree",
|
["<CR>"] = "open_and_close_neotree",
|
||||||
["<S-CR>"] = "open",
|
["<S-CR>"] = "open",
|
||||||
-- Preview image under cursor via kitty's icat kitten
|
-- Avante integration
|
||||||
["<leader>p"] = "image_kitty",
|
["<C-a>"] = "avante_add_files",
|
||||||
-- Git keybindings, mirroring astrocore.lua
|
-- Git keybindings from astrocore
|
||||||
["<C-g>o"] = function() vim.cmd "!gh repo view --web" end,
|
["<C-g>o"] = function() vim.cmd "!gh repo view --web" end,
|
||||||
["<C-g>h"] = term("gh dash", { auto_close = false }),
|
["<C-g>h"] = function()
|
||||||
["<C-g>g"] = function() require("snacks").lazygit() end,
|
require("snacks").terminal("gh dash", {
|
||||||
|
hidden = true,
|
||||||
|
auto_close = false,
|
||||||
|
interactive = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
["<C-g>g"] = function()
|
||||||
|
require("snacks").terminal("lazygit", {
|
||||||
|
hidden = true,
|
||||||
|
auto_close = true,
|
||||||
|
interactive = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
["<C-g>d"] = function() require("snacks").picker.git_diff() end,
|
["<C-g>d"] = function() require("snacks").picker.git_diff() end,
|
||||||
["<C-g>b"] = function() require("snacks").picker.git_branches() end,
|
["<C-g>b"] = function() require("snacks").picker.git_branches() end,
|
||||||
-- Terminals (toggleterm and config.terminals are both gone)
|
-- Terminal keybindings from astrocore
|
||||||
["<C-t>l"] = term("lazydocker", { auto_close = true, start_in_insert = true }),
|
["<C-t>m"] = terminals.mk_toggle,
|
||||||
["<C-t>."] = term("yazi", { auto_close = true, start_in_insert = true }),
|
["<C-t>l"] = function()
|
||||||
|
require("snacks").terminal("lazydocker", {
|
||||||
|
hidden = true,
|
||||||
|
auto_close = true,
|
||||||
|
start_in_insert = true,
|
||||||
|
interactive = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
["<C-t>du"] = function()
|
||||||
|
require("snacks").terminal("devbox services up", {
|
||||||
|
hidden = true,
|
||||||
|
auto_close = false,
|
||||||
|
start_in_insert = true,
|
||||||
|
interactive = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
["<C-t>dd"] = function()
|
||||||
|
require("snacks").terminal("devbox services down", {
|
||||||
|
hidden = true,
|
||||||
|
auto_close = false,
|
||||||
|
start_in_insert = true,
|
||||||
|
interactive = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
["<C-t>da"] = function()
|
||||||
|
require("snacks").terminal("devbox services attach", {
|
||||||
|
hidden = true,
|
||||||
|
auto_close = false,
|
||||||
|
start_in_insert = true,
|
||||||
|
interactive = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
["<C-t>."] = terminals.yazi_toggle,
|
||||||
["<C-t>t"] = function() require("snacks").terminal() end,
|
["<C-t>t"] = function() require("snacks").terminal() end,
|
||||||
-- Open yazi in a detached kitty OS window at the node's directory
|
-- Find keybindings from astrocore
|
||||||
|
-- These Find keybindings are defined in astrocore.lua
|
||||||
|
-- NeoTree only needs the basic navigation and terminal bindings
|
||||||
|
-- Mappings for Mason and formatting
|
||||||
["N"] = function(state)
|
["N"] = function(state)
|
||||||
local node = state.tree:get_node()
|
local node = state.tree:get_node()
|
||||||
local path = node.type == "directory" and node.path or vim.fs.dirname(node.path)
|
local path = node.type == "directory" and node.path or vim.fn.fnamemodify(node.path, ":h")
|
||||||
vim.fn.jobstart { "kitty", "--detach", "--directory", path, "yazi" }
|
|
||||||
|
-- Check if running on Arch Linux
|
||||||
|
local is_arch = vim.fn.filereadable "/etc/arch-release" == 1
|
||||||
|
|
||||||
|
if is_arch then
|
||||||
|
-- Use uwsm to launch alacritty with yazi on Arch Linux
|
||||||
|
vim.fn.jobstart(
|
||||||
|
string.format("uwsm app -- alacritty --working-directory %s -e yazi", vim.fn.shellescape(path)),
|
||||||
|
{
|
||||||
|
detach = true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- Fallback to direct alacritty launch with yazi on other systems
|
||||||
|
vim.fn.jobstart(string.format("alacritty --working-directory %s -e yazi", vim.fn.shellescape(path)), {
|
||||||
|
detach = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
header = {
|
||||||
|
highlight = "NeoTreeHeader",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
default_component_configs = {
|
default_component_configs = {
|
||||||
container = {
|
container = {
|
||||||
@@ -98,27 +157,29 @@ return {
|
|||||||
indent = {
|
indent = {
|
||||||
indent_size = 2,
|
indent_size = 2,
|
||||||
padding = 1, -- extra padding on left hand side
|
padding = 1, -- extra padding on left hand side
|
||||||
|
-- indent guides
|
||||||
with_markers = true,
|
with_markers = true,
|
||||||
indent_marker = "│",
|
indent_marker = "│",
|
||||||
last_indent_marker = "└",
|
last_indent_marker = "└",
|
||||||
highlight = "NeoTreeIndentMarker",
|
highlight = "NeoTreeIndentMarker",
|
||||||
|
-- expander config, needed for nesting files
|
||||||
expander_collapsed = "",
|
expander_collapsed = "",
|
||||||
expander_expanded = "",
|
expander_expanded = "",
|
||||||
expander_highlight = "NeoTreeExpander",
|
expander_highlight = "NeoTreeExpander",
|
||||||
},
|
},
|
||||||
name = {
|
name = {
|
||||||
trailing_slash = false,
|
trailing_slash = false, -- append a trailing slash to folder names
|
||||||
use_git_status_colors = true,
|
use_git_status_colors = true, -- use git status colors instead of the default colors
|
||||||
highlight = "NeoTreeFileName",
|
highlight = "NeoTreeFileName",
|
||||||
},
|
},
|
||||||
git_status = {
|
git_status = {
|
||||||
symbols = {
|
symbols = {
|
||||||
-- change type: empty where the name already carries git colors
|
-- Change type
|
||||||
added = "",
|
added = "", -- or "✚", but this is redundant info if you use git_status_colors on the name
|
||||||
modified = "",
|
modified = "", -- or "", but this is redundant info if you use git_status_colors on the name
|
||||||
deleted = "✖",
|
deleted = "✖", -- this can only be used in the git_status source
|
||||||
renamed = "",
|
renamed = "", -- this can only be used in the git_status source
|
||||||
-- status type
|
-- Status type
|
||||||
untracked = "",
|
untracked = "",
|
||||||
ignored = "",
|
ignored = "",
|
||||||
unstaged = "",
|
unstaged = "",
|
||||||
@@ -126,87 +187,181 @@ return {
|
|||||||
conflict = "",
|
conflict = "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
-- extra columns, each only rendered once the window is wide enough
|
-- If you don't want to use these columns, you can set `enabled = false` for each of them individually
|
||||||
file_size = { enabled = true, width = 12, required_width = 48 },
|
file_size = {
|
||||||
type = { enabled = true, width = 10, required_width = 122 },
|
enabled = true,
|
||||||
last_modified = { enabled = true, width = 20, required_width = 88 },
|
width = 12, -- width of the column
|
||||||
created = { enabled = true, width = 20, required_width = 110 },
|
required_width = 48, -- min width of window required to show this column
|
||||||
symlink_target = { enabled = false },
|
},
|
||||||
|
type = {
|
||||||
|
enabled = true,
|
||||||
|
width = 10, -- width of the column
|
||||||
|
required_width = 122, -- min width of window required to show this column
|
||||||
|
},
|
||||||
|
last_modified = {
|
||||||
|
enabled = true,
|
||||||
|
width = 20, -- width of the column
|
||||||
|
required_width = 88, -- min width of window required to show this column
|
||||||
|
},
|
||||||
|
created = {
|
||||||
|
enabled = true,
|
||||||
|
width = 20, -- width of the column
|
||||||
|
required_width = 110, -- min width of window required to show this column
|
||||||
|
},
|
||||||
|
symlink_target = {
|
||||||
|
enabled = false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
buffers = {
|
||||||
|
follow_current_file = {
|
||||||
|
enabled = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Filesystem specific settings
|
||||||
filesystem = {
|
filesystem = {
|
||||||
commands = {
|
commands = {
|
||||||
-- Close neo-tree after opening a file (directories just expand).
|
-- Avante integration: Add files to avante selected files
|
||||||
-- The node has to be read before `open`: afterwards the tree
|
avante_add_files = function(state)
|
||||||
-- window may be gone and `tree:get_node()` errors.
|
|
||||||
open_and_close_neotree = function(state)
|
|
||||||
local node = state.tree:get_node()
|
local node = state.tree:get_node()
|
||||||
require("neo-tree.sources.filesystem.commands").open(state)
|
local filepath = node:get_id()
|
||||||
if node and node.type == "file" then
|
local relative_path = require("avante.utils").relative_path(filepath)
|
||||||
require("neo-tree.command").execute { action = "close" }
|
|
||||||
|
local sidebar = require("avante").get()
|
||||||
|
|
||||||
|
local open = sidebar:is_open()
|
||||||
|
-- ensure avante sidebar is open
|
||||||
|
if not open then
|
||||||
|
require("avante.api").ask()
|
||||||
|
sidebar = require("avante").get()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sidebar.file_selector:add_selected_file(relative_path)
|
||||||
|
|
||||||
|
-- remove neo tree buffer
|
||||||
|
if not open then sidebar.file_selector:remove_selected_file "neo-tree filesystem [1]" end
|
||||||
end,
|
end,
|
||||||
-- Preview an image with kitty's icat kitten in a snacks terminal
|
-- Refresh patterns when changing directories
|
||||||
image_kitty = function(state)
|
refresh_patterns = function(state)
|
||||||
|
-- Re-read patterns when navigating
|
||||||
|
local new_patterns = ignore_patterns.get_all_patterns()
|
||||||
|
state.filtered_items.hide_by_pattern = new_patterns
|
||||||
|
require("neo-tree.sources.manager").refresh "filesystem"
|
||||||
|
end,
|
||||||
|
-- If item is a file it will close neotree after opening it.
|
||||||
|
open_and_close_neotree = function(state)
|
||||||
|
require("neo-tree.sources.filesystem.commands").open(state)
|
||||||
|
local tree = state.tree
|
||||||
|
local success, node = pcall(tree.get_node, tree)
|
||||||
|
if not success then return end
|
||||||
|
if node.type == "file" then require("neo-tree.command").execute { action = "close" } end
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Custom command to open Mason
|
||||||
|
mason_open = function() vim.cmd "Mason" end,
|
||||||
|
|
||||||
|
-- Custom command to format a file using the LSP
|
||||||
|
format_file = function(state)
|
||||||
local node = state.tree:get_node()
|
local node = state.tree:get_node()
|
||||||
if node and node.type == "file" then
|
if node and node.type == "file" then
|
||||||
term("kitten icat --hold " .. vim.fn.shellescape(node.path), { auto_close = false })()
|
-- Format the file in a temporary buffer without opening it in a window
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_name(bufnr, node.path)
|
||||||
|
vim.api.nvim_buf_load(bufnr)
|
||||||
|
vim.lsp.buf.format { bufnr = bufnr, async = false, timeout_ms = 5000 }
|
||||||
|
vim.api.nvim_buf_call(bufnr, function() vim.cmd "write" end)
|
||||||
|
vim.notify("Formatted " .. vim.fs.basename(node.path), vim.log.levels.INFO, { title = "Neo-tree" })
|
||||||
else
|
else
|
||||||
vim.notify("Not a file", vim.log.levels.WARN, { title = "Neo-tree" })
|
vim.notify("Not a file", vim.log.levels.WARN, { title = "Neo-tree" })
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
bind_to_cwd = true,
|
bind_to_cwd = true,
|
||||||
follow_current_file = { enabled = true, leave_dirs_open = false },
|
follow_current_file = {
|
||||||
use_libuv_file_watcher = true, -- filesystem option, not a top level one
|
enabled = true,
|
||||||
|
leave_dirs_open = false,
|
||||||
|
},
|
||||||
filtered_items = {
|
filtered_items = {
|
||||||
force_visible_in_empty_folder = false,
|
force_visible_in_empty_folder = false,
|
||||||
|
group_empty = false,
|
||||||
show_hidden_count = false,
|
show_hidden_count = false,
|
||||||
hide_dotfiles = false,
|
hide_dotfiles = false,
|
||||||
hide_gitignored = true,
|
hide_gitignored = true,
|
||||||
-- gitignore-style ignore files, read per directory by neo-tree
|
hide_by_pattern = hide_patterns,
|
||||||
hide_ignored = true,
|
always_show = {},
|
||||||
ignore_files = { ".neotreeignore", ".ignore", ".rgignore" },
|
always_show_by_pattern = {},
|
||||||
-- hide_by_pattern / never_show / never_show_by_pattern are seeded
|
visible = function(item)
|
||||||
-- from config.ignore_patterns in `config` below
|
-- Hide all symlinks
|
||||||
|
if item.type == "link" then return false end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
hide_by_name = {
|
||||||
|
-- "contracts",
|
||||||
|
-- "crypto",
|
||||||
|
-- "CONSTITUTION.md",
|
||||||
|
-- "CONTRIBUTING.md",
|
||||||
|
-- "api",
|
||||||
|
-- "turbo.json",
|
||||||
|
-- "OpenCode.md",
|
||||||
|
-- "AGENT.md",
|
||||||
|
-- "AGENTS.md",
|
||||||
|
-- "svgo.config.mjs",
|
||||||
|
-- ".prettierrc.cjs",
|
||||||
|
-- ".eslintrc.cjs",
|
||||||
|
-- "tsconfig.json",
|
||||||
|
-- "tsconfig.prod.json",
|
||||||
|
-- ".prettierignore",
|
||||||
|
-- "CONVENTIONS.md",
|
||||||
|
-- ".gitkeep",
|
||||||
|
-- ".source",
|
||||||
|
-- "settings.json",
|
||||||
|
-- ".gitignore",
|
||||||
|
-- "analysis_options.yaml",
|
||||||
|
-- "slumber.yml",
|
||||||
|
-- ".eslintignore",
|
||||||
|
-- "CONTRIBUTING.md",
|
||||||
|
-- "CHANGELOG.md",
|
||||||
|
-- "devbox.d",
|
||||||
|
-- "env",
|
||||||
|
-- ".husky",
|
||||||
|
-- "translations",
|
||||||
|
-- "LICENSE.md",
|
||||||
|
-- ".envrc",
|
||||||
|
-- "CLAUDE.md",
|
||||||
|
-- "chains",
|
||||||
|
-- "test",
|
||||||
|
-- ".golangci.yml",
|
||||||
|
-- "go.mod",
|
||||||
|
-- "pnpm-workspace.yaml",
|
||||||
|
-- "examples",
|
||||||
|
-- "bridge",
|
||||||
|
-- "client",
|
||||||
|
},
|
||||||
|
never_show_by_pattern = ignore_patterns.never_show_patterns,
|
||||||
|
never_show = ignore_patterns.never_show,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
diagnostics = {
|
||||||
-- deep merge of list values is index wise, so AstroNvim's extra sources
|
enable = false,
|
||||||
-- survive a `sources = { "filesystem" }` override: assign instead.
|
icons = {
|
||||||
-- (`:checkhealth neo-tree` then reports config.buffers/git_status
|
hint = "",
|
||||||
-- `renderers: expected ?, got nil` - stock neo-tree does the same with a
|
info = "",
|
||||||
-- single source, those sections are simply never loaded.)
|
warning = "",
|
||||||
opts.sources = { "filesystem" }
|
error = "",
|
||||||
-- git reports ignored paths in a second pass, and neo-tree's own
|
},
|
||||||
-- GIT_STATUS_CHANGED handler only redraws the nodes it already has, so
|
},
|
||||||
-- `!!` entries that land after the first scan stay visible until
|
|
||||||
-- something rescans. Rescan once per worktree (the flag also keeps this
|
source_selector = {
|
||||||
-- from looping on the status the rescan itself kicks off) - this is what
|
winbar = false,
|
||||||
-- the old BufEnter/DirChanged "refresh patterns" autocmd was papering
|
statusline = false,
|
||||||
-- over.
|
},
|
||||||
local rescanned = {} ---@type table<string, true>
|
|
||||||
table.insert(opts.event_handlers, {
|
-- Main sources
|
||||||
event = "git_status_changed",
|
sources = {
|
||||||
handler = function(args)
|
"filesystem",
|
||||||
local root = args and args.git_root
|
},
|
||||||
if root == nil or rescanned[root] then return end
|
}
|
||||||
rescanned[root] = true
|
|
||||||
require("neo-tree.sources.manager").refresh "filesystem"
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
return opts
|
|
||||||
end,
|
|
||||||
config = function(_, opts)
|
|
||||||
-- setup() compiles the glob lists into Lua patterns (and turns
|
|
||||||
-- `never_show` into a lookup table) in place, so re-seed them from the
|
|
||||||
-- shared module on every setup: `:Lazy reload neo-tree.nvim` re-runs
|
|
||||||
-- this with the same cached opts table, and compiling already compiled
|
|
||||||
-- patterns a second time silently breaks all pattern filtering.
|
|
||||||
local filtered = opts.filesystem.filtered_items
|
|
||||||
filtered.hide_by_pattern = copy(ignore_patterns.patterns)
|
|
||||||
filtered.never_show = copy(ignore_patterns.never_show)
|
|
||||||
filtered.never_show_by_pattern = copy(ignore_patterns.never_show_patterns)
|
|
||||||
require("neo-tree").setup(opts)
|
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
-- omp.nvim: streams the active file / cursor line / visual selection to a
|
|
||||||
-- running Oh My Pi session over a unix socket. Needs the OMP-side extension
|
|
||||||
-- (`omp plugin install omp.nvim`). Toggle bind: <C-a><C-a> in astrocore.lua.
|
|
||||||
return {
|
|
||||||
"rauls-kjarners/omp.nvim",
|
|
||||||
event = "VeryLazy",
|
|
||||||
config = function() require("omp").setup() end,
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
--- <C-h/j/k/l> pane navigation that follows whatever multiplexer Neovim is
|
|
||||||
--- running under (herdr, tmux, or bare kitty) — see config.pane_nav.
|
|
||||||
---
|
|
||||||
--- These entries overwrite AstroNvim's smart-splits mappings, which only know
|
|
||||||
--- tmux/wezterm/kitty and would move the wrong layer inside herdr. Under tmux
|
|
||||||
--- they hand back to smart-splits: navigation through config.pane_nav, resize
|
|
||||||
--- directly, plus <M-h/j/k/l> resize, the keys tmux.conf forwards to an
|
|
||||||
--- @pane-is-vim pane. Elsewhere <C-Up/Down/Left/Right> are plain :resize.
|
|
||||||
|
|
||||||
local nav = require "config.pane_nav"
|
|
||||||
|
|
||||||
--- AstroNvim's key spelling for these chords, so the entries overwrite rather
|
|
||||||
--- than race the smart-splits / term_nav mappings.
|
|
||||||
local moves = {
|
|
||||||
["<C-H>"] = "left",
|
|
||||||
["<C-J>"] = "down",
|
|
||||||
["<C-K>"] = "up",
|
|
||||||
["<C-L>"] = "right",
|
|
||||||
}
|
|
||||||
|
|
||||||
--- AstroNvim's key spelling again, overriding the smart-splits resize entries.
|
|
||||||
--- dir is the smart-splits direction under tmux; cmd the plain fallback.
|
|
||||||
local resizes = {
|
|
||||||
["<C-Up>"] = { dir = "up", cmd = "resize +2", desc = "Resize split taller" },
|
|
||||||
["<C-Down>"] = { dir = "down", cmd = "resize -2", desc = "Resize split shorter" },
|
|
||||||
["<C-Left>"] = { dir = "left", cmd = "vertical resize -2", desc = "Resize split narrower" },
|
|
||||||
["<C-Right>"] = { dir = "right", cmd = "vertical resize +2", desc = "Resize split wider" },
|
|
||||||
}
|
|
||||||
|
|
||||||
--- tmux.conf's vim-aware resize keys; only mapped under tmux.
|
|
||||||
local tmux_resizes = {
|
|
||||||
["<M-h>"] = "left",
|
|
||||||
["<M-j>"] = "down",
|
|
||||||
["<M-k>"] = "up",
|
|
||||||
["<M-l>"] = "right",
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"AstroNvim/astrocore",
|
|
||||||
opts = function(_, opts)
|
|
||||||
local maps = opts.mappings
|
|
||||||
maps.x = maps.x or {}
|
|
||||||
for key, dir in pairs(moves) do
|
|
||||||
local desc = ("Move %s a split (%s aware)"):format(dir, nav.env())
|
|
||||||
maps.n[key] = { function() nav.navigate(dir) end, desc = desc }
|
|
||||||
maps.x[key] = maps.n[key]
|
|
||||||
maps.t[key] = { nav.terminal(dir), desc = desc }
|
|
||||||
end
|
|
||||||
local tmux = nav.env() == "tmux"
|
|
||||||
for key, spec in pairs(resizes) do
|
|
||||||
if tmux then
|
|
||||||
maps.n[key] = { function() require("smart-splits")["resize_" .. spec.dir]() end, desc = "Resize split " .. spec.dir }
|
|
||||||
else
|
|
||||||
maps.n[key] = { "<Cmd>" .. spec.cmd .. "<CR>", desc = spec.desc }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if tmux then
|
|
||||||
for key, dir in pairs(tmux_resizes) do
|
|
||||||
maps.n[key] = { function() require("smart-splits")["resize_" .. dir]() end, desc = "Resize split " .. dir .. " (tmux aware)" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
--- Python: interpreter resolution for basedpyright.
|
|
||||||
---
|
|
||||||
--- The astrocommunity basedpyright subpack pins `python.pythonPath` to
|
|
||||||
--- `exepath "python"`, i.e. the system interpreter. Builtins and the stdlib
|
|
||||||
--- (`enumerate`, `collections.Counter`, ...) come from basedpyright's bundled
|
|
||||||
--- typeshed either way, but third-party imports only resolve — and only
|
|
||||||
--- complete — when the server points at the environment that has them, which
|
|
||||||
--- for a uv/poetry/venv project is the project's own virtualenv.
|
|
||||||
---
|
|
||||||
--- Resolution order: activated environment ($VIRTUAL_ENV / $CONDA_PREFIX) ->
|
|
||||||
--- a virtualenv inside the LSP root -> system python3.
|
|
||||||
--- `<Leader>lv` (venv-selector, from the base pack) still overrides at runtime.
|
|
||||||
--- @module plugins.python
|
|
||||||
|
|
||||||
local VENV_DIRS = { ".venv", "venv", "env" }
|
|
||||||
local ROOT_MARKERS = { "pyproject.toml", "uv.lock", "setup.py", "setup.cfg", "requirements.txt", ".git" }
|
|
||||||
|
|
||||||
--- @param path string? candidate interpreter
|
|
||||||
--- @return string? path when it is an executable file
|
|
||||||
local function executable(path)
|
|
||||||
if not path or path == "" then return nil end
|
|
||||||
local stat = vim.uv.fs_stat(path)
|
|
||||||
if stat and stat.type == "file" and vim.fn.executable(path) == 1 then return path end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param root string? project root
|
|
||||||
--- @return string interpreter absolute path or bare name
|
|
||||||
local function python_path(root)
|
|
||||||
local activated = vim.env.VIRTUAL_ENV or vim.env.CONDA_PREFIX
|
|
||||||
local found = activated and executable(activated .. "/bin/python")
|
|
||||||
if found then return found end
|
|
||||||
|
|
||||||
if root then
|
|
||||||
for _, dir in ipairs(VENV_DIRS) do
|
|
||||||
found = executable(root .. "/" .. dir .. "/bin/python")
|
|
||||||
if found then return found end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return executable(vim.fn.exepath "python3") or "python3"
|
|
||||||
end
|
|
||||||
|
|
||||||
local warned = {} ---@type table<string, true> roots already warned about
|
|
||||||
|
|
||||||
--- A locked project with no virtualenv checked out resolves against the system
|
|
||||||
--- interpreter, where its dependencies are missing. That reads as "the LSP is
|
|
||||||
--- broken" rather than "nothing is installed", so say it out loud once.
|
|
||||||
--- @param root string?
|
|
||||||
--- @param interpreter string
|
|
||||||
local function warn_unsynced_project(root, interpreter)
|
|
||||||
if not root or warned[root] or interpreter:find(root, 1, true) then return end
|
|
||||||
local lock = vim.uv.fs_stat(root .. "/uv.lock") or vim.uv.fs_stat(root .. "/poetry.lock")
|
|
||||||
if not lock then return end
|
|
||||||
warned[root] = true
|
|
||||||
vim.schedule(
|
|
||||||
function()
|
|
||||||
vim.notify(
|
|
||||||
("basedpyright: no virtualenv in %s, using %s.\nRun `uv sync` for third-party completions.")
|
|
||||||
:format(vim.fn.fnamemodify(root, ":~"), interpreter),
|
|
||||||
vim.log.levels.WARN
|
|
||||||
)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
"AstroNvim/astrolsp",
|
|
||||||
optional = true,
|
|
||||||
---@type AstroLSPOpts
|
|
||||||
opts = {
|
|
||||||
config = {
|
|
||||||
basedpyright = {
|
|
||||||
before_init = function(_, config)
|
|
||||||
local root = config.root_dir or vim.fs.root(0, ROOT_MARKERS)
|
|
||||||
local interpreter = python_path(root)
|
|
||||||
warn_unsynced_project(root, interpreter)
|
|
||||||
config.settings = vim.tbl_deep_extend(
|
|
||||||
"force",
|
|
||||||
config.settings or {},
|
|
||||||
{ python = { pythonPath = interpreter } }
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
--- Snacks.nvim configuration
|
||||||
|
--- Overrides and customizations for snacks.nvim features
|
||||||
|
--- Relies on astrocommunity.recipes.telescope-nvim-snacks for base setup
|
||||||
|
--- @module plugins.snacks
|
||||||
|
|
||||||
|
return {
|
||||||
|
"folke/snacks.nvim",
|
||||||
|
opts = {
|
||||||
|
-- Disable dashboard - we use custom startup picker in astrocore.lua
|
||||||
|
dashboard = { enabled = false },
|
||||||
|
|
||||||
|
-- Enable snacks features we use throughout config
|
||||||
|
bigfile = { enabled = true },
|
||||||
|
input = { enabled = true },
|
||||||
|
notifier = { enabled = true },
|
||||||
|
picker = { enabled = true },
|
||||||
|
quickfile = { enabled = true },
|
||||||
|
rename = { enabled = true },
|
||||||
|
scope = { enabled = true },
|
||||||
|
scroll = { enabled = true },
|
||||||
|
statuscolumn = { enabled = true },
|
||||||
|
terminal = { enabled = true },
|
||||||
|
words = { enabled = true },
|
||||||
|
indent = { enabled = true },
|
||||||
|
|
||||||
|
-- Terminal styles
|
||||||
|
styles = {
|
||||||
|
terminal = {
|
||||||
|
bo = { filetype = "snacks_terminal" },
|
||||||
|
wo = {},
|
||||||
|
keys = {
|
||||||
|
q = "hide",
|
||||||
|
term_normal = {
|
||||||
|
"<esc>",
|
||||||
|
function(self)
|
||||||
|
self.esc_timer = self.esc_timer or (vim.uv or vim.loop).new_timer()
|
||||||
|
if self.esc_timer:is_active() then
|
||||||
|
self.esc_timer:stop()
|
||||||
|
vim.cmd "stopinsert"
|
||||||
|
else
|
||||||
|
self.esc_timer:start(200, 0, function() end)
|
||||||
|
return "<esc>"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
mode = "t",
|
||||||
|
expr = true,
|
||||||
|
desc = "Double escape to normal mode",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
+17
-39
@@ -1,44 +1,22 @@
|
|||||||
-- Disable supermaven in buffers under the LeetCode work directory, and
|
--- Supermaven - Fast AI code completion
|
||||||
-- re-enable it when leaving them.
|
--- @module plugins.supermaven
|
||||||
--
|
|
||||||
-- Note: supermaven's built-in `condition` option is one-way — its BufEnter
|
|
||||||
-- listener gates the restart branch on `vim.g.SUPERMAVEN_DISABLED`, which
|
|
||||||
-- `api.stop()` itself sets, so once auto-stopped it never comes back. This
|
|
||||||
-- autocmd owns both directions instead. `vim.g.supermaven_leetcode_stopped`
|
|
||||||
-- distinguishes our auto-stop from a manual :SupermavenStop, which stays off.
|
|
||||||
local leetcode_dir = vim.fn.expand "~/Developer/github.com/prdlk/leetcode/work/"
|
|
||||||
|
|
||||||
local function is_leetcode_buffer()
|
|
||||||
local name = vim.api.nvim_buf_get_name(0)
|
|
||||||
return name:sub(1, #leetcode_dir) == leetcode_dir
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sync()
|
|
||||||
local api = require "supermaven-nvim.api"
|
|
||||||
if is_leetcode_buffer() then
|
|
||||||
if api.is_running() then
|
|
||||||
api.stop()
|
|
||||||
vim.g.supermaven_leetcode_stopped = true
|
|
||||||
end
|
|
||||||
elseif vim.g.supermaven_leetcode_stopped and not api.is_running() then
|
|
||||||
vim.g.supermaven_leetcode_stopped = nil
|
|
||||||
api.start()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"supermaven-inc/supermaven-nvim",
|
"supermaven-inc/supermaven-nvim",
|
||||||
optional = true,
|
config = function()
|
||||||
-- ignore_filetypes gates both the buffer sync (on_update) and the
|
require("supermaven-nvim").setup({
|
||||||
-- suggestion poll, so python buffers are never sent to the binary
|
keymaps = {
|
||||||
opts = { ignore_filetypes = { python = true } },
|
accept_suggestion = "<C-l>",
|
||||||
config = function(_, opts)
|
clear_suggestion = "<C-]>",
|
||||||
require("supermaven-nvim").setup(opts)
|
accept_word = "<C-j>",
|
||||||
vim.api.nvim_create_autocmd("BufEnter", {
|
},
|
||||||
group = vim.api.nvim_create_augroup("supermaven_leetcode", { clear = true }),
|
ignore_filetypes = { "cpp" },
|
||||||
desc = "Toggle supermaven off in LeetCode solution buffers",
|
color = {
|
||||||
callback = sync,
|
suggestion_color = "#ffffff",
|
||||||
|
cterm = 244,
|
||||||
|
},
|
||||||
|
log_level = "info", -- set to "off" to disable logging completely
|
||||||
|
disable_inline_completion = false, -- disables inline completion for use with cmp
|
||||||
|
disable_keymaps = false, -- disables built in keymaps for more manual control
|
||||||
})
|
})
|
||||||
sync() -- plugin loads on VeryLazy; cover the buffer we're already sitting in
|
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
--- template.nvim: insert file templates with {{_expr_}} grammar
|
|
||||||
--- Templates live in <config>/templates/<ft>/*.ext and are matched to the
|
|
||||||
--- buffer by filetype. Picker frontend lives in config.templates (<C-f>t).
|
|
||||||
--- @module plugins.template
|
|
||||||
|
|
||||||
return {
|
|
||||||
"nvimdev/template.nvim",
|
|
||||||
cmd = "Template",
|
|
||||||
opts = {
|
|
||||||
temp_dir = vim.fn.stdpath "config" .. "/templates",
|
|
||||||
author = "Prad Nukala",
|
|
||||||
email = "prad@sonr.io",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
--- User plugins: cursor animation and the central snacks.nvim configuration
|
|
||||||
--- (all snacks modules are configured here in one snacks.Config table)
|
|
||||||
|
|
||||||
-- Confirm action for file-opening pickers: <CR> opens the selection as a
|
|
||||||
-- tab via `tabdrop` (:tab drop semantics — focus the buffer if it's already
|
|
||||||
-- visible in any tab, don't spawn a tab from an empty buffer, else open a
|
|
||||||
-- new tab). Explicit edit-cmd keybinds (<S-CR>/<c-v> vsplit, <c-s> split)
|
|
||||||
-- carry `action.cmd` and are passed through untouched.
|
|
||||||
local function tab_confirm(picker, item, action)
|
|
||||||
local actions = require "snacks.picker.actions"
|
|
||||||
if action.cmd then return actions.jump(picker, item, action) end
|
|
||||||
return actions.jump(picker, item, vim.tbl_extend("force", {}, action, { cmd = "tabdrop" }))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Tab-on-enter + vsplit-on-shift-enter for the file-opening pickers only;
|
|
||||||
-- location pickers (lsp_*, lines, diagnostics, grep) and sources with their
|
|
||||||
-- own confirm (git_branches, projects) keep their defaults.
|
|
||||||
local picker_sources = {}
|
|
||||||
for _, source in ipairs { "files", "git_files", "smart", "recent", "buffers" } do
|
|
||||||
picker_sources[source] = {
|
|
||||||
confirm = tab_confirm,
|
|
||||||
win = {
|
|
||||||
input = { keys = { ["<S-CR>"] = { "edit_vsplit", mode = { "n", "i" } } } },
|
|
||||||
list = { keys = { ["<S-CR>"] = "edit_vsplit" } },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Smart picker: anchor to the current git repository. `config` runs when
|
|
||||||
-- the picker opens: cwd becomes the repo root (so the `files` finder scans
|
|
||||||
-- the whole repo, not just a rooter-chdir'd subdir) and `filter.cwd` drops
|
|
||||||
-- buffers/recent entries from other repos. Outside a repo, plain cwd
|
|
||||||
-- behavior is kept.
|
|
||||||
picker_sources.smart.config = function(opts)
|
|
||||||
local root = require("snacks").git.get_root()
|
|
||||||
if root then
|
|
||||||
opts.cwd = root
|
|
||||||
opts.filter = vim.tbl_extend("force", opts.filter or {}, { cwd = true })
|
|
||||||
end
|
|
||||||
return opts
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
{ "sphamba/smear-cursor.nvim", event = "VeryLazy", opts = {} },
|
|
||||||
|
|
||||||
-- terminals are handled by kitty now
|
|
||||||
{ "akinsho/toggleterm.nvim", enabled = false },
|
|
||||||
|
|
||||||
-- conform owns formatting (plugins/conform.lua). AstroNvim's none-ls ships
|
|
||||||
-- with mason-null-ls `handlers = {}`, which auto-registers every mason tool
|
|
||||||
-- (biome, goimports, buf, ...) as a second formatter/linter client.
|
|
||||||
{ "nvimtools/none-ls.nvim", enabled = false },
|
|
||||||
{ "jay-babu/mason-null-ls.nvim", enabled = false },
|
|
||||||
|
|
||||||
-- Under tmux, smart-splits drives <C-h/j/k/l> and resizing (config.pane_nav
|
|
||||||
-- and plugins/pane-nav.lua delegate to it) and flags the pane @pane-is-vim
|
|
||||||
-- for tmux.conf. herdr and kitty stay on config.pane_nav, so its env() picks
|
|
||||||
-- the integration instead of smart-splits' own env-var guess. Loaded at
|
|
||||||
-- startup under tmux so the flag is set before the first keypress
|
|
||||||
-- (AstroNvim's early load keys off TERM_PROGRAM, which tmux.conf passes
|
|
||||||
-- through from the outer terminal).
|
|
||||||
{
|
|
||||||
"mrjones2014/smart-splits.nvim",
|
|
||||||
lazy = require("config.pane_nav").env() ~= "tmux",
|
|
||||||
opts = function(_, opts)
|
|
||||||
opts.at_edge = "wrap"
|
|
||||||
opts.multiplexer_integration = require("config.pane_nav").env() == "tmux" and "tmux" or false
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
|
|
||||||
-- lsp_signature.nvim is gone; astrolsp's own signature help replaces it
|
|
||||||
{ "AstroNvim/astrolsp", opts = { features = { signature_help = true } } },
|
|
||||||
|
|
||||||
{
|
|
||||||
"folke/snacks.nvim",
|
|
||||||
---@type snacks.Config
|
|
||||||
opts = {
|
|
||||||
picker = {
|
|
||||||
jump = { reuse_win = true },
|
|
||||||
sources = picker_sources,
|
|
||||||
},
|
|
||||||
-- no dashboard: startup either restores the worktree/branch session or
|
|
||||||
-- opens the git files picker (see config.session)
|
|
||||||
dashboard = { enabled = false },
|
|
||||||
|
|
||||||
-- GitHub issue/PR pickers and buffers (`<C-g>` binds in astrocore.lua)
|
|
||||||
gh = {},
|
|
||||||
|
|
||||||
-- auto-highlight LSP references of the symbol under the cursor;
|
|
||||||
-- ]] / [[ jump binds live in plugins/astrolsp.lua (capability-gated)
|
|
||||||
words = { enabled = true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
--- vellum.nvim: live GitHub-markdown preview in a split, mermaid/math as images
|
|
||||||
--- `:Vellum` toggles it, `:Vellum export [file.pdf|file.html]` exports.
|
|
||||||
---
|
|
||||||
--- Runs from the prdlk fork's `mdx` branch until blackhat-7/vellum.nvim#4
|
|
||||||
--- merges: upstream only accepts filetype `markdown`, so `markdown.mdx`
|
|
||||||
--- buffers could not export, take over the preview or be followed by links,
|
|
||||||
--- and their import/export lines and {/* comments */} rendered as text.
|
|
||||||
--- @module plugins.vellum
|
|
||||||
|
|
||||||
---@type LazySpec
|
|
||||||
return {
|
|
||||||
"prdlk/vellum.nvim",
|
|
||||||
branch = "mdx",
|
|
||||||
-- `markdown.mdx` is the mdx pack's filetype; a dotted filetype only matches
|
|
||||||
-- a pattern that spells it out
|
|
||||||
ft = { "markdown", "markdown.mdx" },
|
|
||||||
cmd = "Vellum",
|
|
||||||
opts = {},
|
|
||||||
}
|
|
||||||
+36
-6
@@ -1,7 +1,37 @@
|
|||||||
-- This will run last in the setup process.
|
-- Polish configuration: Final setup and overrides
|
||||||
-- Pure lua; anything that doesn't fit the normal config locations goes here.
|
-- This runs last in the setup process
|
||||||
|
|
||||||
-- <Leader>u UI toggles, mapped via Snacks.toggle so which-key shows the
|
-- Remove <leader>h mapping (conflicts with Telescope)
|
||||||
-- snacks state icons/colors (see config.toggles). LSP-scoped keymaps live in
|
vim.api.nvim_del_keymap("n", "<leader>h")
|
||||||
-- plugins/astrolsp.lua, where astrolsp gates them on server capabilities.
|
|
||||||
require("config.toggles").setup()
|
-- Screen session integration: Handle suspend/resume gracefully
|
||||||
|
local screen_augroup = vim.api.nvim_create_augroup("ScreenIntegration", { clear = true })
|
||||||
|
|
||||||
|
-- Before suspension (Screen detach), save all buffers
|
||||||
|
vim.api.nvim_create_autocmd("VimSuspend", {
|
||||||
|
group = screen_augroup,
|
||||||
|
callback = function()
|
||||||
|
-- Save all modified buffers before suspension
|
||||||
|
vim.cmd "silent! wa"
|
||||||
|
-- Save session if in a Screen session
|
||||||
|
if vim.env.STY ~= nil and vim.env.STY ~= "" then
|
||||||
|
local session_name = vim.env.STY:match "[^%.]+%.(.+)"
|
||||||
|
vim.notify("Suspending from Screen session: " .. (session_name or vim.env.STY), vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
desc = "Save all buffers before Screen detach/suspension",
|
||||||
|
})
|
||||||
|
|
||||||
|
-- After resume from suspension (Screen reattach)
|
||||||
|
vim.api.nvim_create_autocmd("VimResume", {
|
||||||
|
group = screen_augroup,
|
||||||
|
callback = function()
|
||||||
|
-- Check for external file changes after resuming
|
||||||
|
vim.cmd "checktime"
|
||||||
|
if vim.env.STY ~= nil and vim.env.STY ~= "" then
|
||||||
|
local session_name = vim.env.STY:match "[^%.]+%.(.+)"
|
||||||
|
vim.notify("Resumed in Screen session: " .. (session_name or vim.env.STY), vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
desc = "Check for file changes after Screen reattach/resume",
|
||||||
|
})
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
---
|
|
||||||
base: lua51
|
base: lua51
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
vim:
|
vim:
|
||||||
any: true
|
any: true
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// Persistent mermaid syntax validator, driven by lua/config/mermaid.lua.
|
|
||||||
//
|
|
||||||
// mermaid's own parsers are the only authoritative syntax check: tree-sitter's
|
|
||||||
// mermaid grammar rejects a large amount of valid mermaid (`graph`, `style`,
|
|
||||||
// `classDef`, `click`, `box`, `create`, ... measured at 12/40 valid samples),
|
|
||||||
// so it is used for highlighting only and every diagnostic comes from here.
|
|
||||||
//
|
|
||||||
// Protocol: newline-delimited JSON, one request per line on stdin, one
|
|
||||||
// response per line on stdout, correlated by `id`.
|
|
||||||
// -> {"id":1,"text":"flowchart TD\n A --> B\n"}
|
|
||||||
// <- {"id":1,"ok":true}
|
|
||||||
// <- {"id":1,"ok":false,"line":2,"col":8,"message":"Expecting 'SQE', ..."}
|
|
||||||
// `line` is 1-based and `col` is 0-based, both relative to the request text.
|
|
||||||
//
|
|
||||||
// The process exits on stdin EOF and after `--idle-ms` without a request, so
|
|
||||||
// the ~300MB mermaid+jsdom heap is not resident for the whole nvim session.
|
|
||||||
|
|
||||||
const idleArg = process.argv.indexOf("--idle-ms");
|
|
||||||
const IDLE_MS = idleArg === -1 ? 300_000 : Number(process.argv[idleArg + 1]) || 300_000;
|
|
||||||
|
|
||||||
// mermaid reaches for DOMPurify (and therefore a DOM) while parsing several
|
|
||||||
// diagram types, so a jsdom window has to exist before it is imported.
|
|
||||||
const { JSDOM } = await import("jsdom");
|
|
||||||
const dom = new JSDOM("<!DOCTYPE html><body></body>", { pretendToBeVisual: true });
|
|
||||||
for (const key of ["window", "document", "DOMParser", "Element", "Node", "SVGElement", "HTMLElement", "navigator"]) {
|
|
||||||
const value = key === "window" ? dom.window : dom.window[key];
|
|
||||||
// node >= 21 exposes `navigator` as a getter-only global
|
|
||||||
Object.defineProperty(globalThis, key, { value, configurable: true, writable: key !== "navigator" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const mermaid = (await import("mermaid")).default;
|
|
||||||
mermaid.initialize({ startOnLoad: false, suppressErrorRendering: true });
|
|
||||||
|
|
||||||
/** Drop everything mermaid strips before parsing, keeping a line map.
|
|
||||||
*
|
|
||||||
* mermaid removes frontmatter, `%%` comments, `%%{init}%%` directives and
|
|
||||||
* leading blank lines before a diagram parser ever sees the text, so the line
|
|
||||||
* numbers it reports count the *stripped* text. Doing the same removals here
|
|
||||||
* makes mermaid's own pass a no-op and `map` translates back: reduced line i
|
|
||||||
* (0-based) came from original line `map[i]` (0-based).
|
|
||||||
* @param {string} text
|
|
||||||
* @returns {{text: string, map: number[]}}
|
|
||||||
*/
|
|
||||||
function preprocess(text) {
|
|
||||||
const lines = text.split("\n");
|
|
||||||
const kept = [];
|
|
||||||
const map = [];
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
// frontmatter: only when `---` opens the very first non-blank line
|
|
||||||
if (/^\s*---[ \t]*$/.test(lines[index] ?? "")) {
|
|
||||||
for (let scan = index + 1; scan < lines.length; scan++) {
|
|
||||||
if (/^\s*---[ \t]*$/.test(lines[scan])) {
|
|
||||||
index = scan + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; index < lines.length; index++) {
|
|
||||||
const line = lines[index];
|
|
||||||
if (/^\s*%%(?!\{)/.test(line)) continue; // comment line
|
|
||||||
if (/^\s*%%\{/.test(line)) {
|
|
||||||
// directive: single line, or spanning lines until the `}%%` close
|
|
||||||
while (!/\}%%/.test(lines[index]) && index + 1 < lines.length) index++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (kept.length === 0 && /^\s*$/.test(line)) continue; // leading blank
|
|
||||||
kept.push(line);
|
|
||||||
map.push(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { text: kept.join("\n"), map };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Position of a parse failure within the request text.
|
|
||||||
* @param {any} error
|
|
||||||
* @returns {{line: number, col: number}}
|
|
||||||
*/
|
|
||||||
function position(error) {
|
|
||||||
const loc = error?.hash?.loc;
|
|
||||||
// jison parsers: 1-based lines, 0-based columns
|
|
||||||
if (typeof loc?.first_line === "number") {
|
|
||||||
return { line: loc.first_line, col: typeof loc.first_column === "number" ? loc.first_column : 0 };
|
|
||||||
}
|
|
||||||
const message = String(error?.message ?? error);
|
|
||||||
// langium parsers and the lexers report position in prose only
|
|
||||||
const both = /\bline (\d+), column (\d+)/.exec(message);
|
|
||||||
if (both) return { line: Number(both[1]), col: Math.max(0, Number(both[2]) - 1) };
|
|
||||||
const lineOnly = /\bline (\d+)/.exec(message);
|
|
||||||
if (lineOnly) return { line: Number(lineOnly[1]), col: 0 };
|
|
||||||
return { line: 1, col: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Human-readable one-line reason, with position prose and echoed source removed.
|
|
||||||
* @param {any} error
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function describe(error) {
|
|
||||||
if (error?.name === "UnknownDiagramError") return "unknown diagram type";
|
|
||||||
const lines = String(error?.message ?? error)
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
// jison spells the failure out on a trailing `Expecting ...` line, after an
|
|
||||||
// excerpt of the source and a caret line that are noise in a diagnostic
|
|
||||||
const detail = lines.find((line) => /^(Expecting|Expected|Unrecognized|Invalid|Unknown)/.test(line));
|
|
||||||
const text = (detail ?? lines[lines.length - 1] ?? "parse error")
|
|
||||||
.replace(/^Parsing failed:\s*/, "")
|
|
||||||
.replace(/^Parse error on line \d+(, column \d+)?:?\s*/, "")
|
|
||||||
.replace(/^Lexical error on line \d+\.\s*/, "");
|
|
||||||
return text.length > 300 ? `${text.slice(0, 297)}...` : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} text @returns {Promise<object>} */
|
|
||||||
async function validate(text) {
|
|
||||||
const { text: stripped, map } = preprocess(text);
|
|
||||||
try {
|
|
||||||
await mermaid.parse(stripped);
|
|
||||||
return { ok: true };
|
|
||||||
} catch (error) {
|
|
||||||
const { line, col } = position(error);
|
|
||||||
// map back to the request text; an out-of-range line means mermaid pointed
|
|
||||||
// past the stripped text, so fall back to its last line
|
|
||||||
const mapped = map[line - 1] ?? map[map.length - 1] ?? 0;
|
|
||||||
return { ok: false, line: mapped + 1, col, message: describe(error) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let idleTimer;
|
|
||||||
function resetIdleTimer() {
|
|
||||||
clearTimeout(idleTimer);
|
|
||||||
idleTimer = setTimeout(() => process.exit(0), IDLE_MS);
|
|
||||||
idleTimer.unref?.();
|
|
||||||
}
|
|
||||||
|
|
||||||
const queue = [];
|
|
||||||
let draining = false;
|
|
||||||
|
|
||||||
async function drain() {
|
|
||||||
if (draining) return;
|
|
||||||
draining = true;
|
|
||||||
while (queue.length) {
|
|
||||||
const request = queue.shift();
|
|
||||||
const result = await validate(String(request.text ?? ""));
|
|
||||||
process.stdout.write(`${JSON.stringify({ id: request.id, ...result })}\n`);
|
|
||||||
}
|
|
||||||
draining = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer = "";
|
|
||||||
process.stdin.setEncoding("utf8");
|
|
||||||
process.stdin.on("data", (chunk) => {
|
|
||||||
resetIdleTimer();
|
|
||||||
buffer += chunk;
|
|
||||||
let newline = buffer.indexOf("\n");
|
|
||||||
while (newline !== -1) {
|
|
||||||
const line = buffer.slice(0, newline).trim();
|
|
||||||
buffer = buffer.slice(newline + 1);
|
|
||||||
newline = buffer.indexOf("\n");
|
|
||||||
if (!line) continue;
|
|
||||||
try {
|
|
||||||
queue.push(JSON.parse(line));
|
|
||||||
} catch {
|
|
||||||
// a malformed request line cannot be correlated to a response; drop it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void drain();
|
|
||||||
});
|
|
||||||
process.stdin.on("end", () => process.exit(0));
|
|
||||||
process.stdout.on("error", () => process.exit(0));
|
|
||||||
resetIdleTimer();
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<Accordion>
|
|
||||||
<AccordionItem title="{{_cursor_}}">
|
|
||||||
Answer.
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem title="">
|
|
||||||
Answer.
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<Badge variant="accent">{{_cursor_}}</Badge>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<CardGroup cols={2}>
|
|
||||||
<Card title="{{_cursor_}}" href="" icon="">
|
|
||||||
Description.
|
|
||||||
</Card>
|
|
||||||
<Card title="" href="" icon="">
|
|
||||||
Description.
|
|
||||||
</Card>
|
|
||||||
</CardGroup>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<CodeGroup>
|
|
||||||
|
|
||||||
```ts TypeScript
|
|
||||||
{{_cursor_}}
|
|
||||||
```
|
|
||||||
|
|
||||||
```python Python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
</CodeGroup>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<Color variant="compact">
|
|
||||||
<Color.Item name="{{_cursor_}}" value="#3B82F6" />
|
|
||||||
<Color.Item name="" value={{ light: "#FFFFFF", dark: "#0A0A0A" }} />
|
|
||||||
</Color>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<Columns cols={2}>
|
|
||||||
<Column>
|
|
||||||
{{_cursor_}}
|
|
||||||
</Column>
|
|
||||||
<Column>
|
|
||||||
|
|
||||||
</Column>
|
|
||||||
</Columns>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<Component path="{{_cursor_}}" />
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<Diff
|
|
||||||
lang="{{_cursor_}}"
|
|
||||||
old={`before`}
|
|
||||||
new={`after`}
|
|
||||||
/>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Expandable title="{{_cursor_}}">
|
|
||||||
Hidden detail.
|
|
||||||
</Expandable>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<FileTree>
|
|
||||||
|
|
||||||
- {{_cursor_}}
|
|
||||||
- index.mdx
|
|
||||||
|
|
||||||
</FileTree>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Frame caption="{{_cursor_}}" hint="">
|
|
||||||
<img src="" alt="" />
|
|
||||||
</Frame>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
title: {{_cursor_}}
|
|
||||||
description:
|
|
||||||
type: blog
|
|
||||||
date: {{_lua:os.date("%Y-%m-%d")_}}
|
|
||||||
authors:
|
|
||||||
- {{_author_}}
|
|
||||||
---
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
title: {{_cursor_}}
|
|
||||||
description:
|
|
||||||
type: changelog
|
|
||||||
changelog:
|
|
||||||
version: 0.0.0
|
|
||||||
date: {{_lua:os.date("%Y-%m-%d")_}}
|
|
||||||
category: Features
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: {{_cursor_}}
|
|
||||||
description:
|
|
||||||
---
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: {{_cursor_}}
|
|
||||||
description:
|
|
||||||
type: doc
|
|
||||||
slug:
|
|
||||||
draft: false
|
|
||||||
lastModified: {{_lua:os.date("%Y-%m-%d")_}}
|
|
||||||
sidebar:
|
|
||||||
label:
|
|
||||||
order: 1
|
|
||||||
icon:
|
|
||||||
badge:
|
|
||||||
hidden: false
|
|
||||||
seo:
|
|
||||||
title:
|
|
||||||
description:
|
|
||||||
image:
|
|
||||||
canonical:
|
|
||||||
noindex: false
|
|
||||||
search:
|
|
||||||
exclude: false
|
|
||||||
tags: []
|
|
||||||
---
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<GithubInfo owner="{{_cursor_}}" repo="" />
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<Icon icon="{{_cursor_}}" size={16} />
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Panel title="{{_cursor_}}">
|
|
||||||
Supporting detail.
|
|
||||||
</Panel>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Prompt description="{{_cursor_}}" actions={["copy"]}>
|
|
||||||
The prompt text copied to the clipboard.
|
|
||||||
</Prompt>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<Steps>
|
|
||||||
<Step title="{{_cursor_}}">
|
|
||||||
First step.
|
|
||||||
</Step>
|
|
||||||
<Step title="">
|
|
||||||
Second step.
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<Tabs>
|
|
||||||
<Tab title="{{_cursor_}}">
|
|
||||||
First tab.
|
|
||||||
</Tab>
|
|
||||||
<Tab title="">
|
|
||||||
Second tab.
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Tile title="{{_cursor_}}" description="" href="">
|
|
||||||
<Icon icon="rocket" size={28} />
|
|
||||||
</Tile>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<Tooltip tip="{{_cursor_}}" headline="" cta="" href="">term</Tooltip>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<Tree>
|
|
||||||
<Tree.Folder name="{{_cursor_}}" defaultOpen>
|
|
||||||
<Tree.File name="index.ts" />
|
|
||||||
</Tree.Folder>
|
|
||||||
<Tree.File name="" />
|
|
||||||
</Tree>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<AutoTypeTable path="{{_cursor_}}" name="" />
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<TypeTable
|
|
||||||
type={{
|
|
||||||
{{_cursor_}}: {
|
|
||||||
type: "string",
|
|
||||||
required: true,
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<Visibility for="{{_cursor_}}">
|
|
||||||
Audience-specific content.
|
|
||||||
</Visibility>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<YouTube id="{{_cursor_}}" title="" />
|
|
||||||
Reference in new issue
Block a user