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