nanotee / nvim-lua-guide

A guide to using Lua in Neovim
5.85k stars 220 forks source link

Gotchas re: VimL <-> Lua conversion #15

Closed nanotee closed 3 years ago

nanotee commented 4 years ago

The side from which one does the conversion matters when converting functions:

local function hello()
    print('hello')
end

local Myfunc = vim.fn['function'](hello)
print(Myfunc)
-- vim.NIL

vim.g.Hello = hello
-- Error: Cannot convert given lua type
lua << EOF
function _G.hello()
    print('hello')
end
EOF

let s:Myfunc = function(luaeval('_G.hello'))
echo s:Myfunc
" <lambda>1

let g:Hello = luaeval('_G.hello')
echo g:Hello
" <lambda>2

Bumped into this when I tried to pass a Lua callback to vim.fn['fzf#wrap'] from the Lua side

nanotee commented 4 years ago

Some VimScript functions that modify a variable in-place behave differently in Lua, since the conversion creates a copy:

let s:list = [1, 2, 3]
let s:newlist = map(s:list, {_, v -> v * 2})

echo s:list
" [2, 4, 6]
echo s:newlist
" [2, 4, 6]
local tbl = {1, 2, 3}
local newtbl = vim.fn.map(tbl, function(_, v) return v * 2 end)

print(vim.inspect(tbl)) -- { 1, 2, 3 }
print(vim.inspect(newtbl)) -- { 2, 4, 6 }

This may also have performance implications?

matu3ba commented 3 years ago

Does this include set options like

vim.o.list              = true

and in nvim cmdline:

:lua print(vim.o.list)

?

I cant print it, but the set option has an effect (applying listchars).

nanotee commented 3 years ago

'list' is a window-local option, so I don't think the error is related to type conversion, you'd have to use vim.wo.list

WhyNotHugo commented 2 years ago

Thanks for mentioning this, I was rewriting my fzf config into lua and trying to figure out what was wrong was driving me crazy! It's only because of your mention of fzf#wrap that I found this.

My code right now looks something like this:

  local cmd = string.format(
    ":call fzf#run(fzf#wrap({'source': '%s', 'options': ['--prompt', '%s'], 'dir': '%s'}))",
    source,
    prompt,
    root
  )
  vim.cmd(cmd)

Is it possible to re-write this into lua and avoid the string interpolation?

nanotee commented 2 years ago

For Lua callbacks, there's this trick I learned by reading nvim-lspfuzzy's source code: https://github.com/ojroques/nvim-lspfuzzy/blob/f41f8b03a8eacee578b2b4f14866163538fcfe37/lua/lspfuzzy.lua#L138-L142

I'm surprised your example doesn't work though, it looks as though you're only passing strings to fzf#wrap()

WhyNotHugo commented 2 years ago

I think that fzf#wrap returns a function that's then passed to fzf#run. At least that's my theory.

I re-wrote the function into this:

vim.call("fzf#run", vim.call("fzf#wrap", { source = source, options = { "--prompt", prompt }, dir = root }))

And after I pick a file from the prompt, I get an error:

Error detected while processing function 11[30]..<SNR>35_callback:
line   23:
Vim(call):E718: Funcref required

The output of fzf#wrap itself is a table/dict, and I suspect one of those vim.NIL values are actually functions:

{
  _action = {
    ["ctrl-t"] = "tab split",
    ["ctrl-v"] = "vsplit",
    ["ctrl-x"] = "split"
  },
  dir = "/home/hugo/.dotfiles",
  down = "40%",
  options = " '--prompt' 'git> ' --expect=ctrl-v,ctrl-x,ctrl-t",
  ["sink*"] = vim.NIL,
  sinklist = vim.NIL,
  source = "git ls-files --cached --modified --others --exclude-standard | uniq"
}
WhyNotHugo commented 2 years ago

Oh, yeah, that link confirms it: sink is a function.

nanotee commented 2 years ago

fzf#wrap() does indeed return default functions if no sink option is passed: https://github.com/junegunn/fzf/blob/a91a67668e0830a8cf9a792c4949e03b4189f097/plugin/fzf.vim#L422-L429

Passing sink = 'edit' to fzf#wrap() might fix your problem

nanotee commented 2 years ago

Oh, deleting the sink* and sinklist keys from the table might work better:

local fzf_wrapped_options = vim.call("fzf#wrap", { source = source, options = { "--prompt", prompt }, dir = root })
fzf_wrapped_options['sink*'] = nil
fzf_wrapped_options.sinklist = nil

vim.call("fzf#run", fzf_wrapped_options)
WhyNotHugo commented 2 years ago

That worked, thanks!

WhyNotHugo commented 2 years ago

Ugh, I cheered too soon. It doesn't work. It lets me pick a file but does nothing.

I though it worked because I'd picked the same file and it didn't error. :(

nanotee commented 2 years ago

Right, deleting sink* and sinklist removes the default action entirely so selecting an item does nothing. Passing sink = 'edit' to fzf#wrap() should to the trick since it prevents it from returning a table with vim.NIL:

vim.cal('fzf#run', vim.call("fzf#wrap", { source = source, options = { "--prompt", prompt }, dir = root, sink = 'edit' }))

(sorry for the confusion, I'm learning about the internals of fzf.vim as I debug this)