Skip to content

Commit

Permalink
neovim: make the wrapper more evolvable
Browse files Browse the repository at this point in the history
Now that we have structured attributes enabled, it's easier than ever to
access the wrapper config from itself. Let's expose the plugins instead
of the packpathDir with which one can't do much.

With exposed plugins, one could tweak the current wrapper with more
plugins, e.g. neovim.withPlugins([fugitive]).withPlugins([plenary]) .
we could also add a boolean to autoadd the plugins passthru.initLua,
better handle the dependencies (runtime programs, python deps).
�
  • Loading branch information
teto committed Sep 25, 2024
1 parent c741292 commit 4ba0a81
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 103 deletions.
128 changes: 32 additions & 96 deletions pkgs/applications/editors/neovim/utils.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,113 +13,45 @@
let
inherit (vimUtils) toVimPlugin;

/* returns everything needed for the caller to wrap its own neovim:
- the generated content of the future init.vim
- the arguments to wrap neovim with
The caller is responsible for writing the init.vim and adding it to the wrapped
arguments (["-u" writeText "init.vim" GENERATEDRC)]).
This makes it possible to write the config anywhere: on a per-project basis
.nvimrc or in $XDG_CONFIG_HOME/nvim/init.vim to avoid sideeffects.
Indeed, note that wrapping with `-u init.vim` has sideeffects like .nvimrc wont be loaded
anymore, $MYVIMRC wont be set etc
*/
makeNeovimConfig =
{ withPython3 ? true
/* the function you would have passed to python3.withPackages */
, extraPython3Packages ? (_: [ ])
, withNodeJs ? false
, withRuby ? true
/* the function you would have passed to lua.withPackages */
, extraLuaPackages ? (_: [ ])

# expects a list of plugin configuration
# expects { plugin=far-vim; config = "let g:far#source='rg'"; optional = false; }
, plugins ? []
# custom viml config appended after plugin-specific config
, customRC ? ""

# for forward compability, when adding new environments, haskell etc.
, ...
}@args:
let
rubyEnv = bundlerEnv {
name = "neovim-ruby-env";
gemdir = ./ruby_provider;
postBuild = ''
ln -sf ${ruby}/bin/* $out/bin
'';
};

# transform all plugins into an attrset
# { optional = bool; plugin = package; }
pluginsNormalized = let
/* transform all plugins into an attrset
{ optional = bool; plugin = package; }
*/
normalizePlugins = plugins:
let
defaultPlugin = {
plugin = null;
config = null;
optional = false;
};
in
# TODO use (coercedTo package (v: { plugin = v; }) pluginWithConfigType);
map (x: defaultPlugin // (if (x ? plugin) then x else { plugin = x; })) plugins;

pluginRC = lib.foldl (acc: p: if p.config != null then acc ++ [p.config] else acc) [] pluginsNormalized;

pluginsPartitioned = lib.partition (x: x.optional == true) pluginsNormalized;
requiredPlugins = vimUtils.requiredPluginsForPackage myVimPackage;
getDeps = attrname: map (plugin: plugin.${attrname} or (_: [ ]));
myVimPackage = {
start = map (x: x.plugin) pluginsPartitioned.wrong;
opt = map (x: x.plugin) pluginsPartitioned.right;
};

pluginPython3Packages = getDeps "python3Dependencies" requiredPlugins;
python3Env = python3Packages.python.withPackages (ps:
[ ps.pynvim ]
++ (extraPython3Packages ps)
++ (lib.concatMap (f: f ps) pluginPython3Packages));

luaEnv = neovim-unwrapped.lua.withPackages extraLuaPackages;

# as expected by packdir
packpathDirs.myNeovimPackages = myVimPackage;
## Here we calculate all of the arguments to the 1st call of `makeWrapper`
# We start with the executable itself NOTE we call this variable "initial"
# because if configure != {} we need to call makeWrapper twice, in order to
# avoid double wrapping, see comment near finalMakeWrapperArgs
makeWrapperArgs =
let
binPath = lib.makeBinPath (lib.optionals withRuby [ rubyEnv ] ++ lib.optionals withNodeJs [ nodejs ]);
in
[
"--inherit-argv0"
] ++ lib.optionals withRuby [
"--set" "GEM_HOME" "${rubyEnv}/${rubyEnv.ruby.gemPath}"
] ++ lib.optionals (binPath != "") [
"--suffix" "PATH" ":" binPath
] ++ lib.optionals (luaEnv != null) [
"--prefix" "LUA_PATH" ";" (neovim-unwrapped.lua.pkgs.luaLib.genLuaPathAbsStr luaEnv)
"--prefix" "LUA_CPATH" ";" (neovim-unwrapped.lua.pkgs.luaLib.genLuaCPathAbsStr luaEnv)
];

manifestRc = vimUtils.vimrcContent { customRC = ""; };
# we call vimrcContent without 'packages' to avoid the init.vim generation
neovimRcContent = vimUtils.vimrcContent {
beforePlugins = "";
customRC = lib.concatStringsSep "\n" (pluginRC ++ [customRC]);
packages = null;
/* accepts a list of
*/
normalizedPluginsToVimPackage = normalizedPlugins:
let
pluginsPartitioned = lib.partition (x: x.optional == true) normalizedPlugins;
in {
start = map (x: x.plugin) pluginsPartitioned.wrong;
opt = map (x: x.plugin) pluginsPartitioned.right;
};
in

builtins.removeAttrs args ["plugins"] // {
wrapperArgs = makeWrapperArgs;
inherit packpathDirs;
inherit neovimRcContent;
inherit manifestRc;
inherit python3Env;
inherit luaEnv;
inherit withNodeJs;
} // lib.optionalAttrs withRuby {
inherit rubyEnv;
};
/* returns everything needed for the caller to wrap its own neovim:
- the generated content of the future init.vim
- the arguments to wrap neovim with
The caller is responsible for writing the init.vim and adding it to the wrapped
arguments (["-u" writeText "init.vim" GENERATEDRC)]).
This makes it possible to write the config anywhere: on a per-project basis
.nvimrc or in $XDG_CONFIG_HOME/nvim/init.vim to avoid sideeffects.
Indeed, note that wrapping with `-u init.vim` has sideeffects like .nvimrc wont be loaded
anymore, $MYVIMRC wont be set etc
*/
makeNeovimConfig = { customRC ? "", ...}@attrs: attrs // {
neovimRcContent = customRC;
wrapperArgs = []; # for backwards compat
};


# to keep backwards compatibility for people using neovim.override
Expand Down Expand Up @@ -198,6 +130,9 @@ let
in
lib.concatStringsSep ";" hostProviderLua;

/* Converts a lua package into a neovim plugin.
Does so by installing the lua package with a flat hierarchy of folders
*/
buildNeovimPlugin = callPackage ./build-neovim-plugin.nix {
inherit (vimUtils) toVimPlugin;
inherit lua;
Expand Down Expand Up @@ -275,6 +210,7 @@ in
inherit legacyWrapper;
inherit grammarToPlugin;
inherit packDir;
inherit normalizePlugins normalizedPluginsToVimPackage;

inherit buildNeovimPlugin;
buildNeovimPluginFrom2Nix = lib.warn "buildNeovimPluginFrom2Nix was renamed to buildNeovimPlugin" buildNeovimPlugin;
Expand Down
59 changes: 52 additions & 7 deletions pkgs/applications/editors/neovim/wrapper.nix
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{ stdenv, symlinkJoin, lib, makeWrapper
, bundlerEnv
, ruby
, nodejs
, writeText
, nodePackages
, python3
, callPackage
, neovimUtils
, perl
, lndir
, vimUtils
}:

neovim-unwrapped:
Expand All @@ -19,9 +23,12 @@ let
# should contain all args but the binary. Can be either a string or list
, wrapperArgs ? []
# a limited RC script used only to generate the manifest for remote plugins
, manifestRc ? null
# , manifestRc ? null
, withPython2 ? false
, withPython3 ? true, python3Env ? python3
, withPython3 ? true
/* the function you would have passed to python3.withPackages */
, extraPython3Packages ? (_: [ ])

, withNodeJs ? false
, withPerl ? false
, rubyEnv ? null
Expand All @@ -40,22 +47,59 @@ let
# lua code to put into the generated init.lua file
, luaRcContent ? ""
# entry to load in packpath
, packpathDirs
, packpathDirs ? null # not used anymore
, plugins ? []
, ...
}:
}@attrs:
assert withPython2 -> throw "Python2 support has been removed from the neovim wrapper, please remove withPython2 and python2Env.";

# assert packpathDirs -> throw "Packpathdirs removed";

stdenv.mkDerivation (finalAttrs:
let
pluginsNormalized = neovimUtils.normalizePlugins plugins;

myVimPackage = neovimUtils.normalizedPluginsToVimPackage pluginsNormalized;

rubyEnv = bundlerEnv {
name = "neovim-ruby-env";
gemdir = ./ruby_provider;
postBuild = ''
ln -sf ${ruby}/bin/* $out/bin
'';
};

pluginRC = lib.foldl (acc: p: if p.config != null then acc ++ [p.config] else acc) [] pluginsNormalized;

manifestRc = vimUtils.vimrcContent { customRC = ""; };
# we call vimrcContent without 'packages' to avoid the init.vim generation
neovimRcContent' = vimUtils.vimrcContent {
beforePlugins = "";
customRC = lib.concatStringsSep "\n" (pluginRC ++ [neovimRcContent]);
packages = null;
};

finalPackdir = neovimUtils.packDir packpathDirs;

rcContent = ''
${luaRcContent}
'' + lib.optionalString (!isNull neovimRcContent) ''
vim.cmd.source "${writeText "init.vim" neovimRcContent}"
'' + lib.optionalString (!isNull neovimRcContent') ''
vim.cmd.source "${writeText "init.vim" neovimRcContent'}"
'';

getDeps = attrname: map (plugin: plugin.${attrname} or (_: [ ]));

requiredPlugins = vimUtils.requiredPluginsForPackage myVimPackage;
pluginPython3Packages = getDeps "python3Dependencies" requiredPlugins;

python3Env = lib.warnIf (attrs ? python3Env) "Pass your python packages via the `extraPython3Packages`, e.g., `extraPython3Packages = ps: [ ps.pandas ]`"
python3.pkgs.python.withPackages (ps:
[ ps.pynvim ]
++ (extraPython3Packages ps)
++ (lib.concatMap (f: f ps) pluginPython3Packages));

packpathDirs.myNeovimPackages = myVimPackage;

wrapperArgsStr = if lib.isString wrapperArgs then wrapperArgs else lib.escapeShellArgs wrapperArgs;

generatedWrapperArgs =
Expand Down Expand Up @@ -94,6 +138,7 @@ let
in {
name = "${pname}-${version}${extraName}";
inherit pname version;
inherit plugins;

__structuredAttrs = true;
dontUnpack = true;
Expand Down Expand Up @@ -193,7 +238,7 @@ let
passthru = {
inherit providerLuaRc packpathDirs;
unwrapped = neovim-unwrapped;
initRc = neovimRcContent;
initRc = neovimRcContent';

tests = callPackage ./tests {
};
Expand Down

0 comments on commit 4ba0a81

Please sign in to comment.