Configuring nvf
nvf allows for very extensive configuration in Neovim through the Nix module interface. The below chapters describe several of the options exposed in nvf for your convenience. You might also be interested in the helpful tips section for more advanced or unusual configuration options supported by nvf.
Note that this section does not cover module options. For an overview of all module options provided by nvf, please visit the options reference
Custom Plugins
nvf exposes a very wide variety of plugins by default, which are consumed by module options. This is done for your convenience, and to bundle all necessary dependencies into nvf's runtime with full control of versioning, testing and dependencies. In the case a plugin you need is not available, you may consider making a pull request to add the package you're looking for, or you may add it to your configuration locally. The below section describes how new plugins may be added to the user's configuration.
Adding Plugins
Per nvf's design choices, there are several ways of adding custom plugins to your configuration as you need them. As we aim for extensive configuration, it is possible to add custom plugins (from nixpkgs, pinning tools, flake inputs, etc.) to your Neovim configuration before they are even implemented in nvf as a module.
Info
To add a plugin to your runtime, you will need to add it to
vim.startPlugins list in your configuration. This is akin to cloning a
plugin to ~/.config/nvim, but they are only ever placed in the Nix store and
never exposed to the outside world for purity and full isolation.
As you would configure a cloned plugin, you must configure the new plugins that
you've added to startPlugins. nvf provides multiple ways of configuring
any custom plugins that you might have added to your configuration.
Configuring
Just making the plugin to your Neovim configuration available might not always be enough., for example, if the plugin requires a setup table. In that case, you can write custom Lua configuration using one of
config.vim.lazy.plugins.*.setupOptsconfig.vim.extraPlugins.*.setupconfig.vim.luaConfigRC.
Lazy Plugins
config.vim.lazy.plugins.*.setupOpts is useful for lazy-loading plugins, and
uses an extended version of lz.n's PluginSpec to expose a familiar
interface. setupModule and setupOpt can be used if the plugin uses a
require('module').setup(...) pattern. Otherwise, the before and after
hooks should do what you need.
{
config.vim.lazy.plugins = {
aerial.nvim = {
# ^^^^^^^^^ this name should match the package.pname or package.name
package = aerial-nvim;
setupModule = "aerial";
setupOpts = {option_name = false;};
after = "print('aerial loaded')";
};
};
}
Standard API
vim.extraPlugins uses an attribute set, which maps DAG section names to a
custom type, which has the fields package, after, setup. They allow you to
set the package of the plugin, the sections its setup code should be after (note
that the extraPlugins option has its own DAG scope), and the its setup code
respectively. For example:
{pkgs, ...}: {
config.vim.extraPlugins = {
aerial = {
package = pkgs.vimPlugins.aerial-nvim;
setup = "require('aerial').setup {}";
};
harpoon = {
package = pkgs.vimPlugins.harpoon;
setup = "require('harpoon').setup {}";
after = ["aerial"]; # place harpoon configuration after aerial
};
};
}
Setup using luaConfigRC
vim.luaConfigRC also uses an attribute set, but this one is resolved as a DAG
directly. The attribute names denote the section names, and the values lua code.
For example:
{
# This will create a section called "aquarium" in the 'init.lua' with the
# contents of your custom configuration. By default 'entryAnywhere' is implied
# in DAGs, so this will be inserted to an arbitrary position. In the case you
# wish to control the position of this section with more precision, please
# look into the DAGs section of the manual.
config.vim.luaConfigRC.aquarium = "vim.cmd('colorscheme aquiarum')";
}
Note
One of the greatest strengths of nvf is the ability to order
configuration snippets precisely using the DAG system. DAGs
are a very powerful mechanism that allows specifying positions
of individual sections of configuration as needed. We provide helper functions
in the extended library, usually under inputs.nvf.lib.nvim.dag that you may
use.
Please refer to the DAG section in the nvf manual to find out more about the DAG system.
As of version 0.7, an API is exposed to allow configuring lazy-loaded
plugins via lz.n and lzn-auto-require. Below is a comprehensive example of
how it may be loaded to lazy-load an arbitrary plugin.
{
config.vim.lazy.plugins = {
"aerial.nvim" = {
package = pkgs.vimPlugins.aerial-nvim;
setupModule = "aerial";
setupOpts = {
option_name = true;
};
after = ''
-- custom lua code to run after plugin is loaded
print('aerial loaded')
'';
# Explicitly mark plugin as lazy. You don't need this if you define one of
# the trigger "events" below
lazy = true;
# load on command
cmd = ["AerialOpen"];
# load on event
event = ["BufEnter"];
# load on keymap
keys = [
{
key = "<leader>a";
action = ":AerialToggle<CR>";
}
];
};
};
}
LazyFile event
nvf re-implements LazyFile as a familiar user event to load a plugin when
a file is opened:
{
config.vim.lazy.plugins = {
"aerial.nvim" = {
package = pkgs.vimPlugins.aerial-nvim;
event = [{event = "User"; pattern = "LazyFile";}];
# ...
};
};
}
You can consider the LazyFile event as an alias to the combination of
"BufReadPost", "BufNewFile" and "BufWritePre", i.e., a list containing all
three of those events: ["BufReadPost" "BufNewFile" "BufWritePre"]
Non-lazy Method
As of version 0.5, we have a more extensive API for configuring plugins that
should be preferred over the legacy method. This API is available as
vim.extraPlugins. Instead of using DAGs exposed by the library
directly, you may use the extra plugin module as follows:
{pkgs, ...}: {
config.vim.extraPlugins = {
aerial = {
package = pkgs.vimPlugins.aerial-nvim;
setup = ''
require('aerial').setup {
-- some lua configuration here
}
'';
};
harpoon = {
package = pkgs.vimPlugins.harpoon;
setup = "require('harpoon').setup {}";
after = ["aerial"];
};
};
}
This provides a level of abstraction over the DAG system for faster iteration.
Legacy Method
Prior to version 0.5, the method of adding new plugins was adding the plugin
package to vim.startPlugins and adding its configuration as a DAG
under one of vim.configRC or vim.luaConfigRC. While configRC has
been deprecated, users who have not yet updated to 0.5 or those who prefer a
more hands-on approach may choose to use the old method where the load order of
the plugins is explicitly determined by DAGs without internal abstractions.
Adding New Plugins
To add a plugin not available in nvf as a module to your configuration using
the legacy method, you must add it to vim.startPlugins in order to
make it available to Neovim at runtime.
{pkgs, ...}: {
# Add a Neovim plugin from Nixpkgs to the runtime.
# This does not need to come explicitly from packages. 'vim.startPlugins'
# takes a list of *string* (to load internal plugins) or *package* to load
# a Neovim package from any source.
vim.startPlugins = [pkgs.vimPlugins.aerial-nvim];
}
Once the package is available in Neovim's runtime, you may use the luaConfigRC
option to provide configuration as a DAG using the nvf extended library in
order to configure the added plugin,
{inputs, ...}: let
# This assumes you have an input called 'nvf' in your flake inputs
# and 'inputs' in your specialArgs. In the case you have passed 'nvf'
# to specialArgs, the 'inputs' prefix may be omitted.
inherit (inputs.nvf.lib.nvim.dag) entryAnywhere;
in {
# luaConfigRC takes Lua configuration verbatim and inserts it at an arbitrary
# position by default or if 'entryAnywhere' is used.
vim.luaConfigRC.aerial-nvim= entryAnywhere ''
require('aerial').setup {
-- your configuration here
}
'';
}
Overriding plugins
The additional plugins section details the addition of new plugins to nvf under regular circumstances, i.e. while making a pull request to the project. You may override those plugins in your config to change source versions, e.g., to use newer versions of plugins that are not yet updated in nvf.
vim.pluginOverrides = {
lazydev-nvim = pkgs.fetchFromGitHub {
owner = "folke";
repo = "lazydev.nvim";
rev = "";
hash = "";
};
# It's also possible to use a flake input
lazydev-nvim = inputs.lazydev-nvim;
# Or a local path
lazydev-nvim = ./lazydev;
# Or a npins pin... etc
};
This will override the source for the lazydev.nvim plugin that is used in nvf
with your own plugin.
Warning
While updating plugin inputs, make sure that any configuration that has been
deprecated in newer versions is changed in the plugin's setupOpts. If you
depend on a new version, requesting a version bump in the issues section is a
more reliable option.
Language specific support means there is a combination of language specific
plugins, treesitter support, nvim-lspconfig language servers, conform-nvim
formatters, and nvim-lint linter integration. This gets you capabilities
ranging from autocompletion to formatting to diagnostics. The following
languages have sections under the vim.languages attribute.
- Rust: vim.languages.rust.enable
- Nix: vim.languages.nix.enable
- SQL: vim.languages.sql.enable
- C/C++: vim.languages.clang.enable
- Typescript/Javascript: vim.languages.ts.enable
- Python: vim.languages.python.enable:
- Zig: vim.languages.zig.enable
- Markdown: vim.languages.markdown.enable
- HTML: vim.languages.html.enable
- Dart: vim.languages.dart.enable
- Go: vim.languages.go.enable
- Lua: vim.languages.lua.enable
- PHP: vim.languages.php.enable
- F#: vim.languages.fsharp.enable
- Assembly: vim.languages.assembly.enable
- Astro: vim.languages.astro.enable
- Bash: vim.languages.bash.enable
- Clang: vim.languages.clang.enable
- Clojure: vim.languages.clojure.enable
- C#: vim.languages.csharp.enable
- CSS: vim.languages.css.enable
- CUE: vim.languages.cue.enable
- Elixir: vim.languages.elixir.enable
- Gleam: vim.languages.gleam.enable
- HCL: vim.languages.hcl.enable
- Helm: vim.languages.helm.enable
- Julia: vim.languages.julia.enable
- Kotlin: vim.languages.kotlin.enable
- Nim: vim.languages.nim.enable
- Nu: vim.languages.nu.enable
- OCaml: vim.languages.ocaml.enable
- Odin: vim.languages.odin.enable
- R: vim.languages.r.enable
- Ruby: vim.languages.ruby.enable
- Scala: vim.languages.scala.enable
- Svelte: vim.languages.svelte.enable
- Tailwind: vim.languages.tailwind.enable
- Terraform: vim.languages.terraform.enable
- Typst: vim.languages.typst.enable
- Vala: vim.languages.vala.enable
- WGSL: vim.languages.wgsl.enable
- YAML: vim.languages.yaml.enable
Adding support for more languages, and improving support for existing ones are great places where you can contribute with a PR.
LSP Custom Packages/Command
One of the strengths of nvf is convenient aliases to quickly configure LSP
servers through the Nix module system. By default the LSP packages for relevant
language modules will be pulled into the closure. If this is not desirable, you
may provide a custom LSP package (e.g., a Bash script that calls a command)
or a list of strings to be interpreted as the command to launch the language
server. By using a list of strings, you can use this to skip automatic
installation of a language server, and instead use the one found in your $PATH
during runtime, for example:
vim.languages.java = {
lsp = {
enable = true;
# This expects 'jdt-language-server' to be in your PATH or in
# 'vim.extraPackages.' There are no additional checks performed to see
# if the command provided is valid.
package = ["jdt-language-server" "-data" "~/.cache/jdtls/workspace"];
};
}
Using DAGs
We conform to the NixOS options types for the most part, however, a noteworthy
addition for certain options is the
DAG (Directed acyclic graph)
type which is borrowed from home-manager's extended library. This type is most
used for topologically sorting strings. The DAG type allows the attribute set
entries to express dependency relations among themselves. This can, for example,
be used to control the order of configuration sections in your luaConfigRC.
The below section, mostly taken from the home-manager manual explains in more detail the overall usage logic of the DAG type.
entryAnywhere
nvf.lib.nvim.dag.entryAnywhere (value: T) : DagEntry<T>
Indicates that value can be placed anywhere within the DAG. This is also the
default for plain attribute set entries, that is
# For 'nvf' to be available in module's arguments,
# it needs to be inherited from imports in the modules array as:
# modules = [{ _module.args = { inherit nvf; }; } ...];
foo.bar = {
a = nvf.lib.nvim.dag.entryAnywhere 0;
}
and
foo.bar = {
a = 0;
}
are equivalent.
entryAfter
nvf.lib.nvim.dag.entryAfter (afters: list string) (value: T) : DagEntry<T>
Indicates that value must be placed after each of the attribute names in the
given list. For example
foo.bar = {
a = 0;
b = nvf.lib.nvim.dag.entryAfter [ "a" ] 1;
}
would place b after a in the graph.
entryBefore
nvf.lib.nvim.dag.entryBefore (befores: list string) (value: T) : DagEntry<T>
Indicates that value must be placed before each of the attribute names in
the given list. For example
foo.bar = {
b = nvf.lib.nvim.dag.entryBefore [ "a" ] 1;
a = 0;
}
would place b before a in the graph.
entryBetween
nvf.lib.nvim.dag.entryBetween (befores: list string) (afters: list string) (value: T) : DagEntry<T>
Indicates that value must be placed before the attribute names in the first
list and after the attribute names in the second list. For example
foo.bar = {
a = 0;
c = nvf.lib.nvim.dag.entryBetween [ "b" ] [ "a" ] 2;
b = 1;
}
would place c before b and after a in the graph.
There are also a set of functions that generate a DAG from a list. These are
convenient when you just want to have a linear list of DAG entries, without
having to manually enter the relationship between each entry. Each of these
functions take a tag as argument and the DAG entries will be named
${tag}-${index}.
entriesAnywhere
nvf.lib.nvim.dag.entriesAnywhere (tag: string) (values: [T]) : Dag<T>
Creates a DAG with the given values with each entry labeled using the given tag. For example
foo.bar = nvf.lib.nvim.dag.entriesAnywhere "a" [ 0 1 ];
is equivalent to
foo.bar = {
a-0 = 0;
a-1 = nvf.lib.nvim.dag.entryAfter [ "a-0" ] 1;
}
entriesAfter
nvf.lib.nvim.dag.entriesAfter (tag: string) (afters: list string) (values: [T]) : Dag<T>
Creates a DAG with the given values with each entry labeled using the given tag.
The list of values are placed are placed after each of the attribute names in
afters. For example
foo.bar =
{ b = 0; } // nvf.lib.nvim.dag.entriesAfter "a" [ "b" ] [ 1 2 ];
is equivalent to
foo.bar = {
b = 0;
a-0 = nvf.lib.nvim.dag.entryAfter [ "b" ] 1;
a-1 = nvf.lib.nvim.dag.entryAfter [ "a-0" ] 2;
}
entriesBefore
nvf.lib.nvim.dag.entriesBefore (tag: string) (befores: list string) (values: [T]) : Dag<T>
Creates a DAG with the given values with each entry labeled using the given tag.
The list of values are placed before each of the attribute names in befores.
For example
foo.bar =
{ b = 0; } // nvf.lib.nvim.dag.entriesBefore "a" [ "b" ] [ 1 2 ];
is equivalent to
foo.bar = {
b = 0;
a-0 = 1;
a-1 = nvf.lib.nvim.dag.entryBetween [ "b" ] [ "a-0" ] 2;
}
entriesBetween
nvf.lib.nvim.dag.entriesBetween (tag: string) (befores: list string) (afters: list string) (values: [T]) : Dag<T>
Creates a DAG with the given values with each entry labeled using the given tag.
The list of values are placed before each of the attribute names in befores
and after each of the attribute names in afters. For example
foo.bar =
{ b = 0; c = 3; } // nvf.lib.nvim.dag.entriesBetween "a" [ "b" ] [ "c" ] [ 1 2 ];
is equivalent to
foo.bar = {
b = 0;
c = 3;
a-0 = nvf.lib.nvim.dag.entryAfter [ "c" ] 1;
a-1 = nvf.lib.nvim.dag.entryBetween [ "b" ] [ "a-0" ] 2;
}
DAG entries in nvf
From the previous chapter, it should be clear that DAGs are useful, because you can add code that relies on other code. However, if you don't know what the entries are called, it's hard to do that, so here is a list of the internal entries in nvf:
vim.luaConfigRC (top-level DAG)
- (
luaConfigPre) - not a part of the actual DAG, instead, it's simply inserted before the rest of the DAG globalsScript- used to set globals defined invim.globalsbasic- used to set basic configuration optionsoptionsScript- used to set options defined invim.otheme(this is simply placed beforepluginConfigsandlazyConfigs, meaning that surrounding entries don't depend on it) - used to set up the theme, which has to be done before other pluginslazyConfigs-lz.nandlzn-auto-requireconfigs. Ifvim.lazy.enableis false, this will contain each plugin's config instead.pluginConfigs- the result of the nestedvim.pluginRC(internal option, see the Custom Plugins page for adding your own plugins) DAG, used to set up internal pluginsextraPluginConfigs- the result ofvim.extraPlugins, which is not a direct DAG, but is converted to, and resolved as one internallymappings- the result ofvim.maps
Autocommands and Autogroups
This module allows you to declaratively configure Neovim autocommands and autogroups within your Nix configuration.
Autogroups (vim.augroups)
Autogroups (augroup) organize related autocommands. This allows them to be
managed collectively, such as clearing them all at once to prevent duplicates.
Each entry in the list is a submodule with the following options:
| Option | Type | Default | Description | Example |
|---|---|---|---|---|
enable |
bool |
true |
Enables or disables this autogroup definition. | true |
name |
str |
None | Required. The unique name for the autogroup. | "MyFormatGroup" |
clear |
bool |
true |
Clears any existing autocommands within this group before adding new ones defined in vim.autocmds. |
true |
Example:
{
vim.augroups = [
{
name = "MyCustomAuGroup";
clear = true; # Clear previous autocommands in this group on reload
}
{
name = "Formatting";
# clear defaults to true
}
];
}
Autocommands (vim.autocmds)
Autocommands (autocmd) trigger actions based on events happening within Neovim
(e.g., saving a file, entering a buffer). Each entry in the list is a submodule
with the following options:
| Option | Type | Default | Description | Example |
|---|---|---|---|---|
enable |
bool |
true |
Enables or disables this autocommand definition. | true |
event |
nullOr (listOf str) |
null |
Required. List of Neovim events that trigger this autocommand (e.g., BufWritePre, FileType). |
[ "BufWritePre" ] |
pattern |
nullOr (listOf str) |
null |
List of file patterns (globs) to match against (e.g., *.py, *). If null, matches all files for the given event. |
[ "*.lua", "*.nix" ] |
callback |
nullOr luaInline |
null |
A Lua function to execute when the event triggers. Use lib.generators.mkLuaInline. Cannot be used with command. |
lib.generators.mkLuaInline "function() print('File saved!') end" |
command |
nullOr str |
null |
A Vimscript command to execute when the event triggers. Cannot be used with callback. |
"echo 'File saved!'" |
group |
nullOr str |
null |
The name of an augroup (defined in vim.augroups) to associate this autocommand with. |
"MyCustomAuGroup" |
desc |
nullOr str |
null |
A description for the autocommand (useful for introspection). | "Format buffer on save" |
once |
bool |
false |
If true, the autocommand runs only once and then automatically removes itself. |
false |
nested |
bool |
false |
If true, allows this autocommand to trigger other autocommands. |
false |
Warning
You cannot define both callback (for Lua functions) and command (for
Vimscript) for the same autocommand. Choose one.
Examples:
{ lib, ... }:
{
vim.augroups = [ { name = "UserSetup"; } ];
vim.autocmds = [
# Example 1: Using a Lua callback
{
event = [ "BufWritePost" ];
pattern = [ "*.lua" ];
group = "UserSetup";
desc = "Notify after saving Lua file";
callback = lib.generators.mkLuaInline ''
function()
vim.notify("Lua file saved!", vim.log.levels.INFO)
end
'';
}
# Example 2: Using a Vim command
{
event = [ "FileType" ];
pattern = [ "markdown" ];
group = "UserSetup";
desc = "Set spellcheck for Markdown";
command = "setlocal spell";
}
# Example 3: Autocommand without a specific group
{
event = [ "BufEnter" ];
pattern = [ "*.log" ];
desc = "Disable line numbers in log files";
command = "setlocal nonumber";
# No 'group' specified
}
# Example 4: Using Lua for callback
{
event = [ "BufWinEnter" ];
pattern = [ "*" ];
desc = "Simple greeting on entering a buffer window";
callback = lib.generators.mkLuaInline ''
function(args)
print("Entered buffer: " .. args.buf)
end
'';
# Run only once per session trigger
once = true;
}
];
}
These definitions are automatically translated into the necessary Lua code to
configure vim.api.nvim_create_augroup and vim.api.nvim_create_autocmd when
Neovim starts.