Skip to content
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

Merged
merged 27 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4adcc68
First try at restoreNode.
L3MON4D3 Nov 27, 2021
e52060e
Export restoreNode in luasnip-module.
L3MON4D3 Nov 27, 2021
15a13b5
Format with stylua
L3MON4D3 Nov 27, 2021
f94e097
Auto generate docs
L3MON4D3 Nov 27, 2021
b911fa7
feat: restoreNode gets its own type.
L3MON4D3 Nov 27, 2021
282aa86
Format with stylua
L3MON4D3 Nov 27, 2021
f6efcef
Allow passing single nodes to `stored` instead of snippetNode only.
L3MON4D3 Nov 27, 2021
edb8508
Include restoreNode in jumpable nodes.
L3MON4D3 Nov 27, 2021
7850966
Format with stylua
L3MON4D3 Nov 27, 2021
fa3c2e2
Initialize subsnippet for get_static_text or get_docstring.
L3MON4D3 Nov 27, 2021
939d6ee
Call exit before clearing.
L3MON4D3 Nov 27, 2021
5f006a5
Merge branch 'master' into restoreNode
L3MON4D3 Nov 27, 2021
e57be7a
Fix get_docstring/get_static_text.
L3MON4D3 Nov 27, 2021
e934b69
Use insertNode as default for unspecified stored[key].
L3MON4D3 Nov 27, 2021
0ce5900
Allow the default for a given key to be passed to the constructor.
L3MON4D3 Nov 28, 2021
42d2f77
Correctly call update for inner snip.
L3MON4D3 Nov 28, 2021
99f24ed
Init `active` with false, otherwise the rn is jumped through in jump_…
L3MON4D3 Nov 28, 2021
ef1230f
Don't ignore update used by choiceNode.
L3MON4D3 Nov 28, 2021
bfbfa1b
Indent the restoreNodes snippets only once.
L3MON4D3 Nov 28, 2021
0da2419
Format with stylua
L3MON4D3 Nov 28, 2021
602d064
Use same function to create snipNodes from `restoreNode` and `stored`.
L3MON4D3 Nov 29, 2021
ce1c739
Fix restore of dynamicNode.
L3MON4D3 Nov 29, 2021
75ccee1
Format with stylua
L3MON4D3 Nov 29, 2021
bd0513c
Add Docs for restoreNode.
L3MON4D3 Nov 30, 2021
23297af
Auto generate docs
L3MON4D3 Nov 30, 2021
c12732c
Mention indent not being influenced by ISN.
L3MON4D3 Nov 30, 2021
99b0c84
Auto generate docs
L3MON4D3 Nov 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}),
Comment on lines +460 to +467
Copy link
Contributor

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.

Copy link
Owner Author

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

```
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