Repository: nvim-lua/plenary.nvim (lua)

Issue:
Plenary Callback issue in dependent plugin
Hey all,

If there is documentation to write better issues for y'all, feel free to send that along and I can clean up this issue. In the meantime, I've been directed to open an issue here as opposed to with https://github.com/CopilotC-Nvim/CopilotChat.nvim since the issue I am encountering is from plenary.

You can find the issue thread here:
https://github.com/CopilotC-Nvim/CopilotChat.nvim/issues/325#issue-2306083916

Surfacing the issue to this thread below: 

When calling this plugin's main function `:CopilotChat <input>` the following is received: 
```....local/share/nvim/lazy/plenary.nvim/lua/plenary/curl.lua:41: E5560: vimL function must not be called in a lua loop callback
stack traceback:
	[C]: in function 'has'
	....local/share/nvim/lazy/plenary.nvim/lua/plenary/curl.lua:41: in function 'headers'
	....local/share/nvim/lazy/plenary.nvim/lua/plenary/curl.lua:241: in function 'request'
	....local/share/nvim/lazy/plenary.nvim/lua/plenary/curl.lua:277: in function 'post'
	...e/nvim/lazy/CopilotChat.nvim/lua/CopilotChat/copilot.lua:408: in function 'on_done'
	...e/nvim/lazy/CopilotChat.nvim/lua/CopilotChat/copilot.lua:319: in function '_user_on_exit'
	.../.local/share/nvim/lazy/plenary.nvim/lua/plenary/job.lua:241: in function '_shutdown'
	.../.local/share/nvim/lazy/plenary.nvim/lua/plenary/job.lua:48: in function <.../.local/share/nvim/lazy/plenary.nvim/lua/plenary/job.lua:39>```

A collaborator on that thread suggests it may be a regression issue, but I wouldn't know where to start to check, and that individual seemed to be making an intuition call.

Please let me know what, if anything, I can do to help resolve this!

Pull request description:
close #588 

fixes regression introduced in https://github.com/nvim-lua/plenary.nvim/commit/ce8556de8fa51c783bee6304cbe012407ea413b9

Patch:
diff --git a/lua/plenary/compat.lua b/lua/plenary/compat.lua
new file mode 100644
index 00000000..5e7e24d2
--- /dev/null
+++ b/lua/plenary/compat.lua
@@ -0,0 +1,17 @@
+local m = {}
+
+m.flatten = (function()
+  if vim.fn.has "nvim-0.11" == 1 then
+    return function(t)
+      return vim.iter(t):flatten():totable()
+    end
+  else
+    return function(t)
+      return vim.tbl_flatten(t)
+    end
+  end
+end)()
+
+m.islist = vim.islist or vim.tbl_islist
+
+return m
diff --git a/lua/plenary/curl.lua b/lua/plenary/curl.lua
index f9efe25c..b7fc9974 100644
--- a/lua/plenary/curl.lua
+++ b/lua/plenary/curl.lua
@@ -36,14 +36,7 @@ local util, parse = {}, {}
 local F = require "plenary.functional"
 local J = require "plenary.job"
 local P = require "plenary.path"
-
-local flatten = function(t)
-  if vim.fn.has "nvim-0.11" == 1 then
-    return vim.iter(t):flatten():totable()
-  else
-    return vim.tbl_flatten(t)
-  end
-end
+local compat = require "plenary.compat"
 
 -- Utils ----------------------------------------------------
 -------------------------------------------------------------
@@ -62,7 +55,7 @@ util.url_encode = function(str)
 end
 
 util.kv_to_list = function(kv, prefix, sep)
