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