NixOS / nixpkgs

Nix Packages collection & NixOS
MIT License
17.99k stars 14k forks source link

vim_configurable: unable to use plugin syntax files #39364

Closed ivanbrennan closed 4 years ago

ivanbrennan commented 6 years ago

Issue description

Using vim-configurable to bake plugins into a system-wide vim configuration, there doesn't appear to be a way to make a plugin's syntax files precede those in $VIMRUNTIME. Since most syntax files contain a guard-clause to avoid clobbering any previously-set syntax, such plugins can't have the desired effect.

I'm using Vim 8 packages to bake haskell-vim (plus some other plugins) into my system-wide vim configuration. I have an overlay that contains:

vim-configured = self.vim_configurable.customize {
  name = "vim";
  vimrcConfig = {
    packages.myPackage = with pkgs.vimPlugins; {
      start = [ haskell-vim foo bar baz ];
      opt = [];
    };
  };
};

The haskell-vim plugin contains a syntax file. A less feature-rich syntax file also exists at $VIMRUNTIME/syntax/haskell.vim. Both files begin with a guard-clause:

if exists("b:current_syntax")
  finish
endif

and end with

let b:current_syntax = "haskell"

so whichever runs first wins.

I want to use haskell-vim's syntax file, but the way vim_configurable works, I end up with a runtimepath like:

~/.vim
$VIMRUNTIME
~/.vim/after
/nix/store/xxxx-vim-pack-dir/pack/myPackage/start/haskell-vim

I'm trying to figure out how I could bump this particular plugin to an earlier position in runtimepath, whether it makes sense to bump the whole package (all plugins), or whether there's some other solution.

Technical details

nix-shell -p nix-info --run "nix-info -m"
 - system: `"x86_64-linux"`
 - host os: `Linux 4.14.33, NixOS, 18.09pre135256.6c064e6b1f3 (Jellyfish)`
 - multi-user?: `yes`
 - sandbox: `no`
 - version: `nix-env (Nix) 2.0`
 - channels(root): `"nixos-18.09pre135256.6c064e6b1f3"`
 - nixpkgs: `/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs`
ivanbrennan commented 6 years ago

I found a way to work around this. I added a symlink to the relevant syntax file in my user vim config:

ln -s \
    /run/current-system/sw/share/vim-plugins/haskell-vim/syntax/haskell.vim \
    ~/.vim/syntax/haskell.vim

This works because ~/.vim is earlier in Vims runtimepath than $VIMRUNTIME is.

I was mystified for a while as to why /run/current-system/sw/share/vim-plugins/ was empty despite all the plugins I've baked into my Vim derivation, but finally realized they only show up there if they're listed in environment.systemPackages. I previously had no need to include them in that list. Now, I've added the haskell-vim plugin to systemPackages and everything works.

akavel commented 6 years ago

