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