1# tests available at pkgs/test/vim
2{ lib, stdenv, vim, vimPlugins, vim_configurable, buildEnv, writeText, writeScriptBin
3, nix-prefetch-hg, nix-prefetch-git
4, fetchFromGitHub, runtimeShell
5, hasLuaModule
6, python3
7, callPackage, makeSetupHook
8}:
9
10/*
11
12USAGE EXAMPLE
13=============
14
15Install Vim like this eg using nixos option environment.systemPackages which will provide
16vim-with-plugins in PATH:
17
18 vim_configurable.customize {
19 name = "vim-with-plugins";
20
21 # add custom .vimrc lines like this:
22 vimrcConfig.customRC = ''
23 set hidden
24 '';
25
26 # store your plugins in Vim packages
27 vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
28 # loaded on launch
29 start = [ youcompleteme fugitive ];
30 # manually loadable by calling `:packadd $plugin-name`
31 opt = [ phpCompletion elm-vim ];
32 # To automatically load a plugin when opening a filetype, add vimrc lines like:
33 # autocmd FileType php :packadd phpCompletion
34 };
35
36 # plugins can also be managed by VAM
37 vimrcConfig.vam.knownPlugins = pkgs.vimPlugins; # optional
38 vimrcConfig.vam.pluginDictionaries = [
39 # load always
40 { name = "youcompleteme"; }
41 { names = ["youcompleteme" "foo"]; }
42
43 # only load when opening a .php file
44 { name = "phpCompletion"; ft_regex = "^php\$"; }
45 { name = "phpCompletion"; filename_regex = "^.php\$"; }
46
47 # provide plugin which can be loaded manually:
48 { name = "phpCompletion"; tag = "lazy"; }
49
50 # full documentation at github.com/MarcWeber/vim-addon-manager
51 ];
52
53 # there is a pathogen implementation as well, but its startup is slower and [VAM] has more feature
54 # vimrcConfig.pathogen.knownPlugins = vimPlugins; # optional
55 # vimrcConfig.pathogen.pluginNames = ["vim-addon-nix"];
56 };
57
58WHAT IS A VIM PLUGIN?
59=====================
60Typical plugin files:
61
62 plugin/P1.vim
63 autoload/P1.vim
64 ftplugin/xyz.vim
65 doc/plugin-documentation.txt (traditional documentation)
66 README(.md) (nowadays thanks to github)
67
68
69Vim offers the :h rtp setting which works for most plugins. Thus adding
70this to your .vimrc should make most plugins work:
71
72 set rtp+=~/.nix-profile/share/vim-plugins/youcompleteme
73 " or for p in ["youcompleteme"] | exec 'set rtp+=~/.nix-profile/share/vim-plugins/'.p | endfor
74
75which is what the [VAM]/pathogen solutions above basically do.
76
77Learn about about plugin Vim plugin mm managers at
78http://vim-wiki.mawercer.de/wiki/topic/vim%20plugin%20managment.html.
79
80The documentation can be accessed by Vim's :help command if it was tagged.
81See vimHelpTags sample code below.
82
83CONTRIBUTING AND CUSTOMIZING
84============================
85The example file pkgs/misc/vim-plugins/default.nix provides both:
86* manually mantained plugins
87* plugins created by VAM's nix#ExportPluginsForNix implementation
88
89I highly recommend to lookup vim plugin attribute names at the [vim-pi] project
90 which is a database containing all plugins from
91vim.org and quite a lot of found at github and similar sources. vim-pi's documented purpose
92is to associate vim.org script ids to human readable names so that dependencies
93can be describe easily.
94
95How to find a name?
96 * http://vam.mawercer.de/ or VAM's
97 * grep vim-pi
98 * use VAM's completion or :AddonsInfo command
99
100It might happen than a plugin is not known by vim-pi yet. We encourage you to
101contribute to vim-pi so that plugins can be updated automatically.
102
103
104CREATING DERVITATIONS AUTOMATICALLY BY PLUGIN NAME
105==================================================
106Most convenient is to use a ~/.vim-scripts file putting a plugin name into each line
107as documented by [VAM]'s README.md
108It is the same format you pass to vimrcConfig.vam.pluginDictionaries from the
109usage example above.
110
111Then create a temp vim file and insert:
112
113 let opts = {}
114 let opts.path_to_nixpkgs = '/etc/nixos/nixpkgs'
115 let opts.cache_file = '/tmp/export-vim-plugin-for-nix-cache-file'
116 let opts.plugin_dictionaries = map(readfile("vim-plugins"), 'eval(v:val)')
117 " add more files
118 " let opts.plugin_dictionaries += map(.. other file )
119 call nix#ExportPluginsForNix(opts)
120
121Then ":source %" it.
122
123nix#ExportPluginsForNix is provided by ./vim2nix
124
125A buffer will open containing the plugin derivation lines as well list
126fitting the vimrcConfig.vam.pluginDictionaries option.
127
128Thus the most simple usage would be:
129
130 vim_with_plugins =
131 let vim = vim_configurable;
132 inherit (vimUtil.override {inherit vim}) rtpPath addRtp buildVimPlugin vimHelpTags;
133 vimPlugins = [
134 # the derivation list from the buffer created by nix#ExportPluginsForNix
135 # don't set which will default to pkgs.vimPlugins
136 ];
137 in vim.customize {
138 name = "vim-with-plugins";
139
140 vimrcConfig.customRC = '' .. '';
141
142 vimrcConfig.vam.knownPlugins = vimPlugins;
143 vimrcConfig.vam.pluginDictionaries = [
144 # the plugin list form ~/.vim-scripts turned into nix format added to
145 # the buffer created by the nix#ExportPluginsForNix
146 ];
147 }
148
149vim_with_plugins can be installed like any other application within Nix.
150
151[VAM] https://github.com/MarcWeber/vim-addon-manager
152[vim-pi] https://bitbucket.org/vimcommunity/vim-pi
153*/
154
155
156let
157 inherit lib;
158
159 # make sure a plugin is a derivation and its dependencies are derivations. If
160 # plugin already is a derivation, this is a no-op. If it is a string, it is
161 # looked up in knownPlugins.
162 pluginToDrv = knownPlugins: plugin:
163 let
164 drv =
165 if builtins.isString plugin then
166 # make sure `pname` is set to that we are able to convert the derivation
167 # back to a string.
168 ( knownPlugins.${plugin} // { pname = plugin; })
169 else
170 plugin;
171 in
172 # make sure all the dependencies of the plugin are also derivations
173 drv // { dependencies = map (pluginToDrv knownPlugins) (drv.dependencies or []); };
174
175 # transitive closure of plugin dependencies (plugin needs to be a derivation)
176 transitiveClosure = plugin:
177 [ plugin ] ++ (
178 lib.unique (builtins.concatLists (map transitiveClosure plugin.dependencies or []))
179 );
180
181 findDependenciesRecursively = plugins: lib.concatMap transitiveClosure plugins;
182
183 vamDictToNames = x:
184 if builtins.isString x then [x]
185 else (lib.optional (x ? name) x.name)
186 ++ (x.names or []);
187
188 rtpPath = ".";
189
190 # Generates a packpath folder as expected by vim
191 packDir = packages:
192 let
193 # dir is "start" or "opt"
194 linkLuaPlugin = plugin: packageName: dir: ''
195 mkdir -p $out/pack/${packageName}/${dir}/${plugin.pname}/lua
196 ln -sf ${plugin}/share/lua/5.1/* $out/pack/${packageName}/${dir}/${plugin.pname}/lua
197 ln -sf ${plugin}/${plugin.pname}-${plugin.version}-rocks/${plugin.pname}/${plugin.version}/* $out/pack/${packageName}/${dir}/${plugin.pname}/
198 '';
199
200 linkVimlPlugin = plugin: packageName: dir: ''
201 mkdir -p $out/pack/${packageName}/${dir}
202 if test -e "$out/pack/${packageName}/${dir}/${lib.getName plugin}"; then
203 printf "\nERROR - Duplicated vim plugin: ${lib.getName plugin}\n\n"
204 exit 1
205 fi
206 ln -sf ${plugin}/${rtpPath} $out/pack/${packageName}/${dir}/${lib.getName plugin}
207 '';
208
209 link = pluginPath: if hasLuaModule pluginPath
210 then linkLuaPlugin pluginPath
211 else linkVimlPlugin pluginPath;
212
213 packageLinks = packageName: {start ? [], opt ? []}:
214 let
215 # `nativeImpl` expects packages to be derivations, not strings (as
216 # opposed to older implementations that have to maintain backwards
217 # compatibility). Therefore we don't need to deal with "knownPlugins"
218 # and can simply pass `null`.
219 depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt);
220 startWithDeps = findDependenciesRecursively start;
221 allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
222 python3Env = python3.withPackages (ps:
223 lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [])) ps) allPlugins)
224 );
225 in
226 [ "mkdir -p $out/pack/${packageName}/start" ]
227 # To avoid confusion, even dependencies of optional plugins are added
228 # to `start` (except if they are explicitly listed as optional plugins).
229 ++ (builtins.map (x: link x packageName "start") allPlugins)
230 ++ ["mkdir -p $out/pack/${packageName}/opt"]
231 ++ (builtins.map (x: link x packageName "opt") opt)
232 # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
233 # for each plugin.
234 # This directory is only for python import search path, and will not slow down the startup time.
235 ++ [
236 "mkdir -p $out/pack/${packageName}/start/__python3_dependencies"
237 "ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3"
238 ];
239 in
240 stdenv.mkDerivation {
241 name = "vim-pack-dir";
242 src = ./.;
243 installPhase = lib.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList packageLinks packages));
244 preferLocalBuild = true;
245 };
246
247 nativeImpl = packages:
248 ''
249 set packpath^=${packDir packages}
250 set runtimepath^=${packDir packages}
251 '';
252
253 /* Generates a vimrc string
254
255 packages is an attrset with {name: { start = [ vim derivations ]; opt = [ vim derivations ]; }
256 Example:
257 vimrcContent {
258
259 packages = { home-manager = { start = [vimPlugins.vim-fugitive]; opt = [];};
260 beforePlugins = '';
261 customRc = ''let mapleader = " "'';
262
263 };
264 */
265 vimrcContent = {
266 packages ? null,
267 vam ? null,
268 pathogen ? null,
269 plug ? null,
270 beforePlugins ? ''
271 " configuration generated by NIX
272 set nocompatible
273 '',
274 customRC ? null
275 }:
276
277 let
278 /* pathogen mostly can set &rtp at startup time. Its used very commonly.
279 */
280 pathogenImpl = let
281 knownPlugins = pathogen.knownPlugins or vimPlugins;
282
283 plugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) pathogen.pluginNames);
284
285 pluginsEnv = buildEnv {
286 name = "pathogen-plugin-env";
287 paths = map (x: "${x}/${rtpPath}") plugins;
288 };
289 in
290 ''
291 let &rtp.=(empty(&rtp)?"":',')."${vimPlugins.vim-pathogen.rtp}"
292 execute pathogen#infect('${pluginsEnv}/{}')
293
294 filetype indent plugin on | syn on
295 '';
296
297 /* vim-plug is an extremely popular vim plugin manager.
298 */
299 plugImpl =
300 (''
301 source ${vimPlugins.vim-plug.rtp}/plug.vim
302 silent! call plug#begin('/dev/null')
303
304 '' + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg.rtp}'") plug.plugins) + ''
305
306 call plug#end()
307 '');
308
309 /*
310 vim-addon-manager = VAM
311
312 * maps names to plugin location
313
314 * manipulates &rtp at startup time
315 or when Vim has been running for a while
316
317 * can activate plugins laziy (eg when loading a specific filetype)
318
319 * knows about vim plugin dependencies (addon-info.json files)
320
321 * still is minimalistic (only loads one file), the "check out" code it also
322 has only gets loaded when a plugin is requested which is not found on disk
323 yet
324
325 */
326 vamImpl = lib.optionalString (vam != null)
327 (let
328 knownPlugins = vam.knownPlugins or vimPlugins;
329
330 # plugins specified by the user
331 specifiedPlugins = map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries);
332 # plugins with dependencies
333 plugins = findDependenciesRecursively specifiedPlugins;
334
335 # Convert scalars, lists, and attrs, to VimL equivalents
336 toVimL = x:
337 if builtins.isString x then "'${lib.replaceStrings [ "\n" "'" ] [ "\n\\ " "''" ] x}'"
338 else if builtins.isAttrs x && builtins ? out then toVimL x # a derivation
339 else if builtins.isAttrs x then "{${lib.concatStringsSep ", " (lib.mapAttrsToList (n: v: "${toVimL n}: ${toVimL v}") x)}}"
340 else if builtins.isList x then "[${lib.concatMapStringsSep ", " toVimL x}]"
341 else if builtins.isInt x || builtins.isFloat x then builtins.toString x
342 else if builtins.isBool x then (if x then "1" else "0")
343 else throw "turning ${lib.generators.toPretty {} x} into a VimL thing not implemented yet";
344
345 in assert builtins.hasAttr "vim-addon-manager" knownPlugins;
346 ''
347 filetype indent plugin on | syn on
348
349 let g:nix_plugin_locations = {}
350 ${lib.concatMapStrings (plugin: ''
351 let g:nix_plugin_locations['${plugin.pname}'] = "${plugin.rtp}"
352 '') plugins}
353 let g:nix_plugin_locations['vim-addon-manager'] = "${knownPlugins.vim-addon-manager.rtp}"
354
355 let g:vim_addon_manager = {}
356
357 if exists('g:nix_plugin_locations')
358 " nix managed config
359
360 " override default function making VAM aware of plugin locations:
361 fun! NixPluginLocation(name)
362 let path = get(g:nix_plugin_locations, a:name, "")
363 return path == "" ? vam#DefaultPluginDirFromName(a:name) : path
364 endfun
365 let g:vim_addon_manager.plugin_dir_by_name = 'NixPluginLocation'
366 " tell Vim about VAM:
367 let &rtp.=(empty(&rtp)?"":','). g:nix_plugin_locations['vim-addon-manager']
368 else
369 " standalone config
370
371 let &rtp.=(empty(&rtp)?"":',').c.plugin_root_dir.'/vim-addon-manager'
372 if !isdirectory(c.plugin_root_dir.'/vim-addon-manager/autoload')
373 " checkout VAM
374 execute '!git clone --depth=1 https://github.com/MarcWeber/vim-addon-manager '
375 \ shellescape(c.plugin_root_dir.'/vim-addon-manager', 1)
376 endif
377 endif
378
379 " tell vam which plugins to load, and when:
380 let l = []
381 ${lib.concatMapStrings (p: "call add(l, ${toVimL p})\n") vam.pluginDictionaries}
382 call vam#Scripts(l, {})
383 '');
384
385 entries = [
386 beforePlugins
387 vamImpl
388 ]
389 ++ lib.optional (packages != null && packages != []) (nativeImpl packages)
390 ++ lib.optional (pathogen != null) pathogenImpl
391 ++ lib.optional (plug != null) plugImpl
392 ++ [ customRC ];
393
394 in
395 lib.concatStringsSep "\n" (lib.filter (x: x != null && x != "") entries);
396
397 vimrcFile = settings: writeText "vimrc" (vimrcContent settings);
398
399in
400
401rec {
402 inherit vimrcFile;
403 inherit vimrcContent;
404 inherit packDir;
405
406 # shell script with custom name passing [-u vimrc] [-U gvimrc] to vim
407 vimWithRC = {
408 vimExecutable,
409 gvimExecutable,
410 vimManPages,
411 wrapManual,
412 wrapGui,
413 name ? "vim",
414 vimrcFile ? null,
415 gvimrcFile ? null,
416 vimExecutableName,
417 gvimExecutableName,
418 }:
419 let
420 rcOption = o: file: lib.optionalString (file != null) "-${o} ${file}";
421 vimWrapperScript = writeScriptBin vimExecutableName ''
422 #!${runtimeShell}
423 exec ${vimExecutable} ${rcOption "u" vimrcFile} ${rcOption "U" gvimrcFile} "$@"
424 '';
425 gvimWrapperScript = writeScriptBin gvimExecutableName ''
426 #!${stdenv.shell}
427 exec ${gvimExecutable} ${rcOption "u" vimrcFile} ${rcOption "U" gvimrcFile} "$@"
428 '';
429 in
430 buildEnv {
431 inherit name;
432 paths = [
433 vimWrapperScript
434 ] ++ lib.optional wrapGui gvimWrapperScript
435 ++ lib.optional wrapManual vimManPages
436 ;
437 };
438
439 # add a customize option to a vim derivation
440 makeCustomizable = vim: vim // {
441 customize = {
442 name,
443 vimrcConfig,
444 wrapManual ? true,
445 wrapGui ? false,
446 vimExecutableName ? name,
447 gvimExecutableName ? (lib.concatStrings [ "g" name ]),
448 }: vimWithRC {
449 vimExecutable = "${vim}/bin/vim";
450 gvimExecutable = "${vim}/bin/gvim";
451 inherit name wrapManual wrapGui vimExecutableName gvimExecutableName;
452 vimrcFile = vimrcFile vimrcConfig;
453 vimManPages = buildEnv {
454 name = "vim-doc";
455 paths = [ vim ];
456 pathsToLink = [ "/share/man" ];
457 };
458 };
459
460 override = f: makeCustomizable (vim.override f);
461 overrideAttrs = f: makeCustomizable (vim.overrideAttrs f);
462 };
463
464 pluginnames2Nix = {name, namefiles} : vim_configurable.customize {
465 inherit name;
466 vimrcConfig.vam.knownPlugins = vimPlugins;
467 vimrcConfig.vam.pluginDictionaries = ["vim2nix"];
468 vimrcConfig.customRC = ''
469 " Yes - this is impure and will create the cache file and checkout vim-pi
470 " into ~/.vim/vim-addons
471 let g:vim_addon_manager.plugin_root_dir = "/tmp/vim2nix-".$USER
472 if !isdirectory(g:vim_addon_manager.plugin_root_dir)
473 call mkdir(g:vim_addon_manager.plugin_root_dir)
474 else
475 echom repeat("=", 80)
476 echom "WARNING: reusing cache directory :".g:vim_addon_manager.plugin_root_dir
477 echom repeat("=", 80)
478 endif
479 let opts = {}
480 let opts.nix_prefetch_git = "${nix-prefetch-git}/bin/nix-prefetch-git"
481 let opts.nix_prefetch_hg = "${nix-prefetch-hg}/bin/nix-prefetch-hg"
482 let opts.cache_file = g:vim_addon_manager.plugin_root_dir.'/cache'
483 let opts.plugin_dictionaries = []
484 ${lib.concatMapStrings (file: "let opts.plugin_dictionaries += map(readfile(\"${file}\"), 'eval(v:val)')\n") namefiles }
485
486 " uncomment for debugging failures
487 " let opts.try_catch = 0
488
489 " add more files
490 " let opts.plugin_dictionaries += map(.. other file )
491 call nix#ExportPluginsForNix(opts)
492 '';
493 };
494
495 vimGenDocHook = callPackage ({ vim }:
496 makeSetupHook {
497 name = "vim-gen-doc-hook";
498 deps = [ vim ];
499 substitutions = {
500 vimBinary = "${vim}/bin/vim";
501 inherit rtpPath;
502 };
503 } ./vim-gen-doc-hook.sh) {};
504
505 inherit (import ./build-vim-plugin.nix { inherit lib stdenv rtpPath vim vimGenDocHook; })
506 buildVimPlugin buildVimPluginFrom2Nix;
507
508 # used to figure out which python dependencies etc. neovim needs
509 requiredPlugins = {
510 packages ? {},
511 givenKnownPlugins ? null,
512 vam ? null,
513 pathogen ? null,
514 plug ? null, ...
515 }:
516 let
517 # This is probably overcomplicated, but I don't understand this well enough to know what's necessary.
518 knownPlugins = if givenKnownPlugins != null then givenKnownPlugins else
519 if vam != null && vam ? knownPlugins then vam.knownPlugins else
520 if pathogen != null && pathogen ? knownPlugins then pathogen.knownPlugins else
521 vimPlugins;
522 pathogenPlugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) pathogen.pluginNames);
523 vamPlugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries));
524 nonNativePlugins = (lib.optionals (pathogen != null) pathogenPlugins)
525 ++ (lib.optionals (vam != null) vamPlugins)
526 ++ (lib.optionals (plug != null) plug.plugins);
527 nativePluginsConfigs = lib.attrsets.attrValues packages;
528 nativePlugins = lib.concatMap ({start?[], opt?[], knownPlugins?vimPlugins}: start++opt) nativePluginsConfigs;
529 in
530 nativePlugins ++ nonNativePlugins;
531}