Skip to content

bug: No autoload on require when username is lua #1981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
4 tasks done
abeldekat opened this issue Apr 19, 2025 · 7 comments
Open
4 tasks done

bug: No autoload on require when username is lua #1981

abeldekat opened this issue Apr 19, 2025 · 7 comments
Labels
bug Something isn't working

Comments

@abeldekat
Copy link
Contributor

abeldekat commented Apr 19, 2025

Did you check docs and existing issues?

  • I have read all the lazy.nvim docs
  • I have updated the plugin to the latest version before submitting this issue
  • I have searched the existing issues of lazy.nvim
  • I have searched the existing issues of plugins related to this issue

Neovim version (nvim -v)

0.11.0

Operating system/version

Arch linux

Describe the bug

user_is_lua.mp4

When the username is "lua", lazy.nvim silently fails to autoload a plugin onrequire

Steps To Reproduce

Setup:

sudo useradd -m -c "Lua test user" lua
sudo su lua
cd
mkdir -p .config/nvim
# ... cp the supplied repro...
nvim init.lua
# ... wait for the installation to complete ...
nvim init.lua

Reproduce:
Type: :lua require("lualine") and enter

Teardown:

exit
sudo userdel lua
rm -rf /home/lua

Expected Behavior

Normally, given the supplied repro, lualine would appear after typing :lua require("lualine") and enter.

Repro

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()

require("lazy.minit").repro({
  spec = {
    { "nvim-lualine/lualine.nvim", lazy = true, opts = {} },
  },
})
@abeldekat
Copy link
Contributor Author

abeldekat commented Apr 19, 2025

See failed to run config for lualine.nvim on starting NeoVim in LazyVim, and perhaps this comment in trouble.nvim.

@abeldekat
Copy link
Contributor Author

I unfortunately had to close PR #1982, as it broke existing behavior...

@dpetka2001
Copy link
Contributor

dpetka2001 commented Apr 20, 2025

Hey @abeldekat I thought of something but not sure at all if it works. Could you try something along these lines

diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua
index 1501efd..bdcbc38 100644
--- a/lua/lazy/core/loader.lua
+++ b/lua/lazy/core/loader.lua
@@ -529,7 +529,7 @@ function M.colorscheme(name)
 end
 
 function M.auto_load(modname, modpath)
-  local plugin = Plugin.find(modpath, { fast = not M.did_handlers })
+  local plugin = Plugin.find(modpath, modname, { fast = not M.did_handlers })
   if plugin then
     plugin._.rtp_loaded = true
     -- don't load if:
diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua
index 37d1a8f..2981785 100644
--- a/lua/lazy/core/plugin.lua
+++ b/lua/lazy/core/plugin.lua
@@ -384,13 +384,16 @@ end
 
 -- Finds the plugin that has this path
 ---@param path string
+---@param modname? string
 ---@param opts? {fast?:boolean}
-function M.find(path, opts)
+function M.find(path, modname, opts)
   if not Config.spec then
     return
   end
   opts = opts or {}
-  local lua = path:find("/lua/", 1, true)
+  local topmod = Util.topmod(modname)
+  local modlocation = path:find(topmod, 1, true)
+  local lua = path:find("/lua/", modlocation + 1, true)
   if lua then
     local name = path:sub(1, lua - 1)
     local slash = name:reverse():find("/", 1, true)

So, basically change the M.find signature to also accept modname since we can already pass it from M.auto_load into it and then just find the topmod location and search for the first /lua/ after that.

Please keep in mind I might just be spewing total non-sense.

PS: I didn't write the extra conditional check for if modname exists or not, but I hope you get the gist. But like I said maybe it's just non-sense.

PS2: I made wrong assumption that topmod would be equivalent to plugin.name, but that's not true. So, maybe we would like to find the first /lua/ in reverse after topmod maybe? That should be the top lua directory inside the plugins.

@abeldekat
Copy link
Contributor Author

Hi @dpetka2001, thanks for the suggestions!

I created the PR under the incorrect assumption that the text "lua" would by convention only be used in the root level of a plugin. Unfortunately I do not know the code well enough to provide or comment on alternatives.

@dpetka2001
Copy link
Contributor

Hi @abeldekat would you be willing to test the following patch

diff --git a/lua/lazy/core/loader.lua b/lua/lazy/core/loader.lua
index 1501efd..bdcbc38 100644
--- a/lua/lazy/core/loader.lua
+++ b/lua/lazy/core/loader.lua
@@ -529,7 +529,7 @@ function M.colorscheme(name)
 end
 
 function M.auto_load(modname, modpath)
