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