0

I use quite a number of plugins now and the init.vim script for nvim has become quite unwieldy. So I started separating out the individual configuration files for each individual plugin.

And now I realized that sometimes I want to switch some plugins off for testing, or to try out an alternative, etc, without having to comment out the sourcing of the plugin configuration in my init file or renaming or deleting the configuration file.

So far I use a structure like this

if filereadable(expand("~/.vim/coc.vim"))
    source ~/.vim/coc.vim
endif

I would instead like to use a function that as a string the name of a plugin and returns true or false if the plugin is loaded or not.

I know that I can use scriptnames to list all sourced files. So my idea was to run over that list and match each line to the provided string.

I am however very inexperienced with vimscript and don't know how to do that.

Here is what I have so far

function! IsPluginLoaded(pluginName)
    redir =>scripts
        silent scriptnames
    redir END
    for script in split(scripts, "\n")
        if match(script, "^\d+: ~/\.vim/plugged/" . a:pluginName . "/") >= 0
            return v:true
        endif
    endfor
    return v:false
endfunction

But it gives me a weird error of E874: (NFA) Could not pop the stack!. It also doesn't work, as in it always returns false.

How do I accomplish the task of checking whether a plugin is loaded or not and preferably in a readable format that doesn't consist of vimscript shorthands that look worse than regular expressions.

I would use lua, but the script should also work for standard vim.

EDIT

I have found out that the source of the error is that "~" has a special meaning. I have updated the function to be more readable by putting the regular expression in a separate variable and now the error is gone, however it is still not returning true, even though it should match the regex.

function! IsPluginLoaded(pluginName)
    redir =>scripts
        silent scriptnames
    redir END

    let re = "^ *\\d+: \\~/\\.vim/plugged/" . a:pluginName . "/"
    " echo "re: \"" . re . "\"\n"
    for script in split(scripts, "\n")
        " echo "script: \"" . script . "\"\n"
        if match(script, re) >= 0
            return v:true
        endif
    endfor
    return v:false
endfunction

4 Answers 4

2

I would use :runtime instead of source. No need to check whether the file exists. On the subject see: Includes in vimrc?

Note that you can also imagine a command that:

  1. do the Plug stuff
  2. load your configuration file if any.

Something like (untested)

command! -nargs=+ RegisterPlugin call s:register_plugin(<args>)

function! s:register_plugin(plugin_name, ...) abort
    echo "Plug ".string(a:plugin_name). ', ' .join(a:000, ' ')
    call call('plug#', [a:plugin_name]+ a:000)

    " See plug.vim on how to extract a simple and valid name...
    let conf_name = matchstr(a:plugin_name, '/\zs[^/]*$')
    let conf_file = printf("configs/%s.vim", conf_name)
    echo 'runtime '..conf_file
    exe 'runtime '..conf_file
endfunction

RegisterPlugin 'whatever/pluginname'
RegisterPlugin 'preservim/nerdtree', { 'on': 'NERDTreeToggle' }
RegisterPlugin 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
1

The "loaded" notion is quite a bit more complex than what you are envisioning.

First, the effect of sourcing a vimscript can only be reverted explicitly, by letting every variable or option to its prior value or unletting it, removing every mapping, every function, every autocommand, etc. and potentially going over every window and buffer for local options and variables. There is no standard, de-facto or written for that so, if the vimscript in question doesn't provide the means to do that, then you will have a VERY hard time achieving anything.

Filetype plugins are supposed to have a b:undo_ftplugin commmand but a) I wouldn't count on every plugin author to define it and b) it doesn't have a counterpart for syntax and indent scripts anyway, so it won't be very useful to you.

Plugins (as in plugin/foobar.vim) are also supposed to check and define a g:loaded_foobar variable but a) I wouldn't count on it either, b) its semantics are completely different from b:undo_ftplugin, and c) it is restricted to "plugins" anyway, so syntax and indent scripts once again go through the net.

Second, that a given file exists or not in a given directory is not conclusive enough to decide that a given plugin is "loaded" or not. For example, having let g:loaded_foobar = 1 in one's vimrc prevents Vim from sourcing plugin/foobar.vim but it has no effect on ftplugin/foobar.vim or syntax/foobar.vim.

Third, the sometimes large plugins that provide support for one language or another often don't have a main "plugin" but come with various scripts that will be sourced at any time, if ever. Those won't be easy to catch.

Fourth, :help 'runtimepath' might be more useful in a "modern" setting for finding potentially "loadable" plugins, but you will inevitably circle back to how hard it is to "unload" a Vim plugin or even prevent it from "loading".

5
  • I don't want to unload a plugin. I just want to source a user configuration file for that plugin if a plugin is loaded and skip that if a plugin is not loaded.
    – FalcoGer
    Jul 10, 2023 at 17:37
  • My point is that you can't know if a plugin is "loaded" or not in a reliable manner. The term itself is too ill-defined for that.
    – romainl
    Jul 10, 2023 at 17:39
  • I guess I'll just add a | let g:user_loaded_<pluginname> = 1 after every Plug statement and check if that exists or not.
    – FalcoGer
    Jul 10, 2023 at 17:56
  • b:undo_indent is documented, I believe
    – D. Ben Knoble
    Jul 11, 2023 at 12:27
  • Yes, you are right.
    – romainl
    Jul 11, 2023 at 12:29
0

Turns out the regular expression was wrong.

function! IsPluginLoaded(pluginName)
    redir =>scripts
        silent scriptnames
    redir END
    let pluginName = substitute(a:pluginName, "\\.", "\\\\.", "g")
    let re = "^ *\\d\\+: \\~/\\.vim/plugged/" . pluginName . "/"
    "echo "re: \"" . re . "\"\n"
    for script in split(scripts, "\n")
        " echo "script: \"" . script . "\"\n"
        if match(script, re) == 0
            return v:true
        endif
    endfor
    return v:false
endfunction

Though this seems very unreliable.

2
  • Using getscriptinfo() instead of :scriptnames might be a better solution: getscriptinfo()->map({_, v -> v.name})->filter({_, v -> v =~ '/' .. a:pluginName .. '/'})->len() > 0 – this was added in Vim 9.0.0303 from August 2022. Jul 10, 2023 at 20:04
  • Note that calling this function repeatedly is a bit inefficient; if you notice startup time issues, my guess would be this is one culprit.
    – D. Ben Knoble
    Jul 11, 2023 at 12:28
0

I now set a variable at the end of the plugin line and check it's existence.

Plug 'someguy/somerepo.vim' | let g:user_loaded_somerepo = 1

" ...

if exists("g:user_loaded_somerepo")
  source ~/.vim/config_for_somerepo.vim
endif

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.