I encountered the same problem. As far as I understand the situation, this is a bug in vim-utils.nix (edit: in vim8/neovim's handling of packpath? see further below). Specifically, vim-utils.nix in nixpkgs currently contains the following fragment:

      ''
        set packpath-=~/.vim/after
        set packpath+=${packDir packages}
        set packpath+=~/.vim/after
      ''

where += means user plugins are appended at the end of packpath.

I checked the code of one of the top "classical" Vim plugin managers — Vundle — and it prepends the user plugins at the beginning of rtp, with ^= operator, as seen below:

  let paths = map(copy(g:vundle#bundles), 'v:val.rtpath')
  let prepends = join(paths, ',')
  let appends = join(paths, '/after,').'/after'
  exec 'set rtp^='.fnameescape(prepends)
  exec 'set rtp+='.fnameescape(appends)

(Only the explicitly named /after subdirectories of the plugins are appended to the list with +=; the default action is to prepend with ^=.)

The behavior of Vundle also matches the semantics of the default value of rtp (a.k.a. runtimepath) on Unix as specified in Vim manual:

default:
    Unix: "$HOME/.vim,
        $VIM/vimfiles,
        $VIMRUNTIME,
        $VIM/vimfiles/after,
        $HOME/.vim/after"

I was hit by this problem when trying to load the vim-go plugin into neovim. Unforutnately, its filetype plugin fails to load (disabling many important features of vim-go), because the ftplugin/go.vim file from $VIMRUNTIME is earlier in runtime path than vim-go's one, as a result of the above bug in vim_customizable a.k.a. vim-utils.nix.

EDIT: After some more digging, I believe this is actually just how neovim (and vim8?) behaves. I don't understand why it's so. I forked nixpkgs and changed the snippet to:

      ''
        set packpath^=${packDir packages}
      ''

but the plugin directories are still appended at the end of rtp, which makes them problematic to use for syntax and/or ftplugin functionality.

My current workaround is to add the following string at the beginning of my .vimrc:

    let
      loadPlugin = plugin: ''
          set rtp^=${plugin.rtp}
          set rtp+=${plugin.rtp}/after
        '';
    in ''
      " Workaround for broken handling of packpath by vim8/neovim for ftplugins
      filetype off | syn off
      ${builtins.concatStringsSep "\n"
        (map loadPlugin plugins)}
      filetype indent plugin on | syn on
    ''
Azulinho commented 6 years ago

@akavel workaround works for me, I got caught by this when my python-mode vim plugin wasn't working

here is my config, which works file: https://gitlab.com/myConfigs/nixpkgs/blob/master/home.nix

timokau commented 5 years ago

I created the issue https://github.com/NixOS/nixpkgs/issues/52722, which I now realize is a duplicate. I also created a report upstream, which may be interesting for the people here. If you have any additional info, please add it there: https://github.com/neovim/neovim/issues/9390

NickHu commented 5 years ago

I followed @akavel's workaround as per @Azulinho's example, but I kept getting errors about duplicate entries in the runtimepath. The fix is to not set packages.neovim-with-packages (it's unnecessary now anyway, as all we're building the runtimepath ourselves instead of relying on it to generate packpath).

tqbl commented 5 years ago

The problem here is that the default runtimepath includes $VIMRUNTIME already. Vim does appear to respect the order in packpath, but this doesn't matter because the paths will be appended to a runtimepath that is already non-empty. This is not a bug in vim (or neovim) as far as I'm concerned.

The good news is that directories can be included in the default runtimepath before $VIMRUNTIME: either via $VIM/vimfiles or RUNTIME_GLOBAL (and via ~/.vim, of course). RUNTIME_GLOBAL is not set by default, but can be during compile-time using the --with-global-runtime option. In any case, the set packpath code would no longer be needed in the vimrc.

I don't know if there's a clean way to do this if the interface is to be preserved. I managed to get things working with the --with-global-runtime option, but the code is ugly. I basically modified the configure function to first call vim.overrideAttrs before calling itself again with the result. I don't know how you would implement the 'vimfiles' approach; you would want to create a symlink in the original vim_configurable output path, but how?

Ultimately, I think breaking changes need to be made if we want clean code.

P.S. I believe this is also the cause of LnL7/vim-nix#11, to mentioned another issue.

timokau commented 5 years ago

This is not a bug in vim (or neovim) as far as I'm concerned

What do traditional plugin managers like vim-plug differently then?

tqbl commented 5 years ago

I'm not too familiar with the internals of these package managers, but, looking at vim-plug, it does quite a bit to ensure that the user-specified plugins are loaded correctly. The take-away is that it's modifying the runtimepath directly. If you want to go that route, akavel's solution (https://github.com/NixOS/nixpkgs/issues/39364#issuecomment-425536054) is nice and simple.

voidus commented 2 years ago

As far as I can tell, the fix only applied to vim proper, not neovim. Should that be a separate issue or can we re-open this? (Or am I holding it wrong?)