1# tests available at pkgs/test/vim
2{ lib, stdenv, vim, vimPlugins, buildEnv, writeText
3, runCommand, makeWrapper
4, python3
5, callPackage, makeSetupHook
6, linkFarm
7}:
8
9/*
10
11USAGE EXAMPLE
12=============
13
14Install Vim like this eg using nixos option environment.systemPackages which will provide
15vim-with-plugins in PATH:
16
17 vim-full.customize {
18 name = "vim-with-plugins"; # optional
19
20 # add custom .vimrc lines like this:
21 vimrcConfig.customRC = ''
22 set hidden
23 '';
24
25 # store your plugins in Vim packages
26 vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
27 # loaded on launch
28 start = [ youcompleteme fugitive ];
29 # manually loadable by calling `:packadd $plugin-name`
30 opt = [ phpCompletion elm-vim ];
31 # To automatically load a plugin when opening a filetype, add vimrc lines like:
32 # autocmd FileType php :packadd phpCompletion
33 };
34 };
35
36WHAT IS A VIM PLUGIN?
37=====================
38Typical plugin files:
39
40 plugin/P1.vim
41 autoload/P1.vim
42 ftplugin/xyz.vim
43 doc/plugin-documentation.txt (traditional documentation)
44 README(.md) (nowadays thanks to github)
45
46
47Vim offers the :h rtp setting which works for most plugins. Thus adding
48this to your .vimrc should make most plugins work:
49
50 set rtp+=~/.nix-profile/share/vim-plugins/youcompleteme
51 " or for p in ["youcompleteme"] | exec 'set rtp+=~/.nix-profile/share/vim-plugins/'.p | endfor
52
53Learn about about plugin Vim plugin mm managers at
54http://vim-wiki.mawercer.de/wiki/topic/vim%20plugin%20managment.html.
55
56The documentation can be accessed by Vim's :help command if it was tagged.
57See vimHelpTags sample code below.
58
59CONTRIBUTING AND CUSTOMIZING
60============================
61The example file pkgs/applications/editors/vim/plugins/default.nix provides
62both:
63* manually mantained plugins
64* plugins created by VAM's nix#ExportPluginsForNix implementation
65
66I highly recommend to lookup vim plugin attribute names at the [vim-pi] project
67 which is a database containing all plugins from
68vim.org and quite a lot of found at github and similar sources. vim-pi's documented purpose
69is to associate vim.org script ids to human readable names so that dependencies
70can be describe easily.
71
72How to find a name?
73 * http://vam.mawercer.de/ or VAM's
74 * grep vim-pi
75 * use VAM's completion or :AddonsInfo command
76
77It might happen than a plugin is not known by vim-pi yet. We encourage you to
78contribute to vim-pi so that plugins can be updated automatically.
79
80
81CREATING DERIVATIONS AUTOMATICALLY BY PLUGIN NAME
82==================================================
83Most convenient is to use a ~/.vim-scripts file putting a plugin name into each line
84as documented by [VAM]'s README.md
85It is the same format you pass to vimrcConfig.vam.pluginDictionaries from the
86usage example above.
87
88Then create a temp vim file and insert:
89
90 let opts = {}
91 let opts.path_to_nixpkgs = '/etc/nixos/nixpkgs'
92 let opts.cache_file = '/tmp/export-vim-plugin-for-nix-cache-file'
93 let opts.plugin_dictionaries = map(readfile("vim-plugins"), 'eval(v:val)')
94 " add more files
95 " let opts.plugin_dictionaries += map(.. other file )
96 call nix#ExportPluginsForNix(opts)
97
98Then ":source %" it.
99
100nix#ExportPluginsForNix is provided by ./vim2nix
101
102A buffer will open containing the plugin derivation lines as well list
103fitting the vimrcConfig.vam.pluginDictionaries option.
104
105Thus the most simple usage would be:
106
107 vim_with_plugins =
108 let vim = vim-full;
109 inherit (vimUtil.override {inherit vim}) rtpPath addRtp buildVimPlugin vimHelpTags;
110 vimPlugins = [
111 # the derivation list from the buffer created by nix#ExportPluginsForNix
112 # don't set which will default to pkgs.vimPlugins
113 ];
114 in vim.customize {
115 name = "vim-with-plugins";
116
117 vimrcConfig.customRC = '' .. '';
118
119 vimrcConfig.vam.knownPlugins = vimPlugins;
120 vimrcConfig.vam.pluginDictionaries = [
121 # the plugin list form ~/.vim-scripts turned into nix format added to
122 # the buffer created by the nix#ExportPluginsForNix
123 ];
124 }
125
126vim_with_plugins can be installed like any other application within Nix.
127
128[VAM] https://github.com/MarcWeber/vim-addon-manager
129[vim-pi] https://bitbucket.org/vimcommunity/vim-pi
130*/
131
132
133let
134 inherit lib;
135
136 # make sure a plugin is a derivation and its dependencies are derivations. If
137 # plugin already is a derivation, this is a no-op. If it is a string, it is
138 # looked up in knownPlugins.
139 pluginToDrv = knownPlugins: plugin:
140 let
141 drv =
142 if builtins.isString plugin then
143 # make sure `pname` is set to that we are able to convert the derivation
144 # back to a string.
145 ( knownPlugins.${plugin} // { pname = plugin; })
146 else
147 plugin;
148 in
149 # make sure all the dependencies of the plugin are also derivations
150 drv // { dependencies = map (pluginToDrv knownPlugins) (drv.dependencies or []); };
151
152 # transitive closure of plugin dependencies (plugin needs to be a derivation)
153 transitiveClosure = plugin:
154 [ plugin ] ++ (
155 lib.unique (builtins.concatLists (map transitiveClosure plugin.dependencies or []))
156 );
157
158 findDependenciesRecursively = plugins: lib.concatMap transitiveClosure plugins;
159
160 vamDictToNames = x:
161 if builtins.isString x then [x]
162 else (lib.optional (x ? name) x.name)
163 ++ (x.names or []);
164
165 rtpPath = ".";
166
167 vimFarm = prefix: name: drvs:
168 let mkEntryFromDrv = drv: { name = "${prefix}/${lib.getName drv}"; path = drv; };
169 in linkFarm name (map mkEntryFromDrv drvs);
170
171 /* Generates a packpath folder as expected by vim
172 Example:
173 packDir (myVimPackage.{ start = [ vimPlugins.vim-fugitive ]; opt = [] })
174 => "/nix/store/xxxxx-pack-dir"
175 */
176 packDir = packages:
177 let
178 packageLinks = packageName: {start ? [], opt ? []}:
179 let
180 # `nativeImpl` expects packages to be derivations, not strings (as
181 # opposed to older implementations that have to maintain backwards
182 # compatibility). Therefore we don't need to deal with "knownPlugins"
183 # and can simply pass `null`.
184 depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt);
185 startWithDeps = findDependenciesRecursively start;
186 allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
187 allPython3Dependencies = ps:
188 lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [])) ps) allPlugins);
189 python3Env = python3.withPackages allPython3Dependencies;
190
191 packdirStart = vimFarm "pack/${packageName}/start" "packdir-start" allPlugins;
192 packdirOpt = vimFarm "pack/${packageName}/opt" "packdir-opt" opt;
193 # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
194 # for each plugin.
195 # This directory is only for python import search path, and will not slow down the startup time.
196 # see :help python3-directory for more details
197 python3link = runCommand "vim-python3-deps" {} ''
198 mkdir -p $out/pack/${packageName}/start/__python3_dependencies
199 ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3
200 '';
201 in
202 [ packdirStart packdirOpt ] ++ lib.optional (allPython3Dependencies python3.pkgs != []) python3link;
203 in
204 buildEnv {
205 name = "vim-pack-dir";
206 paths = (lib.flatten (lib.mapAttrsToList packageLinks packages));
207 };
208
209 nativeImpl = packages:
210 ''
211 set packpath^=${packDir packages}
212 set runtimepath^=${packDir packages}
213 '';
214
215 /* Generates a vimrc string
216
217 packages is an attrset with {name: { start = [ vim derivations ]; opt = [ vim derivations ]; }
218 Example:
219 vimrcContent {
220
221 packages = { home-manager = { start = [vimPlugins.vim-fugitive]; opt = [];};
222 beforePlugins = '';
223 customRC = ''let mapleader = " "'';
224
225 };
226 */
227 vimrcContent = {
228 packages ? null,
229 vam ? null, # deprecated
230 pathogen ? null, # deprecated
231 plug ? null,
232 beforePlugins ? ''
233 " configuration generated by NIX
234 set nocompatible
235 '',
236 customRC ? null
237 }:
238
239 let
240 /* vim-plug is an extremely popular vim plugin manager.
241 */
242 plugImpl =
243 ''
244 source ${vimPlugins.vim-plug}/plug.vim
245 silent! call plug#begin('/dev/null')
246
247 '' + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg}'") plug.plugins) + ''
248
249 call plug#end()
250 '';
251
252 # vim-addon-manager = VAM (deprecated)
253 vamImpl =
254 let
255 knownPlugins = vam.knownPlugins or vimPlugins;
256
257 # plugins specified by the user
258 specifiedPlugins = map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries);
259 # plugins with dependencies
260 plugins = findDependenciesRecursively specifiedPlugins;
261 vamPackages.vam = {
262 start = plugins;
263 };
264 in
265 nativeImpl vamPackages;
266
267 entries = [
268 beforePlugins
269 ]
270 ++ lib.optional (vam != null) (lib.warn "'vam' attribute is deprecated. Use 'packages' instead in your vim configuration" vamImpl)
271 ++ lib.optional (packages != null && packages != []) (nativeImpl packages)
272 ++ lib.optional (pathogen != null) (throw "pathogen is now unsupported, replace `pathogen = {}` with `packages.home = { start = []; }`")
273 ++ lib.optional (plug != null) plugImpl
274 ++ [ customRC ];
275
276 in
277 lib.concatStringsSep "\n" (lib.filter (x: x != null && x != "") entries);
278
279 vimrcFile = settings: writeText "vimrc" (vimrcContent settings);
280
281in
282
283rec {
284 inherit vimrcFile;
285 inherit vimrcContent;
286 inherit packDir;
287
288 makeCustomizable = let
289 mkVimrcFile = vimrcFile; # avoid conflict with argument name
290 in vim: vim // {
291 # Returns a customized vim that uses the specified vimrc configuration.
292 customize =
293 { # The name of the derivation.
294 name ? "vim"
295 , # A shell word used to specify the names of the customized executables.
296 # The shell variable $exe can be used to refer to the wrapped executable's name.
297 # Examples: "my-$exe", "$exe-with-plugins", "\${exe/vim/v1m}"
298 executableName ?
299 if lib.hasInfix "vim" name then
300 lib.replaceStrings [ "vim" ] [ "$exe" ] name
301 else
302 "\${exe/vim/${lib.escapeShellArg name}}"
303 , # A custom vimrc configuration, treated as an argument to vimrcContent (see the documentation in this file).
304 vimrcConfig ? null
305 , # A custom vimrc file.
306 vimrcFile ? null
307 , # A custom gvimrc file.
308 gvimrcFile ? null
309 , # If set to true, return the *vim wrappers only.
310 # If set to false, overlay the wrappers on top of the original vim derivation.
311 # This ensures that things like man pages and .desktop files are available.
312 standalone ? name != "vim" && wrapManual != true
313
314 , # deprecated arguments (TODO: remove eventually)
315 wrapManual ? null, wrapGui ? null, vimExecutableName ? null, gvimExecutableName ? null,
316 }:
317 lib.warnIf (wrapManual != null) ''
318 vim.customize: wrapManual is deprecated: the manual is now included by default if `name == "vim"`.
319 ${if wrapManual == true && name != "vim" then "Set `standalone = false` to include the manual."
320 else lib.optionalString (wrapManual == false && name == "vim") "Set `standalone = true` to get the *vim wrappers only."
321 }''
322 lib.warnIf (wrapGui != null)
323 "vim.customize: wrapGui is deprecated: gvim is now automatically included if present"
324 lib.throwIfNot (vimExecutableName == null && gvimExecutableName == null)
325 "vim.customize: (g)vimExecutableName is deprecated: use executableName instead (see source code for examples)"
326 (let
327 vimrc =
328 if vimrcFile != null then vimrcFile
329 else if vimrcConfig != null then mkVimrcFile vimrcConfig
330 else throw "at least one of vimrcConfig and vimrcFile must be specified";
331 bin = runCommand "${name}-bin" { nativeBuildInputs = [ makeWrapper ]; } ''
332 vimrc=${lib.escapeShellArg vimrc}
333 gvimrc=${lib.optionalString (gvimrcFile != null) (lib.escapeShellArg gvimrcFile)}
334
335 mkdir -p "$out/bin"
336 for exe in ${
337 if standalone then "{,g,r,rg,e}vim {,g}vimdiff vi"
338 else "{,g,r,rg,e}{vim,view} {,g}vimdiff ex vi"
339 }; do
340 if [[ -e ${vim}/bin/$exe ]]; then
341 dest="$out/bin/${executableName}"
342 if [[ -e $dest ]]; then
343 echo "ambiguous executableName: ''${dest##*/} already exists"
344 continue
345 fi
346 makeWrapper ${vim}/bin/"$exe" "$dest" \
347 --add-flags "-u ''${vimrc@Q} ''${gvimrc:+-U ''${gvimrc@Q}}"
348 fi
349 done
350 '';
351 in if standalone then bin else
352 buildEnv {
353 inherit name;
354 paths = [ (lib.lowPrio vim) bin ];
355 });
356
357 override = f: makeCustomizable (vim.override f);
358 overrideAttrs = f: makeCustomizable (vim.overrideAttrs f);
359 };
360
361 vimWithRC = throw "vimWithRC was removed, please use vim.customize instead";
362
363 vimGenDocHook = callPackage ({ vim }:
364 makeSetupHook {
365 name = "vim-gen-doc-hook";
366 propagatedBuildInputs = [ vim ];
367 substitutions = {
368 vimBinary = "${vim}/bin/vim";
369 inherit rtpPath;
370 };
371 } ./vim-gen-doc-hook.sh) {};
372
373 vimCommandCheckHook = callPackage ({ neovim-unwrapped }:
374 makeSetupHook {
375 name = "vim-command-check-hook";
376 propagatedBuildInputs = [ neovim-unwrapped ];
377 substitutions = {
378 vimBinary = "${neovim-unwrapped}/bin/nvim";
379 inherit rtpPath;
380 };
381 } ./vim-command-check-hook.sh) {};
382
383 neovimRequireCheckHook = callPackage ({ neovim-unwrapped }:
384 makeSetupHook {
385 name = "neovim-require-check-hook";
386 propagatedBuildInputs = [ neovim-unwrapped ];
387 substitutions = {
388 nvimBinary = "${neovim-unwrapped}/bin/nvim";
389 inherit rtpPath;
390 };
391 } ./neovim-require-check-hook.sh) {};
392
393 inherit (import ./build-vim-plugin.nix {
394 inherit lib stdenv rtpPath toVimPlugin;
395 }) buildVimPlugin buildVimPluginFrom2Nix;
396
397
398 # used to figure out which python dependencies etc. neovim needs
399 requiredPlugins = {
400 packages ? {},
401 plug ? null, ...
402 }:
403 let
404 nativePluginsConfigs = lib.attrsets.attrValues packages;
405 nonNativePlugins = (lib.optionals (plug != null) plug.plugins);
406 nativePlugins = lib.concatMap (requiredPluginsForPackage) nativePluginsConfigs;
407 in
408 nativePlugins ++ nonNativePlugins;
409
410
411 # figures out which python dependencies etc. is needed for one vim package
412 requiredPluginsForPackage = { start ? [], opt ? []}:
413 start ++ opt;
414
415 toVimPlugin = drv:
416 drv.overrideAttrs(oldAttrs: {
417 # dont move the "doc" folder since vim expects it
418 forceShare = [ "man" "info" ];
419
420 nativeBuildInputs = oldAttrs.nativeBuildInputs or []
421 ++ lib.optionals (stdenv.hostPlatform == stdenv.buildPlatform) [
422 vimCommandCheckHook vimGenDocHook
423 # many neovim plugins keep using buildVimPlugin
424 neovimRequireCheckHook
425 ];
426
427 passthru = (oldAttrs.passthru or {}) // {
428 vimPlugin = true;
429 };
430 });
431}