Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at release-19.03 497 lines 18 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 213 /* vim-plug is an extremely popular vim plugin manager. 214 */ 215 plugImpl = lib.optionalString (plug != null) 216 ('' 217 source ${vimPlugins.vim-plug.rtp}/plug.vim 218 call plug#begin('/dev/null') 219 220 '' + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg.rtp}'") plug.plugins) + '' 221 222 call plug#end() 223 ''); 224 225 /* 226 vim-addon-manager = VAM 227 228 * maps names to plugin location 229 230 * manipulates &rtp at startup time 231 or when Vim has been running for a while 232 233 * can activate plugins laziy (eg when loading a specific filetype) 234 235 * knows about vim plugin dependencies (addon-info.json files) 236 237 * still is minimalistic (only loads one file), the "check out" code it also 238 has only gets loaded when a plugin is requested which is not found on disk 239 yet 240 241 */ 242 vamImpl = lib.optionalString (vam != null) 243 (let 244 knownPlugins = vam.knownPlugins or vimPlugins; 245 246 # plugins specified by the user 247 specifiedPlugins = map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries); 248 # plugins with dependencies 249 plugins = findDependenciesRecursively specifiedPlugins; 250 251 # Vim almost reads JSON, so eventually JSON support should be added to Nix 252 # TODO: proper quoting 253 toNix = x: 254 if (builtins.isString x) then "'${x}'" 255 else if builtins.isAttrs x && builtins ? out then toNix "${x}" # a derivation 256 else if builtins.isAttrs x then "{${lib.concatStringsSep ", " (lib.mapAttrsToList (n: v: "${toNix n}: ${toNix v}") x)}}" 257 else if builtins.isList x then "[${lib.concatMapStringsSep ", " toNix x}]" 258 else throw "turning ${lib.generators.toPretty {} x} into a VimL thing not implemented yet"; 259 260 in assert builtins.hasAttr "vim-addon-manager" knownPlugins; 261 '' 262 let g:nix_plugin_locations = {} 263 ${lib.concatMapStrings (plugin: '' 264 let g:nix_plugin_locations['${plugin.pname}'] = "${plugin.rtp}" 265 '') plugins} 266 let g:nix_plugin_locations['vim-addon-manager'] = "${knownPlugins."vim-addon-manager".rtp}" 267 268 let g:vim_addon_manager = {} 269 270 if exists('g:nix_plugin_locations') 271 " nix managed config 272 273 " override default function making VAM aware of plugin locations: 274 fun! NixPluginLocation(name) 275 let path = get(g:nix_plugin_locations, a:name, "") 276 return path == "" ? vam#DefaultPluginDirFromName(a:name) : path 277 endfun 278 let g:vim_addon_manager.plugin_dir_by_name = 'NixPluginLocation' 279 " tell Vim about VAM: 280 let &rtp.=(empty(&rtp)?"":','). g:nix_plugin_locations['vim-addon-manager'] 281 else 282 " standalone config 283 284 let &rtp.=(empty(&rtp)?"":',').c.plugin_root_dir.'/vim-addon-manager' 285 if !isdirectory(c.plugin_root_dir.'/vim-addon-manager/autoload') 286 " checkout VAM 287 execute '!git clone --depth=1 https://github.com/MarcWeber/vim-addon-manager ' 288 \ shellescape(c.plugin_root_dir.'/vim-addon-manager', 1) 289 endif 290 endif 291 292 " tell vam about which plugins to load when: 293 let l = [] 294 ${lib.concatMapStrings (p: "call add(l, ${toNix p})\n") vam.pluginDictionaries} 295 call vam#Scripts(l, {}) 296 ''); 297 298 nativeImpl = lib.optionalString (packages != null) 299 (let 300 link = (packageName: dir: pluginPath: "ln -sf ${pluginPath}/share/vim-plugins/* $out/pack/${packageName}/${dir}"); 301 packageLinks = (packageName: {start ? [], opt ? []}: 302 let 303 # `nativeImpl` expects packages to be derivations, not strings (as 304 # opposed to older implementations that have to maintain backwards 305 # compatibility). Therefore we don't need to deal with "knownPlugins" 306 # and can simply pass `null`. 307 depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt); 308 startWithDeps = findDependenciesRecursively start; 309 in 310 ["mkdir -p $out/pack/${packageName}/start"] 311 # To avoid confusion, even dependencies of optional plugins are added 312 # to `start` (except if they are explicitly listed as optional plugins). 313 ++ (builtins.map (link packageName "start") (lib.unique (startWithDeps ++ depsOfOptionalPlugins))) 314 ++ ["mkdir -p $out/pack/${packageName}/opt"] 315 ++ (builtins.map (link packageName "opt") opt) 316 ); 317 packDir = (packages: 318 stdenv.mkDerivation rec { 319 name = "vim-pack-dir"; 320 src = ./.; 321 installPhase = lib.concatStringsSep 322 "\n" 323 (lib.flatten (lib.mapAttrsToList packageLinks packages)); 324 preferLocalBuild = true; 325 } 326 ); 327 in 328 '' 329 set packpath-=~/.vim/after 330 set packpath+=${packDir packages} 331 set packpath+=~/.vim/after 332 ''); 333 334 # somebody else could provide these implementations 335 vundleImpl = ""; 336 337 neobundleImpl = ""; 338 339 340 in writeText "vimrc" '' 341 " minimal setup, generated by NIX 342 set nocompatible 343 344 ${vamImpl} 345 ${pathogenImpl} 346 ${plugImpl} 347 ${vundleImpl} 348 ${neobundleImpl} 349 ${nativeImpl} 350 351 filetype indent plugin on | syn on 352 353 ${customRC} 354 ''; 355 356in 357 358rec { 359 inherit vimrcFile; 360 361 # shell script with custom name passing [-u vimrc] [-U gvimrc] to vim 362 vimWithRC = {vimExecutable, name ? null, vimrcFile ? null, gvimrcFile ? null}: 363 let rcOption = o: file: stdenv.lib.optionalString (file != null) "-${o} ${file}"; 364 in writeScriptBin (if name == null then "vim" else name) '' 365 #!${runtimeShell} 366 exec ${vimExecutable} ${rcOption "u" vimrcFile} ${rcOption "U" gvimrcFile} "$@" 367 ''; 368 369 # add a customize option to a vim derivation 370 makeCustomizable = vim: vim // { 371 customize = { name, vimrcConfig }: vimWithRC { 372 vimExecutable = "${vim}/bin/vim"; 373 inherit name; 374 vimrcFile = vimrcFile vimrcConfig; 375 }; 376 377 override = f: makeCustomizable (vim.override f); 378 overrideAttrs = f: makeCustomizable (vim.overrideAttrs f); 379 }; 380 381 pluginnames2Nix = {name, namefiles} : vim_configurable.customize { 382 inherit name; 383 vimrcConfig.vam.knownPlugins = vimPlugins; 384 vimrcConfig.vam.pluginDictionaries = ["vim2nix"]; 385 vimrcConfig.customRC = '' 386 " Yes - this is impure and will create the cache file and checkout vim-pi 387 " into ~/.vim/vim-addons 388 let g:vim_addon_manager.plugin_root_dir = "/tmp/vim2nix-".$USER 389 if !isdirectory(g:vim_addon_manager.plugin_root_dir) 390 call mkdir(g:vim_addon_manager.plugin_root_dir) 391 else 392 echom repeat("=", 80) 393 echom "WARNING: reusing cache directory :".g:vim_addon_manager.plugin_root_dir 394 echom repeat("=", 80) 395 endif 396 let opts = {} 397 let opts.nix_prefetch_git = "${nix-prefetch-git}/bin/nix-prefetch-git" 398 let opts.nix_prefetch_hg = "${nix-prefetch-hg}/bin/nix-prefetch-hg" 399 let opts.cache_file = g:vim_addon_manager.plugin_root_dir.'/cache' 400 let opts.plugin_dictionaries = [] 401 ${lib.concatMapStrings (file: "let opts.plugin_dictionaries += map(readfile(\"${file}\"), 'eval(v:val)')\n") namefiles } 402 403 " uncomment for debugging failures 404 " let opts.try_catch = 0 405 406 " add more files 407 " let opts.plugin_dictionaries += map(.. other file ) 408 call nix#ExportPluginsForNix(opts) 409 ''; 410 }; 411 412 vim_with_vim2nix = vim_configurable.customize { name = "vim"; vimrcConfig.vam.pluginDictionaries = [ "vim-addon-vim2nix" ]; }; 413 414 inherit (import ./build-vim-plugin.nix { inherit stdenv rtpPath vim; }) buildVimPlugin buildVimPluginFrom2Nix; 415 416 # used to figure out which python dependencies etc. neovim needs 417 requiredPlugins = { 418 packages ? {}, 419 givenKnownPlugins ? null, 420 vam ? null, 421 pathogen ? null, 422 plug ? null, ... 423 }: 424 let 425 # This is probably overcomplicated, but I don't understand this well enough to know what's necessary. 426 knownPlugins = if givenKnownPlugins != null then givenKnownPlugins else 427 if vam != null && vam ? knownPlugins then vam.knownPlugins else 428 if pathogen != null && pathogen ? knownPlugins then pathogen.knownPlugins else 429 vimPlugins; 430 pathogenPlugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) pathogen.pluginNames); 431 vamPlugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries)); 432 nonNativePlugins = (lib.optionals (pathogen != null) pathogenPlugins) 433 ++ (lib.optionals (vam != null) vamPlugins) 434 ++ (lib.optionals (plug != null) plug.plugins); 435 nativePluginsConfigs = lib.attrsets.attrValues packages; 436 nativePlugins = lib.concatMap ({start?[], opt?[], knownPlugins?vimPlugins}: start++opt) nativePluginsConfigs; 437 in 438 nativePlugins ++ nonNativePlugins; 439 440 441 # test cases: 442 test_vim_with_vim_nix_using_vam = vim_configurable.customize { 443 name = "vim-with-vim-addon-nix-using-vam"; 444 vimrcConfig.vam.pluginDictionaries = [{name = "vim-nix"; }]; 445 }; 446 447 test_vim_with_vim_nix_using_pathogen = vim_configurable.customize { 448 name = "vim-with-vim-addon-nix-using-pathogen"; 449 vimrcConfig.pathogen.pluginNames = [ "vim-nix" ]; 450 }; 451 452 test_vim_with_vim_nix_using_plug = vim_configurable.customize { 453 name = "vim-with-vim-addon-nix-using-plug"; 454 vimrcConfig.plug.plugins = with vimPlugins; [ vim-nix ]; 455 }; 456 457 test_vim_with_vim_nix = vim_configurable.customize { 458 name = "vim-with-vim-addon-nix"; 459 vimrcConfig.packages.myVimPackage.start = with vimPlugins; [ vim-nix ]; 460 }; 461 462 # only neovim makes use of `requiredPlugins`, test this here 463 test_nvim_with_vim_nix_using_pathogen = neovim.override { 464 configure.pathogen.pluginNames = [ "vim-nix" ]; 465 }; 466 467 # regression test for https://github.com/NixOS/nixpkgs/issues/53112 468 # The user may have specified their own plugins which may not be formatted 469 # exactly as the generated ones. In particular, they may not have the `pname` 470 # attribute. 471 test_vim_with_custom_plugin = vim_configurable.customize { 472 name = "vim_with_custom_plugin"; 473 vimrcConfig.vam.knownPlugins = 474 vimPlugins // ({ 475 "vim-trailing-whitespace" = buildVimPluginFrom2Nix { 476 name = "vim-trailing-whitespace"; 477 src = fetchFromGitHub { 478 owner = "bronson"; 479 repo = "vim-trailing-whitespace"; 480 rev = "4c596548216b7c19971f8fc94e38ef1a2b55fee6"; 481 sha256 = "0f1cpnp1nxb4i5hgymjn2yn3k1jwkqmlgw1g02sq270lavp2dzs9"; 482 }; 483 # make sure string dependencies are handled 484 dependencies = [ "vim-nix" ]; 485 }; 486 }); 487 vimrcConfig.vam.pluginDictionaries = [ { names = [ "vim-trailing-whitespace" ]; } ]; 488 }; 489 490 # system remote plugin manifest should be generated, deoplete should be usable 491 # without the user having to do `UpdateRemotePlugins`. To test, launch neovim 492 # and do `:call deoplete#enable()`. It will print an error if the remote 493 # plugin is not registered. 494 test_nvim_with_remote_plugin = neovim.override { 495 configure.pathogen.pluginNames = with vimPlugins; [ deoplete-nvim ]; 496 }; 497}