1{
2 stdenv,
3 lib,
4 makeWrapper,
5 bundlerEnv,
6 ruby,
7 nodejs,
8 writeText,
9 neovim-node-client,
10 python3,
11 callPackage,
12 neovimUtils,
13 perl,
14 lndir,
15 vimUtils,
16}:
17
18neovim-unwrapped:
19
20let
21 # inherit interpreter from neovim
22 lua = neovim-unwrapped.lua;
23
24 wrapper =
25 {
26 extraName ? "",
27 # certain plugins need a custom configuration (available in passthru.initLua)
28 # to work with nix.
29 # if true, the wrapper automatically appends those snippets when necessary
30 autoconfigure ? true,
31
32 # append to PATH runtime deps of plugins
33 autowrapRuntimeDeps ? true,
34
35 # should contain all args but the binary. Can be either a string or list
36 wrapperArgs ? [ ],
37 withPython2 ? false,
38 withPython3 ? true,
39 # the function you would have passed to python3.withPackages
40 extraPython3Packages ? (_: [ ]),
41
42 withNodeJs ? false,
43 withPerl ? false,
44 withRuby ? true,
45
46 # wether to create symlinks in $out/bin/vi(m) -> $out/bin/nvim
47 vimAlias ? false,
48 viAlias ? false,
49
50 # additional argument not generated by makeNeovimConfig
51 # it sets the VIMINIT environment variable to "lua dofile('${customRc}')"
52 # set to false if you want to control where to save the generated config
53 # (e.g., in ~/.config/init.vim or project/.nvimrc)
54 wrapRc ? true,
55 # vimL code that should be sourced as part of the generated init.lua file
56 neovimRcContent ? null,
57 # lua code to put into the generated init.lua file
58 luaRcContent ? "",
59 # DEPRECATED: entry to load in packpath
60 # use 'plugins' instead
61 packpathDirs ? null, # not used anymore
62
63 # a list of neovim plugin derivations, for instance
64 # plugins = [
65 # { plugin=far-vim; config = "let g:far#source='rg'"; optional = false; }
66 # ]
67 plugins ? [ ],
68 ...
69 }@attrs:
70 assert
71 withPython2
72 -> throw "Python2 support has been removed from the neovim wrapper, please remove withPython2 and python2Env.";
73
74 assert
75 packpathDirs != null
76 -> throw "packpathdirs is not used anymore: pass a list of neovim plugin derivations in 'plugins' instead.";
77
78 stdenv.mkDerivation (
79 finalAttrs:
80 let
81 pluginsNormalized = neovimUtils.normalizePlugins finalAttrs.plugins;
82
83 myVimPackage = neovimUtils.normalizedPluginsToVimPackage pluginsNormalized;
84
85 rubyEnv = bundlerEnv {
86 name = "neovim-ruby-env";
87 gemdir = ./ruby_provider;
88 postBuild = ''
89 ln -sf ${ruby}/bin/* $out/bin
90 '';
91 };
92
93 pluginRC = lib.foldl (
94 acc: p: if p.config != null then acc ++ [ p.config ] else acc
95 ) [ ] pluginsNormalized;
96
97 # a limited RC script used only to generate the manifest for remote plugins
98 manifestRc = "";
99 # we call vimrcContent without 'packages' to avoid the init.vim generation
100 neovimRcContent' = lib.concatStringsSep "\n" (
101 pluginRC ++ lib.optional (neovimRcContent != null) neovimRcContent
102 );
103
104 packpathDirs.myNeovimPackages = myVimPackage;
105 finalPackdir = neovimUtils.packDir packpathDirs;
106
107 luaPluginRC =
108 let
109 op =
110 acc: normalizedPlugin:
111 acc
112 ++ lib.optional (
113 finalAttrs.autoconfigure && normalizedPlugin.plugin.passthru ? initLua
114 ) normalizedPlugin.plugin.passthru.initLua;
115 in
116 lib.foldl' op [ ] pluginsNormalized;
117
118 rcContent = ''
119 ${luaRcContent}
120 ''
121 + lib.optionalString (neovimRcContent' != "") ''
122 vim.cmd.source "${writeText "init.vim" neovimRcContent'}"
123 ''
124 + lib.concatStringsSep "\n" luaPluginRC;
125
126 getDeps = attrname: map (plugin: plugin.${attrname} or (_: [ ]));
127
128 requiredPlugins = vimUtils.requiredPluginsForPackage myVimPackage;
129 pluginPython3Packages = getDeps "python3Dependencies" requiredPlugins;
130
131 python3Env =
132 lib.warnIf (attrs ? python3Env)
133 "Pass your python packages via the `extraPython3Packages`, e.g., `extraPython3Packages = ps: [ ps.pandas ]`"
134 python3.pkgs.python.withPackages
135 (ps: [ ps.pynvim ] ++ (extraPython3Packages ps) ++ (lib.concatMap (f: f ps) pluginPython3Packages));
136
137 wrapperArgsStr = if lib.isString wrapperArgs then wrapperArgs else lib.escapeShellArgs wrapperArgs;
138
139 generatedWrapperArgs = [
140 # vim accepts a limited number of commands so we join all the provider ones
141 "--add-flags"
142 ''--cmd "lua ${providerLuaRc}"''
143 ]
144 ++
145 lib.optionals
146 (
147 finalAttrs.packpathDirs.myNeovimPackages.start != [ ]
148 || finalAttrs.packpathDirs.myNeovimPackages.opt != [ ]
149 )
150 [
151 "--add-flags"
152 ''--cmd "set packpath^=${finalPackdir}"''
153 "--add-flags"
154 ''--cmd "set rtp^=${finalPackdir}"''
155 ]
156 ++ lib.optionals finalAttrs.withRuby [
157 "--set"
158 "GEM_HOME"
159 "${rubyEnv}/${rubyEnv.ruby.gemPath}"
160 ]
161 ++ lib.optionals (finalAttrs.runtimeDeps != [ ]) [
162 "--suffix"
163 "PATH"
164 ":"
165 (lib.makeBinPath finalAttrs.runtimeDeps)
166 ];
167
168 providerLuaRc = neovimUtils.generateProviderRc {
169 inherit (finalAttrs)
170 withPython3
171 withNodeJs
172 withPerl
173 withRuby
174 ;
175 };
176
177 # If `configure` != {}, we can't generate the rplugin.vim file with e.g
178 # NVIM_SYSTEM_RPLUGIN_MANIFEST *and* NVIM_RPLUGIN_MANIFEST env vars set in
179 # the wrapper. That's why only when `configure` != {} (tested both here and
180 # when `postBuild` is evaluated), we call makeWrapper once to generate a
181 # wrapper with most arguments we need, excluding those that cause problems to
182 # generate rplugin.vim, but still required for the final wrapper.
183 finalMakeWrapperArgs = [
184 "${neovim-unwrapped}/bin/nvim"
185 "${placeholder "out"}/bin/nvim"
186 ]
187 ++ [
188 "--set"
189 "NVIM_SYSTEM_RPLUGIN_MANIFEST"
190 "${placeholder "out"}/rplugin.vim"
191 ]
192 ++ lib.optionals finalAttrs.wrapRc [
193 "--set-default"
194 "VIMINIT"
195 "lua dofile('${writeText "init.lua" rcContent}')"
196 ]
197 ++ finalAttrs.generatedWrapperArgs;
198
199 perlEnv = perl.withPackages (p: [
200 p.NeovimExt
201 p.Appcpanminus
202 ]);
203
204 pname = "neovim";
205 version = lib.getVersion neovim-unwrapped;
206 in
207 {
208 name = "${pname}-${version}${extraName}";
209 inherit pname version;
210 inherit plugins;
211
212 __structuredAttrs = true;
213 dontUnpack = true;
214 inherit
215 viAlias
216 vimAlias
217 withNodeJs
218 withPython3
219 withPerl
220 withRuby
221 ;
222 inherit
223 autoconfigure
224 autowrapRuntimeDeps
225 wrapRc
226 providerLuaRc
227 packpathDirs
228 ;
229 inherit python3Env rubyEnv;
230 inherit wrapperArgs generatedWrapperArgs;
231
232 runtimeDeps =
233 let
234 op = acc: normalizedPlugin: acc ++ normalizedPlugin.plugin.runtimeDeps or [ ];
235 runtimeDeps = lib.foldl' op [ ] pluginsNormalized;
236 in
237 lib.optional finalAttrs.withRuby rubyEnv
238 ++ lib.optional finalAttrs.withNodeJs nodejs
239 ++ lib.optionals finalAttrs.autowrapRuntimeDeps runtimeDeps;
240
241 luaRcContent = rcContent;
242 # Remove the symlinks created by symlinkJoin which we need to perform
243 # extra actions upon
244 postBuild =
245 lib.optionalString stdenv.hostPlatform.isLinux ''
246 rm $out/share/applications/nvim.desktop
247 substitute ${neovim-unwrapped}/share/applications/nvim.desktop $out/share/applications/nvim.desktop \
248 --replace-warn 'Name=Neovim' 'Name=Neovim wrapper'
249 ''
250 + lib.optionalString finalAttrs.withPython3 ''
251 makeWrapper ${python3Env.interpreter} $out/bin/nvim-python3 --unset PYTHONPATH --unset PYTHONSAFEPATH
252 ''
253 + lib.optionalString (finalAttrs.withRuby) ''
254 ln -s ${finalAttrs.rubyEnv}/bin/neovim-ruby-host $out/bin/nvim-ruby
255 ''
256 + lib.optionalString finalAttrs.withNodeJs ''
257 ln -s ${neovim-node-client}/bin/neovim-node-host $out/bin/nvim-node
258 ''
259 + lib.optionalString finalAttrs.withPerl ''
260 ln -s ${perlEnv}/bin/perl $out/bin/nvim-perl
261 ''
262 + lib.optionalString finalAttrs.vimAlias ''
263 ln -s $out/bin/nvim $out/bin/vim
264 ''
265 + lib.optionalString finalAttrs.viAlias ''
266 ln -s $out/bin/nvim $out/bin/vi
267 ''
268 + lib.optionalString (manifestRc != null) (
269 let
270 manifestWrapperArgs = [
271 "${neovim-unwrapped}/bin/nvim"
272 "${placeholder "out"}/bin/nvim-wrapper"
273 ]
274 ++ finalAttrs.generatedWrapperArgs;
275 in
276 ''
277 echo "Generating remote plugin manifest"
278 export NVIM_RPLUGIN_MANIFEST=$out/rplugin.vim
279 makeWrapper ${lib.escapeShellArgs manifestWrapperArgs} ${wrapperArgsStr}
280
281 # Some plugins assume that the home directory is accessible for
282 # initializing caches, temporary files, etc. Even if the plugin isn't
283 # actively used, it may throw an error as soon as Neovim is launched
284 # (e.g., inside an autoload script), causing manifest generation to
285 # fail. Therefore, let's create a fake home directory before generating
286 # the manifest, just to satisfy the needs of these plugins.
287 #
288 # See https://github.com/Yggdroot/LeaderF/blob/v1.21/autoload/lfMru.vim#L10
289 # for an example of this behavior.
290 export HOME="$(mktemp -d)"
291 # Launch neovim with a vimrc file containing only the generated plugin
292 # code. Pass various flags to disable temp file generation
293 # (swap/viminfo) and redirect errors to stderr.
294 # Only display the log on error since it will contain a few normally
295 # irrelevant messages.
296 if ! $out/bin/nvim-wrapper \
297 -u ${writeText "manifest.vim" manifestRc} \
298 -i NONE -n \
299 -V1rplugins.log \
300 +UpdateRemotePlugins +quit! > outfile 2>&1; then
301 cat outfile
302 echo -e "\nGenerating rplugin.vim failed!"
303 exit 1
304 fi
305 rm "${placeholder "out"}/bin/nvim-wrapper"
306 ''
307 )
308 + ''
309 rm $out/bin/nvim
310 touch $out/rplugin.vim
311
312 echo "Looking for lua dependencies..."
313 source ${lua}/nix-support/utils.sh
314
315 _addToLuaPath "${finalPackdir}"
316
317 echo "LUA_PATH towards the end of packdir: $LUA_PATH"
318
319 makeWrapper ${lib.escapeShellArgs finalMakeWrapperArgs} ${wrapperArgsStr} \
320 --prefix LUA_PATH ';' "$LUA_PATH" \
321 --prefix LUA_CPATH ';' "$LUA_CPATH"
322 '';
323
324 buildPhase = ''
325 runHook preBuild
326 mkdir -p $out
327 for i in ${neovim-unwrapped}; do
328 lndir -silent $i $out
329 done
330 runHook postBuild
331 '';
332
333 preferLocalBuild = true;
334
335 nativeBuildInputs = [
336 makeWrapper
337 lndir
338 ];
339
340 # A Vim "package", see ':h packages'
341 vimPackage = myVimPackage;
342
343 checkPhase = ''
344 runHook preCheck
345
346 $out/bin/nvim -i NONE -e +quitall!
347 runHook postCheck
348 '';
349
350 passthru = {
351 inherit providerLuaRc packpathDirs;
352 unwrapped = neovim-unwrapped;
353 initRc = neovimRcContent';
354
355 tests = callPackage ./tests {
356 };
357 };
358
359 meta = {
360 inherit (neovim-unwrapped.meta)
361 description
362 longDescription
363 homepage
364 mainProgram
365 license
366 teams
367 platforms
368 ;
369
370 # To prevent builds on hydra
371 hydraPlatforms = [ ];
372 # prefer wrapper over the package
373 priority = (neovim-unwrapped.meta.priority or lib.meta.defaultPriority) - 1;
374 };
375 }
376 );
377in
378lib.makeOverridable wrapper