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