-
-
Notifications
You must be signed in to change notification settings - Fork 247
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
[RFC] Restore node #228
[RFC] Restore node #228
Conversation
One thing that I'm not sure about is having to define the |
I just tried it for very basic cases. I will respond later, when I tried more complicated snippets.
Some hacky solution can be to add third argument to restore_node: default snippet. I'm not sure about the order in which they are evaluated, but it can look like this (though I'm not sure if it's better, because it is not explicit): s("rest", {
c(1, {
sn(nil, {t('"'), r(1, "key", sn(nil, {i(1, "preset")})), t('"')}),
sn(nil, {t("'"), r(1, "key"), t("'")}),
})
} AFAIK first choice is always evaluated first. So default should be provided there. But it shouldn't replace |
Yeah, debugging it right now :| Concerning the docstring, jep, that also doesn't work yet 😅 |
Should work again, jumpable nodes are found via their type and I forgot to add restoreNode in that check. |
Otherwise text in `restoreNode` won't be saved.
Docstrings might work now 😅 local function simple_restore(args, _)
return sn(nil, {i(1, args[1]), r(2, "dyn")})
end
s("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}, {
stored = {
dyn = i(1, "will be stored")
}
}), Normally the text in |
Yeah, that would also work, we could specify that if exactly one default is given, that one is used, if there's multiple it's undefined. |
If the default is specified more than once it's undefined which one is actually used. If the default is set within a dynamicNode, it will not influence restoreNodes outside the dynamicNode directly after expansion. The reason for this is that dynamicNodes can only be constructed (and their subsnip_init() called) after put_initial() has been called for all of the argnodes (actually all the nodes, with more effort this could be optimized to just the argnodes).
The default may now be set directly inside the snippet: local function simple_restore(args, _)
return sn(nil, {i(1, args[1]), r(2, "dyn", {
i(1, "the node can also be set here"),
t{"",""},
i(2, "some more text")
})})
end
local function simple_restore2(args, _)
return sn(nil, {i(1, args[1]), r(2, "dyn", i(1, "the node can also be set here"))})
end
s("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}), |
I'm sorry for a long response. I tested it more thoroughly, but I was doing it before your last commit ("Allow the default for a given key to be passed to the constructor"). Also I didn't test dynamicNodes at all. I will try to test more tomorrow. Firstly, thank you! Basically it works, it's great! Some things I notices aren't working as I expected (remember it doesn't account for the latest commit):
Here is a long example of what I tested: local ls = require("luasnip")
local s = ls.snippet
local sn = ls.snippet_node
local t = ls.text_node
local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local l = require("luasnip.extras").lambda
local rep = require("luasnip.extras").rep
-- local p = require("luasnip.extras").partial
-- local m = require("luasnip.extras").match
-- local n = require("luasnip.extras").nonempty
-- local dl = require("luasnip.extras").dynamic_lambda
-- local fmt = require("luasnip.extras.fmt").fmt
-- local fmta = require("luasnip.extras.fmt").fmta
-- local types = require("luasnip.util.types")
-- local conds = require("luasnip.extras.expand_conditions")
local r = ls.restore_node
ls.snippets = {
all = {
-- Just for testing
s("test_empty_stored", {
c(1, {
sn(nil, { t('"'), r(1, "text"), t('"') }),
sn(nil, { t("'"), r(1, "text"), t("'") }),
}),
}),
s("test_single_input_in_stored", {
c(1, {
sn(nil, { t("("), r(1, "text"), t(")") }),
sn(nil, { t("["), r(1, "text"), t("]") }),
sn(nil, { t("{"), r(1, "text"), t("}") }),
}),
}, {
stored = { text = i(1, "default_text") }, --< I expect it to be i(nil, default_text), like in choiceNode
}),
s("test_restore_in_second_choice", {
c(1, {
t("irrelevant_text"),
sn(nil, {
t("name: "),
r(1, 1),
}),
sn(nil, {
t("dest: "),
r(1, 1),
}),
}),
}, {
stored = {
[1] = i(1, "default_name"),
},
}),
s("test_bare_resore", {
c(1, {
r(nil, 1), --< doesn't work without wrapping it in snippetNode
sn(nil, {
t("name: "),
r(1, 1),
}),
}),
}, {
stored = {
[1] = i(1, "default_name"),
},
}),
s("test_multiple_inputs_in_restore", {
t("Prefix: "),
c(1, {
sn(nil, { r(1, 1) }),
sn(nil, { r(1, 1), t(", "), i(2, "extra_input") }),
}),
}, {
stored = {
[1] = sn(nil, {
t("Some "),
i(1, "complicated"),
t(" input with multiple "),
i(2, "nodes"),
t(". Also repeat: "),
rep(2), --< repeat doesn't work immediately (need to change text)
}),
},
}),
s("test_repeat_inside_snippet_node", { --< just to be sure it should work
i(1, "Input1"),
t(" "),
i(2, "Input2"),
t(" "),
sn(3, {
i(1, "Cloned_input"),
t(" "),
rep(1),
}),
}),
s("test_multiple_stored", {
c(1, {
sn(nil, { r(1, 1), t(":"), r(2, 2) }),
sn(nil, { r(1, 1), t(":"), r(2, 3) }),
sn(nil, { r(1, 2), t(":"), r(2, 3) }),
}),
}, {
stored = {
[1] = i(1, "simple input"),
[2] = sn(nil, {
t("<"),
i(1, "wrapped_input"),
t(">"),
}),
[3] = sn(nil, {
t("a="),
i(1, "1"),
t(" b="),
i(2, "2"),
}),
},
}),
s("test_stored_inside_stored", {
c(1, {
sn(nil, { t("-->"), r(1, "separated_with_space"), t("<--") }),
sn(nil, { r(1, "separated_with_newline") }),
}),
}, {
stored = {
[1] = sn(nil, { t("First: "), i(1, "input1") }),
[2] = sn(nil, { t("Second: "), i(1, "input2") }),
["separated_with_space"] = sn(nil, { --< Wow, didn't expect it, but it works
r(1, 1),
t(" "),
r(2, 2),
}),
["separated_with_newline"] = sn(nil, {
r(1, 1),
t({ "", "" }),
r(2, 2),
}),
},
}),
s("test_stored_with_lambda", {
i(1, "Pretext"),
t(" "),
c(2, {
sn(nil, r(1, 1)),
sn(nil, {
i(1, "extra_arg"),
t(" "),
r(2, 1),
}),
}),
}, {
stored = {
[1] = sn(nil, {
i(1, "reversed text"),
t(" "),
l(l._1:reverse(), 1), --< lambdas and functions work only after changing text
}),
},
}),
s("test_stored_with_funcion", {
i(1, "Pretext"),
t(" "),
c(2, {
sn(nil, r(1, 1)),
}),
}, {
stored = {
[1] = sn(nil, {
i(1, "prefixed text"),
t(" "),
f(function(args) --< lambdas and functions work only after changing text
return "Prefix:" .. args[1][1]
end, 1),
}),
},
}),
-- Some complicated example of something I wanted to try to do at the first place
-- Ansible module `file`: https://docs.ansible.com/ansible/2.9/modules/file_module.html
-- Other uses is to create snippets with minimum always used options,
-- but always can switch to a form with more optional parameters
s("file", {
t({ "file:", "\t" }),
c(1, {
sn(nil, {
r(1, "path"),
r(2, "permissions_dir"),
t({ "", "\tstate: directory" }),
}),
sn(nil, {
r(1, "path"),
r(2, "src"),
t({ "", "\tstate: link" }),
}),
sn(nil, {
r(1, "path"),
t({ "", "\tstate: absent" }),
}),
sn(nil, {
r(1, "path"),
r(2, "src"),
t({ "", "\tstate: hard" }),
}),
sn(nil, {
r(1, "path"),
r(2, "permissions_file"),
t({ "", "\taccess_time: " }),
i(3, "preserve"),
t({ "", "\tmodification_time: " }),
i(4, "preserve"),
t({ "", "\tstate: touch" }),
}),
sn(nil, {
r(1, "path"),
r(2, "permissions_file"),
t({ "", "\tstate: file" }),
}),
}),
}, {
stored = {
path = sn(nil, {
t("path: "),
i(1, "/path/to/file"),
}),
permissions_file = sn(nil, {
t({ "", "\tmode: " }),
i(1, "0644"),
r(2, "owner"),
}),
permissions_dir = sn(nil, {
t({ "", "\tmode: " }),
i(1, "0755"),
r(2, "owner"),
}),
owner = sn(nil, {
t({ "", "\towner: " }),
i(1, "root"),
t({ "", "\tgroup: " }),
i(2, "root"),
}),
src = sn(nil, {
t({ "", "\tsrc: " }),
i(1, "/path/to/file/to/link/to"),
}),
},
}),
},
}
|
Yes, that's because it will be (silently) turned into a
that looks like it should work... lemme check what's going wrong.
Also thanks for the big example file, really nice to test with 👍 |
Doing it in put_initial will prefix the text/insertNodes with the indentstring multiple times.
Okay, I think all of your testcases work now :D |
I've tested it one more time with new additions. I managed to break in only with dynamic lambda (its content is reset after every choice). It's probably expected because after every choice it is evaluated again. But maybe it's possible to preserve its content also? If not I don't think it's a blocker. I just got carried away with testing features outside of the initial request scope... If nitpicking things, you can make specifying defaults consistent between Overall I think this feature is integrated nicely and in a spirit of luasnip. Just for fun I tried to remove local function jdocsnip(args)
local nodes = {
t({ "/**", " * " }),
r(1, "desc", i(1, "A short Description")),
t({ "", "" }),
}
-- At least one param.
if string.find(args[2][1], ", ") then
vim.list_extend(nodes, { t({ " * ", "" }) })
end
local insert = 2
for _, arg in ipairs(vim.split(args[2][1], ", ", true)) do
-- Get actual name parameter.
arg = vim.split(arg, " ", true)[2]
if arg then
local inode = r(insert, "arg" .. arg, i(1))
vim.list_extend(nodes, { t({ " * @param " .. arg .. " " }), inode, t({ "", "" }) })
insert = insert + 1
end
end
if args[1][1] ~= "void" then
local inode = r(insert, "ret", i(1))
vim.list_extend(nodes, { t({ " * ", " * @return " }), inode, t({ "", "" }) })
insert = insert + 1
end
if vim.tbl_count(args[3]) ~= 1 then
local exc = string.gsub(args[3][2], " throws ", "")
local ins = r(insert, "ex", i(1))
vim.list_extend(nodes, { t({ " * ", " * @throws " .. exc .. " " }), ins, t({ "", "" }) })
insert = insert + 1
end
vim.list_extend(nodes, { t({ " */" }) })
local snip = sn(nil, nodes)
return snip
end
ls.snippets = {
all = {
s("test_stored_with_dyn_lambda", {
i(1, "Pretext"),
t(" "),
c(2, {
sn(nil, r(1, 1)),
sn(nil, {
i(1, "extra_arg"),
t(" "),
r(2, 1),
}),
}),
}, {
stored = {
[1] = sn(nil, {
i(1, "reversed text"),
t(" "),
dl(2, l._1:reverse(), 1), --< dynamic lambdas resets its content
}),
},
}),
s("test_dynamic", {
d(6, jdocsnip, { 2, 4, 5 }),
t({ "", "" }),
c(1, {
t("public "),
t("private "),
}),
c(2, {
t("void"),
t("String"),
t("char"),
t("int"),
t("double"),
t("boolean"),
i(nil, ""),
}),
t(" "),
i(3, "myFunc"),
t("("),
i(4),
t(")"),
c(5, {
t(""),
sn(nil, {
t({ "", " throws " }),
i(1),
}),
}),
t({ " {", "\t" }),
i(0),
t({ "", "}" }),
}),
},
} |
Ha, I think I know what's wrong there :D
Right, yes that should work the same everywhere 👍
Ouh, nice! I'll replace the current one in examples as the point of adding it there was showing as many features as possible, including some more is great! |
Then there won't be anything about |
True, but I'm not even sure if |
Are you waiting for my approval? Anyway, looks like indent_snippet_node considers restore_node as one line (or one node). s("test_indent_snippet_node", {
isn(1, {
t("# "),
t({ "preline1", "preline2", "" }),
r(1, 1, {
t({ "line1", "line2", "" }),
i(1, "input"),
}),
}, "# "),
}),
s("test_indent_snippet_node_double", {
c(1, {
sn(nil, {
t("// "),
isn(1, r(1, 1), "$PARENT_INDENT// "),
}),
sn(nil, {
t("# "),
isn(1, r(1, 1), "$PARENT_INDENT# "),
}),
}),
}, {
stored = {
[1] = {
t("* "),
isn(1, {
t({ "line1", "line2", "" }),
i(1, "user_input"),
}, "* "),
},
},
}), |
What do you think about using |
Had to write some Docs, still :D Concerning the |
Not a bad idea, that shouldn't hurt 👍 |
ss("rest", { | ||
i(1, "preset"), t{"",""}, | ||
d(2, simple_restore, 1) | ||
}), | ||
("rest", { | ||
i(1, "preset"), t{"",""}, | ||
d(2, simple_restore, 1) | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like double pasting to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, yes you're right
Got it, thank you. |
This implements a new type of node, the
restoreNode
.It allows sharing of user-entered data (text in an
insertNode
, changed choices, ..) between multiple nodes.The snippet will start out as
"preset"
, changing the text to eg."something else"
and changing choice will result in'something else'
.The
restoreNode
can also be used to preserve text ininsertNodes
inside of a snippet generated bydynamicNode
accross multiple updates of that snippet.Right now only
snippetNode
s can be stored, though that shouldn't be too annoying, we can always wrap other nodes insidesnippetNode
s.