summary refs log tree commit diff
path: root/pkgs/applications/editors/vim/plugins/vim-utils.nix
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/applications/editors/vim/plugins/vim-utils.nix')
-rw-r--r--pkgs/applications/editors/vim/plugins/vim-utils.nix532
1 files changed, 532 insertions, 0 deletions
diff --git a/pkgs/applications/editors/vim/plugins/vim-utils.nix b/pkgs/applications/editors/vim/plugins/vim-utils.nix
new file mode 100644
index 00000000000..4183b621435
--- /dev/null
+++ b/pkgs/applications/editors/vim/plugins/vim-utils.nix
@@ -0,0 +1,532 @@
+# tests available at pkgs/test/vim
+{ lib, stdenv, vim, vimPlugins, vim_configurable, buildEnv, writeText, writeScriptBin
+, nix-prefetch-hg, nix-prefetch-git
+, fetchFromGitHub, runtimeShell
+, hasLuaModule
+, python3
+, callPackage, makeSetupHook
+}:
+
+/*
+
+USAGE EXAMPLE
+=============
+
+Install Vim like this eg using nixos option environment.systemPackages which will provide
+vim-with-plugins in PATH:
+
+  vim_configurable.customize {
+    name = "vim-with-plugins";
+
+    # add custom .vimrc lines like this:
+    vimrcConfig.customRC = ''
+      set hidden
+    '';
+
+    # store your plugins in Vim packages
+    vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
+      # loaded on launch
+      start = [ youcompleteme fugitive ];
+      # manually loadable by calling `:packadd $plugin-name`
+      opt = [ phpCompletion elm-vim ];
+      # To automatically load a plugin when opening a filetype, add vimrc lines like:
+      # autocmd FileType php :packadd phpCompletion
+    };
+
+    # plugins can also be managed by VAM
+    vimrcConfig.vam.knownPlugins = pkgs.vimPlugins; # optional
+    vimrcConfig.vam.pluginDictionaries = [
+      # load always
+      { name = "youcompleteme"; }
+      { names = ["youcompleteme" "foo"]; }
+
+      # only load when opening a .php file
+      { name = "phpCompletion"; ft_regex = "^php\$"; }
+      { name = "phpCompletion"; filename_regex = "^.php\$"; }
+
+      # provide plugin which can be loaded manually:
+      { name = "phpCompletion"; tag = "lazy"; }
+
+      # full documentation at github.com/MarcWeber/vim-addon-manager
+    ];
+
+    # there is a pathogen implementation as well, but its startup is slower and [VAM] has more feature
+    # vimrcConfig.pathogen.knownPlugins = vimPlugins; # optional
+    # vimrcConfig.pathogen.pluginNames = ["vim-addon-nix"];
+  };
+
+WHAT IS A VIM PLUGIN?
+=====================
+Typical plugin files:
+
+  plugin/P1.vim
+  autoload/P1.vim
+  ftplugin/xyz.vim
+  doc/plugin-documentation.txt (traditional documentation)
+  README(.md) (nowadays thanks to github)
+
+
+Vim offers the :h rtp setting which works for most plugins. Thus adding
+this to your .vimrc should make most plugins work:
+
+  set rtp+=~/.nix-profile/share/vim-plugins/youcompleteme
+  " or for p in ["youcompleteme"] | exec 'set rtp+=~/.nix-profile/share/vim-plugins/'.p | endfor
+
+which is what the [VAM]/pathogen solutions above basically do.
+
+Learn about about plugin Vim plugin mm managers at
+http://vim-wiki.mawercer.de/wiki/topic/vim%20plugin%20managment.html.
+
+The documentation can be accessed by Vim's :help command if it was tagged.
+See vimHelpTags sample code below.
+
+CONTRIBUTING AND CUSTOMIZING
+============================
+The example file pkgs/applications/editors/vim/plugins/default.nix provides
+both:
+* manually mantained plugins
+* plugins created by VAM's nix#ExportPluginsForNix implementation
+
+I highly recommend to lookup vim plugin attribute names at the [vim-pi] project
+ which is a database containing all plugins from
+vim.org and quite a lot of found at github and similar sources. vim-pi's documented purpose
+is to associate vim.org script ids to human readable names so that dependencies
+can be describe easily.
+
+How to find a name?
+  * http://vam.mawercer.de/ or VAM's
+  * grep vim-pi
+  * use VAM's completion or :AddonsInfo command
+
+It might happen than a plugin is not known by vim-pi yet. We encourage you to
+contribute to vim-pi so that plugins can be updated automatically.
+
+
+CREATING DERVITATIONS AUTOMATICALLY BY PLUGIN NAME
+==================================================
+Most convenient is to use a ~/.vim-scripts file putting a plugin name into each line
+as documented by [VAM]'s README.md
+It is the same format you pass to vimrcConfig.vam.pluginDictionaries from the
+usage example above.
+
+Then create a temp vim file and insert:
+
+  let opts = {}
+  let opts.path_to_nixpkgs = '/etc/nixos/nixpkgs'
+  let opts.cache_file = '/tmp/export-vim-plugin-for-nix-cache-file'
+  let opts.plugin_dictionaries = map(readfile("vim-plugins"), 'eval(v:val)')
+  " add more files
+  " let opts.plugin_dictionaries += map(.. other file )
+  call nix#ExportPluginsForNix(opts)
+
+Then ":source %" it.
+
+nix#ExportPluginsForNix is provided by ./vim2nix
+
+A buffer will open containing the plugin derivation lines as well list
+fitting the vimrcConfig.vam.pluginDictionaries option.
+
+Thus the most simple usage would be:
+
+  vim_with_plugins =
+    let vim = vim_configurable;
+        inherit (vimUtil.override {inherit vim}) rtpPath addRtp buildVimPlugin vimHelpTags;
+        vimPlugins = [
+          # the derivation list from the buffer created by nix#ExportPluginsForNix
+          # don't set which will default to pkgs.vimPlugins
+        ];
+    in vim.customize {
+      name = "vim-with-plugins";
+
+      vimrcConfig.customRC = '' .. '';
+
+      vimrcConfig.vam.knownPlugins = vimPlugins;
+      vimrcConfig.vam.pluginDictionaries = [
+          # the plugin list form ~/.vim-scripts turned into nix format added to
+          # the buffer created by the nix#ExportPluginsForNix
+      ];
+    }
+
+vim_with_plugins can be installed like any other application within Nix.
+
+[VAM]    https://github.com/MarcWeber/vim-addon-manager
+[vim-pi] https://bitbucket.org/vimcommunity/vim-pi
+*/
+
+
+let
+  inherit lib;
+
+  # make sure a plugin is a derivation and its dependencies are derivations. If
+  # plugin already is a derivation, this is a no-op. If it is a string, it is
+  # looked up in knownPlugins.
+  pluginToDrv = knownPlugins: plugin:
+  let
+    drv =
+      if builtins.isString plugin then
+        # make sure `pname` is set to that we are able to convert the derivation
+        # back to a string.
+        ( knownPlugins.${plugin} // { pname = plugin; })
+      else
+        plugin;
+  in
+    # make sure all the dependencies of the plugin are also derivations
+    drv // { dependencies = map (pluginToDrv knownPlugins) (drv.dependencies or []); };
+
+  # transitive closure of plugin dependencies (plugin needs to be a derivation)
+  transitiveClosure = plugin:
+    [ plugin ] ++ (
+      lib.unique (builtins.concatLists (map transitiveClosure plugin.dependencies or []))
+    );
+
+  findDependenciesRecursively = plugins: lib.concatMap transitiveClosure plugins;
+
+  vamDictToNames = x:
+      if builtins.isString x then [x]
+      else (lib.optional (x ? name) x.name)
+            ++ (x.names or []);
+
+  rtpPath = ".";
+
+  # Generates a packpath folder as expected by vim
+  packDir = packages:
+  let
+    # dir is "start" or "opt"
+    linkLuaPlugin = plugin: packageName: dir: ''
+      mkdir -p $out/pack/${packageName}/${dir}/${plugin.pname}/lua
+      ln -sf ${plugin}/share/lua/5.1/* $out/pack/${packageName}/${dir}/${plugin.pname}/lua
+      ln -sf ${plugin}/${plugin.pname}-${plugin.version}-rocks/${plugin.pname}/${plugin.version}/* $out/pack/${packageName}/${dir}/${plugin.pname}/
+    '';
+
+    linkVimlPlugin = plugin: packageName: dir: ''
+      mkdir -p $out/pack/${packageName}/${dir}
+      if test -e "$out/pack/${packageName}/${dir}/${lib.getName plugin}"; then
+        printf "\nERROR - Duplicated vim plugin: ${lib.getName plugin}\n\n"
+        exit 1
+      fi
+      ln -sf ${plugin}/${rtpPath} $out/pack/${packageName}/${dir}/${lib.getName plugin}
+    '';
+
+    link = pluginPath: if hasLuaModule pluginPath
+      then linkLuaPlugin pluginPath
+      else linkVimlPlugin pluginPath;
+
+    packageLinks = packageName: {start ? [], opt ? []}:
+    let
+      # `nativeImpl` expects packages to be derivations, not strings (as
+      # opposed to older implementations that have to maintain backwards
+      # compatibility). Therefore we don't need to deal with "knownPlugins"
+      # and can simply pass `null`.
+      depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt);
+      startWithDeps = findDependenciesRecursively start;
+      allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
+      python3Env = python3.withPackages (ps:
+        lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [])) ps) allPlugins)
+      );
+    in
+      [ "mkdir -p $out/pack/${packageName}/start" ]
+      # To avoid confusion, even dependencies of optional plugins are added
+      # to `start` (except if they are explicitly listed as optional plugins).
+      ++ (builtins.map (x: link x packageName "start") allPlugins)
+      ++ ["mkdir -p $out/pack/${packageName}/opt"]
+      ++ (builtins.map (x: link x packageName "opt") opt)
+      # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
+      # for each plugin.
+      # This directory is only for python import search path, and will not slow down the startup time.
+      ++ [
+        "mkdir -p $out/pack/${packageName}/start/__python3_dependencies"
+        "ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3"
+      ];
+  in
+      stdenv.mkDerivation {
+        name = "vim-pack-dir";
+        src = ./.;
+        installPhase = lib.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList packageLinks packages));
+        preferLocalBuild = true;
+    };
+
+  nativeImpl = packages:
+  ''
+    set packpath^=${packDir packages}
+    set runtimepath^=${packDir packages}
+  '';
+
+  /* Generates a vimrc string
+
+    packages is an attrset with {name: { start = [ vim derivations ]; opt = [ vim derivations ]; }
+    Example:
+      vimrcContent {
+
+        packages = { home-manager = { start = [vimPlugins.vim-fugitive]; opt = [];};
+        beforePlugins = '';
+        customRC = ''let mapleader = " "'';
+
+      };
+   */
+  vimrcContent = {
+    packages ? null,
+    vam ? null,
+    pathogen ? null,
+    plug ? null,
+    beforePlugins ? ''
+      " configuration generated by NIX
+      set nocompatible
+    '',
+    customRC ? null
+  }:
+
+    let
+      /* pathogen mostly can set &rtp at startup time. Its used very commonly.
+      */
+      pathogenImpl = let
+        knownPlugins = pathogen.knownPlugins or vimPlugins;
+
+        plugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) pathogen.pluginNames);
+
+        pluginsEnv = buildEnv {
+          name = "pathogen-plugin-env";
+          paths = map (x: "${x}/${rtpPath}") plugins;
+        };
+      in
+      ''
+        let &rtp.=(empty(&rtp)?"":',')."${vimPlugins.vim-pathogen.rtp}"
+        execute pathogen#infect('${pluginsEnv}/{}')
+
+        filetype indent plugin on | syn on
+      '';
+
+      /* vim-plug is an extremely popular vim plugin manager.
+      */
+      plugImpl =
+      (''
+        source ${vimPlugins.vim-plug.rtp}/plug.vim
+        silent! call plug#begin('/dev/null')
+
+        '' + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg.rtp}'") plug.plugins) + ''
+
+        call plug#end()
+      '');
+
+      /*
+       vim-addon-manager = VAM
+
+       * maps names to plugin location
+
+       * manipulates &rtp at startup time
+         or when Vim has been running for a while
+
+       * can activate plugins laziy (eg when loading a specific filetype)
+
+       * knows about vim plugin dependencies (addon-info.json files)
+
+       * still is minimalistic (only loads one file), the "check out" code it also
+         has only gets loaded when a plugin is requested which is not found on disk
+         yet
+
+      */
+      vamImpl = lib.optionalString (vam != null)
+      (let
+        knownPlugins = vam.knownPlugins or vimPlugins;
+
+        # plugins specified by the user
+        specifiedPlugins = map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries);
+        # plugins with dependencies
+        plugins = findDependenciesRecursively specifiedPlugins;
+
+        # Convert scalars, lists, and attrs, to VimL equivalents
+        toVimL = x:
+          if builtins.isString x then "'${lib.replaceStrings [ "\n" "'" ] [ "\n\\ " "''" ] x}'"
+          else if builtins.isAttrs x && builtins ? out then toVimL x # a derivation
+          else if builtins.isAttrs x then "{${lib.concatStringsSep ", " (lib.mapAttrsToList (n: v: "${toVimL n}: ${toVimL v}") x)}}"
+          else if builtins.isList x then "[${lib.concatMapStringsSep ", " toVimL x}]"
+          else if builtins.isInt x || builtins.isFloat x then builtins.toString x
+          else if builtins.isBool x then (if x then "1" else "0")
+          else throw "turning ${lib.generators.toPretty {} x} into a VimL thing not implemented yet";
+
+      in assert builtins.hasAttr "vim-addon-manager" knownPlugins;
+      ''
+        filetype indent plugin on | syn on
+
+        let g:nix_plugin_locations = {}
+        ${lib.concatMapStrings (plugin: ''
+          let g:nix_plugin_locations['${plugin.pname}'] = "${plugin.rtp}"
+        '') plugins}
+        let g:nix_plugin_locations['vim-addon-manager'] = "${knownPlugins.vim-addon-manager.rtp}"
+
+        let g:vim_addon_manager = {}
+
+        if exists('g:nix_plugin_locations')
+          " nix managed config
+
+          " override default function making VAM aware of plugin locations:
+          fun! NixPluginLocation(name)
+            let path = get(g:nix_plugin_locations, a:name, "")
+            return path == "" ? vam#DefaultPluginDirFromName(a:name) : path
+          endfun
+          let g:vim_addon_manager.plugin_dir_by_name = 'NixPluginLocation'
+          " tell Vim about VAM:
+          let &rtp.=(empty(&rtp)?"":','). g:nix_plugin_locations['vim-addon-manager']
+        else
+          " standalone config
+
+          let &rtp.=(empty(&rtp)?"":',').c.plugin_root_dir.'/vim-addon-manager'
+          if !isdirectory(c.plugin_root_dir.'/vim-addon-manager/autoload')
+            " checkout VAM
+            execute '!git clone --depth=1 https://github.com/MarcWeber/vim-addon-manager '
+                \       shellescape(c.plugin_root_dir.'/vim-addon-manager', 1)
+          endif
+        endif
+
+        " tell vam which plugins to load, and when:
+        let l = []
+        ${lib.concatMapStrings (p: "call add(l, ${toVimL p})\n") vam.pluginDictionaries}
+        call vam#Scripts(l, {})
+      '');
+
+      entries = [
+        beforePlugins
+        vamImpl
+      ]
+      ++ lib.optional (packages != null && packages != []) (nativeImpl packages)
+      ++ lib.optional (pathogen != null) pathogenImpl
+      ++ lib.optional (plug != null) plugImpl
+      ++ [ customRC ];
+
+    in
+      lib.concatStringsSep "\n" (lib.filter (x: x != null && x != "") entries);
+
+  vimrcFile = settings: writeText "vimrc" (vimrcContent settings);
+
+in
+
+rec {
+  inherit vimrcFile;
+  inherit vimrcContent;
+  inherit packDir;
+
+  # shell script with custom name passing [-u vimrc] [-U gvimrc] to vim
+  vimWithRC = {
+    vimExecutable,
+    gvimExecutable,
+    vimManPages,
+    wrapManual,
+    wrapGui,
+    name ? "vim",
+    vimrcFile ? null,
+    gvimrcFile ? null,
+    vimExecutableName,
+    gvimExecutableName,
+  }:
+    let
+      rcOption = o: file: lib.optionalString (file != null) "-${o} ${file}";
+      vimWrapperScript = writeScriptBin vimExecutableName ''
+        #!${runtimeShell}
+        exec ${vimExecutable} ${rcOption "u" vimrcFile} ${rcOption "U" gvimrcFile} "$@"
+      '';
+      gvimWrapperScript = writeScriptBin gvimExecutableName ''
+        #!${stdenv.shell}
+        exec ${gvimExecutable} ${rcOption "u" vimrcFile} ${rcOption "U" gvimrcFile} "$@"
+      '';
+    in
+      buildEnv {
+        inherit name;
+        paths = [
+          vimWrapperScript
+        ] ++ lib.optional wrapGui gvimWrapperScript
+          ++ lib.optional wrapManual vimManPages
+        ;
+      };
+
+  # add a customize option to a vim derivation
+  makeCustomizable = vim: vim // {
+    customize = {
+      name,
+      vimrcConfig,
+      wrapManual ? true,
+      wrapGui ? false,
+      vimExecutableName ? name,
+      gvimExecutableName ? (lib.concatStrings [ "g" name ]),
+    }: vimWithRC {
+      vimExecutable = "${vim}/bin/vim";
+      gvimExecutable = "${vim}/bin/gvim";
+      inherit name wrapManual wrapGui vimExecutableName gvimExecutableName;
+      vimrcFile = vimrcFile vimrcConfig;
+      vimManPages = buildEnv {
+        name = "vim-doc";
+        paths = [ vim ];
+        pathsToLink = [ "/share/man" ];
+      };
+    };
+
+    override = f: makeCustomizable (vim.override f);
+    overrideAttrs = f: makeCustomizable (vim.overrideAttrs f);
+  };
+
+  pluginnames2Nix = {name, namefiles} : vim_configurable.customize {
+    inherit name;
+    vimrcConfig.vam.knownPlugins = vimPlugins;
+    vimrcConfig.vam.pluginDictionaries = ["vim2nix"];
+    vimrcConfig.customRC = ''
+      " Yes - this is impure and will create the cache file and checkout vim-pi
+      " into ~/.vim/vim-addons
+      let g:vim_addon_manager.plugin_root_dir = "/tmp/vim2nix-".$USER
+      if !isdirectory(g:vim_addon_manager.plugin_root_dir)
+        call mkdir(g:vim_addon_manager.plugin_root_dir)
+      else
+        echom repeat("=", 80)
+        echom "WARNING: reusing cache directory :".g:vim_addon_manager.plugin_root_dir
+        echom repeat("=", 80)
+      endif
+      let opts = {}
+      let opts.nix_prefetch_git = "${nix-prefetch-git}/bin/nix-prefetch-git"
+      let opts.nix_prefetch_hg  = "${nix-prefetch-hg}/bin/nix-prefetch-hg"
+      let opts.cache_file = g:vim_addon_manager.plugin_root_dir.'/cache'
+      let opts.plugin_dictionaries = []
+      ${lib.concatMapStrings (file: "let opts.plugin_dictionaries += map(readfile(\"${file}\"), 'eval(v:val)')\n") namefiles }
+
+      " uncomment for debugging failures
+      " let opts.try_catch = 0
+
+      " add more files
+      " let opts.plugin_dictionaries += map(.. other file )
+      call nix#ExportPluginsForNix(opts)
+    '';
+  };
+
+  vimGenDocHook = callPackage ({ vim }:
+    makeSetupHook {
+      name = "vim-gen-doc-hook";
+      deps = [ vim ];
+      substitutions = {
+        vimBinary = "${vim}/bin/vim";
+        inherit rtpPath;
+      };
+    } ./vim-gen-doc-hook.sh) {};
+
+  inherit (import ./build-vim-plugin.nix { inherit lib stdenv rtpPath vim vimGenDocHook; })
+    buildVimPlugin buildVimPluginFrom2Nix;
+
+  # used to figure out which python dependencies etc. neovim needs
+  requiredPlugins = {
+    packages ? {},
+    givenKnownPlugins ? null,
+    vam ? null,
+    pathogen ? null,
+    plug ? null, ...
+  }:
+    let
+      # This is probably overcomplicated, but I don't understand this well enough to know what's necessary.
+      knownPlugins = if givenKnownPlugins != null then givenKnownPlugins else
+                     if vam != null && vam ? knownPlugins then vam.knownPlugins else
+                     if pathogen != null && pathogen ? knownPlugins then pathogen.knownPlugins else
+                     vimPlugins;
+      pathogenPlugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) pathogen.pluginNames);
+      vamPlugins = findDependenciesRecursively (map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries));
+      nonNativePlugins = (lib.optionals (pathogen != null) pathogenPlugins)
+                      ++ (lib.optionals (vam != null) vamPlugins)
+                      ++ (lib.optionals (plug != null) plug.plugins);
+      nativePluginsConfigs = lib.attrsets.attrValues packages;
+      nativePlugins = lib.concatMap ({start?[], opt?[], knownPlugins?vimPlugins}: start++opt) nativePluginsConfigs;
+    in
+      nativePlugins ++ nonNativePlugins;
+}