junegunn / vim-plug

:hibiscus: Minimalist Vim Plugin Manager
https://junegunn.github.io/vim-plug/
MIT License
34.14k stars 1.93k forks source link

is modular config for plug possible? #1134

Closed vitaly closed 7 months ago

vitaly commented 3 years ago

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:

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 ;)

raghur commented 3 years ago

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.

vitaly commented 3 years ago

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

junegunn commented 3 years ago

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

vitaly commented 3 years ago

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
ouuan commented 3 years ago

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

kqvanity commented 2 years ago

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 commented 2 years ago

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

junegunn commented 2 years ago

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

ouuan commented 2 years ago

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.

junegunn commented 2 years ago

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

junegunn commented 2 years ago

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

ouuan commented 2 years ago

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.

kqvanity commented 2 years ago

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.

junegunn commented 2 years ago

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
Shougo commented 2 years ago

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.

kqvanity commented 2 years ago
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.

junegunn commented 2 years ago

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.

kqvanity commented 2 years ago

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.

junegunn commented 2 years ago

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.

wookayin commented 2 years ago

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.

rene-descartes2021 commented 2 years ago

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.

kqvanity commented 2 years ago

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.

junegunn commented 2 years ago

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 commented 7 months ago

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.