1# tests available at pkgs/test/vim
2{
3 lib,
4 stdenv,
5 vim,
6 vimPlugins,
7 buildEnv,
8 writeText,
9 runCommand,
10 makeWrapper,
11 python3,
12 callPackage,
13 makeSetupHook,
14 linkFarm,
15 config,
16}:
17
18/*
19 USAGE EXAMPLE
20 =============
21
22 Install Vim like this eg using nixos option environment.systemPackages which will provide
23 vim-with-plugins in PATH:
24
25 vim-full.customize {
26 name = "vim-with-plugins"; # optional
27
28 # add custom .vimrc lines like this:
29 vimrcConfig.customRC = ''
30 set hidden
31 '';
32
33 # store your plugins in Vim packages
34 vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
35 # loaded on launch
36 start = [ youcompleteme fugitive ];
37 # manually loadable by calling `:packadd $plugin-name`
38 opt = [ phpCompletion elm-vim ];
39 # To automatically load a plugin when opening a filetype, add vimrc lines like:
40 # autocmd FileType php :packadd phpCompletion
41 };
42 };
43
44 WHAT IS A VIM PLUGIN?
45 =====================
46 Typical plugin files:
47
48 plugin/P1.vim
49 autoload/P1.vim
50 ftplugin/xyz.vim
51 doc/plugin-documentation.txt (traditional documentation)
52 README(.md) (nowadays thanks to github)
53
54 Vim offers the :h rtp setting which works for most plugins. Thus adding
55 this to your .vimrc should make most plugins work:
56
57 set rtp+=~/.nix-profile/share/vim-plugins/youcompleteme
58 " or for p in ["youcompleteme"] | exec 'set rtp+=~/.nix-profile/share/vim-plugins/'.p | endfor
59
60 Learn about about plugin Vim plugin mm managers at
61 http://vim-wiki.mawercer.de/wiki/topic/vim%20plugin%20managment.html.
62
63 The documentation can be accessed by Vim's :help command if it was tagged.
64 See vimHelpTags sample code below.
65
66 CONTRIBUTING AND CUSTOMIZING
67 ============================
68 The example file pkgs/applications/editors/vim/plugins/default.nix provides
69 both:
70 * manually maintained plugins
71 * plugins created by VAM's nix#ExportPluginsForNix implementation
72
73 I highly recommend to lookup vim plugin attribute names at the [vim-pi] project
74 which is a database containing all plugins from
75 vim.org and quite a lot of found at github and similar sources. vim-pi's documented purpose
76 is to associate vim.org script ids to human readable names so that dependencies
77 can be describe easily.
78
79 How to find a name?
80 * http://vam.mawercer.de/ or VAM's
81 * grep vim-pi
82 * use VAM's completion or :AddonsInfo command
83
84 It might happen than a plugin is not known by vim-pi yet. We encourage you to
85 contribute to vim-pi so that plugins can be updated automatically.
86
87 CREATING DERIVATIONS AUTOMATICALLY BY PLUGIN NAME
88 ==================================================
89 Most convenient is to use a ~/.vim-scripts file putting a plugin name into each line
90 as documented by [VAM]'s README.md
91 It is the same format you pass to vimrcConfig.vam.pluginDictionaries from the
92 usage example above.
93
94 Then create a temp vim file and insert:
95
96 let opts = {}
97 let opts.path_to_nixpkgs = '/etc/nixos/nixpkgs'
98 let opts.cache_file = '/tmp/export-vim-plugin-for-nix-cache-file'
99 let opts.plugin_dictionaries = map(readfile("vim-plugins"), 'eval(v:val)')
100 " add more files
101 " let opts.plugin_dictionaries += map(.. other file )
102 call nix#ExportPluginsForNix(opts)
103
104 Then ":source %" it.
105
106 nix#ExportPluginsForNix is provided by ./vim2nix
107
108 A buffer will open containing the plugin derivation lines as well list
109 fitting the vimrcConfig.vam.pluginDictionaries option.
110
111 Thus the most simple usage would be:
112
113 vim_with_plugins =
114 let vim = vim-full;
115 inherit (vimUtil.override {inherit vim}) rtpPath addRtp buildVimPlugin vimHelpTags;
116 vimPlugins = [
117 # the derivation list from the buffer created by nix#ExportPluginsForNix
118 # don't set which will default to pkgs.vimPlugins
119 ];
120 in vim.customize {
121 name = "vim-with-plugins";
122
123 vimrcConfig.customRC = '' .. '';
124
125 vimrcConfig.vam.knownPlugins = vimPlugins;
126 vimrcConfig.vam.pluginDictionaries = [
127 # the plugin list form ~/.vim-scripts turned into nix format added to
128 # the buffer created by the nix#ExportPluginsForNix
129 ];
130 }
131
132 vim_with_plugins can be installed like any other application within Nix.
133
134 [VAM] https://github.com/MarcWeber/vim-addon-manager
135 [vim-pi] https://bitbucket.org/vimcommunity/vim-pi
136*/
137
138let
139 inherit lib;
140
141 # make sure a plugin is a derivation and its dependencies are derivations. If
142 # plugin already is a derivation, this is a no-op. If it is a string, it is
143 # looked up in knownPlugins.
144 pluginToDrv =
145 knownPlugins: plugin:
146 let
147 drv =
148 if builtins.isString plugin then
149 # make sure `pname` is set to that we are able to convert the derivation
150 # back to a string.
151 (knownPlugins.${plugin} // { pname = plugin; })
152 else
153 plugin;
154 in
155 # make sure all the dependencies of the plugin are also derivations
156 drv // { dependencies = map (pluginToDrv knownPlugins) (drv.dependencies or [ ]); };
157
158 # transitive closure of plugin dependencies (plugin needs to be a derivation)
159 transitiveClosure =
160 plugin:
161 [ plugin ]
162 ++ (lib.unique (builtins.concatLists (map transitiveClosure plugin.dependencies or [ ])));
163
164 findDependenciesRecursively = plugins: lib.concatMap transitiveClosure plugins;
165
166 vamDictToNames =
167 x: if builtins.isString x then [ x ] else (lib.optional (x ? name) x.name) ++ (x.names or [ ]);
168
169 rtpPath = ".";
170
171 vimFarm =
172 prefix: name: drvs:
173 let
174 mkEntryFromDrv = drv: {
175 name = "${prefix}/${lib.getName drv}";
176 path = drv;
177 };
178 in
179 linkFarm name (map mkEntryFromDrv drvs);
180
181 /*
182 Generates a packpath folder as expected by vim
183 Example:
184 packDir (myVimPackage.{ start = [ vimPlugins.vim-fugitive ]; opt = [] })
185 => "/nix/store/xxxxx-pack-dir"
186 */
187 packDir =
188 packages:
189 let
190 packageLinks =
191 packageName:
192 {
193 start ? [ ],
194 opt ? [ ],
195 }:
196 let
197 # `nativeImpl` expects packages to be derivations, not strings (as
198 # opposed to older implementations that have to maintain backwards
199 # compatibility). Therefore we don't need to deal with "knownPlugins"
200 # and can simply pass `null`.
201 depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt);
202 startWithDeps = findDependenciesRecursively start;
203 allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
204 allPython3Dependencies =
205 ps: lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [ ])) ps) allPlugins);
206 python3Env = python3.withPackages allPython3Dependencies;
207
208 packdirStart = vimFarm "pack/${packageName}/start" "packdir-start" allPlugins;
209 packdirOpt = vimFarm "pack/${packageName}/opt" "packdir-opt" opt;
210 # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
211 # for each plugin.
212 # This directory is only for python import search path, and will not slow down the startup time.
213 # see :help python3-directory for more details
214 python3link = runCommand "vim-python3-deps" { } ''
215 mkdir -p $out/pack/${packageName}/start/__python3_dependencies
216 ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3
217 '';
218 in
219 [
220 packdirStart
221 packdirOpt
222 ]
223 ++ lib.optional (allPython3Dependencies python3.pkgs != [ ]) python3link;
224 in
225 buildEnv {
226 name = "vim-pack-dir";
227 paths = (lib.flatten (lib.mapAttrsToList packageLinks packages));
228 };
229
230 nativeImpl = packages: ''
231 set packpath^=${packDir packages}
232 set runtimepath^=${packDir packages}
233 '';
234
235 /*
236 Generates a vimrc string
237
238 packages is an attrset with {name: { start = [ vim derivations ]; opt = [ vim derivations ]; }
239 Example:
240 vimrcContent {
241
242 packages = { home-manager = { start = [vimPlugins.vim-fugitive]; opt = [];};
243 beforePlugins = '';
244 customRC = ''let mapleader = " "'';
245
246 };
247 */
248 vimrcContent =
249 {
250 packages ? null,
251 vam ? null, # deprecated
252 pathogen ? null, # deprecated
253 plug ? null,
254 beforePlugins ? ''
255 " configuration generated by NIX
256 set nocompatible
257 '',
258 customRC ? null,
259 }:
260
261 let
262 # vim-plug is an extremely popular vim plugin manager.
263 plugImpl = ''
264 source ${vimPlugins.vim-plug}/plug.vim
265 silent! call plug#begin('/dev/null')
266
267 ''
268 + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg}'") plug.plugins)
269 + ''
270
271 call plug#end()
272 '';
273
274 # vim-addon-manager = VAM (deprecated)
275 vamImpl =
276 let
277 knownPlugins = vam.knownPlugins or vimPlugins;
278
279 # plugins specified by the user
280 specifiedPlugins = map (pluginToDrv knownPlugins) (
281 lib.concatMap vamDictToNames vam.pluginDictionaries
282 );
283 # plugins with dependencies
284 plugins = findDependenciesRecursively specifiedPlugins;
285 vamPackages.vam = {
286 start = plugins;
287 };
288 in
289 nativeImpl vamPackages;
290
291 entries = [
292 beforePlugins
293 ]
294 ++ lib.optional (vam != null) (
295 lib.warn "'vam' attribute is deprecated. Use 'packages' instead in your vim configuration" vamImpl
296 )
297 ++ lib.optional (packages != null && packages != [ ]) (nativeImpl packages)
298 ++ lib.optional (pathogen != null) (
299 throw "pathogen is now unsupported, replace `pathogen = {}` with `packages.home = { start = []; }`"
300 )
301 ++ lib.optional (plug != null) plugImpl
302 ++ [ customRC ];
303
304 in
305 lib.concatStringsSep "\n" (lib.filter (x: x != null && x != "") entries);
306
307 vimrcFile = settings: writeText "vimrc" (vimrcContent settings);
308
309in
310
311rec {
312 inherit vimrcFile;
313 inherit vimrcContent;
314 inherit packDir;
315
316 makeCustomizable =
317 let
318 mkVimrcFile = vimrcFile; # avoid conflict with argument name
319 in
320 vim:
321 vim
322 // {
323 # Returns a customized vim that uses the specified vimrc configuration.
324 customize =
325 {
326 # The name of the derivation.
327 name ? "vim",
328 # A shell word used to specify the names of the customized executables.
329 # The shell variable $exe can be used to refer to the wrapped executable's name.
330 # Examples: "my-$exe", "$exe-with-plugins", "\${exe/vim/v1m}"
331 executableName ?
332 if lib.hasInfix "vim" name then
333 lib.replaceStrings [ "vim" ] [ "$exe" ] name
334 else
335 "\${exe/vim/${lib.escapeShellArg name}}",
336 # A custom vimrc configuration, treated as an argument to vimrcContent (see the documentation in this file).
337 vimrcConfig ? null,
338 # A custom vimrc file.
339 vimrcFile ? null,
340 # A custom gvimrc file.
341 gvimrcFile ? null,
342 # If set to true, return the *vim wrappers only.
343 # If set to false, overlay the wrappers on top of the original vim derivation.
344 # This ensures that things like man pages and .desktop files are available.
345 standalone ? name != "vim" && wrapManual != true,
346
347 # deprecated arguments (TODO: remove eventually)
348 wrapManual ? null,
349 wrapGui ? null,
350 vimExecutableName ? null,
351 gvimExecutableName ? null,
352 }:
353 lib.warnIf (wrapManual != null)
354 ''
355 vim.customize: wrapManual is deprecated: the manual is now included by default if `name == "vim"`.
356 ${
357 if wrapManual == true && name != "vim" then
358 "Set `standalone = false` to include the manual."
359 else
360 lib.optionalString (
361 wrapManual == false && name == "vim"
362 ) "Set `standalone = true` to get the *vim wrappers only."
363 }''
364 lib.warnIf
365 (wrapGui != null)
366 "vim.customize: wrapGui is deprecated: gvim is now automatically included if present"
367 lib.throwIfNot
368 (vimExecutableName == null && gvimExecutableName == null)
369 "vim.customize: (g)vimExecutableName is deprecated: use executableName instead (see source code for examples)"
370 (
371 let
372 vimrc =
373 if vimrcFile != null then
374 vimrcFile
375 else if vimrcConfig != null then
376 mkVimrcFile vimrcConfig
377 else
378 throw "at least one of vimrcConfig and vimrcFile must be specified";
379 bin = runCommand "${name}-bin" { nativeBuildInputs = [ makeWrapper ]; } ''
380 vimrc=${lib.escapeShellArg vimrc}
381 gvimrc=${lib.optionalString (gvimrcFile != null) (lib.escapeShellArg gvimrcFile)}
382
383 mkdir -p "$out/bin"
384 for exe in ${
385 if standalone then "{,g,r,rg,e}vim {,g}vimdiff vi" else "{,g,r,rg,e}{vim,view} {,g}vimdiff ex vi"
386 }; do
387 if [[ -e ${vim}/bin/$exe ]]; then
388 dest="$out/bin/${executableName}"
389 if [[ -e $dest ]]; then
390 echo "ambiguous executableName: ''${dest##*/} already exists"
391 continue
392 fi
393 makeWrapper ${vim}/bin/"$exe" "$dest" \
394 --add-flags "-u ''${vimrc@Q} ''${gvimrc:+-U ''${gvimrc@Q}}"
395 fi
396 done
397 '';
398 in
399 if standalone then
400 bin
401 else
402 buildEnv {
403 inherit name;
404 paths = [
405 (lib.lowPrio vim)
406 bin
407 ];
408 }
409 );
410
411 override = f: makeCustomizable (vim.override f);
412 overrideAttrs = f: makeCustomizable (vim.overrideAttrs f);
413 };
414
415 vimGenDocHook = callPackage (
416 { vim }:
417 makeSetupHook {
418 name = "vim-gen-doc-hook";
419 propagatedBuildInputs = [ vim ];
420 substitutions = {
421 vimBinary = "${vim}/bin/vim";
422 inherit rtpPath;
423 };
424 } ../hooks/vim-gen-doc-hook.sh
425 ) { };
426
427 vimCommandCheckHook = callPackage (
428 { neovim-unwrapped }:
429 makeSetupHook {
430 name = "vim-command-check-hook";
431 propagatedBuildInputs = [ neovim-unwrapped ];
432 substitutions = {
433 vimBinary = "${neovim-unwrapped}/bin/nvim";
434 inherit rtpPath;
435 };
436 } ../hooks/vim-command-check-hook.sh
437 ) { };
438
439 neovimRequireCheckHook = callPackage (
440 { neovim-unwrapped }:
441 makeSetupHook {
442 name = "neovim-require-check-hook";
443 propagatedBuildInputs = [ neovim-unwrapped ];
444 substitutions = {
445 nvimBinary = "${neovim-unwrapped}/bin/nvim";
446 inherit rtpPath;
447 };
448 } ../hooks/neovim-require-check-hook.sh
449 ) { };
450
451 inherit
452 (import ./build-vim-plugin.nix {
453 inherit
454 lib
455 stdenv
456 rtpPath
457 toVimPlugin
458 ;
459 })
460 buildVimPlugin
461 ;
462
463 buildVimPluginFrom2Nix = lib.warn "buildVimPluginFrom2Nix is deprecated: use buildVimPlugin instead" buildVimPlugin;
464
465 # used to figure out which python dependencies etc. neovim needs
466 requiredPlugins =
467 {
468 packages ? { },
469 plug ? null,
470 ...
471 }:
472 let
473 nativePluginsConfigs = lib.attrsets.attrValues packages;
474 nonNativePlugins = (lib.optionals (plug != null) plug.plugins);
475 nativePlugins = lib.concatMap (requiredPluginsForPackage) nativePluginsConfigs;
476 in
477 nativePlugins ++ nonNativePlugins;
478
479 # figures out which python dependencies etc. is needed for one vim package
480 requiredPluginsForPackage =
481 {
482 start ? [ ],
483 opt ? [ ],
484 }:
485 start ++ opt;
486
487 toVimPlugin =
488 drv:
489 drv.overrideAttrs (oldAttrs: {
490 name = "vimplugin-${oldAttrs.name}";
491 # dont move the "doc" folder since vim expects it
492 forceShare = [
493 "man"
494 "info"
495 ];
496
497 nativeBuildInputs =
498 oldAttrs.nativeBuildInputs or [ ]
499 ++ lib.optionals (stdenv.buildPlatform.canExecute stdenv.hostPlatform) [
500 vimGenDocHook
501 ];
502
503 doCheck = oldAttrs.doCheck or true;
504
505 nativeCheckInputs =
506 oldAttrs.nativeCheckInputs or [ ]
507 ++ lib.optionals (stdenv.buildPlatform.canExecute stdenv.hostPlatform) [
508 vimCommandCheckHook
509 # many neovim plugins keep using buildVimPlugin
510 neovimRequireCheckHook
511 ];
512
513 passthru = (oldAttrs.passthru or { }) // {
514 vimPlugin = true;
515 };
516 });
517}
518// lib.optionalAttrs config.allowAliases {
519 vimWithRC = throw "vimWithRC was removed, please use vim.customize instead";
520}