Skip to content

Commit

Permalink
Merge pull request L3MON4D3#228 from L3MON4D3/restoreNode
Browse files Browse the repository at this point in the history
feat: Add restoreNode.

`RestoreNode` makes it possible to store and restore `snippetNode`s.
Check the docs for more info :)
  • Loading branch information
L3MON4D3 authored Nov 30, 2021
2 parents e63b586 + 99b0c84 commit a3b71bf
Show file tree
Hide file tree
Showing 10 changed files with 448 additions and 27 deletions.
77 changes: 77 additions & 0 deletions DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local events = require("luasnip.util.events")
```

Expand Down Expand Up @@ -393,6 +394,82 @@ eg. 3, it would change to "3\nSample Text\nSample Text\nSample Text". Text
that was inserted into any of the dynamicNodes insertNodes is kept when
changing to a bigger number.

# RESTORENODE

This node can store and restore a snippetNode that was modified (changed
choices, inserted text) by the user. It's usage is best demonstrated by an
example:

```lua
s("paren_change", {
c(1, {
sn(nil, { t("("), r(1, "user_text"), t(")") }),
sn(nil, { t("["), r(1, "user_text"), t("]") }),
sn(nil, { t("{"), r(1, "user_text"), t("}") }),
}),
}, {
stored = {
user_text = i(1, "default_text")
}
})
```

Here the text entered into `user_text` is preserved upon changing choice.

The constructor for the restoreNode, `r`, takes (at most) three parameters:
- `pos`, when to jump to this node.
- `key`, the key that identifies which `restoreNode`s should share their
content.
- `nodes`, the contents of the `restoreNode`. Can either be a single node or
a table of nodes (both of which will be wrapped inside a `snippetNode`,
except if the single node already is a `snippetNode`).
The content of a given key may be defined multiple times, but if the
contents differ, it's undefined which will actually be used.
If a keys content is defined in a `dynamicNode`, it will not be used for
`restoreNodes` outside that `dynamicNode`. A way around this limitation is
defining the content in the `restoreNode` outside the `dynamicNode`.

The content for a key may also be defined in the `opts`-parameter of the
snippet-constructor, as seen in the example above. The `stored`-table accepts
the same values as the `nodes`-parameter passed to `r`.
If no content is defined for a key, it defaults to the empty `insertNode`.

The `restoreNode` is also useful for storing user-input across updates of a
`dynamicNode`. Consider this:

```lua
local function simple_restore(args, _)
return sn(nil, {i(1, args[1]), i(2, "user_text")})
end

