Files
nixpkgs/doc/languages-frameworks/neovim.section.md
Austin Horstman 72a716a79c docs/neovim: add plugin license notes
Explain how to set the license for a plugin that doesn't get detected.
2026-04-27 08:17:01 -05:00

14 KiB

Neovim

Install neovim-unwrapped to get a bare-bones Neovim to configure imperatively. This is the closest to what you encounter on other distributions.

neovim is a wrapper around Neovim with some extra configuration, for instance, to set the various language providers like Python. The wrapper can be further configured to include your favorite plugins and configurations for a reproducible neovim across machines. See the next section for more details.

Custom configuration

There are two wrappers available to provide additional configuration around the vanilla package pkgs.neovim-unwrapped:

  1. wrapNeovim: the historical one you should use
  2. wrapNeovimUnstable intended to replace the former. It has more features but the interface is not stable yet.

You can configure the former via:

neovim.override {
  withPython3 = true; # see `:h g:python3_host_prog`
  withNodeJs = false;
  withRuby = false;
  configure = {
    customRC = ''
      # here your custom viml configuration goes!
    '';
    packages.myVimPackage = with pkgs.vimPlugins; {
      # See examples below on how to use custom packages.
      start = [ ];
      # If a Vim plugin has a dependency that is not explicitly listed in
      # `opt`, that dependency will always be added to `start` to avoid confusion.
      opt = [ ];
    };
  };
}

myVimPackage is an arbitrary name for the generated package. You can choose any name you like.

If you want to use neovim-qt as a graphical editor, you can configure it by overriding Neovim in an overlay or passing it an overridden Neovim:

neovim-qt.override {
  neovim = neovim.override {
    configure = {
      customRC = ''
        # your custom viml configuration
      '';
    };
  };
}

You can use the new unstable wrapper but the interface may change:

  • autoconfigure: certain plugins need a custom configuration to work with Nix. For instance, sqlite-lua needs g:sqlite_clib_path to be set to work. Nixpkgs historically patched these in the plugins with several drawbacks: harder maintenance and making upstream work harder. Per convention, these mandatory bits of configuration are bookmarked in nixpkgs in passthru.initLua. Enabling autoconfigure automatically adds the snippets required for the plugins to work.
  • autowrapRuntimeDeps: Appends plugin's runtime dependencies to PATH. For instance, rest.nvim requires curl to work. Enabling autowrapRuntimeDeps adds it to the PATH visible by your Neovim wrapper (but not your global PATH).
  • luaRcContent: Extra lua code to add to the generated init.lua.
  • neovimRcContent: Extra vimL code sourced by the generated init.lua.
  • wrapperArgs: Extra arguments forwarded to the makeWrapper call.
  • wrapRc: Nix, not being able to write in your $HOME, loads the generated Neovim configuration via the $VIMINIT environment variable, i.e. : export VIMINIT='lua dofile("/nix/store/…-init.lua")'. This has side effects like preventing Neovim from sourcing your init.lua in $XDG_CONFIG_HOME/nvim (see bullet 7 of :help startup in Neovim). Disable it if you want to generate your own wrapper. You can still reuse the generated vimscript init code via neovim.passthru.initRc.
  • plugins: A list of plugins to add to the wrapper.
  • extraLuaPackages: A function passed on to lua.withPackages
  • withPython3, withNodeJs, withRuby control when to enable neovim providers (see :h provider).
wrapNeovimUnstable neovim-unwrapped {
  autoconfigure = true;
  autowrapRuntimeDeps = true;
  luaRcContent = ''
    vim.o.sessionoptions = 'buffers,curdir,help,tabpages,winsize,winpos,localoptions'
    vim.g.mapleader = ' '
    vim.g.maplocalleader = ' '
    vim.opt.smoothscroll = true
    vim.opt.colorcolumn = { 100 }
    vim.opt.termguicolors = true
  '';
  # plugins accepts a list of either plugins or { plugin = ...; config = ..vimscript.. };
  plugins = with vimPlugins; [
    {
      plugin = vim-obsession;
      config = ''
        map <Leader>$ <Cmd>Obsession<CR>
      '';
    }
    (nvim-treesitter.withPlugins (p: [ p.nix p.python ]))
    hex-nvim
  ];
  extraLuaPackages = lp: [ lp.mpack ];
  withPython3 = true;
  withNodeJs = false;
  withRuby = false;
}

You can explore the configuration withnix repl to discover these options and override them. For instance:

neovim.overrideAttrs (oldAttrs: {
  autowrapRuntimeDeps = false;
})

Specificities for some plugins

Plugin optional configuration

Some plugins require specific configuration to work. We choose not to patch those plugins but expose the necessary configuration under PLUGIN.passthru.initLua for neovim plugins. For instance, the unicode-vim plugin needs the path towards a unicode database so we expose the following snippet vim.g.Unicode_data_directory="${self.unicode-vim}/autoload/unicode" under vimPlugins.unicode-vim.passthru.initLua.

Plugin license overrides

Generated Vim and Neovim plugins get their meta.license from GitHub license metadata when possible. Some upstream repositories do not expose a license file that GitHub can detect, or only mention the license in a README. In those cases, add a manual meta.license override in overrides.nix.

For example, if upstream documents that a plugin uses the Vim license but GitHub does not detect it:

{
  foo-nvim = super.foo-nvim.overrideAttrs (old: {
    meta = old.meta // {
      # README says this plugin is distributed under the Vim license.
      license = lib.licenses.vim;
    };
  });
}

LuaRocks based plugins

In order to automatically handle plugin dependencies, several Neovim plugins upload their package to LuaRocks. This means less work for nixpkgs maintainers in the long term as dependencies get updated automatically. This means several Neovim plugins are first packaged as nixpkgs lua packages, and converted via buildNeovimPlugin in a vim plugin. This conversion is necessary because Neovim expects lua folders to be top-level while LuaRocks installs them in various subfolders by default.

For instance:

{
  rtp-nvim = neovimUtils.buildNeovimPlugin { luaAttr = luaPackages.rtp-nvim; };
}

To update these packages, you should use the lua updater rather than vim's.

Treesitter

Treesitter provides syntax parsing for Neovim, enabling features like: Advanced syntax highlighting, Code folding, Indentation and more.

Most Neovim users manage treesitter through the nvim-treesitter plugin, which provides:

  • Commands for managing grammars and queries, e.g. :TSInstall, which downloads, compiles and installs them at runtime.
  • A custom indentation implementation (:h indentexpr) for languages with indents.scm queries.

These features build on top of treesitter functionality that is built into Neovim.

In nixpkgs, grammars and queries are precompiled and packaged separately. This means:

  • You can use treesitter features without installing nvim-treesitter.
  • You only need nvim-treesitter if you want its custom indentation implementation.
  • Plugins that depend on grammars can reference them directly.

Treesitter setup using nvim-treesitter

::: {.tip} Choose this approach if you want to use nvim-treesitter's custom indentation expression. :::

To install nvim-treesitter combined with a set of precompiled grammars, you can use the nvim-treesitter.withPlugins function:

(pkgs.neovim.override {
  configure = {
    packages.myPlugins = with pkgs.vimPlugins; {
      start = [
        (nvim-treesitter.withPlugins (
          plugins: with plugins; [
            nix
            python
          ]
        ))
      ];
    };
  };
})

To enable all grammars packaged in nixpkgs, use pkgs.vimPlugins.nvim-treesitter.withAllGrammars.

For how to configure nvim-treesitter and set up syntax highlighting, indentation, folding, etc., please refer to the :help nvim-treesitter-quickstart plugin documentation.

::: {.note} When using Nix-managed grammars, :checkhealth nvim-treesitter will report no installed languages. This is expected behavior because:

  • The nvim-treesitter health check searches its configured install directory.
  • Nix installs grammars to the Nix store and adds them to the runtimepath instead.

To verify Nix-managed parsers and queries, use :checkhealth vim.treesitter instead. :::

Treesitter setup using standalone grammars and queries

::: {.tip} Choose this approach if you

  • Want minimal dependencies.
  • Don't need nvim-treesitter's custom indentation expression. :::

You can install the standalone parsers and queries directly without installing nvim-treesitter:

(pkgs.neovim.override {
  configure = {
    packages.myPlugins =
      with pkgs.vimPlugins;
      let
        # Select the grammars you need
        treesitter-grammars = with nvim-treesitter-parsers; [
          nix
          python
        ];
        # Queries are needed for treesitter based syntax highlighting and folds.
        treesitter-queries = map (p: p.associatedQuery) treesitter-grammars;
      in
      {
        start = [
          # regular plugins
        ]
        ++ treesitter-grammars
        ++ treesitter-queries;
      };
  };
})

You can enable treesitter features for installed grammars in a FileType autocommand or in an ftplugin/<language>.lua script, e.g.

vim.api.nvim_create_autocmd('FileType', {
  pattern = { 'rust', 'javascript', 'zig' },
  callback = function(ev)
    local bufnr = ev.buf

    -- Enable treesitter syntax highlighting and parsing for the current buffer
    -- (Requires queries to be installed)
    vim.treesitter.start(bufnr)

    -- Enable treesitter based code folding
    -- (folds are window-scoped, not buffer-scoped)
    -- (Requires queries to be installed)
    vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
    vim.wo.foldmethod = 'expr'
  end,
})

Treesitter grammars as plugin dependencies

Some Neovim plugins (like neotest adapters, markdoc-nvim, hurl-nvim) depend on treesitter grammars. These dependencies are usually declared in plugin overrides.

::: {.important} Some plugin READMEs may suggest that they depend on nvim-treesitter. This is almost always not the case.

nvim-treesitter no longer provides a Lua module API for other plugins to use. In the vast majority of cases, these plugins:

  • Depend on parsers (not on nvim-treesitter or its queries).
  • Bundle their own queries (either as *.scm files or hardcoded in the Lua sources). :::

To add grammars as a plugin dependency, add an override:

{
  foo-nvim = super.foo-nvim.overrideAttrs {
    dependencies = with self.nvim-treesitter-parsers; [
      markdown
      markdown_inline
      html
    ];
  };
}

If a plugin actually does depend on the nvim-treesitter legacy module API, you can add nvim-treesitter-legacy as a dependency:

{
  foo-legacy-nvim = super.foo-legacy-nvim.overrideAttrs {
    dependencies = with self; [
      nvim-treesitter-legacy
      nvim-treesitter-parsers.nix
    ];
  };
}

::: {.caution} nvim-treesitter-legacy exists for the purpose of easing transition and will be removed in 26.11. If a Neovim configuration contains both nvim-treesitter and nvim-treesitter-legacy, it will fail to evaluate. :::

Testing Neovim plugins

neovimRequireCheck

neovimRequireCheck is a simple test which checks if Neovim can require lua modules without errors. This is often enough to catch missing dependencies.

It accepts a single string for a module, or a list of module strings to test.

  • nvimRequireCheck = MODULE;
  • nvimRequireCheck = [ MODULE1 MODULE2 ];

When nvimRequireCheck is not specified, we will search the plugin's directory for lua modules to attempt loading. This quick smoke test can catch obvious dependency errors that might be missed. The check hook will fail the build if any modules cannot be loaded. This encourages inspecting the logs to identify potential issues.

To only check a specific module, add it manually to the plugin definition overrides.

{
  gitsigns-nvim = super.gitsigns-nvim.overrideAttrs {
    dependencies = [ self.plenary-nvim ];
    nvimRequireCheck = "gitsigns";
  };
}

Some plugins will have lua modules that require a user configuration to function properly or can contain optional lua modules that we don't want to test by requiring. We can skip specific modules using nvimSkipModules. Similar to nvimRequireCheck, it accepts a list of strings.

  • nvimSkipModules = [ MODULE1 MODULE2 ];
{
  asyncrun-vim = super.asyncrun-vim.overrideAttrs {
    nvimSkipModules = [
      # vim plugin with optional toggleterm integration
      "asyncrun.toggleterm"
      "asyncrun.toggleterm2"
    ];
  };
}

In rare cases, we might not want to actually test loading lua modules for a plugin. In those cases, we can disable neovimRequireCheck with doCheck = false;.

This can be manually added through plugin definition overrides in the overrides.nix.

{
  vim-test = super.vim-test.overrideAttrs {
    # Vim plugin with a test lua file
    doCheck = false;
  };
}