Closed banana59 closed 1 month ago
Hi :)
One thing that will seriously inhibit luasnip is the current way check_import_work_in_progress
inserts text, replacing the entire buffer via nvim_buf_set_lines
will invalidate all extmarks set by luasnip for tracking nodes, and thus all snippets (to fix, insert only the new line into the buffer)
To make sure that's not the current issue, could you try something simple, like [events.post_expand] = function() print("foo") end
?
Thanks, that's useful to know. I have replaced the nvim_buf_set_lines
range to only affect the first line.
Unfortunately I can't find an example on how the correct syntax is for this. Do you have any more documentation than the DOC.md and Examples/snippets.lua? With your minimal example function() ... end
I get this error:
Error detected while processing BufWritePost Autocommands for "*":
Error executing lua callback: ...share/nvim/lazy/LuaSnip/lua/luasnip/loaders/from_lua.lua:169: Failed to execute .../LuaSnip/python.lua
: .../default/LuaSnip/python.lua:120: table index is nil
stack traceback:
[C]: in function 'error'
...share/nvim/lazy/LuaSnip/lua/luasnip/loaders/from_lua.lua:169: in function '_luasnip_load_file'
...share/nvim/lazy/LuaSnip/lua/luasnip/loaders/from_lua.lua:285: in function 'load_file'
...share/nvim/lazy/LuaSnip/lua/luasnip/loaders/from_lua.lua:354: in function 'reload'
...share/nvim/lazy/LuaSnip/lua/luasnip/loaders/from_lua.lua:241: in function 'change_file'
...re/nvim/lazy/LuaSnip/lua/luasnip/loaders/fs_watchers.lua:366: in function 'change_file'
...re/nvim/lazy/LuaSnip/lua/luasnip/loaders/fs_watchers.lua:223: in function 'BufWritePost_callback'
...re/nvim/lazy/LuaSnip/lua/luasnip/loaders/fs_watchers.lua:97: in function <...re/nvim/lazy/LuaSnip/lua/luasnip/loaders/fs_watchers.lua:66>
Oh MB, there actually isn't a post_expand-event, should've spotted that earlier :sweat_smile:
IIUC you could do this in pre_expand
, that way you don't even have to worry about setting the cursor correctly again, since it will be set by luasnip during expansion :)
The code is accepted. I try to do this with pre_expand
, but getting the lines before the snippet will result in capturing a state before the snippet exists. This will result that the import matplotlib...
string is always inserted after the snippet. That's why I thought about something like a post_expand
. Do you know any alternative? This is the currenet state of the function:
-- Function to check if import statement exists and add if not
local function check_import_work_in_progress(args)
local user_args = "import matplotlib.pyplot as plt"
if type(user_args) ~= "string" then
error("Invalid check_import input")
end
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local import_exists = false
for _, line in ipairs(lines) do
if line:match(user_args) then
import_exists = true
break -- if found don't import anything
end
end
if not import_exists then
-- Prepend the import statement to the lines
table.insert(lines, 2, user_args)
vim.api.nvim_buf_set_lines(0, 0, 0, false, lines)
end
end
This is the current state of the snippet
s({trig="plot_2d", snippetType="snippet"},
fmta(
[[
fig, ax = plt.subplots(figsize=[12, 10])
ax.plot(<>, <>)
ax.grid()
ax.set_xlabel("<>")
ax.set_ylabel("<>")
plt.show()
<>
]],
{
i(1), i(2),
i(3, "x-Axis"),
i(4, "y-Axis"),
i(0)
}
),
{
callbacks = {
[-1] = { -- -1 refers to the snippet as a whole
[events.pre_expand] = check_import_work_in_progress
}
}
}
),
Thank you very much for your help by the way. I really appreciate your time.
Ah, okay, I think I know what's going on (and it's a bit involved, sorry)
So, I think what's happening here is that the code inserts the import-statement right on top of the extmark, which makes it shift to the left of, aka before, the snippet. So, this only occurs if the snippet is expanded at the first column of the first line (aka if the trigger starts exactly there).
I think it'd be nice to be able to handle even this case gracefully, so I'll make it so the pre_expand
-callback receives the id of that extmark, so one may place it correctly after inserting the line (90333731):
ls.setup_snip_env()
-- Function to check if import statement exists and add if not
local function check_import_work_in_progress(node, args)
local user_args_pattern = "^import matplotlib.pyplot as plt"
local user_args = "import matplotlib.pyplot as plt"
if type(user_args) ~= "string" then
error("Invalid check_import input")
end
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local import_exists = false
for _, line in ipairs(lines) do
if line:match(user_args_pattern) then
import_exists = true
break -- if found don't import anything
end
end
if not import_exists then
-- make linebreak with `, ""`
vim.api.nvim_buf_set_text(0, 0,0, 0,0, {user_args, ""})
-- check for failure-case
if vim.deep_equal(args.expand_pos, {0,0}) then
-- move expand-pos-extmark.
vim.api.nvim_buf_set_extmark(0, require("luasnip.session").ns_id, 1,0, {id = args.expand_pos_mark_id})
end
end
end
ls.add_snippets("all", {
s({trig="plot_2d", snippetType="snippet"},
fmta(
[[
fig, ax = plt.subplots(figsize=[12, 10])
ax.plot(<>, <>)
ax.grid()
ax.set_xlabel("<>")
ax.set_ylabel("<>")
plt.show()
<>
]],
{
i(1), i(2),
i(3, "x-Axis"),
i(4, "y-Axis"),
i(0)
}
),
{
callbacks = {
[-1] = { -- -1 refers to the snippet as a whole
[events.pre_expand] = check_import_work_in_progress
}
}
}
),
}, {key ="a"})
What you mentioned with the post_expand-callback sounds conceptually simpler, until one knows that the snippet also consists of extmarks, all of which may also have to be adjusted to support this (make sure that they will shift into the correct direction), so sticking this into pre_expand makes it a bit easier :)
All right. The functionality is nearly perfect. The code implementation is working as expected. The only this here is that in case 1: import ...
not there when expanding - the cursor is before the inserted import ...
element and further expand_or_jump()
calls for the choice nodes are not possible. I am simply in insert mode. In case 2: import ...
there when expanding - the cursor is at the bottom of snippet and the snippet choice nodes are not activated / triggered.
Concluding, in both cases the other options in the snippet like choice nodes are not triggered.
Hmm, for me everything seems to be working, have you updated to the latest version of luasnip? I added the referenced commit just yesterday :) If you're up-to-date, could you give some more details?
https://github.com/user-attachments/assets/5b13cd54-36d9-43de-9277-88f1cf557b1e
I now have the newest commit. I used tag 2.3.0. Now it is working, awesome! May I ask you for one last help with this? I want to make this function universal by adding the string as a parameter. The result is, that the parameter is added after the snippet again. I haven't quite get my head around the mechanism, why this is happening anyway.
-- Function to check if import statement exists and add if not
local function check_import(node, args)
if type(args[1]) ~= "string" then
error("Invalid check_import input")
end
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local import_exists = false
for _, line in ipairs(lines) do
if line:match("^" .. args[1]) then
import_exists = true
break -- if found don't import anything
end
end
if not import_exists then
-- make linebreak with `, ""`
vim.api.nvim_buf_set_text(0, 0,0, 0,0, {args[1], ""})
-- check for failure-case
if vim.deep_equal(args.expand_pos, {0,0}) then
-- move expand-pos-extmark.
vim.api.nvim_buf_set_extmark(0, require("luasnip.session").ns_id, 1,0, {id = args.expand_pos_mark_id})
end
end
end
return {
s({trig="plot_2d", snippetType="snippet"},
fmta(
[[
fig, ax = plt.subplots(figsize=[12, 10])
ax.plot(<>, <>)
ax.grid()
ax.set_xlabel("<>")
ax.set_ylabel("<>")
plt.show()
<>
]],
{
i(1), i(2),
i(3, "x-Axis"),
i(4, "y-Axis"),
i(0)
}
),
{
callbacks = {
[-1] = { -- -1 refers to the snippet as a whole
[events.pre_expand] = function(node, args)
check_import(node, {"import matplotlib.pyplot as plt"})
end
}
}
}
),
}
Ha, nice :)
That's almost correct, just make sure to pass the second parameter, args
through to check_import, so check_import(node, args, user_args)
(or omit the node altogether, I don't think it's necessary)
I am sorry but I don't seem to get it. Thanks for your patience. With this code I still insert the text after the snippet.
-- Function to check if import statement exists and add if not
local function check_import(args, user_args)
if type(user_args[1]) ~= "string" then
error("Invalid check_import input")
end
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local import_exists = false
for _, line in ipairs(lines) do
if line:match("^" .. user_args[1]) then
import_exists = true
break -- if found don't import anything
end
end
if not import_exists then
-- make linebreak with `, ""`
vim.api.nvim_buf_set_text(0, 0,0, 0,0, {user_args[1], ""})
-- check for failure-case
if vim.deep_equal(args.expand_pos, {0,0}) then
-- move expand-pos-extmark.
vim.api.nvim_buf_set_extmark(0, require("luasnip.session").ns_id, 1,0, {id = args.expand_pos_mark_id})
end
end
end
return {
s({trig="plot_2d", snippetType="snippet"},
fmta(
[[
fig, ax = plt.subplots(figsize=[12, 10])
ax.plot(<>, <>)
ax.grid()
ax.set_xlabel("<>")
ax.set_ylabel("<>")
plt.show()
<>
]],
{
i(1), i(2),
i(3, "x-Axis"),
i(4, "y-Axis"),
i(0)
}
),
{
callbacks = {
[-1] = { -- -1 refers to the snippet as a whole
[events.pre_expand] = function(args)
check_import(args, {"import matplotlib.pyplot as plt"})
end
}
}
}
),
}
No worries:
the callback, so the function you pass to events.pre_expand
receives two arguments, first node
(the node that triggered the event), and second args
, which has some additional data (check_import
needs these args
because they contain args.expand_pos
and args.expand_pos_mark_id
)
So what you have right now almost works correctly, only the argument args
in the event-callback receives the node.
If you instead write [events.pre_expand] = function(_, args)
it should work correctly (_
is usually used for unused arguments)
Hope that fixes it :)
That's it! Thank you L3MON4D3.
So in case of iterating over the table inputted check_import(args, {"from mpl_toolkits.mplot3d import Axes3D", "import numpy as np"})
I somehow have to make sure that the args parameter gets updated sequentially somehow. How do I do that?
-- Function to check if import statement exists and add if not add it to the top
local function check_import(args, user_args)
if type(user_args) ~= "table" then
error("Invalid check_import input")
end
for _, import_statement in ipairs(user_args) do
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
if type(import_statement) ~= "string" then
error("Each element in user_args should be a string")
end
local import_exists = false
for _, line in ipairs(lines) do
if line:match("^" .. import_statement) then
import_exists = true
break -- if found don't import anything
end
end
if not import_exists then
vim.api.nvim_buf_set_text(0, 0,0, 0,0, {import_statement, ""}) -- make linebreak with `, ""`
if vim.deep_equal(args.expand_pos, {0,0}) then -- check for failure-case
vim.api.nvim_buf_set_extmark(0, require("luasnip.session").ns_id, 1,0, {id = args.expand_pos_mark_id}) -- move expand-pos-extmark.
end
end
end
end
I think I'd
if vim.deep_equal
out of the user_args
-loopFor the sake of completeness, this is the code checking for multiple imports / includes
-- Function to check if import statement exists and add if not add it to the top
local function check_import(args, user_args)
if type(user_args) ~= "table" then
error("Invalid check_import input")
end
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local inserted_lines = 0
for _, import_statement in ipairs(user_args) do
if type(import_statement) ~= "string" then
error("Each element in user_args should be a string")
end
local import_exists = false
for _, line in ipairs(lines) do
if line:match("^" .. import_statement) then
import_exists = true
break -- don't import anything
end
end
if not import_exists then
vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, {import_statement, ""}) -- make linebreak with `, ""`
inserted_lines = inserted_lines + 1
end
end
if vim.deep_equal(args.expand_pos, {0, 0}) then -- check for failure-case
vim.api.nvim_buf_set_extmark(0, require("luasnip.session").ns_id, inserted_lines, 0, {id = args.expand_pos_mark_id}) -- move expand-pos-extmark.
end
end
and this a corresponding snippet example
return {
s({trig="plot_3d_surface", snippetType="snippet"},
fmta(
[[
# Create data
x = np.linspace(<>, <>, 100)
y = np.linspace(<>, <>, 100)
X, Y = np.meshgrid(x, y)
Z = <>
# Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()
<>
]],
{
i(1),
i(2),
i(3),
i(4),
i(5),
i(0),
}
),
{
callbacks = {
[-1] = { -- -1 refers to the snippet as a whole
[events.pre_expand] = function(_, args)
check_import(args, {"from mpl_toolkits.mplot3d import Axes3D", "import numpy as np"})
end
}
}
}
)
}
assuming you are in ./LuaSnip/python.lua and declared above
local ls = require("luasnip")
local s = ls.snippet
local t = ls.text_node
local i = ls.insert_node
local c = ls.choice_node
local fmta = require("luasnip.extras.fmt").fmta
local events = require("luasnip.util.events")
Hello, I am currently working on a callback function which should be executed after a snippet is expanded.
This is my function
This in my snippet in python.lua
But the function input doesn't seem to be accepted. Can anyone help me with that?