In this blog post, I originally had come up with a one-off idea for how Lazy.nvim could add this new field to handle what I called implicit dependencies, but Folke pointed out a better way this could be done. I edited the original article, but wanted to preserve the idea here for anyone that wanted to reference it.

Original Snippet

With this as our new base, some of our pseudo dependency relationships become inverted. Instead of [cmp] depending on [cmp-path], now [cmp-path] will list [cmp] as one of its dependencies, and when [cmp-path] is loaded, it will call into [cmp]‘s config() function to register itself as a source. The only challenge here is that we need a new way to express that [cmp-path]‘s load has to run before [cmp]‘s load.

If we’re using Lazy.nvim, I could imagine a new field available in our specifications called modifies. Similar to dependencies, modifies would take a string or list of strings corresponding to plugin names (e.g. "hrsh7th/nvim-cmp"). A plugin spec that looks like:

{
    {
        "hrsh7th/cmp-path",
        modifies = "hrsh7th/nvim-cmp",
    },
    {
        "hrsh7th/nvim-cmp",
        event = "InsertEnter",
        opts = function()
          vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })
          local cmp = require("cmp")
          local defaults = require("cmp.config.default")()
          return {
            snippet = {
              expand = function(args)
                require("luasnip").lsp_expand(args.body) -- Explicit dependency
              end,
            },
            mapping = cmp.mapping.preset.insert({
              -- mappings omitted for brevity
            })
            -- note the lack of the `sources` field
          }
        end,
    },
}

With this configuration, when the InsertEnter event fires:

  1. The opts field for our nvim-cmp spec is passed to nvim-cmp.config()
  2. The spec for cmp-path is evaluated, and in the plugins load function, nvim-cmp.config({ sources = cmp.config.sources({ { name = "path" } }) }) is called.
  3. nvim-cmp.load() is called.

This let’s us keep our lazy-loading goodness, and separates out configuration from initialization without resorting to unwieldy and hard to manage global state. It also means that, even if we completely remove our specification for [cmp], our configuration wouldn’t break since it still has to be installed for [cmp-path] to work (except now [cmp] would be using its new default configuration). Creating sensible defaults becomes the de-facto responsibility of the plugin authors. Personally, I see this as a benefit to the community, if slightly more work for authors.