nvf

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 appendix

Custom Neovim Package

As of v0.5, you may now specify the Neovim package that will be wrapped with your configuration. This is done with the option.

{inputs, pkgs, ...}: {
  # using the neovim-nightly overlay
  vim.package = inputs.neovim-overlay.packages.${pkgs.stdenv.system}.neovim;
}

The neovim-nightly-overlay always exposes an unwrapped package. If using a different source, you are highly recommended to get an "unwrapped" version of the neovim package, similar to neovim-unwrapped in nixpkgs.

{ pkgs, ...}: {
  # using the neovim-nightly overlay
  vim.package = pkgs.neovim-unwrapped;
}

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 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.

custom-plugins/configuring.md
custom-plugins/lazy-method.md
custom-plugins/non-lazy-method.md
custom-plugins/legacy-method.md

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 neodev.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 Support {#ch-languages}

Language specific support means there is a combination of language specific plugins, treesitter support, nvim-lspconfig language servers, and null-ls integration. This gets you capabilities ranging from autocompletion to formatting to diagnostics. The following languages have sections under the vim.languages attribute.

Adding support for more languages, and improving support for existing ones are great places where you can contribute with a PR.

languages/lsp.md

Using DAGs

We conform to the NixOS options types for the most part, however, a noteworthy addition for certain options is the https://en.wikipedia.org/wiki/Directed_acyclic_graph">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 https://raw.githubusercontent.com/nix-community/home-manager/master/docs/manual/writing-modules/types.md">home-manager manual explains in more detail the overall usage logic of the DAG type.

entryAnywhere

lib.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

foo.bar = {
  a = lib.dag.entryAnywhere 0;
}

and

foo.bar = {
  a = 0;
}

are equivalent.

entryAfter

lib.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 = lib.dag.entryAfter [ "a" ] 1;
}

would place b after a in the graph.

entryBefore

lib.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 = lib.dag.entryBefore [ "a" ] 1;
  a = 0;
}

would place b before a in the graph.

entryBetween

lib.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 = lib.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}-$.

entriesAnywhere

lib.dag.entriesAnywhere (tag: string) (values: [T]) : Dag`

Creates a DAG with the given values with each entry labeled using the given tag. For example

foo.bar = lib.dag.entriesAnywhere "a" [ 0 1 ];

is equivalent to

foo.bar = {
  a-0 = 0;
  a-1 = lib.dag.entryAfter [ "a-0" ] 1;
}

entriesAfter

lib.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; } // lib.dag.entriesAfter "a" [ "b" ] [ 1 2 ];

is equivalent to

foo.bar = {
  b = 0;
  a-0 = lib.dag.entryAfter [ "b" ] 1;
  a-1 = lib.dag.entryAfter [ "a-0" ] 2;
}

entriesBefore

lib.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; } // lib.dag.entriesBefore "a" [ "b" ] [ 1 2 ];

is equivalent to

foo.bar = {
  b = 0;
  a-0 = 1;
  a-1 = lib.dag.entryBetween [ "b" ] [ "a-0" ] 2;
}

entriesBetween

lib.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; } // lib.dag.entriesBetween "a" [ "b" ] [ "c" ] [ 1 2 ];

is equivalent to

foo.bar = {
  b = 0;
  c = 3;
  a-0 = lib.dag.entryAfter [ "c" ] 1;
  a-1 = lib.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)

  1. (luaConfigPre) - not a part of the actual DAG, instead, it's simply inserted before the rest of the DAG
  2. globalsScript - used to set globals defined in vim.globals
  3. basic - used to set basic configuration options
  4. optionsScript - used to set options defined in vim.o
  5. theme (this is simply placed before pluginConfigs and lazyConfigs, meaning that surrounding entries don't depend on it) - used to set up the theme, which has to be done before other plugins
  6. lazyConfigs - lz.n and lzn-auto-require configs. If vim.lazy.enable is false, this will contain each plugin's config instead.
  7. pluginConfigs - the result of the nested vim.pluginRC (internal option, see the Custom Plugins page for adding your own plugins) DAG, used to set up internal plugins
  8. extraPluginConfigs - the result of vim.extraPlugins, which is not a direct DAG, but is converted to, and resolved as one internally
  9. mappings - the result of vim.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.nvim.types.luaInline or lib.options.literalExpression "mkLuaInline '''...'''". Cannot be used with command. lib.nvim.types.luaInline "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.nvim.types.luaInline ''
        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.