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