Compare commits

..
Author SHA1 Message Date
root 7d448348b2 feat(pane-nav): smart-splits under tmux, pane_nav for herdr/kitty 2026-11-02 10:46:21 -05:00
Prad Nukala 3a0e1cb13e chore(deps): update plugin lockfile revisions 2026-11-02 09:12:30 -05:00
Prad Nukala a53eef0451 style(core): indent Python community imports consistently 2026-11-02 09:12:27 -05:00
Prad Nukala 85190ff7f4 feat(core): add Lua, Nix, Black, and isort community packs 2026-11-02 09:11:42 -05:00
Prad Nukala 0b4971cb8a chore(deps): update plugin lockfile dependencies 2026-10-18 09:39:43 -04:00
Prad Nukala 1f8a9364e5 chore(scripts): remove unused ANSI art script 2026-10-18 09:39:13 -04:00
Prad Nukala 8372131167 refactor(plugins): streamline Neovim plugin configuration and keybindings 2026-10-18 09:39:10 -04:00
Prad Nukala 32f43b8335 docs(readme): document plugin ownership and updated keymaps 2026-10-18 09:39:07 -04:00
Prad Nukala b15fa1405f chore(deps): remove unused plugin dependencies 2026-10-18 09:39:05 -04:00
Prad Nukala 78caca7822 chore(core): remove unused AstroCommunity plugin imports 2026-10-18 09:39:02 -04:00
Prad Nukala c64e145e67 chore(config): update ignore rules and remove unused editor configs 2026-10-18 09:39:00 -04:00
Prad Nukala 6548333e62 docs(readme): document Neovim configuration setup and keymaps 2026-10-18 09:31:18 -04:00
Prad Nukala 92ede624f2 fix(plugins): hide global statusline and remove bufferline sidebar offset 2026-10-16 18:18:38 -04:00
Prad Nukala 9f4de909e4 chore(deps): update plugin lockfile revisions 2026-10-16 18:18:36 -04:00
Prad Nukala 4caaff89bb feat(plugins): add cyberdream.nvim config and enhance Astroui options 2026-10-16 11:08:38 -04:00
Prad Nukala 58b8b7cbc1 feat(core): switch default colorscheme to cyberdream 2026-10-16 11:08:36 -04:00
Prad Nukala ebcad6f036 feat(config): add .crush to ignored patterns 2026-10-16 11:08:32 -04:00
Prad Nukala c843135457 docs(plugins): update NeoTree description in astrocore plugin 2026-10-14 11:38:27 -04:00
Prad Nukala ec38b89179 feat(config): show NeoTree on VimEnter before restoring session 2026-10-14 11:38:23 -04:00
Prad Nukala 29bac037b3 feat(plugins): add close_win and save_close_buf helpers and update mappings for conditional save/close behavior 2026-10-13 18:27:45 -04:00
Prad Nukala aff99b89f3 refactor(overseer): remove snacks_output component 2026-10-13 09:08:02 -04:00
Prad Nukala b4e53d160a feat(plugins): add codediff, tv, vellum plugins and drop overseer support 2026-10-13 09:07:58 -04:00
Prad Nukala e741a1564d chore(deps): update plugin commits, add codediff, tv, and vellum plugins 2026-10-13 09:07:54 -04:00
Prad Nukala 221a9ba069 refactor(core): remove overseer task runner import and related comments 2026-10-13 09:07:52 -04:00
Prad Nukala dcdb0d3527 feat(plugins): add omp.nvim plugin and toggle keybinding 2026-10-05 11:38:12 -04:00
Prad Nukala d3cc8043d1 chore(deps): update plugin versions in lazy-lock.json 2026-10-05 11:38:10 -04:00
Prad Nukala 637fbc4cb3 feat(config): add Oh My Pi terminal toggle configuration 2026-10-05 11:38:06 -04:00
Prad Nukala a3e067b701 feat(plugins): move NeoTree window to left and shrink width to 32 2026-10-05 11:05:29 -04:00
Prad Nukala a783211623 feat(plugins): move NeoTree window to right and increase width to 36 2026-10-05 11:03:57 -04:00
Prad Nukala ad29f03972 feat(plugins): add markdown edit visual-mode formatting bindings 2026-09-22 14:14:03 -04:00
Prad Nukala f6c6b05332 feat(config): add markdown edit config and ignore .githooks pattern 2026-09-22 14:14:01 -04:00
Prad Nukala c34b24d91d feat(overseer): keep task output visible after completion and add manual close keys 2026-09-21 16:36:40 -04:00
Prad Nukala d663a571d3 feat(overseer): add Snacks output component for task display 2026-09-21 16:33:28 -04:00
Prad Nukala 1994b3f6bb feat(plugins): replace NeoCodeium with Supermaven and add Supermaven plugins 2026-09-21 16:32:53 -04:00
Prad Nukala 11af9682ad chore(deps): add blink-cmp-supermaven and supermaven-nvim, remove neocodeium 2026-09-21 15:58:29 -04:00
Prad Nukala a3a04bb152 feat(core): add supermaven-nvim completion plugin import 2026-09-21 15:58:25 -04:00
Prad Nukala 8f78c67019 feat(plugins): add URL picker and today‑modified file finder, replace Supermaven with NeoCodeium 2026-09-21 15:14:41 -04:00
Prad Nukala 8a76b9c4f5 feat(deps): replace supermaven plugins with neocodeium and add overseer.nvim 2026-09-21 15:14:38 -04:00
Prad Nukala abc520032f feat(core): add overseer task runner import and related comments 2026-09-21 15:14:36 -04:00
Prad Nukala c3e2a4a949 feat(scripts): add persistent mermaid syntax validator CLI 2026-08-29 20:57:40 -04:00
Prad Nukala 9b92ea4dd1 feat(plugins): add mermaid plugin with tree-sitter highlighting and diagnostics 2026-08-29 20:57:36 -04:00
Prad Nukala 13e638e77b feat(config): add mermaid diagnostics configuration 2026-08-29 20:57:34 -04:00
Prad Nukala 77b718c77f feat(plugins): replace scooter find‑replace with grug‑far and add Python interpreter resolution 2026-08-25 14:47:15 -04:00
Prad Nukala 28bd9b89f5 chore(deps): update plugin revisions and add new plugins 2026-08-25 14:47:11 -04:00
Prad Nukala 3380197bab feat(core): add python community packs and UI toggle setup 2026-08-25 14:47:08 -04:00
Prad Nukala f5106d9cba feat(config): add pre-save LSP code actions and UI toggle helpers 2026-08-25 14:47:06 -04:00
Prad Nukala 20b7884c1e chore(deps): update fff plugin commit and add lsp_signature.nvim 2026-08-25 13:51:19 -04:00
Prad Nukala 9b2009a878 feat(core): add astrocommunity.lsp.lsp-signature-nvim import 2026-08-25 13:51:17 -04:00
Prad Nukala c442ad6ac9 feat(plugins): integrate ruff via mason-tool-installer and configure Python formatting 2026-08-24 15:21:55 -04:00
Prad Nukala 96d6dc2270 chore(deps): update dependency commits in lazy-lock.json 2026-08-24 15:21:51 -04:00
Prad Nukala 3a5fa33136 chore(plugins): remove heirline statusline configuration 2026-08-20 16:07:20 -04:00
Prad Nukala 3633eb6022 chore(deps): remove octo.nvim and update schemastore.nvim commit hash 2026-08-20 16:07:17 -04:00
Prad Nukala a3b9aca33a feat(core): add heirline‑nvchad statusline import 2026-08-20 16:06:43 -04:00
Prad Nukala 0c4041d56f refactor(heirline): replace angled NvChad statusline with flat VSCode‑style layout 2026-08-20 15:26:49 -04:00
Prad Nukala f183c770f3 feat(plugins): integrate snacks.nvim keymaps and extend plugin config with GitHub, LSP highlights, and UI styles 2026-08-20 15:26:43 -04:00
Prad Nukala 0fb67acc3d feat(core): add LSP reference navigation keymaps and drop octo.nvim import 2026-08-20 15:26:41 -04:00
Prad Nukala 4b15aead5c refactor(supermaven): simplify LeetCode buffer detection logic 2026-08-20 10:47:21 -04:00
Prad Nukala 779e081d80 feat(plugins): add fff fuzzy finder and supermaven LeetCode toggle plugins 2026-08-20 10:43:32 -04:00
Prad Nukala 8f49f66837 feat(plugins): switch to fff for find/grep and enhance keymaps 2026-08-20 10:43:28 -04:00
Prad Nukala 6b8a1ec910 chore(deps): bump plugin versions and add fff, template.nvim 2026-08-20 10:43:25 -04:00
Prad Nukala e3d82eb403 feat(config): add template.nvim plugin for Snacks picker 2026-08-17 14:17:02 -04:00
Prad Nukala a6043d6c9f chore(deps): update cyberdream.nvim commit hash to latest version 2026-08-15 21:36:15 -04:00
Prad Nukala aee8fe3b2d feat(session): skip session restore on startup with file arguments 2026-08-15 15:43:32 -04:00
Prad Nukala d9a7970d3e feat(config): add git-aware directory session handling 2026-08-15 15:15:26 -04:00
Prad Nukala 8c010498c7 feat(plugins): add git-branch-aware session handling and disable dashboard startup 2026-08-15 15:15:22 -04:00
Prad Nukala 6244488943 feat(pane-nav): add multiplexer-aware pane navigation 2026-08-15 13:47:41 -04:00
Prad Nukala 4ad4881992 chore(plugins): remove kitty-navigator plugin configuration 2026-08-15 13:47:37 -04:00
Prad Nukala 8e436e8c77 chore(deps): remove kitty-navigator.nvim dependency 2026-08-15 13:47:35 -04:00
Prad Nukala 9c02aa9fb9 docs(plugins): improve comment on neo-tree source merge handling 2026-08-14 18:52:57 -04:00
Prad Nukala 657726cc1d fix(plugins): correct neo-tree key mappings case and improve git status handling 2026-08-14 18:52:27 -04:00
Prad Nukala 1a0e078b5d chore(plugins): remove mini-files plugin configuration 2026-08-14 18:42:59 -04:00
Prad Nukala 767ed7a4bf feat(plugins): add Neo-tree file explorer configuration 2026-08-14 18:42:56 -04:00
Prad Nukala ccdad0fbcc chore(deps): update plugin commits in lazy-lock.json 2026-08-14 18:42:54 -04:00
Prad Nukala 8b123d9038 refactor(config): drop obsolete ignore pattern utilities 2026-08-14 18:42:51 -04:00
Prad Nukala fce59322a7 feat(plugins): add two‑stage find‑by‑extension command with <C-f>e shortcut 2026-08-13 20:55:26 -04:00
Prad Nukala fe02c6e258 feat(mini-files): change delete prompt default to yes and accept empty answer as confirmation 2026-08-13 16:47:17 -04:00
Prad Nukala cbd6528c83 feat(mini-files): add split opening helpers and never‑show filter integration 2026-08-13 16:18:19 -04:00
Prad Nukala 24f40765fb feat(config): add is_never_shown helper for mini.files filter 2026-08-13 16:18:15 -04:00
Prad Nukala d394e88469 feat(mini-files): add delete entry command and cwd‑relative labels for add prompts 2026-08-13 16:17:10 -04:00
Prad Nukala 07ab9d44dd fix(plugins): ignore dev-tools server in astrocore configuration 2026-08-13 16:14:14 -04:00
Prad Nukala 5a7a87d170 feat(plugins): replace yazi integration with mini.files explorer 2026-08-13 16:04:43 -04:00
Prad Nukala 2843d45bec chore(config): remove legacy yazi session module 2026-08-13 16:04:32 -04:00
Prad Nukala ee5fb43c00 feat(plugins): add dashboard, Octo shortcuts, and simplify title handling 2026-08-13 16:04:29 -04:00
Prad Nukala 90f8f0bd64 chore(deps): update plugin versions in lazy-lock.json 2026-08-13 16:04:27 -04:00
Prad Nukala 05cc850e9e feat(core): add octo.nvim and lsp-file-operations imports, remove Snacks keymaps 2026-08-13 16:04:25 -04:00
Prad Nukala d0d02a28e6 docs(config): update comment to reference mini.files instead of neo-tree 2026-08-13 16:04:22 -04:00
Prad Nukala 76c733cddf feat(yazi): add persistent yazi session integration 2026-08-10 17:10:15 -04:00
Prad Nukala 546dc64684 feat(plugins): replace yazi.nvim with persistent yazi session and disable neo-tree 2026-08-10 17:10:08 -04:00
Prad Nukala 384e9cf4ed chore(deps): remove yazi.nvim from lazy-lock.json 2026-08-10 17:10:05 -04:00
Prad Nukala 4f82b1fca6 feat(core): eagerly load Yazi session module for remote edit support 2026-08-10 17:10:03 -04:00
Prad Nukala 5dff0e2eb8 docs(config): clarify ignore patterns comment to reference yazi session) 2026-08-10 17:10:02 -04:00
Prad Nukala 9eb60b6cdf feat(heirline): add angled separators, Unicode icons, and progress pill to statusline 2026-08-10 15:41:56 -04:00
Prad Nukala 579db51ee6 feat(plugins): set terminal title to '<file> — <org/repo>' 2026-08-10 15:41:52 -04:00
Prad Nukala e7cad4d374 feat(plugins): add Blink supermaven, external TUI, and heirline statusline plugins 2026-08-10 15:25:37 -04:00
Prad Nukala 3d606e6e1d feat(plugins): integrate snacks.nvim enhancements and update keybindings 2026-08-10 15:25:31 -04:00
Prad Nukala 7effe9d11f chore(deps): bump plugin versions and add new plugins in lazy-lock.json 2026-08-10 15:25:29 -04:00
Prad Nukala 6bef91ddcc feat(core): add LSP-aware reference navigation keymaps and remove unused community imports 2026-08-10 15:25:26 -04:00
Prad Nukala 99ee481bfa feat(plugins): use cwd file picker for <C-f>f shortcut 2026-07-22 12:45:50 -04:00
Prad Nukala 7737e22d73 feat(plugins): switch to git file picker and disable multiple yazi tabs 2026-07-22 12:44:17 -04:00
Prad Nukala 0b30af954a feat(plugins): add custom key mappings for AstroNvim 2026-07-16 12:41:43 -04:00
Prad Nukala bc71fc7ca4 fix(plugins): disable AstroNvim's default <C-q> keybinding to avoid conflicts 2026-07-13 20:33:41 -04:00
Prad Nukala 4845f0d163 feat(core): improve GUI cursor settings for better usability 2026-07-13 20:33:40 -04:00
Prad Nukala 2311ab09c5 feat(plugins): add option to skip format-on-save for JavaScript buffers 2026-07-13 15:07:21 -04:00
Prad Nukala 6789eab509 refactor(flash): update flash plugin keybindings for navigation 2026-07-12 15:55:48 -04:00
Prad Nukala e9d286d705 feat(plugins): add keymaps for Supermaven and Yazi integrations 2026-07-12 15:55:46 -04:00
Prad Nukala 56b347b450 feat(flash): enhance search and jump labels for better usability 2026-07-12 11:18:35 -04:00
Prad Nukala f5a1ec1e88 feat(plugins): add LSP symbols finder to smart picker 2026-07-12 11:18:33 -04:00
Prad Nukala 0410c40ac1 feat(flash): add search-style flash jump with regex pattern 2026-07-12 11:01:47 -04:00
Prad Nukala c86fca60d6 feat(lua): add flash.nvim plugin for enhanced motions and labeled jumps 2026-07-12 00:18:11 -04:00
Prad Nukala 287b6f7f7a deps: add flash.nvim to lazy-lock.json 2026-07-12 00:18:09 -04:00
Prad Nukala 11a44f9fc5 feat(plugins): improve yazi toggle functionality to handle existing instances gracefully 2026-07-12 00:08:33 -04:00
Prad Nukala bf8c8e4a94 chore(.gitignore): add .understand-anything directory to ignore list 2026-07-11 19:06:28 -04:00
Prad Nukala 71038d80de feat(plugins): add toggle command for yazi plugin 2026-07-11 18:48:45 -04:00
Prad Nukala 1e12c549d9 refactor(astrocore): remove git_files picker bindings 2026-07-11 18:46:52 -04:00
Prad Nukala 3671bb7996 chore(tools): add understand-anything configuration files 2026-07-11 18:46:50 -04:00
Prad Nukala 2eedac5134 refactor(plugins): replace neo-tree with yazi as the file manager 2026-07-11 18:46:49 -04:00
Prad Nukala 63bae171e3 chore(deps): add yazi.nvim dependency 2026-07-11 18:46:48 -04:00
Prad Nukala f80b18544a refactor(config): enhance ignore patterns and remove terminals.lua 2026-07-11 18:46:47 -04:00
Prad Nukala d8bf571fdc chore(tools): add prdlk.sh for displaying ansi art 2026-07-11 16:43:32 -04:00
Prad Nukala 0e59d020a6 refactor(plugins): Update keybindings and remove Claude Code references 2026-07-11 16:43:30 -04:00
Prad Nukala 2683601999 docs(core): clarify polish.lua documentation 2026-07-11 16:43:29 -04:00
Prad Nukala f875a9a904 docs(config): fix typo in ignore_patterns.lua documentation 2026-07-11 16:43:28 -04:00
Prad Nukala 5b860287fa fix(plugins): enhance safety checks for applying LSP code actions on save 2026-07-11 15:15:26 -04:00
Prad Nukala fd3d384d4b chore(deps): remove unused dependencies from lazy-lock.json 2026-07-11 15:15:25 -04:00
Prad Nukala f2cf914582 chore(deps): update dependencies in lazy-lock.json 2026-07-11 15:11:43 -04:00
Prad Nukala 35523bce7e refactor(core): remove unused plugins from community.lua 2026-07-11 15:11:42 -04:00
Prad Nukala 0bef2ff9f1 refactor(plugins): simplify user plugin configuration 2026-07-09 14:09:38 -04:00
Prad Nukala 7e55435b39 chore(deps): update lazy-lock.json dependencies 2026-07-09 14:09:36 -04:00
Prad Nukala a776e04e35 refactor(core): remove image preview setup on init 2026-07-09 14:09:34 -04:00
Prad Nukala 792cbb8091 chore(config): add ignore patterns for .beads and .worktrees directories 2026-07-05 16:56:46 -04:00
Prad Nukala 370e2a524d feat(plugins): add LeetCode keymap and support for alternative quit keybinds 2026-07-05 11:15:53 -04:00
Prad Nukala b954d1df9b deps(lazy-lock): update dependency commits in lazy-lock.json 2026-07-05 11:15:52 -04:00
Prad Nukala 685871f5e6 refactor(core): remove overseer-nvim config and plugin 2026-07-05 11:15:50 -04:00
Prad Nukala 65f19a0105 feat(plugins): add image preview and lazydocker toggle functionality 2026-06-28 13:54:52 -04:00
Prad Nukala b8f871a954 deps(lazy-lock): update dependency commits in lazy-lock.json 2026-06-28 13:54:51 -04:00
Prad Nukala f475c01e9d feat(core): add image preview setup on nvim initialization 2026-06-28 13:54:50 -04:00
Prad Nukala 1b2e71e34f refactor(config): replace mk and scratch terminals with yazi integration 2026-06-28 13:54:50 -04:00
Prad Nukala 6d7b286e74 feat(zed): add keymap configuration mirroring AstroNvim config 2026-06-23 11:22:17 -04:00
Prad Nukala 76fe56644e chore(git): add .gitignore file for omc and omc/state directories 2026-06-21 13:25:32 -04:00
Prad Nukala 5185c455cf chore(kairo.db): remove kairo.db file 2026-06-21 13:25:31 -04:00
Prad Nukala 03ef0ecf15 refactor(plugins): Update AstroCore, Kitty, and NeoTree configs 2026-06-21 13:25:29 -04:00
Prad Nukala d1425bc422 chore(deps): update dependencies in lazy-lock.json 2026-06-21 13:25:27 -04:00
Prad Nukala 2389507dee feat(core): enhance Nvim initialization with title setting and optimized shell handling 2026-06-21 13:25:26 -04:00
Prad Nukala 311730bc97 chore(config): add .omc to ignore patterns 2026-06-21 13:25:25 -04:00
Prad Nukala f93476e3a7 fix: .gitignore 2026-06-11 10:56:31 -04:00
Prad Nukala 6e85922f2a refactor(plugins): replace kitty-navigator with vim-tmux-navigator 2026-06-11 10:55:58 -04:00
Prad Nukala 79bd007c0d deps(lazy-lock): update dependencies to latest commits 2026-06-11 10:55:57 -04:00
Prad Nukala f1f1b248d1 chore(config): add .epiq to ignore patterns 2026-06-11 10:55:56 -04:00
Prad Nukala a31af2698a init(config): add initial configuration files 2026-05-29 18:35:12 -04:00
Prad Nukala 29e071d3e6 docs(readme): add installation guide for AstroNvim template 2026-05-29 18:35:11 -04:00
Prad Nukala 5d19c56820 chore(deps): add lazy-lock.json file with dependencies information 2026-05-29 18:35:10 -04:00
Prad Nukala 9a6bdfcf8c feat(core): add astrocommunity configuration 2026-05-29 18:35:10 -04:00
Prad Nukala 4a6b90cba3 chore(config): add configuration files for lua and stylua formatting 2026-05-29 18:35:08 -04:00
73 changed files with 3380 additions and 1825 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# AI agent / tooling state written into the config dir
/.omc/
/.claude/
/.crush/
/.beads/
/.codegraph/
/.understand-anything/
/.ai/
# Neovim scratch
*.log
*.swp
*~
# OS
.DS_Store
Thumbs.db
+1 -3
View File
@@ -1,5 +1,3 @@
{ {
"diagnostics.disable": [ "format.enable": false
"deprecated"
]
} }
+18 -18
View File
@@ -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
} }
} }
} }
View File
Whitespace-only changes.
+165 -18
View File
@@ -1,12 +1,28 @@
# AstroNvim Template # prdlk/nvim
**NOTE:** This is for AstroNvim v5+ Personal Neovim config built on [AstroNvim](https://github.com/AstroNvim/AstroNvim) v6 (`version = "^6"` in `lua/lazy_setup.lua`), targeting Neovim 0.12.
A template for getting started with [AstroNvim](https://github.com/AstroNvim/AstroNvim) 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.
## 🛠️ Installation ## Requirements
#### Make a backup of your current nvim and shared folder | Tool | Used by |
| --- | --- |
| 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
@@ -15,20 +31,151 @@ mv ~/.local/state/nvim ~/.local/state/nvim.bak
mv ~/.cache/nvim ~/.cache/nvim.bak mv ~/.cache/nvim ~/.cache/nvim.bak
``` ```
#### Create a new user repository from this template Clone and start:
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`.
-53
View File
@@ -1,53 +0,0 @@
{
"lockfile_version": "1",
"packages": {
"neovim@latest": {
"last_modified": "2025-02-07T11:26:36Z",
"resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#neovim",
"source": "devbox-search",
"version": "0.10.4",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/dsn4k9wp16xv1j4b7l0d4liwbk98ya44-neovim-0.10.4",
"default": true
}
],
"store_path": "/nix/store/dsn4k9wp16xv1j4b7l0d4liwbk98ya44-neovim-0.10.4"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/mpq2wyqkippwzj8m2lx4qzn01kqjbfa5-neovim-0.10.4",
"default": true
}
],
"store_path": "/nix/store/mpq2wyqkippwzj8m2lx4qzn01kqjbfa5-neovim-0.10.4"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/izwv6gp1la8rcjax5xb8z0k8ws9j8al2-neovim-0.10.4",
"default": true
}
],
"store_path": "/nix/store/izwv6gp1la8rcjax5xb8z0k8ws9j8al2-neovim-0.10.4"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/fhzw8ah6s37q2p9pak7d2alzhx0m137h-neovim-0.10.4",
"default": true
}
],
"store_path": "/nix/store/fhzw8ah6s37q2p9pak7d2alzhx0m137h-neovim-0.10.4"
}
}
}
}
}
+29 -45
View File
@@ -1,54 +1,38 @@
-- Bootstrap Lazy.nvim and load config safely across environments -- This file simply bootstraps the installation of Lazy.nvim and then calls other files for execution
local uv = vim.uv or vim.loop -- This file doesn't necessarily need to be touched, BE CAUTIOUS editing this file and proceed at your own risk.
pcall(function() local lazypath = vim.env.LAZY or vim.fn.stdpath "data" .. "/lazy/lazy.nvim"
if vim.loader then vim.loader.enable() end
end)
-- Set leaders early for consistency if not (vim.env.LAZY or (vim.uv or vim.loop).fs_stat(lazypath)) then
vim.g.mapleader = " " -- stylua: ignore
vim.g.maplocalleader = "," 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
-- Allow overriding lazy path via LAZY env -- stylua: ignore
local lazypath = vim.env.LAZY or (vim.fn.stdpath "data" .. "/lazy/lazy.nvim") vim.api.nvim_echo({ { ("Error cloning lazy.nvim:\n%s\n"):format(result), "ErrorMsg" }, { "Press any key to exit...", "MoreMsg" } }, true, {})
vim.fn.getchar()
-- Bootstrap lazy.nvim only if not provided via env and not installed vim.cmd.quit()
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
-- Add to runtimepath if available, else exit quietly (headless-safe) vim.opt.rtp:prepend(lazypath)
if uv and uv.fs_stat(lazypath) then
vim.opt.rtp:prepend(lazypath)
else
vim.notify(("lazy.nvim not available at %s"):format(lazypath), vim.log.levels.WARN)
return
end
-- validate that lazy is available -- validate that lazy is available
local ok_lazy = pcall(require, "lazy") if not pcall(require, "lazy") then
if not ok_lazy then -- stylua: ignore
vim.notify(("Unable to load lazy from: %s"):format(lazypath), vim.log.levels.ERROR) 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
-- Load specs and final polish with guards require "lazy_setup"
local ok_setup = pcall(require, "lazy_setup") require "polish"
if not ok_setup then
vim.notify("Failed to load lazy_setup", vim.log.levels.ERROR)
return
end
-- Polish is optional; don't fail startup if it's missing -- Enable terminal title setting in Nvim
pcall(require, "polish") vim.opt.title = true
-- Set the format of the title
vim.opt.titlestring = "%t - Nvim"
-- Ensure Nvim doesn't slow down on exit by resetting shell
vim.cmd('set shell=sh') -- Prevents fish-incompatible lag when exiting nvim in fish/tmux
vim.opt.guicursor:append("v:hor20")
vim.opt.guicursor:append("a:blinkon0")
+59 -77
View File
@@ -1,89 +1,71 @@
{ {
"AstroNvim": { "branch": "main", "commit": "7daf9cd3121172bcecb25c3d4a28c7c01cf0e812" }, "AstroNvim": { "branch": "main", "commit": "fc95d5d331b897b1c6b46cbfa4e0251d8d9be6b7" },
"LuaSnip": { "branch": "master", "commit": "03c8e67eb7293c404845b3982db895d59c0d1538" }, "LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
"SchemaStore.nvim": { "branch": "main", "commit": "d307d291f38678d064cb987112926df6d9134de3" }, "aerial.nvim": { "branch": "master", "commit": "645d108a5242ec7b378cbe643eb6d04d4223f034" },
"aerial.nvim": { "branch": "master", "commit": "3284a2cb858ba009c79da87d5e010ccee3c99c4d" }, "astrocommunity": { "branch": "main", "commit": "d79a4b5d88bc38cf4f5926c0f32655c8e3d12b2b" },
"astrocommunity": { "branch": "main", "commit": "07d473a0e1a345ea320812a4197f11a54e50ac80" }, "astrocore": { "branch": "main", "commit": "f1ebf1d5b0bf4d92ab8c276a222fb39e02aad63e" },
"astrocore": { "branch": "main", "commit": "5d6b00072ef226507d68ef88857aa522b126f7a8" }, "astrolsp": { "branch": "main", "commit": "41810aa99789c5fcd2480a0e4ba27d3038afdd61" },
"astrolsp": { "branch": "main", "commit": "0ab285f47b9f667402f08eb471e3e98f9a50b5ee" }, "astrotheme": { "branch": "main", "commit": "83c077c29195882f9583b45ea41154b2fb8935fb" },
"astrotheme": { "branch": "main", "commit": "f12dcf64b1f9a05839c3ac2146f550f43bae9dab" }, "astroui": { "branch": "main", "commit": "9a797715484a6e68fc32bdd4c81cb1246f72139f" },
"astroui": { "branch": "main", "commit": "7a46b59fc51afb91bcb37f7a2ef3f8811b66bc93" }, "better-escape.nvim": { "branch": "master", "commit": "199dcc2643dec5d8dbdab4ec672cf405224dcb3b" },
"avante.nvim": { "branch": "main", "commit": "b6000d5bbcb85e3b11177846220bfd438fc013dd" }, "blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
"blink.cmp": { "branch": "main", "commit": "49f211fe5d729df53df4c042d7c3464cf47d199e" }, "blink.compat": { "branch": "main", "commit": "2ed6d9a28b07fa6f3bface818470605f8896408c" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"cmp-dap": { "branch": "master", "commit": "ea92773e84c0ad3288c3bc5e452ac91559669087" }, "cmp-dap": { "branch": "master", "commit": "ea92773e84c0ad3288c3bc5e452ac91559669087" },
"conform.nvim": { "branch": "master", "commit": "f9ef25a7ef00267b7d13bfc00b0dea22d78702d5" }, "codediff.nvim": { "branch": "main", "commit": "09d9ebef2cc5a5c04db7a349cd6c61bdf84ecc8e" },
"copilot.lua": { "branch": "master", "commit": "20723713aa5fbfd535fcf0cd28753a899ca3d526" }, "conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
"cyberdream.nvim": { "branch": "main", "commit": "f36d7991b6373ebda9cfca20030aa1fca7328ea8" }, "crates.nvim": { "branch": "main", "commit": "7039bc1d130e424232a7aa64ceddb170a154ff5b" },
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, "cyberdream.nvim": { "branch": "main", "commit": "8ed987565d081a8ebeda0bac939c22a0f451d074" },
"flash.nvim": { "branch": "main", "commit": "3c942666f115e2811e959eabbdd361a025db8b63" }, "fff": { "branch": "main", "commit": "d1c7dd47130ca670ab71e7466a1a3b4a57d5348d" },
"friendly-snippets": { "branch": "main", "commit": "efff286dd74c22f731cdec26a70b46e5b203c619" }, "flash.nvim": { "branch": "main", "commit": "5f0f270fdc7c5b0c21d903ee85b9cb06f2ac636a" },
"fzf-lua": { "branch": "main", "commit": "1dcef2467af609e0688f8136b8ea720645c9c41c" }, "friendly-snippets": { "branch": "main", "commit": "b4d01b0fdf3c9a549961c2f9ffe8dc09be166219" },
"gitsigns.nvim": { "branch": "main", "commit": "7010000889bfb6c26065e0b0f7f1e6aa9163edd9" }, "gitsigns.nvim": { "branch": "main", "commit": "a462f416e2ce4744531c6256252dee99a7d34a83" },
"gopher.nvim": { "branch": "main", "commit": "ea28fc4e6fd9edd4556b3431199c6112cfdb9c65" }, "gopher.nvim": { "branch": "main", "commit": "f397bed1c35f29f4f85d60a210650e677a8c5cb4" },
"grug-far.nvim": { "branch": "main", "commit": "91b7e58b6ffc6ddad5acb7a7c1df21257724449e" },
"guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" }, "guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" },
"heirline.nvim": { "branch": "master", "commit": "cc359b628266cb9a84b2d71c883f2b99e16473a0" }, "heirline.nvim": { "branch": "master", "commit": "fae936abb5e0345b85c3a03ecf38525b0828b992" },
"inc-rename.nvim": { "branch": "main", "commit": "87ebe7bee0b83d3b6e4f1494c74abed21b318175" }, "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, "lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
"lazydev.nvim": { "branch": "main", "commit": "f59bd14a852ca43db38e3662395354cb2a9b13e0" }, "mason-lspconfig.nvim": { "branch": "main", "commit": "00b5473cca4db5b410ff4bae724e3b6e21f2687f" },
"lazygit.nvim": { "branch": "main", "commit": "b9eae3badab982e71abab96d3ee1d258f0c07961" },
"leap.nvim": { "branch": "main", "commit": "346a16ef942635a8ca5ff92e603d07e7e8be6cbe" },
"live-preview.nvim": { "branch": "main", "commit": "4a11bece3fbe898569da72800f101e86f2241bcf" },
"lsp_signature.nvim": { "branch": "master", "commit": "c909a01eb923101c76a57fd2187835e9013ab59a" },
"markview.nvim": { "branch": "main", "commit": "556cac8bb8eeb461013ebe0d015e2c48d3527c36" },
"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": "ea387e0819ab189d536bb4067e4d8ece3801a292" }, "mason-nvim-dap.nvim": { "branch": "main", "commit": "e51f9b259f066c4347f9a79ffde54c29a0619384" },
"mason-tool-installer.nvim": { "branch": "main", "commit": "98767d37f8e5255a5111fc1e3163232d4dc07bda" }, "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" },
"mason.nvim": { "branch": "main", "commit": "fc98833b6da5de5a9c5b1446ac541577059555be" }, "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
"mini.basics": { "branch": "main", "commit": "246fc266f3bc3f6ae88c51e011609540b582b196" }, "mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" },
"mini.clue": { "branch": "main", "commit": "9bfd8ce71ae31460837fcec82a03946008929777" }, "neo-tree.nvim": { "branch": "main", "commit": "f3f3bf73414e400cf9fc13fda50f00404a8f8ab1" },
"mini.icons": { "branch": "main", "commit": "86a633f0dffcfd80110bac86681dbf4b5c37ba5c" }, "none-ls.nvim": { "branch": "main", "commit": "01f8e62ea11603e59ad9ff7afcfa94fd183f76d6" },
"mini.pick": { "branch": "main", "commit": "4dfc0a21f16d34e9b2429b96d62788ed8a65e6cd" }, "nui.nvim": { "branch": "main", "commit": "f535005e6ad1016383f24e39559833759453564e" },
"neo-tree.nvim": { "branch": "main", "commit": "9b5d67119c46e3262ffe1508fe6d8540b79ad75d" }, "nvim-autopairs": { "branch": "master", "commit": "3172f40c16563db871a58f20d205c710ad782311" },
"neoconf.nvim": { "branch": "main", "commit": "d4848c791496eab6989951d46586fabf7a22942c" }, "nvim-dap": { "branch": "master", "commit": "cfa2d58f4537aca6ca83e2de1a0d9f1491121264" },
"noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" }, "nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
"none-ls.nvim": { "branch": "main", "commit": "a117163db44c256d53c3be8717f3e1a2a28e6299" }, "nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" },
"nui.nvim": { "branch": "main", "commit": "a0fd35fcbb4cb479366f1dc5f20145fd718a3733" }, "nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" },
"nvim": { "branch": "main", "commit": "5b5e3aef9ad7af84f463d17b5479f06b87d5c429" }, "nvim-highlight-colors": { "branch": "main", "commit": "e4c7af0211866162d999ce0bdd6a029302e19139" },
"nvim-aider": { "branch": "main", "commit": "7604b46dc02b5f74cddac13d3922ee89dda69ba1" }, "nvim-lsp-file-operations": { "branch": "master", "commit": "a76cdd2c4f0ad3ce6219506aad1208c81d600e20" },
"nvim-autopairs": { "branch": "master", "commit": "6522027785b305269fa17088395dfc0f456cedd2" }, "nvim-lspconfig": { "branch": "master", "commit": "4d363f93c3581b9212a24f7a830d7590b3f050af" },
"nvim-dap": { "branch": "master", "commit": "6a5bba0ddea5d419a783e170c20988046376090d" },
"nvim-dap-go": { "branch": "main", "commit": "8763ced35b19c8dc526e04a70ab07c34e11ad064" },
"nvim-dap-ui": { "branch": "master", "commit": "bc81f8d3440aede116f821114547a476b082b319" },
"nvim-highlight-colors": { "branch": "main", "commit": "7eeaa05eab26fb840c4fe7bca91710bf37cb13ed" },
"nvim-lint": { "branch": "master", "commit": "93b8040115c9114dac1047311763bef275e752dc" },
"nvim-lsp-file-operations": { "branch": "master", "commit": "9744b738183a5adca0f916527922078a965515ed" },
"nvim-lspconfig": { "branch": "master", "commit": "85e0dd26b710e834a105d679200d01e326a3d2b0" },
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
"nvim-rip-substitute": { "branch": "main", "commit": "1b5d78f3d69fcdf8e734114c665994ffe1672fde" }, "nvim-treesitter": { "branch": "main", "commit": "ca581c3436fc2b236847f5b739ebc263739e5ca6" },
"nvim-surround": { "branch": "main", "commit": "caf6f633d4d77a29b6e265b560c5a035d171a913" }, "nvim-treesitter-textobjects": { "branch": "main", "commit": "5c7b0263797dfd1bd6202f2b219f3b53a80b2187" },
"nvim-tree.lua": { "branch": "master", "commit": "44d9b58f11d5a426c297aafd0be1c9d45617a849" }, "nvim-ts-autotag": { "branch": "main", "commit": "d7220b55e16ed3c04e88086256def5d282429493" },
"nvim-treesitter": { "branch": "master", "commit": "da5825bf70cc2ea7eacd1e60c32c07baf19adeba" }, "nvim-vtsls": { "branch": "main", "commit": "0b5f73c9e50ce95842ea07bb3f05c7d66d87d14a" },
"nvim-treesitter-textobjects": { "branch": "master", "commit": "9937e5e356e5b227ec56d83d0a9d0a0f6bc9cad4" },
"nvim-ts-autotag": { "branch": "main", "commit": "a1d526af391f6aebb25a8795cbc05351ed3620b5" },
"nvim-vtsls": { "branch": "main", "commit": "45c6dfea9f83a126e9bfc5dd63430562b3f8af16" },
"nvim-window-picker": { "branch": "main", "commit": "6382540b2ae5de6c793d4aa2e3fe6dbb518505ec" }, "nvim-window-picker": { "branch": "main", "commit": "6382540b2ae5de6c793d4aa2e3fe6dbb518505ec" },
"overseer.nvim": { "branch": "master", "commit": "72c68aab0358c92f451168b704c411c4a3e3410e" }, "omp.nvim": { "branch": "main", "commit": "a538777ecdd76f9632550672f998706221459adf" },
"package-info.nvim": { "branch": "master", "commit": "3de4f3569ce60e248707df042b72769b3ec343b8" }, "package-info.nvim": { "branch": "master", "commit": "b86cf49efc3855dd94afb33304b278f6f122bf1a" },
"peek.nvim": { "branch": "master", "commit": "5820d937d5414baea5f586dc2a3d912a74636e5b" }, "pkl-neovim": { "branch": "main", "commit": "20bfa7232262f36da45470170e7a954e487f4ac6" },
"persistent-breakpoints.nvim": { "branch": "main", "commit": "d1656221836207787b8a7969cc2dc72668c4742a" }, "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
"pkl-neovim": { "branch": "main", "commit": "e8e41d6b7e0a33cd6dddba74c8941ff1801d89de" },
"plenary.nvim": { "branch": "master", "commit": "857c5ac632080dba10aae49dba902ce3abf91b35" },
"portal.nvim": { "branch": "main", "commit": "77d9d53fec945bfa407d5fd7120f1b4f117450ed" },
"presence.nvim": { "branch": "main", "commit": "87c857a56b7703f976d3a5ef15967d80508df6e6" },
"quicker.nvim": { "branch": "master", "commit": "1798be71cdcb15fb84fa8054148a56e17fd391dc" },
"resession.nvim": { "branch": "master", "commit": "cc819b0489938d03e4f3532a583354f0287c015b" }, "resession.nvim": { "branch": "master", "commit": "cc819b0489938d03e4f3532a583354f0287c015b" },
"smart-splits.nvim": { "branch": "master", "commit": "4a231987665d3c6e02ca88833d050e918afe3e1e" }, "rustaceanvim": { "branch": "main", "commit": "b7ca983c6ec88c94cf25e5deaad96df9a68b9a98" },
"snacks.nvim": { "branch": "main", "commit": "5eac729fa290248acfe10916d92a5ed5e5c0f9ed" }, "schemastore.nvim": { "branch": "main", "commit": "cc6302a0957806dfc0f2687f1f35ee9652686101" },
"smart-splits.nvim": { "branch": "master", "commit": "32f67888ac71942442fdab3819864a10449dfc41" },
"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" },
"telescope-fzy-native.nvim": { "branch": "master", "commit": "282f069504515eec762ab6d6c89903377252bf5b" }, "template.nvim": { "branch": "main", "commit": "308f6f8f0bf98cb7c71855ffa8a3019a5642d1cd" },
"telescope-lazy.nvim": { "branch": "main", "commit": "810f7859c98e88b9c4e45de16cb5317207c9b3e8" }, "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
"telescope.nvim": { "branch": "master", "commit": "a4ed82509cecc56df1c7138920a1aeaf246c0ac5" },
"todo-comments.nvim": { "branch": "main", "commit": "ae0a2afb47cf7395dc400e5dc4e05274bf4fb9e0" },
"toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" }, "toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" },
"tsc.nvim": { "branch": "main", "commit": "5bd25bb5c399b6dc5c00392ade6ac6198534b53a" }, "trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
"vim-illuminate": { "branch": "master", "commit": "19cb21f513fc2b02f0c66be70107741e837516a1" }, "tsc.nvim": { "branch": "main", "commit": "e083bcf1e54bc3af7df92b33235efb334e8c782c" },
"vim-repeat": { "branch": "master", "commit": "65846025c15494983dafe5e3b46c8f88ab2e9635" }, "vellum.nvim": { "branch": "mdx", "commit": "c9e79e6c8f12047562760df7faa2d6762251fad7" },
"venv-selector.nvim": { "branch": "main", "commit": "cc4bb3975de8835291f9bb45889e96c6b2795fc4" },
"which-key.nvim": { "branch": "main", "commit": "fcbf4eea17cb299c02557d576f0d568878e354a4" } "which-key.nvim": { "branch": "main", "commit": "fcbf4eea17cb299c02557d576f0d568878e354a4" }
} }
+27 -21
View File
@@ -6,28 +6,34 @@
return { return {
"AstroNvim/astrocommunity", "AstroNvim/astrocommunity",
version = false, -- Use latest astrocommunity version = false, -- Use latest astrocommunity
{ import = "astrocommunity.pack.astro" },
{ import = "astrocommunity.pack.bash" },
{ import = "astrocommunity.pack.biome" },
{ import = "astrocommunity.pack.chezmoi" },
{ import = "astrocommunity.pack.lua" },
{ import = "astrocommunity.pack.markdown" },
{ import = "astrocommunity.pack.json" },
{ import = "astrocommunity.pack.mdx" },
{ import = "astrocommunity.pack.go" },
{ import = "astrocommunity.pack.html-css" },
{ import = "astrocommunity.pack.templ" },
{ import = "astrocommunity.pack.tailwindcss" },
{ import = "astrocommunity.pack.typescript-all-in-one" },
{ import = "astrocommunity.pack.yaml" },
{ import = "astrocommunity.git.octo-nvim" },
{ import = "astrocommunity.bars-and-lines.bufferline-nvim" }, { import = "astrocommunity.bars-and-lines.bufferline-nvim" },
{ import = "astrocommunity.code-runner.overseer-nvim" },
{ import = "astrocommunity.colorscheme.cyberdream-nvim" },
{ import = "astrocommunity.completion.supermaven-nvim" },
{ import = "astrocommunity.completion.blink-cmp" },
{ import = "astrocommunity.editing-support.auto-save-nvim" },
{ import = "astrocommunity.editing-support.conform-nvim" }, { import = "astrocommunity.editing-support.conform-nvim" },
{ import = "astrocommunity.lsp.inc-rename-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.golangci-lint" },
{ 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.typescript" },
{ import = "astrocommunity.pack.tailwindcss" },
-- python: treesitter/debugpy/venv-selector + basedpyright language server.
-- The full `pack.python` also pulls black + isort; formatting stays on ruff
-- (see plugins/conform.lua), so only the base and basedpyright subpacks are
-- 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.lsp.nvim-lsp-file-operations" },
{ import = "astrocommunity.colorscheme.cyberdream-nvim" },
} }
+310
View File
@@ -0,0 +1,310 @@
--- Shared ignore patterns for file explorers and pickers
--- This module provides a centralized list of patterns to ignore across
--- snacks.picker and other file browsing tools. (`patterns` and the
--- `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
local uv = vim.uv or vim.loop
local M = {}
--- Core ignore patterns used across the configuration
--- These patterns use glob syntax (* and ? wildcards), matched against
--- file basenames and individual path segments.
M.patterns = {
-- Build artifacts and generated files
".beads",
"chain_*.json",
".npmrc",
".epiq",
".omc",
".claude",
".changeset",
".codegraph",
".gitignore",
"turbo.json",
"vite.config.ts",
"bunfig.toml",
"*.pb.go",
"*.pkl.go",
"*.pulsar.go",
"*.pb.gorm.go",
"*.pb.gw.go",
"*_templ.go",
"*_mock.go",
"*_integration.go",
"*_test.go",
"AGENTS.md",
"test",
"tests",
-- Configuration files
".editorconfig",
".gitpod.*",
-- Lock files and dependencies
"*.lock",
"*.lockb",
"*.sum",
"*.work.*",
"package-lock.json",
"pnpm-lock.yaml",
".gitattributes",
".oxlintrc.json",
"tsconfig*",
-- Temporary and cache files
"*.tmp",
".typecopy",
".python-version",
".prettierrc",
".trunk",
".config",
".pkl-lsp",
".tsbuildinfo",
".wrangler",
".parcel-cache",
".conform*",
".null-ls_*",
-- Binary and media files
"*.wasm",
"*.png",
"*.jpg",
"*.icns",
"*.ico",
-- Directories and special files
"contrib",
"interchaintest",
"PULL_REQUEST_TEMPLATE.md",
"Icon?",
"iCloud~",
"com~",
".githooks",
}
--- Patterns that should never be shown in any file browser
M.never_show = {
-- Version control
".git",
".ai",
".omc",
-- Dependencies
"node_modules",
".wrangler",
"CODEOWNERS",
".golangci.yml",
-- Build outputs
".next",
".parcel-cache",
-- Task runners and tools
".task",
".devbox",
".turbo",
".jj",
".spawn",
-- IDE and editor
".idea",
".venv",
".metadata",
".dart_tool",
".devcontainer",
-- Build tools
".gradle",
"gradle.bat",
"heighliner",
-- Temporary
".tmp",
"tmp",
-- Logs
"sonr.log",
"junit.xml",
-- macOS
".DS_Store",
".DocumentRevisions-V100",
".Spotlight-V100",
".TemporaryItems",
".Trashes",
".fseventsd",
".timemachine",
-- Templates
"DISCUSSION_TEMPLATE",
"ISSUE_TEMPLATE",
-- License
"LICENSE",
}
--- Patterns that should never be shown (pattern matching)
M.never_show_patterns = {
".crush",
"*DS_Store",
".obsidian",
".pkl-lsp",
".tsbuildinfo",
"package-lock.json",
".prettierrc",
"node_modules",
".DocumentRevisions-V100",
".Spotlight-V100",
".TemporaryItems",
".Trashes",
".fseventsd",
".editorconfig",
"*.min.js",
"*.lock",
"*lock*",
"*.lockb",
"*.pulsar.go",
"*.pb.gorm.go",
"*.pb.gw.go",
"*_templ.go",
"*.tmp",
"*.work.*",
"*.sum",
".parcel-cache",
"*.icns",
"*.ico",
"*.iml",
"Icon?",
"iCloud~",
"com~",
".conform*",
".null-ls_*",
}
--- Directories to ignore when filtering files
M.ignored_directories = {
"contracts",
"crypto",
"api",
"chains",
"test",
"examples",
"scripts",
"bridge",
"client",
"translations",
"env",
".husky",
".worktrees",
}
local EMPTY = {}
--- Convert a glob (* and ? wildcards) to an anchored Lua pattern
--- @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 file = io.open(path, "r")
if file then
for line in file:lines() do
if line ~= "" and not line:match "^#" then
patterns[#patterns + 1] = (line:gsub("^%./", ""))
end
end
file:close()
end
rgignore_cache[path] = { mtime = stat.mtime.sec, patterns = patterns }
return patterns
end
--- Create a filter for snacks picker `transform` options.
--- Snacks contract: return `false` to DROP an item, `true`/`nil` to keep it
--- (returning nil keeps the item!). Runs per item in a fast event context,
--- so only pure Lua and libuv are used here.
--- @return fun(item: snacks.picker.finder.Item): boolean
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)
local file = item and item.file
if not file then return true end
static = static or compile_globs(M.patterns)
local raw = M.read_rgignore(item.cwd)
if raw ~= last_raw then
last_raw, last_rg = raw, compile_globs(raw)
end
-- Match every path segment (directories and basename) anchored,
-- so `test` drops `test/foo.lua` but never `latest.md`.
for seg in file:gmatch "[^/\\]+" do
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
return true
end
end
return M
+104
View File
@@ -0,0 +1,104 @@
--- 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
+116
View File
@@ -0,0 +1,116 @@
--- 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
+420
View File
@@ -0,0 +1,420 @@
--- 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
+26
View File
@@ -0,0 +1,26 @@
--- 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
+171
View File
@@ -0,0 +1,171 @@
--- 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
+21
View File
@@ -0,0 +1,21 @@
--- 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
+93
View File
@@ -0,0 +1,93 @@
--- 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
+75
View File
@@ -0,0 +1,75 @@
--- 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
+59
View File
@@ -0,0 +1,59 @@
--- 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
+9 -7
View File
@@ -1,19 +1,21 @@
require("lazy").setup({ require("lazy").setup({
{ {
"AstroNvim/AstroNvim", "AstroNvim/AstroNvim",
version = "^4", -- Ensure we use AstroNvim v4+ version = "^6", -- Remove version tracking to elect for nightly AstroNvim
import = "astronvim.plugins", import = "astronvim.plugins",
opts = { opts = { -- AstroNvim options must be set here with the `import` key
-- Leaders are set early in init.lua to ensure consistency mapleader = " ", -- This ensures the leader key must be configured before Lazy is set up
icons_enabled = (vim.g.have_nerd_font ~= false), maplocalleader = ",", -- This ensures the localleader key must be configured before Lazy is set up
pin_plugins = nil, icons_enabled = true, -- Set to false to disable icons (if no Nerd Font is available)
update_notifications = false, pin_plugins = nil, -- Default will pin plugins when tracking `version` of AstroNvim, set to true/false to override
update_notifications = true, -- Enable/disable notification about running `:Lazy update` twice to update pinned plugins
}, },
}, },
{ import = "community" }, { import = "community" },
{ import = "plugins" }, { import = "plugins" },
} --[[@as LazySpec]], { } --[[@as LazySpec]], {
install = { colorscheme = { "astrotheme" } }, -- Configure any other `lazy.nvim` configuration options here
install = { colorscheme = { "cyberdream", "habamax" } },
ui = { backdrop = 100 }, ui = { backdrop = 100 },
performance = { performance = {
rtp = { rtp = {
+428 -603
View File
File diff suppressed because it is too large. Load diff
+70
View File
@@ -0,0 +1,70 @@
--- AstroLSP: the configuration that should only exist while a language server
--- is attached (`:h astrolsp`). Every mapping carries a `cond` — a server
--- method name — so it is created buffer-locally on attach and is simply
--- absent (including in which-key) wherever no server advertises it, instead
--- of being a global bind that errors or silently does nothing.
---
--- Deliberately configured elsewhere:
--- * 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
--- @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 {
"AstroNvim/astrolsp",
---@type AstroLSPOpts
opts = {
mappings = {
n = {
-- snacks.words auto-highlights the references of the symbol under the
-- cursor (`words` in plugins/user.lua); these cycle through them,
-- complementing the core astrolsp `gd` / `gr` binds
["]]"] = {
function() require("snacks").words.jump(vim.v.count1, true) end,
desc = "Next reference",
cond = "textDocument/documentHighlight",
},
["[["] = {
function() require("snacks").words.jump(-vim.v.count1, true) end,
desc = "Previous reference",
cond = "textDocument/documentHighlight",
},
-- <C-a>: assist. Code actions and symbol search, each with the
-- trouble.nvim hand-off on <C-t> where a picker is involved.
["<C-a>a"] = {
function() vim.lsp.buf.code_action() end,
desc = "Show code actions",
cond = "textDocument/codeAction",
},
["<C-a>s"] = {
trouble_picker "lsp_symbols",
desc = "Search LSP symbols (<C-t> sends to Trouble)",
cond = "textDocument/documentSymbol",
},
["<C-a>S"] = {
trouble_picker "lsp_workspace_symbols",
desc = "Search LSP workspace symbols (<C-t> sends to Trouble)",
cond = "workspace/symbol",
},
["<C-f><C-f>"] = {
function() require("snacks").picker.lsp_symbols { layout = { preset = "vscode", preview = "main" } } end,
desc = "Find LSP symbols",
cond = "textDocument/documentSymbol",
},
},
},
},
}
+32 -186
View File
@@ -1,197 +1,43 @@
-- 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 = "",
ScrollText = "",
GitBranch = "",
GitAdd = "",
GitChange = "",
GitDelete = "",
},
-- modify variables used by heirline but not defined in the setup call directly
status = {
-- define the separators between each section
separators = {
left = { "", "" }, -- separator for the left side of the statusline
right = { "", "" }, -- separator for the right side of the statusline
tab = { "", "" },
},
-- add new colors that can be used by heirline
colors = function(hl)
local get_hlgroup = require("astroui").get_hlgroup
-- use helper function to get highlight group properties
local comment_fg = get_hlgroup("Comment").fg
hl.git_branch_fg = comment_fg
hl.git_added = comment_fg
hl.git_changed = comment_fg
hl.git_removed = comment_fg
hl.blank_bg = get_hlgroup("Folded").fg
hl.file_info_bg = get_hlgroup("Visual").bg
hl.nav_icon_bg = get_hlgroup("String").fg
hl.nav_fg = hl.nav_icon_bg
hl.folder_icon_bg = get_hlgroup("Error").fg
return hl
end,
attributes = {
mode = { bold = true },
},
icon_highlights = {
file_icon = {
statusline = false,
},
},
},
}, },
}, },
{ {
"rebelot/heirline.nvim", -- layered on the astrocommunity spec (transparent, italic_comments,
opts = function(_, opts) -- hide_fillchars, terminal_colors stay as it sets them)
local status = require "astroui.status" "scottmckendry/cyberdream.nvim",
-- Disable heirline's tabline since we're using bufferline ---@type cyberdream.Config
opts.tabline = nil opts = {
opts.statusline = { variant = "muted",
-- default highlight for the entire statusline -- astrocommunity still passes the pre-rename `borderless_telescope`,
hl = { fg = "fg", bg = "bg" }, -- which cyberdream ignores; this is the current key (snacks picker)
-- each element following is a component in astroui.status module borderless_pickers = true,
-- only the installed plugins; base highlights always load. A newly
-- add the vim mode component -- added plugin with a cyberdream extension must be listed here.
status.component.mode { extensions = {
-- enable mode text with padding as well as an icon before it default = false,
mode_text = { blinkcmp = true,
icon = { kind = "VimIcon", padding = { right = 1, left = 1 } }, dapui = true,
}, gitsigns = true,
-- surround the component with a separators grugfar = true,
surround = { heirline = true, -- AstroNvim's statusline reads the Heirline<Mode> groups
-- it's a left element, so use the left separator lazy = true,
separator = "left", mini = true, -- mini.icons
-- set the color of the surrounding based on the current mode using astronvim.utils.status module snacks = true,
color = function() return { main = status.hl.mode_bg(), right = "blank_bg" } end, treesitter = true,
}, trouble = true,
}, whichkey = true,
-- we want an empty space here so we can use the component builder to make a new section with just an empty string },
status.component.builder { },
{ provider = "" },
-- define the surrounding separator and colors to be used inside of the component
-- and the color to the right of the separated out section
surround = {
separator = "left",
color = { main = "blank_bg", right = "file_info_bg" },
},
},
-- add a section for the currently opened file information
status.component.file_info {
-- enable the file_icon and disable the highlighting based on filetype
filename = { fallback = "Empty" },
-- disable some of the info
filetype = false,
file_read_only = false,
-- add padding
padding = { right = 1 },
-- define the section separator
surround = { separator = "left", condition = false },
},
-- add a component for the current git branch if it exists and use no separator for the sections
status.component.git_branch {
git_branch = { padding = { left = 1 } },
surround = { separator = "none" },
},
-- add a component for the current git diff if it exists and use no separator for the sections
status.component.git_diff {
padding = { left = 1 },
surround = { separator = "none" },
},
-- fill the rest of the statusline
-- the elements after this will appear in the middle of the statusline
status.component.fill(),
-- add a component to display if the LSP is loading, disable showing running client names, and use no separator
status.component.lsp {
lsp_client_names = false,
surround = { separator = "none", color = "bg" },
},
-- fill the rest of the statusline
-- the elements after this will appear on the right of the statusline
status.component.fill(),
-- add a component for the current diagnostics if it exists and use the right separator for the section
status.component.diagnostics { surround = { separator = "right" }, padding = { right = 1 } },
-- add a component to display LSP clients, disable showing LSP progress, and use the right separator
status.component.lsp {
lsp_progress = false,
padding = { right = 1 },
surround = { separator = "right" },
},
-- NvChad has some nice icons to go along with information, so we can create a parent component to do this
-- all of the children of this table will be treated together as a single component
{
flexible = 1,
{
-- define a simple component where the provider is just a folder icon
status.component.builder {
-- astronvim.get_icon gets the user interface icon for a closed folder with a space after it
{ provider = require("astroui").get_icon "FolderClosed" },
-- add padding after icon
padding = { right = 1 },
-- set the foreground color to be used for the icon
hl = { fg = "bg" },
-- use the right separator and define the background color
surround = { separator = "right", color = "folder_icon_bg" },
},
-- add a file information component and only show the current working directory name
status.component.file_info {
-- we only want filename to be used and we can change the fname
-- function to get the current working directory name
filename = {
fname = function(nr) return vim.fn.getcwd(nr) end,
padding = { left = 1, right = 1 },
},
-- disable all other elements of the file_info component
filetype = false,
file_icon = false,
file_modified = false,
file_read_only = false,
-- use no separator for this part but define a background color
surround = {
separator = "none",
color = "file_info_bg",
condition = false,
},
},
},
{},
},
-- the final component of the NvChad statusline is the navigation section
-- this is very similar to the previous current working directory section with the icon
{ -- make nav section with icon border
-- define a custom component with just a file icon
status.component.builder {
{ provider = require("astroui").get_icon "ScrollText" },
-- add padding after icon
padding = { right = 1 },
-- set the icon foreground
hl = { fg = "bg" },
-- use the right separator and define the background color
-- as well as the color to the left of the separator
surround = {
separator = "right",
color = { main = "nav_icon_bg", left = "file_info_bg" },
},
},
-- add a navigation component and just display the percentage of progress in the file
status.component.nav {
-- add some padding for the percentage provider
percentage = { padding = { right = 1 } },
-- disable all other providers
ruler = false,
scrollbar = false,
-- use no separator and define the background color
surround = { separator = "none", color = "file_info_bg" },
},
},
}
end,
}, },
} }
+9 -147
View File
@@ -1,151 +1,13 @@
-- Bufferline configuration with AstroNvim integration --- bufferline.nvim overrides on top of the astrocommunity spec
--- @module plugins.bufferline
---@type LazySpec
return { return {
"akinsho/bufferline.nvim", "akinsho/bufferline.nvim",
version = "*", opts = function(_, opts)
dependencies = { "nvim-tree/nvim-web-devicons", "AstroNvim/astrocore" }, -- the community spec reserves a "Neo-tree" titled offset over the sidebar;
event = "VeryLazy", -- drop it so tabs start at the far left
config = function() opts.options = opts.options or {}
local bufferline = require "bufferline" opts.options.offsets = nil
local groups = require "bufferline.groups"
bufferline.setup {
options = {
mode = "buffers",
numbers = "none",
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 = "icon", 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 = "",
offsets = {
{ filetype = "snacks_picker", text = "", padding = 0 },
},
groups = {
options = { toggle_hidden_on_enter = true },
items = {
groups.builtin.ungrouped,
-- Module groups
{
name = "",
auto_close = true,
highlight = { fg = "#5ef1ff" },
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "x/dex/.*%.go$" or bufname:match "proto/dex/.*%.proto$"
end,
},
{
name = "DID",
auto_close = true,
highlight = { fg = "#5ef1ff" },
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "x/did/.*%.go$" or bufname:match "proto/did/.*%.proto$"
end,
},
{
name = "DWN",
auto_close = true,
highlight = { fg = "#5ef1ff" },
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "x/dwn/.*%.go$" or bufname:match "proto/dwn/.*%.proto$"
end,
},
{
name = "SVC",
highlight = { fg = "#5ef1ff" },
auto_close = true,
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "x/svc/.*%.go$" or bufname:match "proto/svc/.*%.proto$"
end,
},
-- Tests
{
name = "Tests",
auto_close = true,
highlight = { fg = "#ff5ea0" },
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "_test%.go$" or bufname:match "/test/"
end,
},
-- Auto-close groups
{
name = "Actions",
highlight = { fg = "#ff5ef1" },
auto_close = true,
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "%.ya?ml$" and bufname:match "%.github/"
end,
},
{
name = "Claude",
highlight = { fg = "#ffbd5e" },
auto_close = true,
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "%.claude/" or bufname:match "CLAUDE%.md$"
end,
},
{
name = "Docs",
highlight = { fg = "#ffffff" },
auto_close = true,
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return bufname:match "%.mdx?$" or bufname:match "/docs/"
end,
},
{
name = "Config",
highlight = { fg = "#3c4048" },
auto_close = true,
matcher = function(buf)
local bufname = vim.api.nvim_buf_get_name(buf.id)
return (bufname:match "%.ya?ml$" and not bufname:match "%.github/")
or bufname:match "%.json$"
or bufname:match "%.toml$"
or bufname:match "%.sh$"
end,
},
},
},
},
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 },
},
}
end, end,
} }
+13
View File
@@ -0,0 +1,13 @@
--- 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 = {},
}
+46
View File
@@ -0,0 +1,46 @@
-- 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,
},
}
-439
View File
@@ -1,439 +0,0 @@
-- Function to read patterns from .rgignore file
local function read_rgignore_patterns()
local patterns = {}
local git_root = vim.fn.systemlist("git rev-parse --show-toplevel")[1]
if git_root and git_root ~= "" then
local rgignore_path = git_root .. "/.rgignore"
local file = io.open(rgignore_path, "r")
if file then
for line in file:lines() do
-- Skip comments and empty lines
if line ~= "" and not line:match "^#" then
-- Remove leading ./ if present
line = line:gsub("^%./", "")
-- Add the pattern
table.insert(patterns, line)
end
end
file:close()
end
end
return patterns
end
-- Build exclude patterns for snacks.explorer
local function build_exclude_patterns()
-- Get patterns from .rgignore
local rgignore_patterns = read_rgignore_patterns()
-- Default hide patterns (from original neotree config)
local hide_patterns = {
"chain_*.json",
"Library",
".typecopy",
".python-version",
"*.pb.go",
"*config.*.js",
"*config.js",
"deps.mjs",
".parcelrc",
"worker-configuration.d.ts",
"wrangler.jsonc",
"*.pkl.go",
".trunk",
".config",
".cz.toml",
"*_mock.go",
}
-- Never show by pattern (from original neotree config)
local never_show_by_pattern = {
"*DS_Store",
".obsidian",
".pkl-lsp",
".tsbuildinfo",
"package-lock.json",
".prettierrc",
"node_modules",
".DocumentRevisions-V100",
".Spotlight-V100",
".TemporaryItems",
".Trashes",
".fseventsd",
".editorconfig",
"*.min.js",
"*.lock",
"*lock*",
"*.lockb",
"*.pulsar.go",
"*.pb.gorm.go",
"*.pb.gw.go",
"*_templ.go",
"*.tmp",
"*.work.*",
"*.sum",
".parcel-cache",
"*.icns",
"*.ico",
".aider.tags.cache.v4",
"*.iml",
"Icon?",
"iCloud~",
"com~",
"*.icns",
".conform*",
".null-ls_*",
}
-- Never show directories (from original neotree config)
local never_show = {
".git",
"pnpm-lock.yaml",
".next",
".task",
".devbox",
".dart_tool",
".idea",
".metadata",
".venv",
".gradle",
"gradle.bat",
".aider.chat.history.md",
".aider.input.history",
".aider.tags.cache.v3",
".devcontainer",
"heighliner",
".tmp",
"go.work.sum",
".DS_Store",
".git",
"LICENSE",
"tmp",
"sonr.log",
"DISCUSSION_TEMPLATE",
"ISSUE_TEMPLATE",
".devbox",
".timemachine",
"junit.xml",
".jj",
".spawn",
".turbo",
}
-- Merge all patterns
local all_patterns = {}
-- Add default hide patterns
for _, pattern in ipairs(hide_patterns) do
table.insert(all_patterns, pattern)
end
-- Add rgignore patterns
for _, pattern in ipairs(rgignore_patterns) do
table.insert(all_patterns, pattern)
end
-- Add never show by pattern
for _, pattern in ipairs(never_show_by_pattern) do
table.insert(all_patterns, pattern)
end
-- Add never show directories
for _, pattern in ipairs(never_show) do
table.insert(all_patterns, pattern)
end
return all_patterns
end
-- Custom transform function to filter items
local function create_transform_filter()
local exclude_patterns = build_exclude_patterns()
return function(item, ctx)
if not item then return false end
local name = item.name or (item.file and vim.fn.fnamemodify(item.file, ":t")) or ""
local path = item.file or ""
-- Check each pattern
for _, pattern in ipairs(exclude_patterns) do
-- Convert glob pattern to Lua pattern
local lua_pattern = pattern
:gsub("([%.%+%-%[%]%(%)%$%^%%%?])", "%%%1") -- Escape special chars except *
:gsub("%*%*", ".*") -- ** matches any characters including /
:gsub("%*", "[^/]*") -- * matches any characters except /
:gsub("%?", ".") -- ? matches single character
-- Check against filename
if name:match("^" .. lua_pattern .. "$") then
return false -- Filter out this item
end
-- Check against full path
if path:match("/" .. lua_pattern .. "$") or path:match("/" .. lua_pattern .. "/") then
return false -- Filter out this item
end
end
return item -- Keep the item
end
end
return {
{
"folke/snacks.nvim",
priority = 1000,
lazy = false,
---@type snacks.Config
opts = function()
return {
-- Enable explorer and replace netrw
explorer = {
replace_netrw = true,
},
-- Dashboard configuration
dashboard = {
preset = {
header = table.concat({
"",
"",
" ████████ ",
" ██████████████ ",
" ████████████████ ",
" ██████████ █████ ",
" ██████████ █ ",
" ██████████ █████ ",
" █████████ █████████ ",
" █████████ ████ █████████ ",
" ████████ ████████ ████████ ",
"███████ ████████████ ███████",
"███████ ██████████████ ██████",
"███████ ████████████ ███████",
" ████████ ████████ ████████ ",
" █████████ ████ █████████ ",
" ████████ ██████████ ",
" ██████ ██████████ ",
" ██ █████████ ",
" ████ ██████████ ",
" ████████████████ ",
" ██████████████ ",
" ████████ ",
"",
"",
}, "\n"),
},
sections = {
{ section = "header" },
{
pane = 2,
section = "terminal",
cmd = "colorscript -e square",
height = 5,
padding = 1,
},
{ section = "keys", gap = 1, padding = 1 },
{
pane = 2,
icon = " ",
desc = "Browse Repo",
padding = 1,
key = "b",
action = function() require("snacks").gitbrowse() end,
},
function()
local in_git = require("snacks").git.get_root() ~= nil
local cmds = {
{
title = "Notifications",
cmd = "gh notify -s -a -n5",
action = function() vim.ui.open "https://github.com/notifications" end,
key = "n",
icon = " ",
height = 5,
enabled = true,
},
{
title = "Open Issues",
cmd = "gh issue list -L 3",
key = "i",
action = function() vim.fn.jobstart("gh issue list --web", { detach = true }) end,
icon = " ",
height = 7,
},
{
icon = " ",
title = "Open PRs",
cmd = "gh pr list -L 3",
key = "P",
action = function() vim.fn.jobstart("gh pr list --web", { detach = true }) end,
height = 7,
},
{
icon = " ",
title = "Git Status",
cmd = "git --no-pager diff --stat -B -M -C",
height = 10,
},
}
return vim.tbl_map(
function(cmd)
return vim.tbl_extend("force", {
pane = 2,
section = "terminal",
enabled = in_git,
padding = 1,
ttl = 5 * 60,
indent = 3,
}, cmd)
end,
cmds
)
end,
{ section = "startup" },
},
},
-- Configure the explorer picker source
picker = {
sources = {
explorer = {
-- Explorer configuration matching neotree
tree = true,
hidden = true, -- Show hidden files by default (. files)
ignored = false, -- Don't show gitignored files
follow_file = true,
git_status = true,
git_untracked = true,
diagnostics = true,
watch = true,
-- Use transform to filter out unwanted files/directories
transform = create_transform_filter(),
focus = "list",
auto_close = false,
jump = { close = true }, -- Close explorer when jumping to a file
layout = {
preset = "sidebar",
preview = false,
layout = {
width = 34,
},
},
formatters = {
file = { filename_only = true },
},
matcher = { sort_empty = false, fuzzy = false },
win = {
list = {
keys = {
-- Navigation (matching neotree)
["H"] = "explorer_up", -- Navigate to parent directory
["L"] = "explorer_focus", -- Open file/expand directory (with auto-close for files)
["<CR>"] = "confirm", -- Open file/expand directory (with auto-close for files)
["<S-CR>"] = "confirm", -- Open without closing
["<BS>"] = "explorer_close_all", -- Collapse all folders
["!"] = "toggle_ignored", -- Toggle ignored files
["."] = "toggle_hidden", -- Toggle hidden files
["/"] = function(self)
-- Focus search input
self:focus "input"
end,
["f"] = function(self)
-- Focus search input (filter)
self:focus "input"
end,
["<c-x>"] = function(self)
-- Clear filter
self.input:set ""
end,
["s"] = "edit_vsplit", -- Open in vertical split
["S"] = "edit_split", -- Open in horizontal split
["t"] = "tab", -- Open in new tab
-- File operations
["a"] = "explorer_add", -- Add file
["d"] = "explorer_del", -- Delete
["r"] = "explorer_rename", -- Rename
["y"] = "explorer_yank", -- Copy
["x"] = "explorer_move", -- Cut/Move
["p"] = "explorer_paste", -- Paste
["c"] = "explorer_yank", -- Copy to clipboard
["R"] = "explorer_update", -- Refresh
-- Git keybindings from neotree
["<C-g>o"] = function() vim.cmd "!gh repo view --web" end,
["<C-g>h"] = function()
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>b"] = function() require("snacks").picker.git_branches() end,
-- Terminal keybindings from neotree
["<C-t>m"] = function() _G.Mk_toggle() end,
["<C-t>j"] = function() _G.Lazyjournal_toggle() end,
["<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>."] = function() _G.Yazi_toggle() end,
["<C-t>t"] = function() require("snacks").terminal() end,
-- Additional navigation
["h"] = "explorer_close", -- Close directory
["l"] = "confirm", -- Open/expand
["o"] = "explorer_open", -- Open with system application
["P"] = "toggle_preview",
["I"] = "toggle_ignored",
["Z"] = "explorer_close_all",
["]g"] = "explorer_git_next",
["[g"] = "explorer_git_prev",
["]d"] = "explorer_diagnostic_next",
["[d"] = "explorer_diagnostic_prev",
},
},
},
},
},
},
}
end,
},
}
+18
View File
@@ -0,0 +1,18 @@
--- 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,
},
}
+76
View File
@@ -0,0 +1,76 @@
--- 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" },
},
}
+22
View File
@@ -0,0 +1,22 @@
--- 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",
},
}
+27
View File
@@ -0,0 +1,27 @@
--- `<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,
},
},
},
},
}
-32
View File
@@ -1,32 +0,0 @@
-- Customize Mason
---@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 = {
-- install language servers
"lua-language-server",
"golangci-lint-langserver",
"pkl-lsp",
-- install formatters
"stylua",
-- install debuggers
"debugpy",
-- install any other package
"tree-sitter-cli",
"protols",
"protolint",
"templ",
"yamlfmt",
},
},
},
}
+44
View File
@@ -0,0 +1,44 @@
--- 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",
},
},
},
}
+212
View File
@@ -0,0 +1,212 @@
--- Neo-tree file explorer (replaces the mini.files explorer)
--- @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.
local ignore_patterns = require "config.ignore_patterns"
--- 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
return {
{
"nvim-neo-tree/neo-tree.nvim",
keys = {
-- kept from the mini.files era; AstroNvim's <Leader>e is overridden by
-- the git files picker in astrocore.lua, <Leader>o still focuses the tree
{ "<C-e>", "<Cmd>Neotree toggle<CR>", mode = { "n", "v" }, desc = "Toggle Explorer" },
},
opts = function(_, opts)
opts = require("astrocore").extend_tbl(opts, {
popup_border_style = "rounded",
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,
source_selector = { winbar = false, statusline = false },
window = {
position = "left",
width = 32,
-- 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 = {
["H"] = "navigate_up",
["L"] = "set_root",
["<bs>"] = "toggle_hidden",
["."] = "toggle_hidden",
["/"] = "fuzzy_finder",
["f"] = "filter_on_submit",
["<C-x>"] = "clear_filter",
["<cr>"] = "open_and_close_neotree",
["<S-CR>"] = "open",
-- Preview image under cursor via kitty's icat kitten
["<leader>p"] = "image_kitty",
-- Git keybindings, mirroring astrocore.lua
["<C-g>o"] = function() vim.cmd "!gh repo view --web" end,
["<C-g>h"] = term("gh dash", { auto_close = false }),
["<C-g>g"] = function() require("snacks").lazygit() end,
["<C-g>d"] = function() require("snacks").picker.git_diff() end,
["<C-g>b"] = function() require("snacks").picker.git_branches() end,
-- Terminals (toggleterm and config.terminals are both gone)
["<C-t>l"] = term("lazydocker", { auto_close = true, start_in_insert = true }),
["<C-t>."] = term("yazi", { auto_close = true, start_in_insert = true }),
["<C-t>t"] = function() require("snacks").terminal() end,
-- Open yazi in a detached kitty OS window at the node's directory
["N"] = function(state)
local node = state.tree:get_node()
local path = node.type == "directory" and node.path or vim.fs.dirname(node.path)
vim.fn.jobstart { "kitty", "--detach", "--directory", path, "yazi" }
end,
},
},
default_component_configs = {
container = {
enable_character_fade = true,
},
indent = {
indent_size = 2,
padding = 1, -- extra padding on left hand side
with_markers = true,
indent_marker = "│",
last_indent_marker = "└",
highlight = "NeoTreeIndentMarker",
expander_collapsed = "",
expander_expanded = "",
expander_highlight = "NeoTreeExpander",
},
name = {
trailing_slash = false,
use_git_status_colors = true,
highlight = "NeoTreeFileName",
},
git_status = {
symbols = {
-- change type: empty where the name already carries git colors
added = "",
modified = "",
deleted = "✖",
renamed = "󰁕",
-- status type
untracked = "",
ignored = "",
unstaged = "󰄱",
staged = "",
conflict = "",
},
},
-- extra columns, each only rendered once the window is wide enough
file_size = { enabled = true, width = 12, required_width = 48 },
type = { enabled = true, width = 10, required_width = 122 },
last_modified = { enabled = true, width = 20, required_width = 88 },
created = { enabled = true, width = 20, required_width = 110 },
symlink_target = { enabled = false },
},
filesystem = {
commands = {
-- Close neo-tree after opening a file (directories just expand).
-- The node has to be read before `open`: afterwards the tree
-- window may be gone and `tree:get_node()` errors.
open_and_close_neotree = function(state)
local node = state.tree:get_node()
require("neo-tree.sources.filesystem.commands").open(state)
if node and node.type == "file" then
require("neo-tree.command").execute { action = "close" }
end
end,
-- Preview an image with kitty's icat kitten in a snacks terminal
image_kitty = function(state)
local node = state.tree:get_node()
if node and node.type == "file" then
term("kitten icat --hold " .. vim.fn.shellescape(node.path), { auto_close = false })()
else
vim.notify("Not a file", vim.log.levels.WARN, { title = "Neo-tree" })
end
end,
},
bind_to_cwd = true,
follow_current_file = { enabled = true, leave_dirs_open = false },
use_libuv_file_watcher = true, -- filesystem option, not a top level one
filtered_items = {
force_visible_in_empty_folder = false,
show_hidden_count = false,
hide_dotfiles = false,
hide_gitignored = true,
-- gitignore-style ignore files, read per directory by neo-tree
hide_ignored = true,
ignore_files = { ".neotreeignore", ".ignore", ".rgignore" },
-- hide_by_pattern / never_show / never_show_by_pattern are seeded
-- from config.ignore_patterns in `config` below
},
},
})
-- deep merge of list values is index wise, so AstroNvim's extra sources
-- survive a `sources = { "filesystem" }` override: assign instead.
-- (`:checkhealth neo-tree` then reports config.buffers/git_status
-- `renderers: expected ?, got nil` - stock neo-tree does the same with a
-- single source, those sections are simply never loaded.)
opts.sources = { "filesystem" }
-- 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
-- from looping on the status the rescan itself kicks off) - this is what
-- the old BufEnter/DirChanged "refresh patterns" autocmd was papering
-- over.
local rescanned = {} ---@type table<string, true>
table.insert(opts.event_handlers, {
event = "git_status_changed",
handler = function(args)
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,
},
}
-76
View File
@@ -1,76 +0,0 @@
return {
"obsidian-nvim/obsidian.nvim",
-- the obsidian vault in this default config ~/obsidian-vault
-- If you want to use the home shortcut '~' here you need to call 'vim.fn.expand':
event = { "bufreadpre " .. vim.fn.expand "~" .. "/Notes/**.md" },
dependencies = {
"nvim-lua/plenary.nvim",
{ "hrsh7th/nvim-cmp", optional = true },
{
"AstroNvim/astrocore",
opts = {
mappings = {
n = {
["gf"] = {
function()
if require("obsidian").util.cursor_on_markdown_link() then
return "<Cmd>Obsidian follow_link<CR>"
else
return "gf"
end
end,
desc = "Obsidian Follow Link",
},
},
},
},
},
},
opts = function(_, opts)
local astrocore = require "astrocore"
return astrocore.extend_tbl(opts, {
workspaces = {
{
path = vim.env.HOME .. "/Notes", -- specify the vault location. no need to call 'vim.fn.expand' here
},
},
open = {
use_advanced_uri = true,
},
finder = (astrocore.is_available "snacks.pick" and "snacks.pick")
or (astrocore.is_available "telescope.nvim" and "telescope.nvim")
or (astrocore.is_available "fzf-lua" and "fzf-lua")
or (astrocore.is_available "mini.pick" and "mini.pick"),
templates = {
subdir = "2 - Areas/Templates",
date_format = "%Y-%m-%d-%a",
time_format = "%H:%M",
},
daily_notes = {
folder = "3 - Journals",
},
completion = {
nvim_cmp = astrocore.is_available "nvim-cmp",
blink = astrocore.is_available "blink",
},
note_frontmatter_func = function(note)
-- This is equivalent to the default frontmatter function.
local out = { id = note.id, aliases = note.aliases, tags = note.tags }
-- `note.metadata` contains any manually added fields in the frontmatter.
-- So here we just make sure those fields are kept in the frontmatter.
if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then
for k, v in pairs(note.metadata) do
out[k] = v
end
end
return out
end,
-- Optional, by default when you use `:ObsidianFollowLink` on a link to an external
-- URL it will be ignored but you can customize this behavior here.
follow_url_func = vim.ui.open,
})
end,
}
+8
View File
@@ -0,0 +1,8 @@
-- 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,
}
+63
View File
@@ -0,0 +1,63 @@
--- <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,
}
+88
View File
@@ -0,0 +1,88 @@
--- 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,
},
},
},
}
+44
View File
@@ -0,0 +1,44 @@
-- Disable supermaven in buffers under the LeetCode work directory, and
-- re-enable it when leaving them.
--
-- 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 {
"supermaven-inc/supermaven-nvim",
optional = true,
-- ignore_filetypes gates both the buffer sync (on_update) and the
-- suggestion poll, so python buffers are never sent to the binary
opts = { ignore_filetypes = { python = true } },
config = function(_, opts)
require("supermaven-nvim").setup(opts)
vim.api.nvim_create_autocmd("BufEnter", {
group = vim.api.nvim_create_augroup("supermaven_leetcode", { clear = true }),
desc = "Toggle supermaven off in LeetCode solution buffers",
callback = sync,
})
sync() -- plugin loads on VeryLazy; cover the buffer we're already sitting in
end,
}
+14
View File
@@ -0,0 +1,14 @@
--- 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",
},
}
+96
View File
@@ -0,0 +1,96 @@
--- 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 },
},
},
}
+19
View File
@@ -0,0 +1,19 @@
--- 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 = {},
}
+6 -100
View File
@@ -1,101 +1,7 @@
-- Polish configuration: Final setup and overrides -- This will run last in the setup process.
-- This runs last in the setup process -- Pure lua; anything that doesn't fit the normal config locations goes here.
-- Remove <leader>h mapping (conflicts with Telescope) -- <Leader>u UI toggles, mapped via Snacks.toggle so which-key shows the
vim.api.nvim_del_keymap("n", "<leader>h") -- snacks state icons/colors (see config.toggles). LSP-scoped keymaps live in
-- plugins/astrolsp.lua, where astrolsp gates them on server capabilities.
-- Overseer setup and template (optional) require("config.toggles").setup()
local ok_overseer, overseer = pcall(require, "overseer")
if ok_overseer then
overseer.setup {
strategy = {
"toggleterm",
use_shell = false,
auto_scroll = true,
close_on_exit = false,
direction = "vertical",
quit_on_exit = "success",
open_on_start = true,
hidden = true,
},
}
end
-- ToggleTerm setup (optional)
local ok_toggleterm, toggleterm = pcall(require, "toggleterm")
if ok_toggleterm then
toggleterm.setup {
size = function(term)
if term.direction == "horizontal" then
return 15
elseif term.direction == "vertical" then
return math.floor(vim.o.columns * 0.35)
else
return 20
end
end,
shade_terminals = false,
start_in_insert = true,
}
end
-- Register Devbox template for Overseer (if available)
if ok_overseer then
overseer.register_template {
name = "devbox",
params = {
script = {
type = "string",
optional = false,
desc = "The devbox script to run",
},
},
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
+2
View File
@@ -1,4 +1,6 @@
---
base: lua51 base: lua51
globals: globals:
vim: vim:
any: true any: true
+173
View File
@@ -0,0 +1,173 @@
#!/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();
+8
View File
@@ -0,0 +1,8 @@
<Accordion>
<AccordionItem title="{{_cursor_}}">
Answer.
</AccordionItem>
<AccordionItem title="">
Answer.
</AccordionItem>
</Accordion>
+1
View File
@@ -0,0 +1 @@
<Badge variant="accent">{{_cursor_}}</Badge>
+8
View File
@@ -0,0 +1,8 @@
<CardGroup cols={2}>
<Card title="{{_cursor_}}" href="" icon="">
Description.
</Card>
<Card title="" href="" icon="">
Description.
</Card>
</CardGroup>
+11
View File
@@ -0,0 +1,11 @@
<CodeGroup>
```ts TypeScript
{{_cursor_}}
```
```python Python
```
</CodeGroup>
+4
View File
@@ -0,0 +1,4 @@
<Color variant="compact">
<Color.Item name="{{_cursor_}}" value="#3B82F6" />
<Color.Item name="" value={{ light: "#FFFFFF", dark: "#0A0A0A" }} />
</Color>
+8
View File
@@ -0,0 +1,8 @@
<Columns cols={2}>
<Column>
{{_cursor_}}
</Column>
<Column>
</Column>
</Columns>
+1
View File
@@ -0,0 +1 @@
<Component path="{{_cursor_}}" />
+5
View File
@@ -0,0 +1,5 @@
<Diff
lang="{{_cursor_}}"
old={`before`}
new={`after`}
/>
+3
View File
@@ -0,0 +1,3 @@
<Expandable title="{{_cursor_}}">
Hidden detail.
</Expandable>
+6
View File
@@ -0,0 +1,6 @@
<FileTree>
- {{_cursor_}}
- index.mdx
</FileTree>
+3
View File
@@ -0,0 +1,3 @@
<Frame caption="{{_cursor_}}" hint="">
<img src="" alt="" />
</Frame>
+8
View File
@@ -0,0 +1,8 @@
---
title: {{_cursor_}}
description:
type: blog
date: {{_lua:os.date("%Y-%m-%d")_}}
authors:
- {{_author_}}
---
+9
View File
@@ -0,0 +1,9 @@
---
title: {{_cursor_}}
description:
type: changelog
changelog:
version: 0.0.0
date: {{_lua:os.date("%Y-%m-%d")_}}
category: Features
---
+4
View File
@@ -0,0 +1,4 @@
---
title: {{_cursor_}}
description:
---
+23
View File
@@ -0,0 +1,23 @@
---
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
View File
@@ -0,0 +1 @@
<GithubInfo owner="{{_cursor_}}" repo="" />
+1
View File
@@ -0,0 +1 @@
<Icon icon="{{_cursor_}}" size={16} />
+3
View File
@@ -0,0 +1,3 @@
<Panel title="{{_cursor_}}">
Supporting detail.
</Panel>
+3
View File
@@ -0,0 +1,3 @@
<Prompt description="{{_cursor_}}" actions={["copy"]}>
The prompt text copied to the clipboard.
</Prompt>
+8
View File
@@ -0,0 +1,8 @@
<Steps>
<Step title="{{_cursor_}}">
First step.
</Step>
<Step title="">
Second step.
</Step>
</Steps>
+8
View File
@@ -0,0 +1,8 @@
<Tabs>
<Tab title="{{_cursor_}}">
First tab.
</Tab>
<Tab title="">
Second tab.
</Tab>
</Tabs>
+3
View File
@@ -0,0 +1,3 @@
<Tile title="{{_cursor_}}" description="" href="">
<Icon icon="rocket" size={28} />
</Tile>
+1
View File
@@ -0,0 +1 @@
<Tooltip tip="{{_cursor_}}" headline="" cta="" href="">term</Tooltip>
+6
View File
@@ -0,0 +1,6 @@
<Tree>
<Tree.Folder name="{{_cursor_}}" defaultOpen>
<Tree.File name="index.ts" />
</Tree.Folder>
<Tree.File name="" />
</Tree>
+1
View File
@@ -0,0 +1 @@
<AutoTypeTable path="{{_cursor_}}" name="" />
+9
View File
@@ -0,0 +1,9 @@
<TypeTable
type={{
{{_cursor_}}: {
type: "string",
required: true,
description: "",
},
}}
/>
+3
View File
@@ -0,0 +1,3 @@
<Visibility for="{{_cursor_}}">
Audience-specific content.
</Visibility>
+1
View File
@@ -0,0 +1 @@
<YouTube id="{{_cursor_}}" title="" />