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