A declarative Neovim configuration using nixvim, designed to be used as a flake input in nix-darwin or NixOS systems.
- Modular structure with auto-importing plugin groups
- LSP support for Rust, Nix, Python, TypeScript/JavaScript, HTML, CSS, Bash, and more
- Rust development with rustaceanvim (rust-analyzer, clippy, cargo commands, macro expansion, debugger support)
- JSDoc type checking for JavaScript with neogen auto-generation
- Format-on-save using prettierd, deno_fmt, and language-specific formatters
- Harpoon2 for quick file navigation with bufferline integration
- Telescope for fuzzy finding files, grep, diagnostics, and LSP symbols
- Neo-tree file explorer
- noice.nvim for polished LSP hover, signature help, and diagnostic floating windows with borders
- dressing.nvim for improved code action and input UI with Telescope integration
- mini.nvim modules (icons, indentscope, surround)
- Catppuccin color scheme
- Treesitter for syntax highlighting and code understanding
- Auto-completion with nvim-cmp, luasnip, and LSP integration
- Toggleterm for floating terminal
- which-key for keybinding discovery
nix run github:KDLT/nixvim# Build the configuration
nix build .#default
# Run nixvim
nix run .
# Check for errors
nix flake checkAdd to your flake inputs:
{
inputs = {
nixvim.url = "github:KDLT/nixvim";
};
}Then import the configuration module in your system configuration.
config/
├── default.nix # Entry point
├── settings/
│ ├── autocommands.nix # Auto-commands (highlight on yank)
│ ├── keymaps.nix # Global keybindings
│ └── options.nix # Vim options (line numbers, tabs, etc.)
└── plugins/
├── cmp/ # Completion (nvim-cmp, luasnip)
├── editor/ # Core editing (treesitter, neo-tree, undotree)
├── git/ # Git integration (gitsigns, lazygit)
├── lsp/ # LSP and formatting (conform.nvim)
├── themes/ # Color schemes (catppuccin)
├── ui/ # UI enhancements (lualine, bufferline, noice, zen-mode)
└── utils/ # Utilities (telescope, harpoon, neogen, toggleterm, mini, which-key)
Leader key: <Space>
<Space>- No-op (leader key)<Esc>- Clear search highlights<leader>s- Save buffer<leader>|- Split window vertically<leader>-- Split window horizontally<C-h/j/k/l>- Navigate between splits<C-x>- Close window<C-c>- Toggle between two most recent buffers
<leader>e- Toggle Neo-tree file explorer<C-p>- Telescope git files<C-f>- Telescope find files<C-g>- Telescope live grep<leader><space>- Telescope find project files<leader>/- Ripgrep (root dir)<leader>:- Command history<leader>o- Recently opened files
Harpoon numbers are displayed in bufferline for easy identification.
<leader>a- Add file to Harpoon<C-e>- Toggle Harpoon menu<leader>1to<leader>9- Jump to Harpoon file 1-9
Code Navigation & Actions (<leader>c - all use Telescope pickers):
<leader>ca- Code actions (fixes, refactorings via Telescope)<leader>cr- Rename symbol across entire codebase<leader>cd- Go to definition (Telescope picker)<leader>cD- Go to declaration (Telescope picker)<leader>ci- Go to implementation (Telescope picker)<leader>ct- Go to type definition (Telescope picker)<leader>cR- Find all references (Telescope picker)<leader>ch- Show signature help (function parameters)<leader>cs- Document symbols (file outline)<leader>cS- Workspace symbols (search across project)
Quick LSP actions (native Neovim keybindings):
K- Show hover documentationgd- Jump directly to definitiongra- Code actionsgrr- Find referencesgri- Go to implementationgrn- Rename
Diagnostics:
<leader>d- Show diagnostic in float]d- Next diagnostic[d- Previous diagnostic<leader>q- Add diagnostics to location list<leader>ld- Stop LSP<leader>le- Start LSP
<leader>jd- Generate JSDoc annotation<leader>jf- Generate JSDoc for function
Type /** above a function, press <leader>jf, and neogen will auto-generate parameter tags. Use <Tab> and <Shift-Tab> to jump between placeholders.
sa{motion}{char}- Add surrounding (e.g.,saiw"to surround word with quotes)sd{char}- Delete surrounding (e.g.,sd"to remove quotes)sr{old}{new}- Replace surrounding (e.g.,sr"'to change quotes)sf{char}- Find surrounding (right)sF{char}- Find surrounding (left)sh{char}- Highlight surrounding
<leader>gs- Git status (Telescope)<leader>gc- Git commits (Telescope)
<leader>ff- LSP document symbols (functions/variables in current file)<leader>fg- Live grep<leader>fb- Telescope builtins<leader>fd- Workspace diagnostics<leader>fs- Symbols picker (emoji, kaomoji, math symbols)<leader>b- List buffers<leader>fc- Change colorscheme<leader>fh- Help tags<leader>fk- View keymaps
<S-h>- Cycle to previous buffer<S-l>- Cycle to next buffer<leader>bo- Close all other buffers<leader>bd- Close current buffer<leader>bp- Toggle pin<leader>bP- Delete non-pinned buffers
<leader>tt- Toggle floating terminal<C-h/j/k/l>- Navigate to other windows from terminal (exit method)
<leader>u- Toggle Undotree<leader>z- Toggle Zen-mode<leader>p- Toggle breakpoint (debugging)
Format happens automatically on save. To disable:
:FormatDisable- Disable globally:FormatDisable!- Disable for current buffer only:FormatEnable- Re-enable:FormatToggle- Toggle globally:FormatToggle!- Toggle for current buffer
Powered by rustaceanvim - all commands use interactive pickers.
Core Workflow:
<leader>rr- Run target (picker shows binaries, examples, etc.)<leader>rt- Run tests (picker shows all test targets)<leader>rc- Run check (cargo check with clippy)<leader>rd- Open docs.rs for symbol under cursor
Rust-Specific Features:
<leader>re- Explain error (detailed compiler error explanation)<leader>rm- Expand macro (show macro expansion)<leader>rh- Hover actions (code actions for symbol under cursor)<leader>rg- View crate graph (dependency visualization)<leader>rD- Debug target (requires codelldb)
- Nix: nil_ls
- Python: pylsp
- Rust: rustaceanvim (rust-analyzer with clippy linting, inlay hints, cargo integration)
- TOML: taplo (for Cargo.toml)
- JavaScript/TypeScript: ts_ls (with JSDoc type checking enabled)
- HTML: html (diagnostics only, formatting disabled)
- CSS: cssls
- Bash: bashls
- JSON: jsonls
- YAML: yamlls
- Markdown: marksman (diagnostics only, formatting disabled)
- Docker: dockerls
- SQL: sqls
- Emmet: emmet_ls
The ts_ls language server is configured for JSDoc type safety:
- Type checking enabled for JavaScript files
- Validates JSDoc annotations
- Provides autocomplete, goto definition, and hover
- Use
<leader>jfto auto-generate JSDoc from function signatures
Example:
/**
* Creates a person object
* @param {string} name - The person's name
* @param {number} age - The person's age
* @returns {{name: string, age: number}}
*/
function createPerson(name, age) {
return { name, age };
}Formatters are managed by conform.nvim and run on save:
- Rust: rustfmt (via rustaceanvim)
- TOML: taplo (for Cargo.toml)
- JavaScript/TypeScript: prettierd (with semicolons, single quotes)
- Markdown: deno_fmt
- HTML: prettierd
- Nix: nixfmt-rfc-style
- Bash: shellcheck → shellharden → shfmt (chained)
- C++: clang_format
- Create a new
.nixfile in the appropriate plugin category directory - Add the import to the category's
default.nix
Example - adding a new editor plugin:
# config/plugins/editor/my-plugin.nix
{
plugins.my-plugin = {
enable = true;
settings = {
option = value;
};
};
}Then add to config/plugins/editor/default.nix:
{
imports = [
./my-plugin.nix
# ... other imports
];
}Edit config/plugins/lsp/lsp.nix:
servers = {
# Add your LSP (for Rust, use rustaceanvim instead)
pyright.enable = true;
};If the LSP provides formatting, disable it to avoid conflicts with conform.nvim:
pyright = {
enable = true;
onAttach.function = ''
client.server_capabilities.documentFormattingProvider = false
client.server_capabilities.documentRangeFormattingProvider = false
'';
};Edit config/plugins/lsp/conform.nix:
formatters_by_ft = {
python = [ "black" ];
};
formatters = {
black = {
command = lib.getExe pkgs.black;
};
};Note: Rust formatting is handled by rustaceanvim automatically.
Edit config/settings/keymaps.nix for global keybindings, or add them to the plugin's .nix file for plugin-specific bindings:
keymaps = [
{
mode = "n";
key = "<leader>x";
action = ":MyCommand<CR>";
options.desc = "Description for which-key";
}
];For Lua actions, use __raw:
action.__raw = "function() require('plugin').method() end";Edit config/plugins/utils/which-key.nix:
{
__unkeyed-1 = "<leader>x";
desc = "My custom action";
icon = " ";
}For groups:
{
__unkeyed-1 = "<leader>x";
group = "My Group";
icon = " ";
}LSP floating windows (hover, signature help, diagnostics) are managed by noice.nvim in config/plugins/ui/noice.nix. To customize borders or behavior:
plugins.noice.settings = {
presets = {
lsp_doc_border = true; # Enable borders for LSP windows
};
lsp = {
hover.enabled = true;
signature.enabled = true;
};
};Edit config/settings/options.nix:
opts = {
number = true; # Show line numbers
relativenumber = true; # Relative line numbers
tabstop = 2; # Tab width
# etc.
};The bufferline shows harpoon numbers next to filenames:
1 script.js | index.html | 3 styles.css
Files with harpoon numbers can be quickly accessed with <leader>1, <leader>3, etc.
The flake provides a dev shell with language toolchains (configured in config/shell.nix):
nix developCurrently commented out: Rust, Go, Elixir, F#, Gleam, Kotlin. Uncomment as needed.
# Update all flake inputs
nix flake update
# Update specific input (e.g., nixvim)
nix flake update nixvim
# Format Nix files
nix fmtAfter pushing changes to GitHub, update the flake input in your nix-darwin config:
cd /path/to/nix-darwin-config
nix flake update nixvim --refresh
sudo darwin-rebuild switch --flake .#If multiple formatters fight over a file:
- Check that LSP formatting is disabled in
config/plugins/lsp/lsp.nix - Ensure only one formatter is configured per filetype in
config/plugins/lsp/conform.nix - Verify
lsp-format.enableis set tofalse
If <Tab> doesn't jump between snippet placeholders:
- Check that you're in a luasnip snippet (generated by neogen with
<leader>jf) - Verify the snippet is active (you should see SELECT mode in statusline)
- The Tab/Shift-Tab mappings are configured in
config/plugins/cmp/completions.nix
# Check for errors
nix flake check
# View detailed error logs
nix log /nix/store/xxx-nixvim.drvWhen adding or removing plugin files, ensure the corresponding default.nix import list is updated. The auto-importing only works if the file is listed in the imports array.
Based on configurations from:
- GaetanLepage/nix-config
- neve
- dc-tec's configuration