s("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}),
```

Every time the `i(1)` in the outer snippet is changed, the text inside the
`dynamicNode` is reset to `"user_text"`. This can be prevented by using a
`restoreNode`:

```lua
local function simple_restore(args, _)
return sn(nil, {i(1, args[1]), r(2, "dyn", i(nil, "user_text"))})
end

ss("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}),
("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}),
```
Now the entered text is stored.

`RestoreNode`s indent is not influenced by `indentSnippetNodes` right now. If
that really bothers you feel free to open an issue.

# EXTRAS

Expand Down
19 changes: 13 additions & 6 deletions Examples/snippets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local l = require("luasnip.extras").lambda
local r = require("luasnip.extras").rep
local rep = require("luasnip.extras").rep
local p = require("luasnip.extras").partial
local m = require("luasnip.extras").match
local n = require("luasnip.extras").nonempty
Expand Down Expand Up @@ -58,6 +59,9 @@ end

-- complicated function for dynamicNode.
local function jdocsnip(args, _, old_state)
-- !!! old_state is used to preserve user-input here. DON'T DO IT THAT WAY!
-- Using a restoreNode instead is much easier.
-- View this only as an example on how old_state functions.
local nodes = {
t({ "/**", " * " }),
i(1, "A short Description"),
Expand Down Expand Up @@ -200,12 +204,15 @@ ls.snippets = {
-- Inside Choices, Nodes don't need a position as the choice node is the one being jumped to.
sn(nil, {
t("extends "),
i(1),
-- restoreNode: stores and restores nodes.
-- pass position, store-key and nodes.
r(1, "other_class", i(1)),
t(" {"),
}),
sn(nil, {
t("implements "),
i(1),
-- no need to define the nodes for a given key a second time.
r(1, "other_class"),
t(" {"),
}),
}),
Expand Down Expand Up @@ -309,7 +316,7 @@ ls.snippets = {
i(0),
}),
-- Shorthand for repeating the text in a given node.
s("repeat", { i(1, "text"), t({ "", "" }), r(1) }),
s("repeat", { i(1, "text"), t({ "", "" }), rep(1) }),
-- Directly insert the ouput from a function evaluated at runtime.
s("part", p(os.date, "%Y")),
-- use matchNodes to insert text based on a pattern/function/lambda-evaluation.
Expand Down Expand Up @@ -391,9 +398,9 @@ ls.snippets = {
]],
{
i(1, "x"),
r(1),
rep(1),
i(2, "y"),
r(2),
rep(2),
}
)
),
Expand Down
96 changes: 86 additions & 10 deletions doc/luasnip.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ CONTENTS *luasnip-content
6. SNIPPETNODE...............................................|luasnip-snippetnode|
7. INDENTSNIPPETNODE...................................|luasnip-indentsnippetnode|
8. DYNAMICNODE...............................................|luasnip-dynamicnode|
9. EXTRAS.........................................................|luasnip-extras|
10. LSP-SNIPPETS............................................|luasnip-lsp-snippets|
11. VARIABLES..................................................|luasnip-variables|
12. VSCODE SNIPPETS LOADER........................|luasnip-vscode_snippets_loader|
13. EXT_OPTS....................................................|luasnip-ext_opts|
14. DOCSTRING..................................................|luasnip-docstring|
15. DOCSTRING-CACHE......................................|luasnip-docstring-cache|
16. EVENTS........................................................|luasnip-events|
17. CLEANUP......................................................|luasnip-cleanup|
18. API-REFERENCE..........................................|luasnip-api-reference|
9. RESTORENODE...............................................|luasnip-restorenode|
10. EXTRAS........................................................|luasnip-extras|
11. LSP-SNIPPETS............................................|luasnip-lsp-snippets|
12. VARIABLES..................................................|luasnip-variables|
13. VSCODE SNIPPETS LOADER........................|luasnip-vscode_snippets_loader|
14. EXT_OPTS....................................................|luasnip-ext_opts|
15. DOCSTRING..................................................|luasnip-docstring|
16. DOCSTRING-CACHE......................................|luasnip-docstring-cache|
17. EVENTS........................................................|luasnip-events|
18. CLEANUP......................................................|luasnip-cleanup|
19. API-REFERENCE..........................................|luasnip-api-reference|
>
__ ____
/\ \ /\ _`\ __
Expand Down Expand Up @@ -51,6 +52,7 @@ All code-snippets in this help assume that
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local events = require("luasnip.util.events")
<

Expand Down Expand Up @@ -408,6 +410,80 @@ eg. 3, it would change to "3\nSample Text\nSample Text\nSample Text". Text
that was inserted into any of the dynamicNodes insertNodes is kept when
changing to a bigger number.

================================================================================
RESTORENODE *luasnip-restorenode*

This node can store and restore a snippetNode that was modified (changed
choices, inserted text) by the user. It's usage is best demonstrated by an
example:
>
s("paren_change", {
c(1, {
sn(nil, { t("("), r(1, "user_text"), t(")") }),
sn(nil, { t("["), r(1, "user_text"), t("]") }),
sn(nil, { t("{"), r(1, "user_text"), t("}") }),
}),
}, {
stored = {
user_text = i(1, "default_text")
}
})
<

Here the text entered into `user_text` is preserved upon changing choice.

The constructor for the restoreNode, `r`, takes (at most) three parameters:
- `pos`, when to jump to this node.
- `key`, the key that identifies which `restoreNode`s should share their
content.
- `nodes`, the contents of the `restoreNode`. Can either be a single node or
a table of nodes (both of which will be wrapped inside a `snippetNode`,
except if the single node already is a `snippetNode`).
The content of a given key may be defined multiple times, but if the
contents differ, it's undefined which will actually be used.
If a keys content is defined in a `dynamicNode`, it will not be used for
`restoreNodes` outside that `dynamicNode`. A way around this limitation is
defining the content in the `restoreNode` outside the `dynamicNode`.

The content for a key may also be defined in the `opts`-parameter of the
snippet-constructor, as seen in the example above. The `stored`-table accepts
the same values as the `nodes`-parameter passed to `r`.
If no content is defined for a key, it defaults to the empty `insertNode`.

The `restoreNode` is also useful for storing user-input across updates of a
`dynamicNode`. Consider this:
>
local function simple_restore(args, _)
return sn(nil, {i(1, args[1]), i(2, "user_text")})
end
s("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}),
<

Every time the `i(1)` in the outer snippet is changed, the text inside the
`dynamicNode` is reset to `"user_text"`. This can be prevented by using a
`restoreNode`:
>
local function simple_restore(args, _)
return sn(nil, {i(1, args[1]), r(2, "dyn", i(nil, "user_text"))})
end
ss("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}),
("rest", {
i(1, "preset"), t{"",""},
d(2, simple_restore, 1)
}),
<

Now the entered text is stored.

`RestoreNode`s indent is not influenced by `indentSnippetNodes` right now. If
that really bothers you feel free to open an issue.

================================================================================
EXTRAS *luasnip-extras*

Expand Down
7 changes: 7 additions & 0 deletions lua/luasnip/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ local defaults = {
-- not used!
snippet_passive = { hl_group = "LuasnipSnippetSnippetPassive" },
},
[types.restoreNode] = {
active = { hl_group = "LuasnipRestoreNodeActive" },
passive = { hl_group = "LuasnipRestoreNodePassive" },
snippet_passive = {
hl_group = "LuasnipRestoreNodeSnippetPassive",
},
},
},
ext_base_prio = 200,
ext_prio_increase = 7,
Expand Down
2 changes: 2 additions & 0 deletions lua/luasnip/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ ls = {
i = require("luasnip.nodes.insertNode").I,
c = require("luasnip.nodes.choiceNode").C,
d = require("luasnip.nodes.dynamicNode").D,
r = require("luasnip.nodes.restoreNode").R,
snippet = snip_mod.S,
snippet_node = snip_mod.SN,
parent_indexer = snip_mod.P,
Expand All @@ -447,6 +448,7 @@ ls = {
insert_node = require("luasnip.nodes.insertNode").I,
choice_node = require("luasnip.nodes.choiceNode").C,
dynamic_node = require("luasnip.nodes.dynamicNode").D,
restore_node = require("luasnip.nodes.restoreNode").R,
parser = require("luasnip.util.parser"),
config = require("luasnip.config"),
snippets = { all = {} },
Expand Down
19 changes: 14 additions & 5 deletions lua/luasnip/nodes/choiceNode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,20 @@ function ChoiceNode:change_choice(dir)
self.active_choice:store()
-- tear down current choice.
self.active_choice:input_leave()
self.active_choice:exit()

-- store in old_choice, active_choice has to be disabled to prevent reading
-- from cleared mark in set_mark_rgrav (which will be called in
-- parent:set_text(self,...) a few lines below).
local old_choice = self.active_choice
self.active_choice = nil

-- clear text.
self.parent:set_text(self, { "" })

self.active_choice:exit()

-- stylua: ignore
self.active_choice = dir == 1 and self.active_choice.next_choice
or self.active_choice.prev_choice
self.active_choice = dir == 1 and old_choice.next_choice
or old_choice.prev_choice

self.active_choice.mark = self.mark:copy_pos_gravs(
vim.deepcopy(self.parent.ext_opts[self.active_choice.type].passive)
Expand Down Expand Up @@ -218,7 +224,10 @@ end
-- val_begin/end may be nil, in this case that gravity won't be changed.
function ChoiceNode:set_mark_rgrav(rgrav_beg, rgrav_end)
node.set_mark_rgrav(self, rgrav_beg, rgrav_end)
self.active_choice:set_mark_rgrav(rgrav_beg, rgrav_end)
-- may be set to temporarily in change_choice.
if self.active_choice then
self.active_choice:set_mark_rgrav(rgrav_beg, rgrav_end)
end
end

function ChoiceNode:set_ext_opts(name)
Expand Down
17 changes: 12 additions & 5 deletions lua/luasnip/nodes/dynamicNode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,16 @@ function DynamicNode:update()
self.snip.old_state,
unpack(self.user_args)
)
self.snip:exit()
self.snip = nil

-- enters node.
self.parent:set_text(self, { "" })
self.snip:exit()
else
-- also enter node here.
self.parent:enter_node(self.indx)
tmp = self.fn(self.last_args, self.parent, nil, unpack(self.user_args))
end
self.snip = nil

-- act as if snip is directly inside parent.
tmp.parent = self.parent
Expand Down Expand Up @@ -193,12 +194,18 @@ end
function DynamicNode:update_restore()
-- only restore snippet if arg-values still match.
if self.snip and vim.deep_equal(self:get_args(), self.last_args) then
self.snip.mark = self.mark:copy_pos_gravs(
-- prevent entering the uninitialized snip in enter_node in a few lines.
local tmp = self.snip
self.snip = nil

tmp.mark = self.mark:copy_pos_gravs(
vim.deepcopy(self.parent.ext_opts[types.snippetNode].passive)
)
self.parent:enter_node(self.indx)
self.snip:put_initial(self.mark:pos_begin_raw())
self.snip:update_restore()
tmp:put_initial(self.mark:pos_begin_raw())
tmp:update_restore()

self.snip = tmp
else
self:update()
end
Expand Down
Loading

0 comments on commit a3b71bf

Please sign in to comment.