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