**Batch quickfix actions and LSP formatting across your entire project.**
Biscuit loads files into LSP for project-wide diagnostics and lets you batch-apply quickfix actions for specific diagnostic codes (like gopls's `any`, `slicescontains`, etc.) across hundreds of files at once.
## The Problem
You have 200 Go files with `interface{}` that should be `any`. Opening each file, waiting for LSP, and applying the quickfix manually would take hours.
## The Solution
```vim
:LoadLSP " Load all Go files into LSP
:ApplyFixes any " Fix all 200 files in seconds
```
## Features
- **Load files into LSP** — Get diagnostics for your entire project
- **Batch quickfix by diagnostic code** — Specify codes like `any`, `slicescontains`, `printf`
- **Safe by default** — Only applies quickfix-kind actions, picking the first match
- **Batch LSP formatting** — Run `textDocument/formatting` on all loaded buffers
- **Dry run mode** — Preview changes before applying
## Requirements
- Neovim >= **0.10.0**
- `fd` (recommended) or `find` for file discovery
- LSP server(s) configured for your filetypes
## Installation
### [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua
{
"taigrr/biscuit.nvim",
cmd = { "LoadLSP", "LoadLSPDir", "ApplyFixes", "ListDiagnosticCodes", "FormatBuffers" },
opts = {
codes = {
"any", -- interface{} -> any
"slicescontains", -- Use slices.Contains
"printf", -- Printf format issues
},
},
}
```
## Quick Start
```vim
" 1. Open a Go file to set filetype
:e main.go
" 2. Load all Go files into LSP
:LoadLSP
" 3. See what diagnostic codes are available
:ListDiagnosticCodes
" 4. Apply fixes (dry run first)
:ApplyFixes! any
:ApplyFixes any
" 5. Or apply all configured codes
:ApplyFixes
" 6. Format all loaded buffers
:FormatBuffers
" 7. Clean up
:UnloadHidden
```
## Commands
| Command | Description |
|---------|-------------|
| `:LoadLSP [dir] [exts...]` | Load files into LSP (uses current filetype if no args) |
| `:LoadLSPDir` | Prompt for a directory and load files matching the current filetype |
| `:UnloadHidden` | Unload hidden buffers to free resources |
| `:UnloadTracked` | Unload buffers loaded by `:LoadLSP` |
| `:ApplyFixes[!] [codes...]` | Apply quickfixes for diagnostic codes. `!` for dry run |
| `:ListDiagnosticCodes` | List diagnostic codes from loaded buffers |
| `:ListConfiguredCodes` | List configured codes |
| `:FormatBuffers[!]` | Format all loaded buffers using LSP. `!` for dry run |
## Configuration
```lua
require("biscuit").setup({
-- Diagnostic codes to auto-fix (exact match)
-- These are the "code" field from LSP diagnostics
codes = {
"any", -- gopls: interface{} -> any
"slicescontains", -- gopls: Use slices.Contains
"printf", -- gopls: Printf format specifier issues
"unusedwrite", -- gopls: Unused write to variable
},
-- Performance options
batch_size = 10, -- Files per batch when loading
batch_delay = 50, -- Ms between batches
max_files = 0, -- Limit files (0 = unlimited)
auto_save = true, -- Save after applying actions/formatting
-- Directories to skip
exclude_dirs = {
"node_modules", ".git", "dist", "build",
"__pycache__", ".venv", "vendor", ".next", "coverage",
},
})
```
## Common Diagnostic Codes
### gopls (Go)
| Code | Description |
|------|-------------|
| `any` | Use `any` instead of `interface{}` |
| `slicescontains` | Use `slices.Contains` instead of loop |
| `printf` | Printf format specifier issues |
| `unusedwrite` | Unused write to variable |
| `fillstruct` | Fill struct with zero values |
| `undeclaredname` | Undeclared name |
### typescript-language-server
| Code | Description |
|------|-------------|
| `6133` | Unused variable |
| `6196` | Unused import |
Run `:ListDiagnosticCodes` after `:LoadLSP` to see what codes your LSP provides.
## How It Works
1. **Load files** — `:LoadLSP` loads matching files into buffers for LSP analysis
2. **Filter diagnostics** — Finds diagnostics matching your configured codes
3. **Request code actions** — For each matching diagnostic, requests quickfix actions
4. **Apply if unique** — Only applies when exactly ONE quickfix action exists
5. **Auto-save** — Optionally saves modified files
## Health Check
```vim
:checkhealth biscuit
```
## Troubleshooting
### EMFILE: Too many open files (macOS)
macOS defaults to a file descriptor limit of 256. Loading hundreds of files
will exceed this. Add to your `~/.zshrc` or `~/.bashrc`:
```bash
ulimit -n 4096
```
Biscuit detects low limits and warns you via `:checkhealth biscuit`.
You can also reduce `batch_size` or set `max_files` to stay under the limit.
## Tips
- **Discover codes**: Run `:ListDiagnosticCodes` to see what your LSP provides
- **Start with dry run**: Use `!` to preview changes first
- **Clean up**: Run `:UnloadTracked` after batch operations to free memory
- **Health check**: Run `:checkhealth biscuit` to verify your setup
## License
[0BSD](LICENSE) (c) [Tai Groot](https://github.com/taigrr)