-  return flatten(F.kv_map(function(kvp)
+  return compat.flatten(F.kv_map(function(kvp)
     return { prefix, kvp[1] .. sep .. kvp[2] }
   end, kv))
 end
@@ -251,7 +244,7 @@ parse.request = function(opts)
     table.insert(result, { "-o", opts.output })
   end
   table.insert(result, parse.url(opts.url, opts.query))
-  return flatten(result), opts
+  return compat.flatten(result), opts
 end
 
 -- Parse response ------------------------------------------
diff --git a/lua/plenary/iterators.lua b/lua/plenary/iterators.lua
index 3dd2ccb5..2aac0d20 100644
--- a/lua/plenary/iterators.lua
+++ b/lua/plenary/iterators.lua
@@ -7,6 +7,7 @@
 
 local co = coroutine
 local f = require "plenary.functional"
+local compat = require "plenary.compat"
 
 --------------------------------------------------------------------------------
 -- Tools
@@ -38,8 +39,6 @@ function Iterator:__tostring()
   return "<iterator>"
 end
 
-local is_list = vim.islist or vim.tbl_islist
-
 -- A special hack for zip/chain to skip last two state, if a wrapped iterator
 -- has been passed
 local numargs = function(...)
@@ -109,7 +108,7 @@ local rawiter = function(obj, param, state)
       end
     end
 
-    if is_list(obj) then
+    if compat.islist(obj) then
       return ipairs(obj)
     else
       -- hash
diff --git a/lua/plenary/job.lua b/lua/plenary/job.lua
index b1b83c44..c095c4c1 100644
--- a/lua/plenary/job.lua
+++ b/lua/plenary/job.lua
@@ -1,6 +1,6 @@
 local vim = vim
 local uv = vim.loop
-local is_list = vim.islist or vim.tbl_islist
+local compat = require "plenary.compat"
 
 local F = require "plenary.functional"
 
@@ -417,7 +417,7 @@ function Job:_execute()
   if self.writer then
     if Job.is_job(self.writer) then
       self.writer:_execute()
-    elseif type(self.writer) == "table" and is_list(self.writer) then
+    elseif type(self.writer) == "table" and compat.islist(self.writer) then
       local writer_len = #self.writer
       for i, v in ipairs(self.writer) do
         self.stdin:write(v)
diff --git a/lua/plenary/scandir.lua b/lua/plenary/scandir.lua
index 0a383581..d5b60ee4 100644
--- a/lua/plenary/scandir.lua
+++ b/lua/plenary/scandir.lua
@@ -1,15 +1,9 @@
 local Path = require "plenary.path"
 local os_sep = Path.path.sep
 local F = require "plenary.functional"
+local compat = require "plenary.compat"
 
 local uv = vim.loop
-local flatten = function(t)
-  if vim.fn.has "nvim-0.11" == 1 then
-    return vim.iter(t):flatten():totable()
-  else
-    return vim.tbl_flatten(t)
-  end
-end
 
 local m = {}
 
@@ -158,8 +152,8 @@ m.scan_dir = function(path, opts)
   opts = opts or {}
 
   local data = {}
-  local base_paths = flatten { path }
-  local next_dir = flatten { path }
+  local base_paths = compat.flatten { path }
+  local next_dir = compat.flatten { path }
 
   local gitignore = opts.respect_gitignore and make_gitignore(base_paths) or nil
   local match_search_pat = opts.search_pattern and gen_search_pat(opts.search_pattern) or nil
@@ -211,8 +205,8 @@ m.scan_dir_async = function(path, opts)
   opts = opts or {}
 
   local data = {}
-  local base_paths = flatten { path }
-  local next_dir = flatten { path }
+  local base_paths = compat.flatten { path }
+  local next_dir = compat.flatten { path }
   local current_dir = table.remove(next_dir, 1)
 
   -- TODO(conni2461): get gitignore is not async


Test patch:
diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua
index 9192ce2c..8f916a30 100644
--- a/tests/plenary/path_spec.lua
+++ b/tests/plenary/path_spec.lua
@@ -1,6 +1,6 @@
 local Path = require "plenary.path"
 local path = Path.path
-local is_list = vim.islist or vim.tbl_islist
+local compat = require "plenary.compat"
 
 describe("Path", function()
   it("should find valid files", function()
@@ -593,7 +593,7 @@ describe("Path", function()
     it("should extract the ancestors of the path", function()
       local p = Path:new(vim.loop.cwd())
       local parents = p:parents()
-      assert(is_list(parents))
+      assert(compat.islist(parents))
       for _, parent in pairs(parents) do
         assert.are.same(type(parent), "string")
       end