12.nixvim/modules/top-level/output.nix
2026-05-24 04:27:26 +00:00

448 lines
14 KiB
Nix

{
pkgs,
config,
options,
lib,
...
}:
let
inherit (lib) types mkOption mkPackageOption;
inherit (lib) optional;
builders = lib.nixvim.builders.withPkgs pkgs;
inherit (pkgs.stdenv.hostPlatform) system;
in
{
options = {
autowrapRuntimeDeps = mkOption {
type = types.bool;
default = true;
description = ''
Whether to automatically add plugin runtime dependencies to the Neovim wrapper's `PATH`.
'';
};
viAlias = mkOption {
type = types.bool;
default = false;
description = ''
Symlink `vi` to `nvim` binary.
'';
};
vimAlias = mkOption {
type = types.bool;
default = false;
description = ''
Symlink `vim` to `nvim` binary.
'';
};
waylandSupport = mkOption {
type = types.bool;
default = lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.wayland;
defaultText = lib.literalExpression "lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.wayland";
description = "Enable additional wayland support, such as wl-clipboard for wayland clipboard syncing.";
};
withRuby = mkOption {
type = types.bool;
default = true;
description = "Enable Ruby provider.";
};
withNodeJs = mkOption {
type = types.bool;
default = false;
description = "Enable Node provider.";
};
withPerl = mkOption {
type = types.bool;
default = false;
description = "Enable Perl provider.";
};
withPython3 = mkOption {
type = types.bool;
default = true;
description = "Enable Python 3 provider.";
};
package = mkPackageOption pkgs "Neovim" {
default = "neovim-unwrapped";
};
wrapRc = mkOption {
type = types.bool;
description = ''
Whether the config will be included in the wrapper script.
When enabled, the Nixvim config will be passed to `nvim` using the `VIMINIT` environment variable.
'';
defaultText = lib.literalMD ''
Configured by your installation method: `false` when using the Home Manager module, `true` otherwise.
'';
};
impureRtp = mkOption {
type = types.bool;
description = ''
Whether to keep the (impure) nvim config directory in the runtimepath.
If disabled, the XDG config dirs `nvim` and `nvim/after` will be removed from the runtimepath.
When `wrapRc` is enabled, the wrapper will also ignore system/XDG config dirs during startup.
'';
defaultText = lib.literalMD ''
Configured by your installation method: `true` when using the Home Manager module, `false` otherwise.
'';
};
enablePrintInit = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Install a tool that shows the content of the generated `init.lua` file.
Run it using `${config.build.printInitPackage.meta.mainProgram}`.
'';
};
build = {
# TODO: `standalonePackage`; i.e. package + printInitPackage + man-docs bundled together
package = mkOption {
type = types.package;
description = ''
Wrapped Neovim.
> [!NOTE]
> Evaluating this option will also check `assertions` and print any `warnings`.
> If this is not desired, you can use `build.packageUnchecked` instead.
'';
readOnly = true;
defaultText = lib.literalExpression "config.build.packageUnchecked";
apply =
let
assertions = builtins.concatMap (x: lib.optional (!x.assertion) x.message) config.assertions;
in
if assertions != [ ] then
throw "\nFailed assertions:\n${lib.concatMapStringsSep "\n" (msg: "- ${msg}") assertions}"
else
lib.showWarnings config.warnings;
};
packageUnchecked = mkOption {
type = types.package;
description = ''
Wrapped Neovim (without checking warnings or assertions).
'';
readOnly = true;
};
nvimPackage = mkOption {
type = types.package;
description = ''
Wrapped Neovim (without man-docs, printInitPackage, etc).
'';
readOnly = true;
internal = true;
};
initFile = mkOption {
type = types.path;
description = ''
The generated `init.lua` file.
> [!NOTE]
> If `performance.byteCompileLua` is enabled, this file may not contain human-readable lua source code.
> Consider using `build.initSource` instead.
'';
readOnly = true;
visible = false;
};
initSource = mkOption {
type = types.path;
description = ''
The generated `init.lua` source file.
This is usually identical to `build.initFile`, however if `performance.byteCompileLua` is enabled,
this option will refer to the un-compiled lua source file.
'';
readOnly = true;
visible = false;
};
printInitPackage = mkOption {
type = types.package;
description = ''
A tool to show the content of the generated `init.lua` file.
Run using `${config.build.printInitPackage.meta.mainProgram}`.
'';
readOnly = true;
visible = false;
};
manDocsPackage = mkOption {
type = types.package;
defaultText = lib.literalMD "`packages.<system>.man-docs` from Nixvim's flake";
description = ''
Nixvim's manpage documentation.
'';
readOnly = true;
visible = false;
};
};
};
config =
let
# Optionally byte compile lua library
inherit (pkgs.callPackage ./plugins/byte-compile-lua-lib.nix { inherit lib; })
byteCompileLuaPackages
;
extraLuaPackages =
if config.performance.byteCompileLua.enable && config.performance.byteCompileLua.luaLib then
ps: byteCompileLuaPackages (config.extraLuaPackages ps)
else
config.extraLuaPackages;
vimPackageInfo = pkgs.neovimUtils.makeVimPackageInfo config.build.plugins;
luaPackagesForWrapper =
ps:
extraLuaPackages ps
++ lib.optionals (vimPackageInfo ? luaDependencies) vimPackageInfo.luaDependencies;
luaEnv = package.lua.withPackages luaPackagesForWrapper;
wrappedNeovim =
(pkgs.wrapNeovimUnstable package {
extraLuaPackages = luaPackagesForWrapper;
inherit (config)
autowrapRuntimeDeps
extraPython3Packages
viAlias
vimAlias
waylandSupport
withRuby
withNodeJs
withPerl
withPython3
;
inherit (config.build) plugins;
wrapperArgs = lib.optionals (luaEnv != null) [
"--prefix"
"LUA_PATH"
";"
(package.lua.pkgs.luaLib.genLuaPathAbsStr luaEnv)
"--prefix"
"LUA_CPATH"
";"
(package.lua.pkgs.luaLib.genLuaCPathAbsStr luaEnv)
];
}).overrideAttrs
{
# TODO: 2025-01-06
# Wait for user feedback on disabling the fixup phase.
# Ideally this will be upstreamed to nixpkgs.
# See https://github.com/nix-community/nixvim/pull/3660#discussion_r2326250439
dontFixup = true;
};
nvimPackage = wrappedNeovim.override (prev: {
wrapperArgs =
(if lib.isString prev.wrapperArgs then prev.wrapperArgs else lib.escapeShellArgs prev.wrapperArgs)
+ " "
+ extraWrapperArgs;
wrapRc = false;
});
customRC = lib.nixvim.concatNonEmptyLines [
(lib.nixvim.wrapVimscriptForLua wrappedNeovim.initRc)
(nvimPackage.passthru.providerLuaRc or "")
config.content
];
initSource = builders.writeLua "init.lua" customRC;
initByteCompiled = builders.writeByteCompiledLua "init.lua" customRC;
initFile =
if
config.type == "lua"
&& config.performance.byteCompileLua.enable
&& config.performance.byteCompileLua.initLua
then
initByteCompiled
else
initSource;
# `:help initialization` says sysinit is sourced before `VIMINIT` from
# `$XDG_CONFIG_DIRS/nvim/sysinit.vim` or, as fallback, `$VIM/sysinit.vim`.
impureStartupEnvVars = [
"XDG_CONFIG_DIRS"
"VIM"
];
# Runs via `--cmd` before sysinit, so system/XDG config dirs are hidden
# before Nvim checks them without relying on makeWrapper-only `--run`.
saveAndClearImpureStartupEnvVars = ''
_G.__nixvim_impure_startup_env = {}
for _, name in ipairs(${lib.nixvim.toLuaObject impureStartupEnvVars}) do
local value = vim.env[name]
if value ~= nil then
_G.__nixvim_impure_startup_env[name] = {
set = true,
value = value,
}
else
_G.__nixvim_impure_startup_env[name] = {
set = false,
}
end
vim.env[name] = nil
end
'';
# Restore before loading the generated config so user code sees the original
# environment while sysinit remains skipped.
restoreImpureStartupEnvVars = ''
local saved_impure_startup_env = _G.__nixvim_impure_startup_env or {}
for _, name in ipairs(${lib.nixvim.toLuaObject impureStartupEnvVars}) do
local saved = saved_impure_startup_env[name]
if saved ~= nil and saved.set then
vim.env[name] = saved.value
else
vim.env[name] = nil
end
end
_G.__nixvim_impure_startup_env = nil
'';
extraWrapperArgs = builtins.concatStringsSep " " (
# Setting environment variables in the wrapper
(lib.mapAttrsToList (
name: value:
lib.escapeShellArgs [
"--set"
name
value
]
) config.env)
++ (optional (
config.extraPackages != [ ]
) ''--prefix PATH : "${lib.makeBinPath config.extraPackages}"'')
++ (optional (
config.extraPackagesAfter != [ ]
) ''--suffix PATH : "${lib.makeBinPath config.extraPackagesAfter}"'')
++ (optional (config.wrapRc && !config.impureRtp) (
lib.escapeShellArgs [
"--add-flag"
"--cmd"
"--add-flag"
"lua ${saveAndClearImpureStartupEnvVars}"
]
))
++ (optional config.wrapRc (
lib.escapeShellArgs [
"--set"
"VIMINIT"
"lua ${lib.optionalString (!config.impureRtp) restoreImpureStartupEnvVars} dofile('${initFile}')"
]
))
);
package =
if config.performance.byteCompileLua.enable && config.performance.byteCompileLua.nvimRuntime then
# Using symlinkJoin to avoid rebuilding neovim
pkgs.symlinkJoin {
name = "neovim-byte-compiled-${lib.getVersion config.package}";
paths = [ config.package ];
# Required attributes from original neovim package
inherit (config.package) lua meta;
nativeBuildInputs = [ builders.byteCompileLuaHook ];
postBuild = ''
# Replace Nvim's binary symlink with a regular file,
# or Nvim will use original runtime directory
rm $out/bin/nvim
cp ${config.package}/bin/nvim $out/bin/nvim
runHook postFixup
'';
}
else
config.package;
in
{
build = {
inherit initFile initSource nvimPackage;
package = config.build.packageUnchecked;
packageUnchecked = pkgs.symlinkJoin {
name = "nixvim";
paths =
with config.build;
[
nvimPackage
]
++ lib.optionals config.enableMan [
manDocsPackage
]
++ lib.optionals config.enablePrintInit [
printInitPackage
];
meta.mainProgram = "nvim";
passthru = {
inherit config options;
};
};
printInitPackage = pkgs.writeShellApplication {
name = "nixvim-print-init";
runtimeInputs = [ pkgs.bat ];
runtimeEnv = {
init = config.build.initSource;
};
text = ''
bat --language=lua "$init"
'';
};
# TODO: make `build.manDocsPackage` available in non-flake evals.
# Also update `enableMan` default value.
manDocsPackage =
lib.throwIfNot options.flake.isDefined
"`${options.build.manDocsPackage}` currently requires `${options.flake}`, which is not defined."
config.flake.packages.${system}.man-docs;
};
# Set `wrapRc` and `impureRtp`s option defaults with even lower priority than `mkOptionDefault`
wrapRc = lib.mkOverride 1501 true;
impureRtp = lib.mkOverride 1501 false;
assertions = lib.nixvim.mkAssertions "output" [
{
assertion = config.impureRtp || config.wrapRc;
message = "`impureRtp = false` requires `wrapRc = true` so Nixvim can suppress system/XDG startup config.";
}
];
extraConfigLuaPre = lib.mkOrder 100 (
lib.concatStringsSep "\n" (
lib.optional (!config.impureRtp) ''
-- Ignore the user lua configuration
vim.opt.runtimepath:remove(vim.fn.stdpath('config')) -- ~/.config/nvim
vim.opt.runtimepath:remove(vim.fn.stdpath('config') .. "/after") -- ~/.config/nvim/after
''
# Add a global table at start of init
++ lib.singleton ''
-- Nixvim's internal module table
-- Can be used to share code throughout init.lua
local _M = {}
''
)
);
extraPlugins = lib.mkIf config.wrapRc [ config.build.extraFiles ];
};
}