Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

seblyng
Copy link

@seblyng seblyng commented Sep 4, 2025

Problem:

The user is required to call setup() even if they are perfectly happy with the default options. Also, requiring every user to lazy load is annoying, and also requires that the plugin manager the user is using is supporting this out of the box

Solution:

Make setup optional, and lazy load the plugin automatically

Context:
Neovim has recently added some documentation of developing a lua plugin with some "best practices" https://github.com/neovim/neovim/blob/master/runtime/doc/lua-plugin.txt

In the Initialization section, it is recommended to allow users to opt out of calling setup if the default options are working perfectly fine. For me they are working perfectly fine, and I agree that calling setup is unnecessary in this case.

They also have a section talking about lazy loading, and making a case for this to be handled by the plugin instead of the user themselves. I agree with this, and saw a good potential to automatically do it here😄

return config
end

local current_config = build_config(M.schema)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build the config once to be able to just deep_extend the user_config into the current config. This should make it possible to call setup multiple times without having any side effects on the users config option


core.update()
state.cfg.on_attach(util.current_buf())
state.cfg.on_attach(require("crates.util").current_buf())
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lazy require the util module until it is actually needed


vim.api.nvim_create_autocmd("BufRead", {
pattern = "Cargo.toml",
once = true,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create an autocmd that is fired once upon the first entry in a Cargo.toml file. This shouldn't change any option in case users have setup the plugin before this. Also, if the user calls setup after this, it should properly update the options accordingly

Improves setup time with a couple of milliseconds :)
@@ -1,41 +1,37 @@
local actions = require("crates.actions")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline these wrapped around a function to lazily require the modules😅 This seems to be improve the setup time from around 3ms to 0.5 ish for me

This is now handled by the plugin itself, so no need for users to lazy
load themselves
@saecki
Copy link
Owner

saecki commented Sep 6, 2025

Hey, thanks for this PR! I'll have to give this some thought.
I'm generally not that big a fan of lazy loading everything, since it can hide problems until the plugin is actually loaded. Though this PR generally seems quite reasonable.
I'm also not quite sure I want to make calling setup optional. I've actually removed the default setup from this plugin a while ago (ab8b2d6), because I like the extra control it provides.

Copy link
Owner

@saecki saecki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one thing I've noticed.

local config = build_config(M.schema, user_config)
if config.neoconf.enabled then
return setup_neoconf(config)
current_config = vim.tbl_deep_extend("force", current_config, user_config)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail in some cases. For example if a scalar is provided by the user instead of a table, the config will now be invalid, because the default table will be overwritten.

Also unless I'm forgetting something the user config was never mutated to begin with, or did you hit some case where calling setup wasn't idempotent?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail in some cases. For example if a scalar is provided by the user instead of a table, the config will now be invalid, because the default table will be overwritten.

A few lines above it ensures that the user_config is a table, so I don't think that should be a problem

Also unless I'm forgetting something the user config was never mutated to begin with, or did you hit some case where calling setup wasn't idempotent?

The reason I changed it was because if calling setup once with for example { smart_insert = false }, and then again calling setup with {}, I would prefer it to still be { smart_insert = false }. This was not the case because build_config always preferred the default value over any missing values in the user provided config, and did not take into account the potential previous call to setup

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few lines above it ensures that the user_config is a table, so I don't think that should be a problem

I was talking about a nested table so for example { lsp = true } would overwrite the complete lsp configuration table.

The reason I changed it was because if calling setup once with for example { smart_insert = false }, and then again calling setup with {}, I would prefer it to still be { smart_insert = false }. This was not the case because build_config always preferred the default value over any missing values in the user provided config, and did not take into account the potential previous call to setup

Oh, I see. Maybe we could just extend the previous config before passing it into the build_config function. That way the config is still validated.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was talking about a nested table so for example { lsp = true } would overwrite the complete lsp configuration table.

Hmm good point. It should still use the default value in this case like the warning is saying that it does? Another option (maybe a bit hard) would be to just error instead of giving a warning

Oh, I see. Maybe we could just extend the previous config before passing it into the build_config function. That way the config is still validated.

Yeah I was thinking about that as well, but that would give a lot of warning about the user using deprecated options etc since the user_config is now the full config instead of only what the user provided🤔

@seblyng
Copy link
Author

seblyng commented Sep 7, 2025

I'm generally not that big a fan of lazy loading everything, since it can hide problems until the plugin is actually loaded. Though this PR generally seems quite reasonable.

I generally agree with this myself, but thought maybe it would make sense for this plugin

I'm also not quite sure I want to make calling setup optional. I've actually removed the default setup from this plugin a while ago (ab8b2d6), because I like the extra control it provides.

Interesting. Can you elaborate on why you don't necessarily like automatic setup? I didn't previously give the widely adopted setup function much thought, but I have been thinking about this a bit lately, and I very much agree with the documentation in neovim now discouraging that when there is no required settings to set for the plugin to work.

With lazy.nvim I can see that it generally is not so annoying to just add a config = true or opts = {} in the package spec, but for all other package managers, and also the builtin package manager, it is a bit annoying having to for example do something like this:

vim.pack.add({
    "https://github.com/saecki/crates.nvim",
})

require("crates").setup({})

instead of just doing this

vim.pack.add({
    "https://github.com/saecki/crates.nvim",
})

when I am perfectly happy with the default options this plugin provides

@saecki
Copy link
Owner

saecki commented Sep 11, 2025

Interesting. Can you elaborate on why you don't necessarily like automatic setup? I didn't previously give the widely adopted setup function much thought, but I have been thinking about this a bit lately, and I very much agree with the documentation in neovim now discouraging that when there is no required settings to set for the plugin to work.

I just had cases where I wanted to do some stuff before a plugin ran it's setup. Granted some of those we're hacky things, but the automatic setup was kind of in the way.
I don't really see the problem of adding literally one additional line of configuration code that allows you take control over when the plugin is initialized. Also all of the work done in setup has to be done twice if the user chooses to change the defaults and call setup again with their own options.
Funny enough, there seem to be people agreeing with me: neovim/neovim@0c49167

@seblyng
Copy link
Author

seblyng commented Sep 12, 2025

I just had cases where I wanted to do some stuff before a plugin ran it's setup. Granted some of those we're hacky things, but the automatic setup was kind of in the way.

Wouldn't it be possible to either just use init (if you are using lazy as the package manager), or just configure this before setting it up in lazy?

I don't really see the problem of adding literally one additional line of configuration code that allows you take control over when the plugin is initialized.

It can be debated the other way around as well. I don't see why I have to refer to the plugin in multiple places if the plugin doesn't require any options to work

Also all of the work done in setup has to be done twice if the user chooses to change the defaults and call setup again with their own options.

Yes but it's really not that much work

No problem if you don't want this. I am just looking to migrate to vim.pack.add at some point and very much agree that it is quite unnecessary to have to call setup if the plugin works perfectly without that. It's your project, and I would totally respect it if you disagree with this😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants