vim-plug icon indicating copy to clipboard operation
vim-plug copied to clipboard

is modular config for plug possible?

Open vitaly opened this issue 2 years ago • 23 comments

Is there a way to configure Plug in a modular way?

Right now the configuration for many plugins needs to be split into 2 pieces:

  • one or more lines defining the plugins with the Plug command. Those need to be between plug#begin and plug#end
  • if plugin configuration requires plugin to already be loaded, then it needs to go after plug#end.

I'd like to be able to do something like that:

call plug#begin('~/.vim/bundle')
" I'd like all related configuration to be in one single file
source a_module
source another_module
...

call plug#end()

I've looked around in many popular vim configurations but I didn't find a good solution to this problem.

Some (like I do) split 'module' configuration in 2 files, one sources in between plug commands, and another is sourced after. This works, but it's not clean and harder to maintain.

Others, like SpaceVim (I think), write the sourced files in a way that doesn't immediately define plugins or configuration, instead they define functions, that a centralized plugin manager can call later, e.g. call foo_define_plugins() and then after plug#end - call foo_configure(). this also works, but, imho, it's too much boilerplate for a simple problem to solve.

I've looked through plug's code, and 2 things looked like they could be the solution

first, the User pluginname autocommand. That one would be perfect, but it's only fired for lazy loaded plugins. if the plugin is loaded right away, the command is not fired.

another one is 'do' parameter of the Plug command, but this one only called on 'update'.

I'm thinking about using au VimEnter but that might be too late for some plugins configuration I think.

For me, the perfect solution would be for the User pluginname command to always be fired when a plugin loads, lazily or not.

I wander if I'm missing something? I'm not a vim expect, may be a perfectly simple solution already exists ;)

vitaly avatar Sep 26 '21 02:09 vitaly

For me, the perfect solution would be for the User pluginname command to always be fired when a plugin loads, lazily or not.

Same here - but there are other posts that have requested this and since it doesn't look like something the @junegunn is keen on, I've sort of cobbled something using VimEnter

  1. basically, register Plug 'something/somewhere', { 'on' : 'Vimenter'}
  2. Set up a command Vimenter and trigger it from the VimEnter Autocommand
  3. Plugin is loaded and user autocmd is triggered so you can then call the configuration script

It's clunky - you don't have the 'on' trigger anymore and I'd prefer something in built, but hey - it gets the job done.

raghur avatar Sep 26 '21 13:09 raghur

as I said, VimEnter is a bit too late ;)

vitaly avatar Sep 26 '21 18:09 vitaly

VimEnter is a bit too late

Why is it so? The existence of VimEnter is the very reason I decided not to fire User autocmd for non-lazily loaded plugins.

https://github.com/junegunn/vim-plug/issues/702#issuecomment-521839867

junegunn avatar Sep 27 '21 01:09 junegunn

Well, for starters, VimEnter happens after executing -c commands, so if you setup a command In a hook it won’t be available yet.

Also, let’s say I’ve setup a plug-in to lazy load, but then I want to try to load it eagerly. Now, apart from the lazy option itself, I need to change the hook to a different event, it just doesn’t feel clean, especially if the hook config is in a different place.

Also, if the loading happens after VimEnter already fired, e. g. after initial install, I’ll have 2 problems:

  1. The hook will fire but plug-in is not yet available.
  2. When plug-in is installed, the hook won’t fire again

vitaly avatar Oct 08 '21 11:10 vitaly

I'm using https://github.com/ouuan/vim-plug-config

ouuan avatar Oct 15 '21 08:10 ouuan

That would a be a really nice future to have. It's indeed cumbersome to keep hopping back and forth to load/unload/configure a plugin

kqvanity avatar May 27 '22 13:05 kqvanity

@junegunn Is there any plans to append such a feature?

kqvanity avatar Jun 07 '22 13:06 kqvanity

I'm not convinced. This would be useful only if you frequently turn on and off on/for options. But why? And how often do you do that?

Also, please note that I no longer recommend using lazy-loading options.

https://github.com/junegunn/vim-plug/wiki/faq#when-should-i-use-on-or-for-option

junegunn avatar Jun 08 '22 03:06 junegunn

I'm not convinced. This would be useful only if you frequently turn on and off on/for options. But why? And how often do you do that?