-  local plugin = Plugin.find(modpath, { fast = not M.did_handlers })
+  local plugin = Plugin.find(modpath, modname, { fast = not M.did_handlers })
   if plugin then
     plugin._.rtp_loaded = true
     -- don't load if:
diff --git a/lua/lazy/core/plugin.lua b/lua/lazy/core/plugin.lua
index 37d1a8f..0ac2bbf 100644
--- a/lua/lazy/core/plugin.lua
+++ b/lua/lazy/core/plugin.lua
@@ -384,23 +384,44 @@ end
 
 -- Finds the plugin that has this path
 ---@param path string
+---@param modname? string
 ---@param opts? {fast?:boolean}
-function M.find(path, opts)
+function M.find(path, modname, opts)
   if not Config.spec then
     return
   end
   opts = opts or {}
-  local lua = path:find("/lua/", 1, true)
-  if lua then
-    local name = path:sub(1, lua - 1)
-    local slash = name:reverse():find("/", 1, true)
-    if slash then
-      name = name:sub(#name - slash + 2)
-      if name then
-        if opts.fast then
-          return Config.spec.meta.plugins[name]
+  if modname then
+    local topmod = Util.topmod(modname)
+    local reversed = path:reverse()
+    local topreversed = topmod:reverse()
+    local topreversed_loc = reversed:find(topreversed, 1, true)
+    local rev_start, rev_end = reversed:find("/aul/", topreversed_loc + 1, true)
+    if rev_start then
+      local rev_next_slash = reversed:find("/", rev_end + 1, true)
+      if rev_next_slash then
+        local name = path:sub(#path - rev_next_slash +2, #path - rev_end)
+        if name then
+          if opts.fast then
+            return Config.spec.meta.plugins[name]
+          end
+          return Config.spec.plugins[name]
+        end
+      end
+    end
+  else
+    local lua = path:find("/lua/", 1, true)
+    if lua then
+      local name = path:sub(1, lua - 1)
+      local slash = name:reverse():find("/", 1, true)
+      if slash then
+        name = name:sub(#name - slash + 2)
+        if name then
+          if opts.fast then
+            return Config.spec.meta.plugins[name]
+          end
+          return Config.spec.plugins[name]
         end
-        return Config.spec.plugins[name]
       end
     end
   end

@abeldekat
Copy link
Contributor Author

I think it might work, and thus still isn't good enough...)

It's a tough issue to fix in my opinion. I gathered some of my thoughts, which of cause still suffer from a lack of deep inside knowledge...

Markers in the code:
1 loader
2 autoload
3 find

A plugin on disk has the following path: Outside/N/"lua"/Inner, where:

  • Outside: The outside path, like /home/user/.local/share/nvim/lazy
  • N: The name of the plugin
  • "lua": Neovim specific entrypoint
  • Inside: The organization on the inside, like blink/cmp/fuzzy/lua/match_indices.lua

The existing code for "find" implicitly assumes that there is no "lua" folder in Outside. As a consequence, no assumptions are needed on the organization in Inside. The code can discard that tail when trying to find the name of the plugin.

My PR, and your code as well, now does make assumptions on Inside, because unfortunately, a user can be named "lua" in Outside. Other situations I can think of:

  • Perhaps NVIM_APPNAME could also be "lua"
  • The user overrides directory locations via the XDG spec.
  • Plugins can be configured to be located in a user defined directory.

When an assumption on Inside does not hold true that might lead to bugs that are very difficult to solve.
In your code for example, the topmod for blink.cmp becomes blink. The algorithm only works because there is no other nested "blink" in Inside...

A good fix would eliminate assumptions. As suggested by @Phanen:

local lua = path:trim_rtp():find("/lua/", 1, true)

A solution will need to be performant.

Closing the issue as a "known limitation" is also a possibility perhaps.

@dpetka2001
Copy link
Contributor

Thank you for your insight. Indeed if there's another blink nested in the modname, then it would not actually find the correct location of the directory top module. It also might or not find the top /lua/ directory depending if there's another /lua/ directory before the nested blink module (if there's no /lua/ directory before the nested blink module then it would correctly find the Neovim /lua/ entry point).

But I agree that we make too many assumptions and a better solution should be in place if at all. This all started from a rather niche case of the username being lua, so it wouldn't surprise me if Folke just dismissed it as a known limitation as you say.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants