Introduction
Envy is essentially a Nix-based plugin & configuration manager for neovim, providing:
- A convenient way to pin and package Vim plugins as Nix derivations.
- The ability for Vim plugin derivations to correctly depend on and pull in one another, and system dependencies (including plugin-native-language dependencies, C library dependencies, and executable dependencies).
- A Nix-based configuration mechanism that allows individual plugin configuration to depend on the install-time state of other plugins, dependencies, and potentially the system (in NixOS).
- A way to "layer" per-project plugins+configuration on top of per-user, in turn on top of per-system (albeit at a large memory cost at install-time).
- A method for existing users of NixOS/home-manager to tightly integrate neovim configuration into it, meaning that aspects of their neovim configuration can reliably depend upon aspects of their system or user configuration.
- A means to specify nvimrc configuration in vimscript, Lua, or MoonScript.
- More complexity than you want or need (probably).
- An end to world hunger.
The numbered chapters are intended to be read in order, as they build upon information in earlier chapters. The appendices are intended more as reference material.
Why?
Mostly because neovim plugin configuration is a bit of a clusterfuck. There's a deceptively (and increasingly) large amount of inherent complexity to the problem space, because neo/vim plugins can have dependencies on many axes (inter-plugin, external executable, Lua modules, remote host language modules, etc.), and those in turn have their own dependency closures, and may also depend on the system or user-level configuration.
Trying to reliably manage this cross-language, cross-system complexity using anything other than Nix quickly descends into either A) madness, or B) compromise. I'm not big on compromise.
1. Basic Usage
The main Envy module can basically be used in three different ways:
- It can be used as a NixOS module, to configure neovim system-wide.
- It can be used as a home-manager module, to configure neovim on a per-user basis.
- It can be used standalone, e.g. in order to configure a custom neovim
instance for a project's
shell.nix
.
In each of these uses, the module is configured in basically the same way, but adding/accessing the module to begin with differs.
Module Setup
Both the NixOS and home-manager modules expose the configuration interface
under sn.programs.neovim
in NixOS; if you want to use a different
attribute path for it, take a look at the
envy/nixos.nix
/envy/home-manager.nix
source and manually do the same.
Both modules take an enabled
argument, which defaults to true
, rather than
exposing a sn.programs.neovim.enable
option, due to a technical limitation.
NixOS
Add the envy/nixos.nix
module to imports
in your configuration.nix
:
{ config, lib, pkgs, ... }:
let
envy = (builtins.fetchgit { url = https://github.com/Shados/envy; ref = "master"; });
in
{
imports = [
(import "${envy}/nixos.nix" { })
...
];
...
}
Or, if you've added envy as a flake input and have flake inputs available in your module arguments:
{ config, lib, inputs, pkgs, ... }:
{
imports = [
(envy.nixosModules.default { })
...
];
...
}
home-manager
Add the envy/home-manager.nix
module to imports
in your configuration.nix
:
{ config, lib, pkgs, ... }:
let
envy = (builtins.fetchgit { url = https://github.com/Shados/envy; ref = "master"; });
in
{
imports = [
(import "${envy}/home-manager.nix" { })
...
];
...
}
Or, if you've added envy as a flake input and have flake inputs available in your module arguments:
{ config, lib, inputs, pkgs, ... }:
{
imports = [
(envy.homeModules.default { })
...
];
...
}
Standalone Setup
Usage as a standalone module is slightly more complicated, and is most easily
done by use of the configuredNeovimModule
in Envy's lib
module:
{ nixpkgs ? import <nixpkgs> { } }:
let
envy = (builtins.fetchgit { url = https://github.com/Shados/envy; ref = "master"; });
envyLib = (import "${envy}/lib.nix" { inherit nixpkgs; });
envyModule = envyLib.configuredNeovimModule {
nvimConfig = { config, lib, pkgs, ... }: {
# Envy module config here
pluginRegistry = {
...
};
};
}
in
nixpkgs.mkShell {
...
buildInputs = [
envyModule.wrappedNeovim
...
];
}
2. Module Configuration
The full set of available configuration options is documented in Appendix A; this chapter serves as an overview of the configuration process and provides examples of how the options may be used.
Broadly, the process for configuring Envy is:
- Add non-plugin
init.vim
configuration. - Enable the plugins you want to use.
- Specify any additional dependencies the plugins may have.
- Add per-plugin configuration.
Non-plugin Configuration
Adapt your non-plugin init.vim
configuration to use Envy's module options.
By "non-plugin" here we mean specifically things that either don't depend on/configure any plugin, or that could depend on any one of several plugins (in which case, you should use Nix functionality to check which/whether any of those plugins are enabled and adapt the generated configuration appropriately).
Envy provides a wide array of options here, so some examples may be helpful:
{ config, lib, pkgs, ... }:
{
# Configuration items that should be done prior to any per-plugin configuration
prePluginConfig = ''
let mapleader = "\<Space>"
augroup vimrc
autocmd!
augroup END
set termguicolors
'';
# General configuration items that are appended to the end of the generated
# vimrc. You can use e.g. `lib.mkAfter` if you need something to go at the
# very end of the file.
extraConfig = ''
set incsearch
set hlsearch
set ignorecase
set smartcase
set number
set relativenumber
set autoindent
set shiftwidth=2
set softtabstop=2
set tabstop=2
set expandtab
" Use Ripgrep (rg) for search backend
let g:ackprg = '${pkgs.ripgrep}/bin/rg --vimgrep --smart-case --no-heading --max-filesize=4M'
set grepprg:${pkgs.ripgrep}/bin/rg\ --vimgrep\ --smart-case\ --no-heading\ --max-filesize=4M
nnoremap <Leader>o :exe ':silent !${pkgs.xdg-utils}/bin/xdg-open % &'<CR>
'';
# A list of packages whose executables should be added to the $PATH for
# neovim. These will *only* be added to neovim's path, not to the system or
# user profiles.
extraBinPackages = with pkgs; [
silver-searcher
xdg_utils # xdg-open
];
# See chapter 3
mergePlugins = true;
# `files` can be used to build a symlink tree of files and folders, which
# would typically consist of any extra/local contents of .config/nvim/ in a
# non-Nix neovim setup.
files = {
neosnippets.source = "/my/snippet/files/";
};
}
Enabling Plugins
There are several ways to specify the source for a plugin, depending on what you want:
{ config, lib, pkgs, ... }:
{
pluginRegistry = {
# In this case, `source` is pointed to an existing Vim plugin derivation.
"denite.nvim" = {
enable = true;
source = pkgs.vimPlugins.denite-nvim;
};
# In this case, Envy will dynamically construct a derivation using the
# given Nix store path as the source.
localPlugin = {
enable = true;
source = /some/local/path/or/store/path;
};
# Here we instead instruct Envy to have Vim load the plugin from a local
# filesystem path at run-time, rather than bundling it into the Nix store.
# This is useful for doing plugin development, or if you otherwise want to
# use a plugin outside of the Nix store, while still allowing you to
# leverage Envy's configuration and dependency specification/resolution
# mechanisms.
inDevPlugin = {
enable = true;
dir = "/a/local/path/string";
};
# Envy also provides a helper for constructing a vim plugin derivation from
# a Niv sources.nix attrset, which you can then use as a source.
vim-systemd-syntax = {
enable = true;
source = config.sn.programs.neovim.lib.buildVimPluginFromNiv (import ./pins { }) "vim-systemd-syntax";
};
};
}
Specifying Dependencies
Envy allows for specifying a wide variety of dependency types:
{ config, lib, pkgs, ... }:
{
pluginRegistry = {
# Inter-plugin dependencies; the items should either be `pluginRegistry`
# attribute names or vim plugin derivations.
neosnippet-snippets.dependencies = [ "neosnippet-vim" ];
# Specifies that a plugin needs external executables from the given
# packages made available in neovim's $PATH.
ale.binDeps = with pkgs; [
bash
shellcheck
shfmt
];
# Ensures that the specified Lua modules will be made available in
# neovim's LUA_PATH/LUA_CPATH, meaning that the main neovim process can
# load them for in-process Lua plugins and scripts to use.
"Shados/precog.nvim".luaDeps = ps: with ps; [
luafilesystem
];
# Flags a plugin as being a 'remote' plugin requiring a plugin host for a
# specific language (here, Python 3).
denite-nvim.remote.python3 = true;
# Pulls in plugin-host-language dependencies.
# Automatically implies `remote.python3 = true;`.
aPythonPlugin.remote.python3Deps = ps: with ps; [
requests
];
};
}
Inter-plugin dependencies also determine the order in which Vim plugins are loaded at Vim run-time (by vim-plug).
There are additionally two "soft" dependency options (before
and after
,
both of which take lists of plugin names only), that change how plugins are
ordered if both are enabled, but does not cause the "dependency" to be enabled
if the "dependent" plugin is, e.g.:
{ config, lib, pkgs, ... }:
{
pluginRegistry = {
# vim-devicons needs to be loaded after these plugins, if they
# are being used, as per its installation guide.
# Both `after` and `before` can be specified as either `pluginRegistry`
# attribute names or vim plugin derivations.
vim-devicons.after = [
"nerdtree" "vim-airline" "ctrlp-vim" "powerline/powerline" "denite-nvim"
"unite-vim" "lightline-vim" "vim-startify" "vimfiler" "vim-flagship"
];
};
}
It should be noted that lazy-loaded plugins don't fully respect ordering options, and don't trigger lazy-loading of dependencies. Either ensure dependencies aren't also being lazy-loaded, or put them behind the same lazy-load triggers as their dependents.
Plugin Configuration
There is a per-plugin version of the extraConfig
option, under
pluginRegistry.<pluginName>.extraConfig
. These are inserted into the
generated nvimrc after the prePluginConfig
, before the top-level
extraConfig
, and in the same order plugins are loaded (that is, based on
dependencies and explicit ordering information).
3. Advanced Usage
This chapter provides some notes on more complicated usages of Envy.
Plugin Merging
The mergePlugins
option can be used to merge
plugin directories into symlink trees. This is useful because it reduces the
number of directories that have to be added to neovim's runtimepath
, and as a
result it can significantly improve neovim startup times.
There are some restrictions:
- It may break some plugins outright.
- It will only merge plugins that are within the same 'bucket' in the plugin load order (so that dependent plugins are still loaded after their dependencies).
- It will likely cause issues in cases of colliding file names.
As such, it is not enabled by default. If you do enable it, you can disable
merging on a per-plugin basis using the
pluginRegistry.<pluginName>.mergeable
option.
A. Configuration Options
_module.args
Additional arguments passed to each module in addition to ones like lib
,
config
, and pkgs
, modulesPath
.
This option is also available to all submodules. Submodules do not inherit args
from their parent module, nor do they provide args to their parent module or
sibling submodules. The sole exception to this is the argument name
which is
provided by parent modules to a submodule and contains the attribute name the
submodule is bound to, or a unique generated name if it is not bound to an
attribute.
Some arguments are already passed by default, of which the following cannot be changed with this option:
-
{var}
lib
: The nixpkgs library. -
{var}
config
: The results of all options after merging the values from all modules together. -
{var}
options
: The options declared in all modules. -
{var}
specialArgs
: ThespecialArgs
argument passed toevalModules
. -
All attributes of {var}
specialArgs
Whereas option values can generally depend on other option values thanks to laziness, this does not apply to
imports
, which must be computed statically before anything else.For this reason, callers of the module system can provide
specialArgs
which are available during import resolution.For NixOS,
specialArgs
includes {var}modulesPath
, which allows you to import extra modules from the nixpkgs package tree without having to somehow make the module aware of the location of thenixpkgs
or NixOS directories.{ modulesPath, ... }: { imports = [ (modulesPath + "/profiles/minimal.nix") ]; }
For NixOS, the default value for this option includes at least this argument:
- {var}
pkgs
: The nixpkgs package set according to the {option}nixpkgs.pkgs
option.
- Type: lazy attribute set of raw value
configLanguage
The language you wish to use for user-supplied configuration line options
(extraConfig
, prePluginConfig
, and
pluginRegistry.<pluginName>.extraConfig
).
-
Type: one of "vimscript", "lua", "moonscript"
-
Default:
"vimscript"
extraBinPackages
A list of derivations containing executables that need to be available in the
$PATH
of the neovim process for this plugin to use.
Using the per-plugin binDeps
is generally preferred; this should only be
necessary if you need to make executables available for either:
- A plugin that is not being managed by this module.
- A binding or function in your
init.vim
, or other direct use from within neovim.
-
Type: list of package
-
Default:
[ ]
extraConfig
Extra lines of init.vim
configuration to append to the generated ones,
immediately following any pluginRegistry.<pluginName>.extraConfig
config
lines.
-
Type: strings concatenated with "\n"
-
Default:
""
extraPython3Packages
A function in python.withPackages
format, which returns a list of Python 3
packages required for your plugins to work.
Using the per-plugin python3Deps
is strongly preferred; this should only be
necessary if you need some Python 3 packages made available to neovim for a
plugin that is not being managed by this module.
-
Type: python3 packages in `python3.withPackages` format
-
Default:
"ps: []"
-
Example:
(ps: with ps; [ python-language-server ])
files
Files and folders to link into a folder in the runtimepath; outside of Envy
these would typically be locally-managed files in the ~/.config/nvim
folder.
-
Type: attribute set of (submodule)
-
Default:
{ }
-
Example:
{ autoload.source = ./neovim/autoload; ftplugin.source = ./neovim/ftplugin; "neosnippets/nix.snip".text = '' snippet nxo abbr NixOS Module Option mkOption { type = with types; ''${1:str}; default = "''${2}"; description = '' ''${3} ''; example = "''${4}''${0}"; }; ''; }
files.<name>.enable
Whether or not this neovim file should be generated. This option allows specific files to be disabled.
-
Type: boolean
-
Default:
true
files.<name>.source
Path to the file or directory to symlink in.
If the source is a directory, a directory with a corresponding name will be created in the folder added to the neovim runtimepath, with symlinks to files in the source directory, and same the treatment applied recursively for child directories.
Overrides the text option if both are set.
- Type: path
files.<name>.target
Name of the symlink, relative to the folder added to the neovim runtimepath. Defaults to the attribute name.
- Type: string
files.<name>.text
Literal text of the file. Used to generate a file to set the source option.
-
Type: null or strings concatenated with "\n"
-
Default:
null
mergePlugins
Whether or not to collect plugins into "buckets" based upon their position in
the load order, and then merge those which can be merged, in order to minimise
the number of directories added to vim's runtimepath
, decreasing startup time.
-
Type: boolean
-
Default:
false
pluginRegistry
An attribute set describing the available/known neovim plugins.
-
Type: attribute set of (submodule)
-
Default:
{ }
-
Example:
(let inherit (pkgs) vimPlugins; pins = import ./niv/sources.nix { }; in { nvim-moonmaker = { enable = true; # Build plugin derivation from a Niv source pins attribute set source = config.sn.programs.neovim.lib.buildVimPluginFromNiv pins "nvim-moonmaker"; # Decide whether or not to load at run-time based on the result of # a VimL expression condition = "executable('moonc')"; }; vim-auto-save = { enable = true; source = vimPlugins.vim-auto-save; # Lazily load on opening a tex file for = "tex"; }; nerdtree = { enable = true; source = vimPlugins.nerdtree; # Lazily load on command usage on_cmd = "NERDTreeToggle"; extraConfig = '' " Prettify NERDTree let NERDTreeMinimalUI = 1 let NERDTreeDirArrows = 1 ''; }; # A "path" plugin built from a source path "nginx.vim" = { enable = true; source = ../nvim-files/local/nginx; }; # A "local" plugin not directly managed by Nix, merely loaded at nvim # run-time from the specified directory "devplugin" = { enable = true; dir = "/home/shados/projects/vim/devplugin"; }; # A plugin configured but not enabled vim-devicons = { source = vimPlugins.vim-devicons; # vim-devicons needs to be loaded after these plugins, if they # are being used, as per its installation guide after = [ "nerdtree" "vim-airline" "ctrlp-vim" "powerline/powerline" "denite-nvim" "unite-vim" "lightline-vim" "vim-startify" "vimfiler" "vim-flagship" ]; }; })
pluginRegistry.<name>.enable
Whether or not this neovim plugin should be installed and used.
-
Type: boolean
-
Default:
false
pluginRegistry.<name>.after
List of other vim plugins that this plugin should be loaded after.
This can be seen as a "soft" form of making each of the listed plugins dependencies of this plugin.
Items can either be existing vim plugin derivations, or strings corresponding to
pluginRegistry
attributes.
-
Type: list of string
-
Default:
[ ]
pluginRegistry.<name>.before
List of other vim plugins that this plugin should be loaded before.
This can be seen as a "soft" form of making this plugin a dependency of each of the listed plugins.
Items can either be existing vim plugin derivations, or strings corresponding to
pluginRegistry
attributes.
-
Type: list of string
-
Default:
[ ]
pluginRegistry.<name>.binDeps
A list of derivations containing executables that need to be available in the
$PATH
of the neovim process for this plugin to use.
-
Type: list of package
-
Default:
[ ]
pluginRegistry.<name>.condition
A VimL expression that will be evaluated to determine whether or not to execute the vim-plug 'Plug' command for this plugin (which will typically load the plugin, or configure it to be lazily loaded).
Leave null in order to unconditionally always run the 'Plug' command for this plugin.
-
Type: null or string
-
Default:
null
pluginRegistry.<name>.dependencies
List of other vim plugins that are dependencies of this plugin.
Items can either be existing vim plugin derivations, or strings corresponding to
pluginRegistry
attributes.
-
Type: list of (string or package)
-
Default:
[ ]
pluginRegistry.<name>.dir
If set, specifies a directory path that the plugin should be loaded from at neovim run-time, avoiding the use of a Nix-provided plugin directory.
Relative paths will be relative to the generated init.vim
, which is in the Nix
store. As the value is passed into a '-quoted VimL string, it is possible to
escape this to use a relative path, e.g.:
' . $HOME. '/.config/vim/local/some-plugin
If this is set, source
will not be used.
-
Type: null or string
-
Default:
null
pluginRegistry.<name>.extraConfig
Extra lines of init.vim
configuration associated with this plugin, that need
to be executed after the plugin loading.
Leave null if no such extra configuration is required.
-
Type: null or strings concatenated with "\n"
-
Default:
null
pluginRegistry.<name>.for
One or more filetypes that should trigger on-demand/lazy loading of this plugin.
Can be specified with either a single string or list of strings.
NOTE: Lazy-loading functionality will likely conflict with the use of any additional, non-Envy plugin manager.
-
Type: string or list of string
-
Default:
[ ]
pluginRegistry.<name>.luaDeps
A function that takes an attribute set of Lua packages (typically passed from nixpkgs) and returns a list of Lua packages that this plugin depends on.
-
Type: lua packages in `lua.withPackages` format
-
Default:
"packageSet: []"
-
Example:
(packageSet: with packageSet: [ luafilesystem ])
pluginRegistry.<name>.mergeable
Whether or not it is safe to merge this plugin with others in the same bucket in the load order.
-
Type: boolean
-
Default:
true
pluginRegistry.<name>.on_cmd
One or more commands that should trigger on-demand/lazy loading of this plugin.
Can be specified with either a single string or list of strings.
NOTE: Lazy-loading functionality will likely conflict with the use of any additional, non-Envy plugin manager.
-
Type: string or list of string
-
Default:
[ ]
pluginRegistry.<name>.on_map
One or more <Plug>-mappings that should trigger on-demand/lazy loading of this plugin.
Can be specified with either a single string or list of strings.
NOTE: Lazy-loading functionality will likely conflict with the use of any additional, non-Envy plugin manager.
-
Type: string or list of string
-
Default:
[ ]
pluginRegistry.<name>.remote.python3
Whether or not this plugin requires the remote plugin host for Python 3.
Will effectively be set to true if any Python 3 package dependencies are specified for this plugin.
-
Type: boolean
-
Default:
false
pluginRegistry.<name>.remote.python3Deps
A function that takes an attribute set of Python 3 packages (typically passed from nixpkgs) and returns a list of Python 3 packages that this plugin depends on.
-
Type: python3 packages in `python3.withPackages` format
-
Default:
"packageSet: []"
-
Example:
(packageSet: with packageSet: [ python-language-server ])
pluginRegistry.<name>.rtp
Subdirectory of the plugin source that contains the Vim plugin.
Leave as null
to simply use the root of the source.
-
Type: null or string
-
Default:
null
pluginRegistry.<name>.source
Source of the vim plugin.
Set to an existing vim plugin derivation, or to a Nix store path to build a vim
plugin derivation from. Otherwise, leave this as null
and set the dir
configuration option for this plugin instead.
-
Type: null or path or package or string
-
Default:
null
prePluginConfig
Extra lines of init.vim
configuration to append to the generated ones,
immediately prior to any pluginRegistry.<pluginName>.extraConfig
config lines.
Leave null if no such extra configuration is required.
-
Type: null or strings concatenated with "\n"
-
Default:
null
withPython3
Enable Python 3 provider. Set to true
to use Python 3 plugins.
-
Type: boolean
-
Default:
false