I'm not requesting this feature as I'm already using ouuan/vim-plug-config to solve this problem.

But how does this have anything to do with toggling on/for options? I don't get it.

ouuan avatar Jun 08 '22 03:06 ouuan

Because otherwise, you can just use VimEnter for non-lazily loaded plugins, and User for lazily loaded plugins.

junegunn avatar Jun 08 '22 04:06 junegunn

Actually, most plugins are customized via g:*** variables, so autocmds are usually not even necessary.

junegunn avatar Jun 08 '22 04:06 junegunn

It was explained in this thread above that VimEnter could be too late. And I think maybe it could be easier to organize the configs if they follow the same format (the same event) so that we don't need to care about whether a plugin is lazy-loaded or not (so that maybe this process could be automated?).

BTW, perhaps my expectation is different from this issue. I'm looking for a way to manage configs modularly, i.e. the issue title. But according to the issue body, the feature being requested is to make User pluginname always be fired when a plugin loads. It seems that the goal is to run commands after some plugins are loaded, which helps the OP to manage the configs modularly, but I personally don't need to run commands after some plugins are loaded. So the issue title may be misleading.

ouuan avatar Jun 08 '22 04:06 ouuan

This would be useful only if you frequently turn on and off on/for options.

In this case, It's only requested for modular configuration.

you can just use VimEnter for non-lazily loaded plugins

As others have raised some gotchas that has to do with this autocmd, i'm a bit skeptic about that.

kqvanity avatar Jun 08 '22 06:06 kqvanity

It was explained in this thread above that VimEnter could be too late

I can't think of a non-hypothetical scenario where that is really a problem. Can you give an example? For that to be an issue two conditions should be met.

  1. The plugin has to be configured via autoload functions (e.g. call foo#bar#configure({ 'level': 1 })) which can't be put between plug#begin and plug#end
  2. And you want to run a command from the plugin from the command-line (e.g. vim -c Foobar)

As I mentioned above, most plugins are configured via g:* variables, and I rarely run a plugin command using -c on the command-line, so I haven't run into such a case yet.

But if you do have such a problem, so you can't use VimEnter, you can still set up a custom User autocmd and trigger it after plug#end() to defer the execution.

call plug#begin()
autocmd! User after_plug

Plug 'foo/bar'
  autocmd User after_plug call bar#bar()

Plug 'foo/baz'
  autocmd User after_plug call baz#baz()


call plug#end()
doautocmd User after_plug

junegunn avatar Jun 08 '22 06:06 junegunn

The modular config feature may be useful for some people(heavy users). But it increases complexity. I think it is opposite of vim-plug's policy.

If you really need the feature and you have heavy plugin config, you should use other plugin manager.

Shougo avatar Jun 08 '22 07:06 Shougo

call plug#begin()
autocmd! User after_plug

Plug 'foo/bar'
  autocmd User after_plug call bar#bar()

Plug 'foo/baz'
  autocmd User after_plug call baz#baz()

call plug#end()
doautocmd User after_plug

That seems like a lot of boilerplate. was hoping for a more straightforward solution.

Still, If what @Shougo mentioned

If you really need the feature and you have heavy plugin config, you should use other plugin manager.

would be the case, and ultimately settle for this way, then please close this issue.

kqvanity avatar Jun 08 '22 08:06 kqvanity

That seems like a lot of boilerplate

I don't think so. The autocmd! can be removed, and we are left with only one extra doautocmd. I wouldn't say that's "a lot".

hoping for a more straightforward solution

I don't agree with this either. How is autocmd! User bar more straightforward than autocmd User after_plug_or_whatever_name_you_prefer? To understand what autocmd! User bar does, you have to know in advance that a User autocmd with the name of a plugin is triggered by vim-plug when it's loaded. On the other hand, the suggested method is very straightforward if you know about autocmd functionality which is a standard, built-in feature of vim itself you use with or without vim-plug.

junegunn avatar Jun 08 '22 13:06 junegunn

Can you please give a real life example of this hack. I'm sorry, but i can't really follow along. I already looked doautocmd and User up, and it seems development-centric. I just ask for another syntactic entity like for instance plug-after, and simply place it anywhere outside of the begin and end block.

kqvanity avatar Jun 08 '22 14:06 kqvanity

Can you please give a real life example of this hack

I don't know what exactly you mean by "this hack", but you can find autocmds in my vimrc all over the place. If you're not willing to learn how to use it, you're missing out.

junegunn avatar Jun 09 '22 04:06 junegunn

Actually, most plugins are customized via g:*** variables, so autocmds are usually not even necessary.

@junegunn, I believe this is actually no longer the case if you are using neovim and a bunch of lua-based plugins that would require require("nvim-plugin").setup { ... } to initialize. For these plugins, customization happens after the plugin is loaded. So I would say autocmd seems the only way at this point to implement post-loading hook functionality (#702).

A more straightforward solution @z0xyz might have implied in their comment might be something like #702, Plug foo/bar, {'post': ':lua blahblah'}, which I think is easier for beginners to use than autocmds.

As in @z0xyz's case, if one wants to execute such plugin config code, their execution needs to be deferred after the plugin has been loaded, otherwise lua modules cannot be imported. So you'll need autocmds to execute the plugin configs: autocmd VimEnter lua ... -- I as well have a bunch of such autocmds in my config. @z0xyz may want to have a look at this yet another real-life example. Note that they are not required to be put inside the plug#begin..end block.

In my use cases, I would want to lazy-load some heavy lua-based plugins (such as treesitters or nvim-cmp) to make the startup time even faster. So I agree that a consistent post-load hook mechanism would be a great feature to have.

wookayin avatar Jun 09 '22 05:06 wookayin

I've looked through plug's code, and 2 things looked like they could be the solution

first, the User pluginname autocommand. That one would be perfect, but it's only fired for lazy loaded plugins. if the plugin is loaded right away, the command is not fired.

another one is 'do' parameter of the Plug command, but this one only called on 'update'.

I'm thinking about using au VimEnter but that might be too late for some plugins configuration I think.

For me, the perfect solution would be for the User pluginname command to always be fired when a plugin loads, lazily or not

I propose the following event at the end of plug#end():

+++ plug.vim    2022-06-22 13:57:57.363352027 -0600
@@ -429,8 +429,9 @@
     end
   else
     call s:reload_plugins()
   endif
+  call s:doautocmd('User', 'VimPlugPluginsLoaded')
 endfunction

 function! s:loaded_names()
   return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')

The proposed event should work both when Vim starts and with commands like :PlugInstall!.

  1. VimEnter won't work after :PlugInstall!,
  2. That custom User after_plug example won't work after :PlugInstall!.
  3. The lazy-load User pluginname won't work after a :PlugInstall! without a timer, due to the s:reload_plugins() call. Hopefully the timer callback is after s:reload_plugins() had finished, this is a race-condition.

In my use-case, I have one plugin which depends on another plugin being loaded (i.e. s:reload_plugins() finishing) prior to doing some things. The proposed User VimPlugPluginsLoaded event works for my need.

Here is an minimal example vimrc of the User pluginname, lazy-load, timer_start, messy workaround. PostBuild() being called verifies that asyncrun.vim hadn't been reloaded by s:reload_plugins() after AsyncRun had started (which breaks AsyncRun):

set nocompatible

function! PostBuild()
	echom "do post build stuff"
endfunction

function! PrimeBuild(info)
	echom 'Vim-Plug Post-Update... setup things for AsyncRun make'
	autocmd User asyncrun.vim ++once execute('AsyncRun -mode=term -pos=tab -post=call\ PostBuild() @ echo "do stuff on terminal like make"')
	call timer_start(2000, { -> plug#load('asyncrun.vim') })
endfunction

call plug#begin()
Plug 'skywind3000/asyncrun.vim', { 'on': [] }
Plug 'yaml/yaml-schema', { 'do': function('PrimeBuild') }
call plug#end()

Run the above example vimrc with :PlugInstall! or :PlugInstall! yaml-schema.

Note: yaml/yaml-schema is just a placeholder repository in this minimal example, it isn't meaningful, you can replace with any other Vim plugin, I just picked a repo with one file to make things minimal and showcase the issue.

EDIT: To be crystal clear, the proposed vim-plug event makes it so the lazy-load and timer are not required and has no race-condition! Here is a patch making the change to the minimal example above:

@@ -6,11 +6,10 @@

 function! PrimeBuild(info)
        echom 'Vim-Plug Post-Update... setup things for AsyncRun make'
-       autocmd User asyncrun.vim ++once execute('AsyncRun -mode=term -pos=tab -post=call\ PostBuild() @ echo "do stuff on terminal like make"')
-       call timer_start(2000, { -> plug#load('asyncrun.vim') })
+       autocmd User VimPlugPluginsLoaded ++once execute('AsyncRun -mode=term -pos=tab -post=call\ PostBuild() @ echo "do stuff on terminal like make"')
 endfunction

 call plug#begin()
-Plug 'skywind3000/asyncrun.vim', { 'on': [] }
+Plug 'skywind3000/asyncrun.vim'
 Plug 'yaml/yaml-schema', { 'do': function('PrimeBuild') }
 call plug#end()

EDIT2: On thinking about this more, Shougo is right regarding the complexity need. I chose to migrate to using the dein.vim (and dein-ui.vim) package manager as it has hooks/complexity that meet my needs, which is different from the minimalist vim-plug philosophy.

rene-descartes2021 avatar Jun 22 '22 20:06 rene-descartes2021

Note that they are not required to be put inside the plug#begin..end block.

I mean the initial plugin inclusion still has be enclosed within the begin&end function calls, which doesn't really serve the purpose of having a totally separate interrelated chunks i.e generic settings, generic mapping, and plugin inclusion & its configuration.

The former solution of having virtually everything within the begin&end block, with the after-loading settings being deferred by prefixing them with autocmd User, does work, but as i've already said, i was hoping for a new plugin-inclusion syntactic sugar, by which one can include any plugin anywhere, irrespective of the begin&end block.

kqvanity avatar Jun 24 '22 14:06 kqvanity

One clarification.

Plugins are not actually loaded on plug#end() when Vim is starting. plug#end() only puts the plugin directories to &runtimepath so Vim, not vim-plug, can load them after processing the whole vimrc. (See :help load-plugins.)

1. Set the 'shell' and 'term' option
2. Process the arguments
3. Execute Ex commands, from environment variables and/or files
4. Load the plugin scripts.
5. Set 'shellpipe' and 'shellredir'
6. Set 'updatecount' to zero, if "-n" command argument used
7. Set binary options
8. Perform GUI initializations
9. Read the viminfo file
10. Read the quickfix file
11. Open all windows
12. Execute startup commands

You can only call autoload functions right after plug#end(). Commands or non-autoload functions are not available at the point.

# Doesn't work
# E492: Not an editor command: FZF
vim -Nu <(cat << EOF
call plug#begin()
Plug 'junegunn/fzf'
call plug#end()
FZF
EOF
)

# Doesn't work either. fzf#run() is not an autoload function.
vim -Nu <(cat << EOF
call plug#begin()
Plug 'junegunn/fzf'
call plug#end()
call fzf#run(fzf#wrap())
EOF
)

# This works
vim -Nu <(cat << EOF
call plug#begin()
Plug 'junegunn/fzf'
call plug#end()
EOF
) -c FZF

junegunn avatar Jul 15 '22 09:07 junegunn

I'm going to close this.

If you need to run some autoload functions to configure a plugin, but you want to specify that before plug#end() for readability of the configuration file, use VimEnter autocmd to defer the execution.

call plug#begin()

" autoload functions are available after plug#end(). You can defer the execution using VimEnter autocmd
Plug 'junegunn/vim-after-object'
  autocmd VimEnter * silent! call after_object#enable('=', ':', '#', ' ', '|')

" Of course you can source an external file
Plug 'junegunn/fzf'
  autocmd VimEnter * source fzf-configuation.vim

call plug#end()

In rare cases where you find VimEnter is too late for your purpose (e.g. you want to start Vim with -c commands), use a custom User autocmd as suggested above.

[!NOTE] vim-plug doesn't load non-lazy plugins by itself, it only updates the &runtimepath and the loading just happens afterward, after Vim has finished processing your Vim configuration file, not right after plug#end(). So there is no direct way to trigger some code after the plugins are loaded. If we want to do that, we would need to rely on an after/plugin script, but that is not possible because vim-plug is distributed as a single autoload script.

junegunn avatar Mar 29 '24 05:03 junegunn