diff --git a/DOC.md b/DOC.md index 91181e0a2..79a411650 100644 --- a/DOC.md +++ b/DOC.md @@ -63,7 +63,32 @@ Snippets are always created using the `s(trigger:string, nodes:table)`-function. It is explained in more detail in [SNIPPETS](#snippets), but the gist is that it creates a snippet that contains the nodes specified in `nodes`, which will be inserted into a buffer if the text before the cursor matches `trigger` when -`expand` is called. +`ls.expand` is called. + +## Jump-index +Nodes that can be jumped to (`insertNode`, `choiceNode`, `dynamicNode`, +`restoreNode`, `snippetNode`) all require a "jump-index" so luasnip knows the +order in which these nodes are supposed to be visited ("jumped to"). + +```lua +s("trig", { + i(1), t"text", i(2), t"text again", i(3) +}) +``` + +These indices don't "run" through the entire snippet, like they do in +textmate-snippets (`"$1 ${2: $3 $4}"`), they restart at 1 in each nested +snippetNode: +```lua +s("trig", { + i(1), t" ", sn(2, { + t" ", i(1), t" ", i(2) + }) +}) +``` +(roughly equivalent to the given textmate-snippet). + +## Adding Snippets The snippets for a given filetype have to be added to luasnip via `ls.add_snippets(filetype, snippets)`. Snippets that should be accessible globally (in all filetypes) have to be added to the special filetype `all`. @@ -92,95 +117,111 @@ The most direct way to define snippets is `s`: ```lua s({trig="trigger"}, {}) ``` +(This snippet is useless beyond serving as a minimal example) + +`s(context, nodes, opts) -> snippet` + +- `context`: Either table or a string. Passing a string is equivalent to passing -(This snippet is useless beyond being a minimal example) - -`s` accepts, as the first argument, a table with the following possible -entries: - -- `trig`: string, plain text by default. The only entry that must be given. -- `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. -- `dscr`: string, description of the snippet, \n-separated or table - for multiple lines. -- `wordTrig`: boolean, if true, the snippet is only expanded if the word - (`[%w_]+`) before the cursor matches the trigger entirely. - True by default. -- `regTrig`: boolean, whether the trigger should be interpreted as a - lua pattern. False by default. -- `docstring`: string, textual representation of the snippet, specified like - `dscr`. Overrides docstrings loaded from json. -- `docTrig`: string, for snippets triggered using a lua pattern: define the - trigger that is used during docstring-generation. -- `hidden`: hint for completion-engines, if set, the snippet should not show - up when querying snippets. -- `priority`: Priority of the snippet, a positive number, 1000 by default. - Snippets with high priority will be matched to a trigger before those with a - lower one. - The priority for multiple snippets can also be set in `add_snippets`. -- `snippetType`: string, should be either `snippet` or `autosnippet` (ATTENTION: - singular form is used), decides whether this snippet has to be triggered by - `ls.expand()` or whether is triggered automatically (don't forget to set - `ls.config.setup({ enable_autosnippets = true })` if you want to use this - feature). If unset it depends on how the snippet is added of which type the - snippet will be. - -`s` can also be a single string, in which case it is used instead of `trig`, all -other values being defaulted: - -```lua -s("trigger", {}) -``` - -The second argument to `s` is a table containing all nodes that belong to the -snippet. If the table only has a single node, it can be passed directly -without wrapping it in a table. - -The third argument (`opts`) is a table with the following valid keys: - -- `condition`: the condition-function `fn(line_to_cursor, matched_trigger, - captures) -> bool`. - The snippet will be expanded only if it returns true (default is a function - that just returns `true`). - The function is called before the text is modified in any way. - Some parameters are passed to the function: The line up to the cursor, the - matched trigger, and the captures (table). -- `show_condition`: Function with signature `f(line_to_cursor) -> bool`. - It is a hint for completion-engines, indicating when the snippet should be - included in current completion candidates. - Defaults to a function returning `true`. - This is different from `condition` because `condition` is evaluated by - LuaSnip on snippet expansion (and thus has access to the matched trigger and - captures), while `show_condition` is evaluated by the completion-engine when - scanning for available snippet candidates. -- `callbacks`: Contains functions that are called upon enterin/leaving a node - of this snippet. - To print text upon entering the _second_ node of a snippet, `callbacks` - should be set as follows: ```lua { - -- position of the node, not the jump-position!! - -- s("trig", {t"first node", t"second node", i(1, "third node")}). - [2] = { - [events.enter] = function(node, _event_args) print("2!") end - } + trig = context } ``` - To register a callback for the snippets' own events, the key `[-1]` may - be used. - More info on events [here](#here) -- `child_ext_opts`, `merge_child_ext_opts`: `ext_opts` applied to the children - of this snippet. More info [here](#ext_opts). - -This `opts`-table can also be passed to e.g. `snippetNode` or `indentSnippetNode`, -but only `callbacks` and the `ext_opts`-related options are used there. - -Snippets contain some interesting tables, e.g. `snippet.env` contains variables -used in the LSP-protocol like `TM_CURRENT_LINE` or `TM_FILENAME` or -`snippet.captures`, where capture-groups of regex-triggers are stored. -Additionally, the string that was used to trigger the snippet is stored in -`snippet.trigger`. These variables/tables are primarily useful in -dynamic/functionNodes, where the snippet can be accessed through the immediate -parent (`parent.snippet`), which is passed to the function. + + The following keys are valid: + - `trig`: string, plain text by default. The only entry that must be given. + - `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. + - `dscr`: string, description of the snippet, \n-separated or table + for multiple lines. + - `wordTrig`: boolean, if true, the snippet is only expanded if the word + (`[%w_]+`) before the cursor matches the trigger entirely. + True by default. + - `regTrig`: boolean, whether the trigger should be interpreted as a + lua pattern. False by default. + - `docstring`: string, textual representation of the snippet, specified like + `dscr`. Overrides docstrings loaded from json. + - `docTrig`: string, for snippets triggered using a lua pattern: define the + trigger that is used during docstring-generation. + - `hidden`: boolean, hint for completion-engines. + If set, the snippet should not show up when querying snippets. + - `priority`: positive number, Priority of the snippet, 1000 by default. + Snippets with high priority will be matched to a trigger before those with a + lower one. + The priority for multiple snippets can also be set in `add_snippets`. + - `snippetType`: string, should be either `snippet` or `autosnippet` (ATTENTION: + singular form is used), decides whether this snippet has to be triggered by + `ls.expand()` or whether is triggered automatically (don't forget to set + `ls.config.setup({ enable_autosnippets = true })` if you want to use this + feature). If unset it depends on how the snippet is added of which type the + snippet will be. + +- `nodes`: A single node, or a list of nodes. The nodes that make up the + snippet. + +- `opts`: A table, with the following valid keys: + + - `condition`: `fn(line_to_cursor, matched_trigger, captures) -> bool`, where + - `line_to_cursor`: `string`, the line up to the cursor. + - `matched_trigger`: `string`, the fully matched trigger (can be retrieved + from `line_to_cursor`, but we already have that info here :D) + - `captures`: if the trigger is pattern, this list contains the + capture-groups. Again, could be computed from `line_to_cursor`, but we + already did so. + + The snippet will be expanded only if this function returns true (default is + a function that just returns `true`). + The function is called before the text on the line is modified in any way. + - `show_condition`: `f(line_to_cursor) -> bool`. + - `line_to_cursor`: `string`, the line up to the cursor. + + This function is (should be) evaluated by completion-engines, indicating + whether the snippet should be included in current completion candidates. + Defaults to a function returning `true`. + This is different from `condition` because `condition` is evaluated by + LuaSnip on snippet expansion (and thus has access to the matched trigger and + captures), while `show_condition` is (should be) evaluated by the + completion-engine when scanning for available snippet candidates. + - `callbacks`: Contains functions that are called upon enterin/leaving a node + of this snippet. + For example: to print text upon entering the _second_ node of a snippet, + `callbacks` should be set as follows: + ```lua + { + -- position of the node, not the jump-index!! + -- s("trig", {t"first node", t"second node", i(1, "third node")}). + [2] = { + [events.enter] = function(node, _event_args) print("2!") end + } + } + ``` + To register a callback for the snippets' own events, the key `[-1]` may + be used. + More info on events [here](#events) + - `child_ext_opts`, `merge_child_ext_opts`: Control `ext_opts` applied to the + children of this snippet. More info on those [here](#ext_opts). + +The `opts`-table can also be passed to e.g. `snippetNode` and +`indentSnippetNode`, but only `callbacks` and the `ext_opts`-related options are +used there. + +### Snippet-Data + +Snippets contain some interesting tables during runtime: +- `snippet.env`: Contains variables used in the LSP-protocol, for example + `TM_CURRENT_LINE` or `TM_FILENAME`. It's possible to add customized variables + here too, check [Environment Namespaces](#environment-namespaces) +- `snippet.captures`: If the snippet was triggered by a pattern (`regTrig`), and + the pattern contained capture-groups, they can be retrieved here. +- `snippet.trigger`: The string that triggered this snippet. Again, only + interesting if the snippet was triggered through `regTrig`, for getting the + full match. + +These variables/tables primarily come in handy in `dynamic/functionNodes`, where +the snippet can be accessed through the immediate parent (`parent.snippet`), +which is passed to the function. +(in most cases `parent == parent.snippet`, but the `parent` of the dynamicNode +is not always the surrounding snippet, it could be a `snippetNode`). ## Api: @@ -212,11 +253,14 @@ s("trigger", { }) ``` +`t(text, node_opts)`: +- `text`: `string` or `string[]` +- `node_opts`: `table`, see [here](#node) # INSERTNODE These Nodes contain editable text and can be jumped to- and from (e.g. -traditional placeholders, like `$1` in textmate-snippets). +traditional placeholders and tabstops, like `$1` in textmate-snippets). The functionality is best demonstrated with an example: @@ -234,18 +278,18 @@ s("trigger", { -The InsertNodes are jumped over in order from `1 to n`. -The 0-th node is special as it's always the last one. -So the order of InsertNode jump is as follows: +The InsertNodes are visited in order `1,2,3,..,n,0`. +(The jump-index 0 also _has_ to belong to an `insertNode`!) +So the order of InsertNode-jumps is as follows: -1. After expansion, we will be at InsertNode 1. -2. After jumping forward, we will be at InsertNode 2. -3. After jumping forward again, we will be at InsertNode 0. +1. After expansion, the cursor is at InsertNode 1, +2. after jumping forward once at InsertNode 2, +3. and after jumping forward again at InsertNode 0. If no 0-th InsertNode is found in a snippet, one is automatically inserted after all other nodes. -The jumping-order doesn't have to follow the "textual" order of the nodes: +The jump-order doesn't have to follow the "textual" order of the nodes: ```lua s("trigger", { t({"After jumping forward once, cursor is here ->"}), i(2), @@ -260,7 +304,7 @@ The above snippet will behave as follows: 3. After jumping forward again, we will be at InsertNode 0. An **important** (because here luasnip differs from other snippet-engines) detail -is that the jump-positions restart at 1 in nested snippets: +is that the jump-indices restart at 1 in nested snippets: ```lua s("trigger", { i(1, "First jump"), @@ -285,18 +329,18 @@ ${1:First jump} :: ${2: ${3:Third jump} : ${4:Fourth jump}} ``` (this is not exactly the same snippet of course, but as close as possible) (the restart-rule only applies when defining snippets in lua, the above -textmate-snippet will expand correctly). +textmate-snippet will expand correctly when parsed). -It's possible to have initial text inside an InsertNode, which is comfortable -for potentially keeping some default-value: -```lua - s("trigger", i(1, "This text is SELECTed after expanding the snippet.")) -``` -This initial text is defined the same way as textNodes, e.g. can be multiline. +`i(jump_index, text, node_opts)` -`i(0)`s can have initial text, but do note that when the SELECTed text is -replaced, its' replacement won't end up in the `i(0)`, but behind it (for -reasons, check out Luasnip#110). +- [`jump_index`](#jump-index): `number`, this determines when this node will be jumped to. +- `text`: `string|string[]`, a single string for just one line, a list with >1 + entries for multiple lines. + This text will be SELECTed when the `insertNode` is jumped into. +- `node_opts`: `table`, see [here](#node) + +If the `jump_index` is `0`, replacing its' `text` will leave it outside the +`insertNode` (for reasons, check out Luasnip#110). # FUNCTIONNODE @@ -329,233 +373,148 @@ s("trig", { -The first parameter of `f` is the function. Its parameters are: - -1. A table of the text of currently contained in the argnodes. - (e.g. `{{line1}, {line1, line2}}`). The snippet-indent will be removed from - all lines following the first. - -2. The immediate parent of the `functionNode`. It is included here as it allows - easy access to anything that could be useful in functionNodes (i.e. - `parent.snippet.env` or `parent.snippet.captures`, which contains capture - groups of regex-triggered snippets). In most cases `parent.env` works, - but if a `functionNode` is nested within a `snippetNode`, the immediate - parent (a `snippetNode`) will contain neither `captures` nor `env`. Those - are only stored in the `snippet`, which can be accessed as `parent.snippet`. - -3. The `user_args` passed in `opts`. Note that there may be multiple user_args - (e.g. `user_args1, ..., user_argsn`). - -The function shall return a string, which will be inserted as-is, or a table -of strings for multiline-string, here all lines following the first will be -prefixed with the snippets' indentation. - - -The second parameter is a table of indices of jumpable nodes whose text is -passed to the function. -The table may be empty, in this case the function is evaluated once upon -snippet-expansion. -If the table only has a single node, it can be passed directly without wrapping -it in a table. -The indices can be specified either as relative to the functionNodes' parent -using numbers or as absolute, using the [`absolute_indexer`](#absolute_indexer). - -The last parameter is, as with any node, `opts`. -`functionNode` accepts one additional option: `user_args`, a table of values -passed to the function. -These exist to more easily reuse functionNode-functions, when applicable: - -```lua -local function reused_func(_,_, user_arg1) - return user_arg1 -end - -s("trig", { - f(reused_func, {}, { - user_args = {"text"} - }), - f(reused_func, {}, { - user_args = {"different text"} - }), -}) -``` - - - -![FunctionNode2](https://user-images.githubusercontent.com/25300418/184359244-ef83b8f7-28a3-45ff-a2af-5b564f213749.gif) - - - -Examples: -Use captures from the regex-trigger using a functionNode: +`f(fn, argnode_references, node_opts)`: +- `fn`: `function(argnode_text, parent, user_args1,...,user_argsn) -> text` + - `argnode_text`: `string[][]`, the text currently contained in the argnodes + (e.g. `{{line1}, {line1, line2}}`). The snippet-indent will be removed from + all lines following the first. + + - `parent`: The immediate parent of the `functionNode`. + It is included here as it allows easy access to some information that could + be useful in functionNodes (see [here](#snippet-data) for some examples). + Many snippets access the surrounding snippet just as `parent`, but if the + `functionNode` is nested within a `snippetNode`, the immediate parent is a + `snippetNode`, not the surrounding snippet (only the surrounding snippet + contains data like `env` or `captures`). + + - `user_args`: The `user_args` passed in `opts`. Note that there may be multiple user_args + (e.g. `user_args1, ..., user_argsn`). + + `fn` shall return a string, which will be inserted as-is, or a table of + strings for multiline-string, here all lines following the first will be + prefixed with the snippets' indentation. + +- `argnode_references`: `node_reference[]|node_refernce|nil`. + Either no, a single, or multiple [node-references](#node_reference). + Changing any of these will trigger a re-evaluation of `fn`, and insertion of + the updated text. + If no node-reference is passed, the `functionNode` is evaluated once upon + expansion. + +- `node_opts`: `table`, see [here](#node). One additional key is supported: + - `user_args`: `any[]`, these will be passed to `fn` as `user_arg1`-`user_argn`. + These make it easier to reuse similar functions, for example a functionNode + that wraps some text in different delimiters (`()`, `[]`, ...). -```lua -s({trig = "b(%d)", regTrig = true}, - f(function(args, snip) return - "Captured Text: " .. snip.captures[1] .. "." end, {}) -) -``` - - - -![FunctionNode3](https://user-images.githubusercontent.com/25300418/184359248-6b13a80c-f644-4979-a566-958c65a4e047.gif) - - - -The table passed to functionNode: - -```lua -s("trig", { - i(1, "text_of_first"), - i(2, {"first_line_of_second", "second_line_of_second"}), - -- order is 2,1, not 1,2!! - f(function(args, snip) - --here - end, {2, 1} )}) -``` + ```lua + local function reused_func(_,_, user_arg1) + return user_arg1 + end - + s("trig", { + f(reused_func, {}, { + user_args = {"text"} + }), + f(reused_func, {}, { + user_args = {"different text"} + }), + }) + ``` -![FunctionNode4](https://user-images.githubusercontent.com/25300418/184359259-ebb7cfc0-e30b-4735-9627-9ead45d9f27c.gif) + + + ![FunctionNode2](https://user-images.githubusercontent.com/25300418/184359244-ef83b8f7-28a3-45ff-a2af-5b564f213749.gif) + + - +**Examples**: -At `--here`, `args` would look as follows (provided no text was changed after -expansion): -```lua -args = { - {"first_line_of_second", "second_line_of_second"}, - {"text_of_first"} -} -``` +- Use captures from the regex-trigger using a functionNode: - - -![FunctionNode5](https://user-images.githubusercontent.com/25300418/184359263-89323682-6128-40ea-890e-b184a1accf80.gif) - - + ```lua + s({trig = "b(%d)", regTrig = true}, + f(function(args, snip) return + "Captured Text: " .. snip.captures[1] .. "." end, {}) + ) + ``` + + + + ![FunctionNode3](https://user-images.githubusercontent.com/25300418/184359248-6b13a80c-f644-4979-a566-958c65a4e047.gif) + + -One more example to show usage of `absolute_indexer`: -```lua -s("trig", { - i(1, "text_of_first"), - i(2, {"first_line_of_second", "second_line_of_second"}), - f(function(args, snip) - -- just concat first lines of both. - return args[1][1] .. args[2][1] - end, {ai[2], ai[1]} )}) -``` +- `argnodes_text` during function-evaluation: - + ```lua + s("trig", { + i(1, "text_of_first"), + i(2, {"first_line_of_second", "second_line_of_second"}), + f(function(args, snip) + --here + -- order is 2,1, not 1,2!! + end, {2, 1} )}) + ``` + + + + ![FunctionNode4](https://user-images.githubusercontent.com/25300418/184359259-ebb7cfc0-e30b-4735-9627-9ead45d9f27c.gif) + + + + At `--here`, `args` would look as follows (provided no text was changed after + expansion): + ```lua + args = { + {"first_line_of_second", "second_line_of_second"}, + {"text_of_first"} + } + ``` + + + + ![FunctionNode5](https://user-images.githubusercontent.com/25300418/184359263-89323682-6128-40ea-890e-b184a1accf80.gif) + + -![FunctionNode6](https://user-images.githubusercontent.com/25300418/184359271-018a703d-a9c8-4c9d-8833-b16495be5b08.gif) +- [`absolute_indexer`](#absolute_indexer): - + ```lua + s("trig", { + i(1, "text_of_first"), + i(2, {"first_line_of_second", "second_line_of_second"}), + f(function(args, snip) + -- just concat first lines of both. + return args[1][1] .. args[2][1] + end, {ai[2], ai[1]} )}) + ``` + + + + ![FunctionNode6](https://user-images.githubusercontent.com/25300418/184359271-018a703d-a9c8-4c9d-8833-b16495be5b08.gif) + + If the function only performs simple operations on text, consider using the `lambda` from [`luasnip.extras`](#extras) -# POSTFIX SNIPPET - -Postfix snippets, famously used in -[rust analyzer](https://rust-analyzer.github.io/) and various IDEs, are a type -of snippet, which alters text before the snippets trigger. While these -can be implemented using regTrig snippets, this helper makes the process easier -in most cases. - -The simplest example, which surrounds the text preceeding the `.br` with -brackets `[]`, looks like: - -```lua - - postfix(".br", { - f(function(_, parent) - return "[" .. parent.snippet.env.POSTFIX_MATCH .. "]" - end, {}), - }) -``` - - - -![postfix](https://user-images.githubusercontent.com/25300418/184359322-d8547259-653e-4ada-86e8-666da2c52010.gif) - - - -and is triggered with `xxx.br` and expands to `[xxx]`. - -Note the `parent.snippet.env.POSTFIX_MATCH` in the function node. This is additional -field generated by the postfix snippet. This field is generated by extracting -the text matched (using a configurable matching string, see below) from before -the trigger. In the case above, the field would equal `"xxx"`. This is also -usable within dynamic nodes. - -This field can also be used within lambdas and dynamic nodes. - -```lua - postfix(".br", { - l("[" .. l.POSTFIX_MATCH .. "]"), - }) -``` - -```lua - postfix(".brd", { - d(1, function (_, parent) - return sn(nil, {t("[" .. parent.env.POSTFIX_MATCH .. "]")}) - end) - }), -``` - - - -![postfix2](https://user-images.githubusercontent.com/25300418/184359323-1b250b6d-7b23-43a3-846f-b6cc2c9df9fc.gif) - - - -The arguments to `postfix` are identical to the arguments to `s` but with a few -extra options. - -The first argument can be either a string or a table. If it is a string, that -string will act as the trigger, and if it is a table it has the same valid keys -as the table in the same position for `s` except: - -- `wordTrig`: This key will be ignored if passed in, as it must always be - false for postfix snippets. -- `match_pattern`: The pattern that the line before the trigger is matched - against. The default match pattern is `"[%w%.%_%-]+$"`. Note the `$`. This - matches since only the line _up until_ the beginning of the trigger is - matched against the pattern, which makes the character immediately - preceeding the trigger match as the end of the string. - -Some other match strings, including the default, are available from the postfix -module. `require("luasnip.extras.postfix).matches`: - -- `default`: [%w%.%_%-%"%']+$ -- 'line': `^.+$` - -The second argument is identical to the second argument for `s`, that is, a -table of nodes. - -The optional third argument is the same as the third (`opts`) argument to the -`s` function, but with one difference: - -The postfix snippet works using a callback on the pre_expand event of the -snippet. If you pass a callback on the pre_expand event (structure example -below) it will get run after the builtin callback. This means that your -callback will have access to the `POSTFIX_MATCH` field as well. - -```lua - - { - callbacks = { - [-1] = { - [events.pre_expand] = function(snippet, event_args) - -- function body to match before the dot - -- goes here - end - } - } - } -``` +## NODE_REFERENCE +Node-references are used to refer to other nodes in various parts of luasnip's +API. +For example, argnodes in functionNode, dynamicNode or lambda are +node-references. +These references can be either of: + - `number`: the jump-index of the node. + This will be resolved relative to the parent of the node this is passed to. + (So, only nodes with the same parent can be referenced. This is very easy to + grasp, but also limiting) + - [`absolute_indexer`](#absolute_indexer): the absolute position of the + node. This will come in handy if the referred-to node is not in the same + snippet/snippetNode as the one this node-reference is passed to. + - `node`: just the node. Usage of this is discouraged, since it can lead to + subtle errors (for example if the node passed here is captured in a closure + and therefore not copied with the remaining tables in the snippet (there's a + big comment about just this in commit 8bfbd61)). # CHOICENODE @@ -575,36 +534,37 @@ ChoiceNodes allow choosing between multiple nodes. -`c()` expects as its first arg, as with any jumpable node, its position in the -jumplist, and as its second a table with nodes, the choices. This table can -either contain a single node or a table of nodes. In the latter case the table -will be converted into a `snippetNode`. -The third parameter is a table of options with the following keys: - -- `restore_cursor`: `false` by default. If it is set, and the node that was - being edited also appears in the switched-to choice (can be the case if a - `restoreNode` is present in both choice) the cursor is restored relative to - that node. - The default is `false` as enabling might lead to worse performance. It's - possible to override the default by wrapping the `choiceNode`-constructor - in another function that sets `opts.restore_cursor` to `true` and then using - that to construct `choiceNode`s: - ```lua - local function restore_cursor_choice(pos, choices, opts) - if opts then - opts.restore_cursor = true - else - opts = {restore_cursor = true} - end - return c(pos, choices, opts) - end - ``` +`c(jump_index, choices, node_opts)` +- [`jump_index`](#jump-index): `number`, since choiceNodes can be jumped to, they need their + jump-indx. +- `choices`: `node[]|node`, the choices. The first will be initialliy active. + A list of nodes will be turned into a `snippetNode`. +- `node_opts`: `table`. `choiceNode` supports the keys common to all nodes + described [here](#node), and one additional key: + - `restore_cursor`: `false` by default. If it is set, and the node that was + being edited also appears in the switched-to choice (can be the case if a + `restoreNode` is present in both choice) the cursor is restored relative to + that node. + The default is `false` as enabling might lead to decreased performance. It's + possible to override the default by wrapping the `choiceNode`-constructor + in another function that sets `opts.restore_cursor` to `true` and then using + that to construct `choiceNode`s: + ```lua + local function restore_cursor_choice(pos, choices, opts) + if opts then + opts.restore_cursor = true + else + opts = {restore_cursor = true} + end + return c(pos, choices, opts) + end + ``` Jumpable nodes that normally expect an index as their first parameter don't -need one inside a choiceNode; their index is the same as the choiceNodes'. +need one inside a choiceNode; their jump-index is the same as the choiceNodes'. As it is only possible (for now) to change choices from within the choiceNode, -make sure that all of the choices have some place for the cursor to stop at. +make sure that all of the choices have some place for the cursor to stop at! This means that in `sn(nil, {...nodes...})` `nodes` has to contain e.g. an `i(1)`, otherwise luasnip will just "jump through" the nodes, making it impossible to change the choice. @@ -618,8 +578,11 @@ c(1, { }) ``` -The active choice for a choiceNode can be changed by calling `ls.change_choice(1)` -(forwards) or `ls.change_choice(-1)` (backwards), for example via +The active choice for a choiceNode can be changed by either calling one of +`ls.change_choice(1)` (forwards) or `ls.change_choice(-1)` (backwards), or by +calling `ls.set_choice(choice_indx)`. +One way to easily interact with choiceNodes is binding `change_choice(1/-1)` to +keys: ```lua -- set keybinds for both INSERT and VISUAL. @@ -635,12 +598,14 @@ choice can be selected right away, via `vim.ui.select`. # SNIPPETNODE SnippetNodes directly insert their contents into the surrounding snippet. -This is useful for choiceNodes, which only accept one child, or dynamicNodes, -where nodes are created at runtime and inserted as a snippetNode. +This is useful for `choiceNode`s, which only accept one child, or +`dynamicNode`s, where nodes are created at runtime and inserted as a +`snippetNode`. + +Their syntax is similar to `s`, however, where snippets require a table +specifying when to expand, `snippetNode`s, similar to `insertNode`s, expect +a jump-index. -Syntax is similar to snippets, however, where snippets require a table -specifying when to expand, snippetNodes, similar to insertNodes, expect a -number, as they too are jumpable: ```lua s("trig", sn(1, { t("basically just text "), @@ -648,14 +613,25 @@ number, as they too are jumpable: })) ``` -Note that snippetNodes don't expect an `i(0)`. - ![SnippetNode](https://user-images.githubusercontent.com/25300418/184359349-2127147e-2f57-4612-bdb5-4c9eafc93fad.gif) +`sn(jump_index, nodes, node_opts)` + +- [`jump_index`](#jump-index): `number`, the usual. +- `nodes`: `node[]|node`, just like for `s`. + Note that `snippetNode`s don't accept an `i(0)`, so the jump-indices of the nodes + inside them have to be in `1,2,...,n`. +- `node_opts`: `table`: again, all [common](#node) keys are supported, but also + - `callbacks`, + - `child_ext_opts` and + - `merge_child_ext_opts`, + + which are further explained in [`SNIPPETS`](#snippets). + # INDENTSNIPPETNODE By default, all nodes are indented at least as deep as the trigger. With these @@ -700,9 +676,16 @@ s("isn2", { Here the `//` before `This is` is important, once again, because indent is only applied after linebreaks. To enable such usage, `$PARENT_INDENT` in the indentstring is replaced by the -parents' indent (duh). +parent's indent (duh). + +`isn(jump_index, nodes, indentstring, node_opts)` +All of these except `indentstring` are exactly the same as [`snippetNode`](#snippetnode). +- `indentstring`: `string`, will be used to indent the nodes inside this + `snippetNode`. + All occurences of `"$PARENT_INDENT"` are replaced with the actual indent of + the parent. # DYNAMICNODE @@ -710,36 +693,40 @@ Very similar to functionNode, but returns a snippetNode instead of just text, which makes them very powerful as parts of the snippet can be changed based on user-input. -The prototype for the dynamicNodes' constructor is -`d(position:int, function, argnodes:table of nodes, opts: table)`: +`d(jump_index, function, node-references, opts)`: -1. `position`: just like all jumpable nodes, when this node will be jumped into. -2. `function`: `fn(args, parent, old_state, user_args1, ..., user_argsn) -> snippetNode` - This function is called when the argnodes' text changes. It generates and - returns (wrapped inside a `snippetNode`) the nodes that should be inserted - at the dynamicNodes place. +- [`jump_index`](#jump-index): `number`, just like all jumpable nodes, its' position in the + jump-list. +- `function`: `fn(args, parent, old_state, user_args) -> snippetNode` + This function is called when the argnodes' text changes. It should generate + and returns (wrapped inside a `snippetNode`) nodes, these will be inserted at + the dynamicNodes place. `args`, `parent` and `user_args` are also explained in - [functionNode](#functionnode) - * `args`: `table of text` (`{{"node1line1", "node1line2"}, {"node2line1"}}`) - from nodes the dynamicNode depends on. - * `parent`: the immediate parent of the `dynamicNode`). - * `old_state`: a user-defined table. This table may contain - anything, its intended usage is to preserve information from the previously - generated `snippetNode`: If the `dynamicNode` depends on other nodes it may - be reconstructed, which means all user input (text inserted in `insertNodes`, - changed choices) to the previous dynamicNode is lost. + [FUNCTIONNODE](#functionnode) + - `args`: `table of text` (`{{"node1line1", "node1line2"}, {"node2line1"}}`) + from nodes the `dynamicNode` depends on. + - `parent`: the immediate parent of the `dynamicNode`. + - `old_state`: a user-defined table. This table may contain anything, its + intended usage is to preserve information from the previously generated + `snippetNode`: If the `dynamicNode` depends on other nodes it may be + reconstructed, which means all user input (text inserted in `insertNodes`, + changed choices) to the previous dynamicNode is lost. The `old_state` table must be stored in `snippetNode` returned by - the function (`snippetNode.old_state`). + the function (`snippetNode.old_state`). The second example below illustrates the usage of `old_state`. - * `user_args1, ..., user_argsn`: passed through from `dynamicNode`-opts. -3. `argnodes`: Indices of nodes the dynamicNode depends on: if any of these trigger an - update, the `dynamicNode`s' function will be executed, and the result inserted at - the `dynamicNodes` place. - Can be a single index or a table of indices. -4. `opts`: Just like `functionNode`, `dynamicNode` also accepts `user_args` in - addition to options common to all nodes. + - `user_args`: passed through from `dynamicNode`-opts, may be more than one + argument. +- `node_references`: `node_reference[]|node_references|nil`, + [References](#node_reference) to the nodes the dynamicNode depends on: if any + of these trigger an update (for example if the text inside them + changes), the `dynamicNode`s' function will be executed, and the result + inserted at the `dynamicNodes` place. + (`dynamicNode` behaves exactly the same as `functionNode` in this regard). -Examples: +- `opts`: In addition to the [usual](#node) keys, there is, again, + - `user_args`, which is described in [FUNCTIONNODE](#functionnode). + +**Examples**: This `dynamicNode` inserts an `insertNode` which copies the text inside the first `insertNode`. @@ -806,9 +793,8 @@ functions. # RESTORENODE -This node can store and restore a snippetNode that was modified (changed -choices, inserted text) by the user. Its usage is best demonstrated by an -example: +This node can store and restore a snippetNode as-is. This includes changed +choices and changed text. Its' usage is best demonstrated by an example: ```lua s("paren_change", { @@ -819,7 +805,8 @@ s("paren_change", { }), }, { stored = { - user_text = i(1, "default_text") + -- key passed to restoreNodes. + ["user_text"] = i(1, "default_text") } }) ``` @@ -832,18 +819,20 @@ s("paren_change", { 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`. +`r(jump_index, key, nodes, node_opts)`: + +- [`jump_index`](#jump-index), when to jump to this node. +- `key`, `string`: `restoreNode`s with the same key share their content. +- `nodes`, `node[]|node`: the content 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 for a given key may be defined multiple times, but if the + contents differ, it's undefined which will actually be used. + If a key's content is defined in a `dynamicNode`, it will not be initially + 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 @@ -854,7 +843,7 @@ An important-to-know limitation of `restoreNode` is that, for a given key, only one may be visible at a time. See [this issue](https://github.com/L3MON4D3/LuaSnip/issues/234) for details. -The `restoreNode` is also useful for storing user-input across updates of a +The `restoreNode` is especially useful for storing input across updates of a `dynamicNode`. Consider this: ```lua @@ -901,10 +890,13 @@ that really bothers you feel free to open an issue. # ABSOLUTE_INDEXER -The `absolute_indexer` can be used to pass text of nodes to a function/dynamicNode -that it doesn't share a parent with. -Normally, accessing the outer `i(1)` isn't possible from inside e.g. a -snippetNode (nested inside a choiceNode to make this example more practical): +A more capable way of [referencing nodes](#node_reference)! +Using only [`jump indices`](#jump-index), accessing an outer `i(1)` isn't possible +from inside e.g. a snippetNode, since only nodes with the same parent can be +referenced (jump indices are interpreted relative to the parent of the node +that's passed the reference). +The `absolute_indexer` can be used to reference nodes based on their absolute +position in the snippet, which lifts this restriction. ```lua s("trig", { @@ -938,7 +930,7 @@ s("trig", { There are some quirks in addressing nodes: ```lua s("trig", { - i(2), -- ai[2]: indices based on insert-order, not position. + i(2), -- ai[2]: indices based on jump-index, not position. sn(1, { -- ai[1] i(1), -- ai[1][1] t"lel", -- not addressable. @@ -978,139 +970,193 @@ ai[1][2][3] == ai(1, 2, 3) == ai{1, 2, 3} # EXTRAS -The module `"luasnip.extras"` contains nodes that ease writing snippets (This -is only a short outline, their usage is shown more expansively in -`Examples/snippets.lua`): - -- `lambda`: A shortcut for `functionNode`s that only do very basic string- -manipulation. For example, to replace all occurences of "a" in the nth insert -with "e", one could use `lambda(lambda._1:gsub("a", "e"), n)` (signature is -similar to that of `functionNode`). -If a node has multiple lines, they will be concatenated using "\n". - -- `match`: Can insert text based on a predicate (shorthand for `functionNode`s). -The complete signature for the node is `match(argnodes, condition, then, else)`, where - * `argnodes` can be specified as in `functionNode`, - * `condition` may be a - * string: interpreted as a lua-pattern. Matched on the `\n`-joined (in case +## Lambda +A shortcut for `functionNode`s that only do very basic string- +manipulation. +`l(lambda, argnodes)`: +- `lambda`: An object created by applying string-operations to `l._n`, objects + representing the `n`th argnode. + For example: + - `l._1:gsub("a", "e")` replaces all occurences of "a" in the text of the + first argnode with "e", or + - `l._1 .. l._2` concats text of the first and second argnode. + If an argnode contains multiple lines of text, they are concatenated with + `"\n"` prior to any operation. +- `argnodes`, [`node-references`](#node_reference), just like in function- and + dynamicNode. + +There are many examples for `lamda` in `Examples/snippets.lua` + +## Match +`match` can insert text based on a predicate (again, a shorthand for `functionNode`). + +`match(argnodes, condition, then, else)`, where + * `argnode`: A single [`node-reference`](#node_reference). May not be nil, or + a table. + * `condition` may be either of + * `string`: interpreted as a lua-pattern. Matched on the `\n`-joined (in case it's multiline) text of the first argnode (`args[1]:match(condition)`). - * function: `fn(args, snip) -> bool`: takes the same parameters as the + * `function`: `fn(args, snip) -> bool`: takes the same parameters as the `functionNode`-function, any value other than nil or false is interpreted as a match. - * lambda: `l._n` is the `\n`-joined text of the nth argnode. - Useful if string-manipulations have to be performed before the string is matched. - - * `then` is inserted if the condition matches, `else` if it doesn't. They can - both be either text, lambda or function (with the same parameters as - specified above). - If `then` is not given, the `then`-value depends on what was specified as the - `condition`: - * pattern: Simply the return value from the `match`, e.g. the entire match, - or, if there were capture groups, the first capture group. - * function: the return value of the function if it is either a string, or a - table (if there is no `then`, the function cannot return a table containing - something other than strings). - * lambda: Simply the first value returned by the lambda. - - Examples: - * `match(n, "^ABC$", "A")` inserts "A" if the `n`th jumpable node matches - "ABC" exactly, nothing otherwise. - * `match(n, lambda._1:match(lambda._1:reverse()), "PALINDROME")` inserts - "PALINDROME" if the nth jumpable node is a palindrome. + * `lambda`: `l._n` is the `\n`-joined text of the nth argnode. + Useful if string-manipulations have to be performed before the string is matched. + Should end with `match`, but any other truthy result will be interpreted + as matching. + + * `then` is inserted if the condition matches, + * `else` if it does not. + +Both `then` and `else` can be either text, lambda or function (with the same parameters as +specified above). +`then`'s default-value depends on the `condition`: + * `pattern`: Simply the return value from the `match`, e.g. the entire match, + or, if there were capture groups, the first capture group. + * `function`: the return value of the function if it is either a string, or a + table (if there is no `then`, the function cannot return a table containing + something other than strings). + * `lambda`: Simply the first value returned by the lambda. - ```lua - s("trig", { - i(1), t":", - i(2), t"::", - m({1, 2}, l._1:match("^"..l._2.."$"), l._1:gsub("a", "e")) - }) - ``` - This inserts the text of the first insertNode, with all occurences of `a` - replaced with `e` if the second insertNode matches the first exactly. +Examples: +* `match(n, "^ABC$", "A")` . +* `match(n, lambda._1:match(lambda._1:reverse()), "PALINDROME")` -- `rep`: repeats the node with the passed index. `rep(1)` to repeat the content -of the first insert. + ```lua + s("trig", { + i(1), t":", + i(2), t"::", + m({1, 2}, l._1:match("^"..l._2.."$"), l._1:gsub("a", "e")) + }) + ``` -- `partial`: directly inserts the output of a function. Useful for e.g. -`partial(os.date, "%Y")` (arguments passed after the function are passed to it). -- `nonempty`: inserts text if the insert at the given index doesn't contain any -text. `nonempty(n, "not empty!", "empty!")` inserts "empty!" if insert n is -empty, "not empty!" if it isn't. +* ```lua + s("extras1", { + i(1), t { "", "" }, m(1, "^ABC$", "A") + }), + ``` + Inserts "A" if the node with jump-index `n` matches "ABC" exactly, nothing otherwise. -- `dynamic_lambda`: Operates almost exactly like `lambda`, only that it can be -jumped to, and it's contents therefore be easily overridden. -`dynamic_lambda(2, lambda._1..lambda._1, 1)` will first contain the content of -insert 1 appended to itself, but the second jump will lead to it, making it -easy to override the generated text. -The text will only be changed when a argnode updates it. + + + ![extras1](https://user-images.githubusercontent.com/25300418/184359431-50f90599-3db0-4df0-a3a9-27013e663649.gif) + + -```lua -ls.add_snippets("all", { - s("extras1", { - i(1), t { "", "" }, m(1, "^ABC$", "A") - }), +* ```lua s("extras2", { i(1, "INPUT"), t { "", "" }, m(1, l._1:match(l._1:reverse()), "PALINDROME") }), + ``` + Inserts `"PALINDROME"` if i(1) contains a palindrome. + + + + ![extras2](https://user-images.githubusercontent.com/25300418/184359435-21e4de9f-c56b-4ee1-bff4-331b68e1c537.gif) + + +* ```lua s("extras3", { i(1), t { "", "" }, i(2), t { "", "" }, m({ 1, 2 }, l._1:match("^" .. l._2 .. "$"), l._1:gsub("a", "e")) }), - s("extras4", { i(1), t { "", "" }, extras.rep(1) }), - s("extras5", { extras.partial(os.date, "%Y") }), - s("extras6", { i(1, ""), t { "", "" }, extras.nonempty(1, "not empty!", "empty!") }), - s("extras7", { i(1), t { "", "" }, extras.dynamic_lambda(2, l._1 .. l._1, 1) }), -}) + ``` + This inserts the text of the node with jump-index 1, with all occurences of + `a` replaced with `e`, if the second insertNode matches the first exactly. + + + + ![extras3](https://user-images.githubusercontent.com/25300418/184359436-515ca1cc-207f-400d-98ba-39fa166e22e4.gif) + + + +## Repeat + +Inserts the text of the passed node. + +`rep(node_reference)` +- `node_reference`, a single [`node-reference`](#node_reference). + +```lua +s("extras4", { i(1), t { "", "" }, extras.rep(1) }), ``` -- `conditions.show`: Contains typical predicates/functions used as - `show`-condition. Currently this is just `line_end` -- `conditions.expand`: Contains typical predicates/functions used as - `expand`-condition. Currently this is just `line_begin` - Contains everything from `conditions.show` as well. -- `conditions`: Provides a function `make_condition(foo)` which takes a function - as argument and returns a *condition object* for which several operators are - defined: - - `c1 + c2 -> c1 or c2` - - `c1 * c2 -> c1 and c2` - - `-c1 -> not c1` - - `c1 ^ c2 -> c1 xor/!= c2` - - `c1 % c2 -> c1 xnor/== c2`: This decision may look weird but as we weren't - able to use `==`, we decided to take something that makes one scratch ones - head (and thus avoid making false assumptions). - For more details look at [this comment](https://github.com/L3MON4D3/LuaSnip/pull/612#issuecomment-1264487743). - - `conditions.show`s and `conditions.expand`s members all are also condition - objects so you can work with those too. - - Thus you can easily combine existing predicates. Like in - `conditions.expand.line_end + conditions.expand.line_begin` instead of doing - something like - `function(...) return conditions.expand.line_end(...) or conditions.expand.line_begin(...) end`. + + +![extras4](https://user-images.githubusercontent.com/25300418/184359193-6525d60d-8fd8-4fbd-9d3f-e3e7d5a0259f.gif) + + + +## Partial + +Evaluates a function on expand and inserts its' value. + +`partial(fn, params...)` +- `fn`: any function +- `params`: varargs, any, will be passed to `fn`. + +For example `partial(os.date, "%Y")` inserts the current year on expansion. + + +```lua +s("extras5", { extras.partial(os.date, "%Y") }), +``` + + + +![extras5](https://user-images.githubusercontent.com/25300418/184359206-6c25fc3b-69e1-4529-9ebf-cb92148f3597.gif) + + + +## Nonempty +Inserts text if the referenced node doesn't contain any text. +`nonempty(node_reference, not_empty, empty)` +- `node_reference`, a single [node-reference](#node_reference). +- `not_empty`, `string`: inserted if the node is not empty. +- `empty`, `string`: inserted if the node is empty. + +```lua +s("extras6", { i(1, ""), t { "", "" }, extras.nonempty(1, "not empty!", "empty!") }), +``` -extras1: ![extras1](https://user-images.githubusercontent.com/25300418/184359431-50f90599-3db0-4df0-a3a9-27013e663649.gif) +![extras6](https://user-images.githubusercontent.com/25300418/184359213-79a71d1e-079c-454d-a092-c231ac5a98f9.gif) + + + +## Dynamic Lambda -extras2: ![extras2](https://user-images.githubusercontent.com/25300418/184359435-21e4de9f-c56b-4ee1-bff4-331b68e1c537.gif) +Pretty much the same as lambda, it just inserts the resulting text as an +insertNode, and, as such, it can be quickly overridden. -extras3: ![extras3](https://user-images.githubusercontent.com/25300418/184359436-515ca1cc-207f-400d-98ba-39fa166e22e4.gif) +`dynamic_lambda(jump_indx, lambda, node_references)` +- `jump_indx`, as usual, the jump-indx. -extras4: ![extras4](https://user-images.githubusercontent.com/25300418/184359193-6525d60d-8fd8-4fbd-9d3f-e3e7d5a0259f.gif) +The remaining arguments carry over from lambda. -extras5: ![extras5](https://user-images.githubusercontent.com/25300418/184359206-6c25fc3b-69e1-4529-9ebf-cb92148f3597.gif) +```lua +s("extras7", { i(1), t { "", "" }, extras.dynamic_lambda(2, l._1 .. l._1, 1) }), +``` -extras6: ![extras6](https://user-images.githubusercontent.com/25300418/184359213-79a71d1e-079c-454d-a092-c231ac5a98f9.gif) + -extras7: ![extras7](https://user-images.githubusercontent.com/25300418/184359221-1f090895-bc59-44b0-a984-703bf8d278a3.gif) +![extras7](https://user-images.githubusercontent.com/25300418/184359221-1f090895-bc59-44b0-a984-703bf8d278a3.gif) ## FMT -`require("luasnip.extras.fmt").fmt` can be used to create snippets in a more -readable way. +Authoring snippets can be quite clunky, especially since every second node is +probably a `textNode`, inserting a small number of characters between two more +complicated nodes. +`fmt` can be used to define snippets in a much more readable way. This is +achieved by borrowing (as the name implies) from `format`-functionality (our +syntax is very similar to +[python's](https://docs.python.org/3/library/stdtypes.html#str.format)). +`fmt` accepts a string and a table of nodes. Each occurrence of a delimiter-pair +in the string is replaced by one node from the table, while text outside the +delimiters is turned into textNodes. Simple example: @@ -1147,6 +1193,9 @@ ls.add_snippets("all", { +One important detail here is that the position of the delimiters does not, in +any way, correspond to the jump-index of the nodes! + `fmt(format:string, nodes:table of nodes, opts:table|nil) -> table of nodes` * `format`: a string. Occurences of `{}` ( `{,}` are customizable, more @@ -1173,24 +1222,91 @@ ls.add_snippets("all", { There is also `require("luasnip.extras.fmt").fmta`. This only differs from `fmt` by using angle-brackets (`<>`) as the default-delimiter. +## Conditions + +This module (`luasnip.extras.condition`) contains function that can be passed to +a snippet's `condition` or `show_condition`. These are grouped accordingly into +`luasnip.extras.conditions.expand` and `luasnip.extras.conditions.show`: + +**`expand`**: +- `line_begin`: only expand if the cursor is at the beginning of the line. + +**`show`**: +- `line_end`: only expand at the end of the line. + +`expand` contains, additionally, all conditions provided by `show`. + +### Condition-Objects + +`luasnip.extras.conditions` also contains condition-objects. These can, just +like functions, be passed to `condition` or `show_condition`, but can also be +combined with each other into logical expressions: + +- `c1 + c2 -> c1 or c2` +- `c1 * c2 -> c1 and c2` +- `-c1 -> not c1` +- `c1 ^ c2 -> c1 xor(!=) c2` +- `c1 % c2 -> c1 xnor(==) c2`: This decision may seem weird, considering how + there is an overload for the `==`-operator. Unfortunately, it's not possible + to use this for our purposes (some info + [here](https://github.com/L3MON4D3/LuaSnip/pull/612#issuecomment-1264487743)), + so we decided to make use of a more obscure symbol (which will hopefully avoid + false assumptions about its meaning). + +This makes logical combinations of conditions very readable. Compare +```lua +condition = conditions.expand.line_end + conditions.expand.line_begin +``` + +with the more verbose + +```lua +condition = function(...) return conditions.expand.line_end(...) or conditions.expand.line_begin(...) end +``` + +The conditions provided in `show` and `expand` are already condition-objects. To +create new ones, use +`require("luasnip.extras.conditions").make_condition(condition_fn)` + ## On The Fly snippets -You can create snippets that are not for being used all the time but only -in a single session. -This behaves as an "operator" takes what is in a register and transforms it into a -snippet using words prefixed as $ as inputs or copies (depending on if the same word appears -more than once). You can escape $ by repeating it. +Sometimes it's desirable to create snippets tailored for exactly the current +situation. For example inserting repetitive, but just slightly different +invocations of some function, or supplying data in some schema. +On The Fly-snippets enable exactly this usecase: they can be quickly created +and expanded, with as little disruption as possible. + +Since they should mainly fast to write, and don't necessarily need all bells and +whistles, they don't make use of lsp/textmate-syntax, but a more simplistic one: +* `$anytext` denotes a placeholder (`insertNode`) with text "anytext". The text + also serves as a unique key: if there are multiple placeholders with the same + key, only the first will be editable, the others will just mirror it. +* ... That's it. `$` can be escaped by preceding it with a second `$`, all other + symbols will be interpreted literally. + +There is currently only one way to expand On The Fly-snippets: +`require('luasnip.extras.otf').on_the_fly("")` will interpret +whatever text is in the register `` as a snippet, and expand it +immediately. +The idea behind this mechanism is that it enables a very immediate way of +supplying and retrieving (expanding) the snippet: write the snippet-body into +the buffer, cut/yank it into some register, and call `on_the_fly("")` +to expand the snippet. + +Here's one set of example-keybindings: -In order to use, add something like this to your config: ```vim -vnoremap "eclua require('luasnip.extras.otf').on_the_fly() +" in the first call: passing the register is optional since `on_the_fly` +" defaults to the unnamed register, which will always contain the previously cut +" text. +vnoremap "eclua require('luasnip.extras.otf').on_the_fly("e") inoremap lua require('luasnip.extras.otf').on_the_fly("e") ``` -Notice that you can use your own mapping instead of , and you can pick another register -instead of `"p`. You can even use it several times, as if it where a macro if you add several -mapppings like: +Obviously, `` is arbritary, and can be changed to any other key-combo. +Another interesting application is allowing multiple on the fly-snippets at the +same time, by retrieving snippets from multiple registers: ```vim " For register a vnoremap a "aclua require('luasnip.extras.otf').on_the_fly() @@ -1211,14 +1327,15 @@ inoremap b lua require('luasnip.extras.otf').on_the_fly("b") ## Select_choice It's possible to leverage `vim.ui.select` for selecting a choice directly, -without cycling through choices. -All that is needed for this is calling `require("luasnip.extras.select_choice")`, -preferably via some keybind, e.g. +without cycling through the available choices. +All that is needed for this is calling +`require("luasnip.extras.select_choice")`, most likely via some keybind, e.g. ```vim inoremap lua require("luasnip.extras.select_choice")() ``` -, while inside a choiceNode. The `opts.kind` hint for `vim.ui.select` will be set to `luasnip`. +while inside a choiceNode. +The `opts.kind` hint for `vim.ui.select` will be set to `luasnip`. @@ -1261,6 +1378,109 @@ Contains some utility-functions that can be passed to the `ft_func` or }) ``` +## POSTFIX SNIPPET + +Postfix snippets, famously used in +[rust analyzer](https://rust-analyzer.github.io/) and various IDEs, are a type +of snippet, which alters text before the snippets trigger. While these +can be implemented using regTrig snippets, this helper makes the process easier +in most cases. + +The simplest example, which surrounds the text preceeding the `.br` with +brackets `[]`, looks like: + +```lua + + postfix(".br", { + f(function(_, parent) + return "[" .. parent.snippet.env.POSTFIX_MATCH .. "]" + end, {}), + }) +``` + + + +![postfix](https://user-images.githubusercontent.com/25300418/184359322-d8547259-653e-4ada-86e8-666da2c52010.gif) + + + +and is triggered with `xxx.br` and expands to `[xxx]`. + +Note the `parent.snippet.env.POSTFIX_MATCH` in the function node. This is additional +field generated by the postfix snippet. This field is generated by extracting +the text matched (using a configurable matching string, see below) from before +the trigger. In the case above, the field would equal `"xxx"`. This is also +usable within dynamic nodes. + +This field can also be used within lambdas and dynamic nodes. + +```lua + postfix(".br", { + l("[" .. l.POSTFIX_MATCH .. "]"), + }) +``` + +```lua + postfix(".brd", { + d(1, function (_, parent) + return sn(nil, {t("[" .. parent.env.POSTFIX_MATCH .. "]")}) + end) + }), +``` + + + +![postfix2](https://user-images.githubusercontent.com/25300418/184359323-1b250b6d-7b23-43a3-846f-b6cc2c9df9fc.gif) + + + +The arguments to `postfix` are identical to the arguments to `s` but with a few +extra options. + +The first argument can be either a string or a table. If it is a string, that +string will act as the trigger, and if it is a table it has the same valid keys +as the table in the same position for `s` except: + +- `wordTrig`: This key will be ignored if passed in, as it must always be + false for postfix snippets. +- `match_pattern`: The pattern that the line before the trigger is matched + against. The default match pattern is `"[%w%.%_%-]+$"`. Note the `$`. This + matches since only the line _up until_ the beginning of the trigger is + matched against the pattern, which makes the character immediately + preceeding the trigger match as the end of the string. + +Some other match strings, including the default, are available from the postfix +module. `require("luasnip.extras.postfix).matches`: + +- `default`: [%w%.%_%-%"%']+$ +- 'line': `^.+$` + +The second argument is identical to the second argument for `s`, that is, a +table of nodes. + +The optional third argument is the same as the third (`opts`) argument to the +`s` function, but with one difference: + +The postfix snippet works using a callback on the pre_expand event of the +snippet. If you pass a callback on the pre_expand event (structure example +below) it will get run after the builtin callback. This means that your +callback will have access to the `POSTFIX_MATCH` field as well. + +```lua + + { + callbacks = { + [-1] = { + [events.pre_expand] = function(snippet, event_args) + -- function body to match before the dot + -- goes here + end + } + } + } +``` + + # EXTEND_DECORATOR Most of luasnip's functions have some arguments to control their behaviour. diff --git a/Examples/snippets.lua b/Examples/snippets.lua index 8a213b698..512716ad2 100644 --- a/Examples/snippets.lua +++ b/Examples/snippets.lua @@ -175,7 +175,7 @@ local function bash(_, _, command) return res end --- Returns a snippet_node wrapped around an insert_node whose initial +-- Returns a snippet_node wrapped around an insertNode whose initial -- text value is set to the current date in the desired format. local date_input = function(args, snip, old_state, fmt) local fmt = fmt or "%Y-%m-%d" @@ -219,7 +219,7 @@ ls.add_snippets("all", { t(" "), c(3, { t("{"), - -- sn: Nested Snippet. Instead of a trigger, it has a position, just like insert-nodes. !!! These don't expect a 0-node!!!! + -- sn: Nested Snippet. Instead of a trigger, it has a position, just like insertNodes. !!! These don't expect a 0-node!!!! -- Inside Choices, Nodes don't need a position as the choice node is the one being jumped to. sn(nil, { t("extends "), @@ -288,9 +288,9 @@ ls.add_snippets("all", { "fmt6", fmt("use {} only", { t("this"), t("not this") }, { strict = false }) ), - -- Use a dynamic_node to interpolate the output of a + -- Use a dynamicNode to interpolate the output of a -- function (see date_input above) into the initial - -- value of an insert_node. + -- value of an insertNode. s("novel", { t("It was a dark and stormy night on "), d(1, date_input, {}, { user_args = { "%A, %B %d of %Y" } }), diff --git a/doc/luasnip.txt b/doc/luasnip.txt index 1b949987f..455abcc59 100644 --- a/doc/luasnip.txt +++ b/doc/luasnip.txt @@ -1,47 +1,57 @@ -*luasnip.txt* For NVIM v0.5.0 Last change: 2022 October 08 +*luasnip.txt* For NVIM v0.5.0 Last change: 2022 October 09 ============================================================================== Table of Contents *luasnip-table-of-contents* 1. BASICS |luasnip-basics| + - Jump-index |luasnip-jump-index| + - Adding Snippets |luasnip-adding-snippets| 2. NODE |luasnip-node| 3. SNIPPETS |luasnip-snippets| - Api: |luasnip-api:| 4. TEXTNODE |luasnip-textnode| 5. INSERTNODE |luasnip-insertnode| 6. FUNCTIONNODE |luasnip-functionnode| -7. POSTFIX SNIPPET |luasnip-postfix-snippet| -8. CHOICENODE |luasnip-choicenode| -9. SNIPPETNODE |luasnip-snippetnode| -10. INDENTSNIPPETNODE |luasnip-indentsnippetnode| -11. DYNAMICNODE |luasnip-dynamicnode| -12. RESTORENODE |luasnip-restorenode| -13. ABSOLUTE_INDEXER |luasnip-absolute_indexer| -14. EXTRAS |luasnip-extras| + - NODE_REFERENCE |luasnip-node_reference| +7. CHOICENODE |luasnip-choicenode| +8. SNIPPETNODE |luasnip-snippetnode| +9. INDENTSNIPPETNODE |luasnip-indentsnippetnode| +10. DYNAMICNODE |luasnip-dynamicnode| +11. RESTORENODE |luasnip-restorenode| +12. ABSOLUTE_INDEXER |luasnip-absolute_indexer| +13. EXTRAS |luasnip-extras| + - Lambda |luasnip-lambda| + - Match |luasnip-match| + - Repeat |luasnip-repeat| + - Partial |luasnip-partial| + - Nonempty |luasnip-nonempty| + - Dynamic Lambda |luasnip-dynamic-lambda| - FMT |luasnip-fmt| + - Conditions |luasnip-conditions| - On The Fly snippets |luasnip-on-the-fly-snippets| - Select_choice |luasnip-select_choice| - filetype_functions |luasnip-filetype_functions| -15. EXTEND_DECORATOR |luasnip-extend_decorator| -16. LSP-SNIPPETS |luasnip-lsp-snippets| + - POSTFIX SNIPPET |luasnip-postfix-snippet| +14. EXTEND_DECORATOR |luasnip-extend_decorator| +15. LSP-SNIPPETS |luasnip-lsp-snippets| - Snipmate Parser |luasnip-snipmate-parser| - Transformations |luasnip-transformations| -17. VARIABLES |luasnip-variables| +16. VARIABLES |luasnip-variables| - Environment Namespaces |luasnip-environment-namespaces| - LSP-Variables |luasnip-lsp-variables| -18. LOADERS |luasnip-loaders| +17. LOADERS |luasnip-loaders| - Troubleshooting |luasnip-troubleshooting| - VSCODE |luasnip-vscode| - SNIPMATE |luasnip-snipmate| - LUA |luasnip-lua| - EDIT_SNIPPETS |luasnip-edit_snippets| -19. SNIPPETPROXY |luasnip-snippetproxy| -20. EXT_OPTS |luasnip-ext_opts| -21. DOCSTRING |luasnip-docstring| -22. DOCSTRING-CACHE |luasnip-docstring-cache| -23. EVENTS |luasnip-events| -24. CLEANUP |luasnip-cleanup| -25. API-REFERENCE |luasnip-api-reference| +18. SNIPPETPROXY |luasnip-snippetproxy| +19. EXT_OPTS |luasnip-ext_opts| +20. DOCSTRING |luasnip-docstring| +21. DOCSTRING-CACHE |luasnip-docstring-cache| +22. EVENTS |luasnip-events| +23. CLEANUP |luasnip-cleanup| +24. API-REFERENCE |luasnip-api-reference| > __ ____ @@ -106,10 +116,41 @@ Snippets are always created using the `s(trigger:string, nodes:table)`-function. It is explained in more detail in |luasnip-snippets|, but the gist is that it creates a snippet that contains the nodes specified in `nodes`, which will be inserted into a buffer if the text before the cursor -matches `trigger` when `expand` is called. The snippets for a given filetype -have to be added to luasnip via `ls.add_snippets(filetype, snippets)`. Snippets -that should be accessible globally (in all filetypes) have to be added to the -special filetype `all`. +matches `trigger` when `ls.expand` is called. + +JUMP-INDEX *luasnip-jump-index* + +Nodes that can be jumped to (`insertNode`, `choiceNode`, `dynamicNode`, +`restoreNode`, `snippetNode`) all require a "jump-index" so luasnip knows the +order in which these nodes are supposed to be visited ("jumped to"). + +> + s("trig", { + i(1), t"text", i(2), t"text again", i(3) + }) +< + + +These indices don’t "run" through the entire snippet, like they do in +textmate-snippets (`"$1 ${2: $3 $4}"`), they restart at 1 in each nested +snippetNode: + +> + s("trig", { + i(1), t" ", sn(2, { + t" ", i(1), t" ", i(2) + }) + }) +< + + +(roughly equivalent to the given textmate-snippet). + +ADDING SNIPPETS *luasnip-adding-snippets* + +The snippets for a given filetype have to be added to luasnip via +`ls.add_snippets(filetype, snippets)`. Snippets that should be accessible +globally (in all filetypes) have to be added to the special filetype `all`. > ls.add_snippets("all", { @@ -143,95 +184,102 @@ The most direct way to define snippets is `s`: < -(This snippet is useless beyond being a minimal example) - -`s` accepts, as the first argument, a table with the following possible -entries: - - -- `trig`: string, plain text by default. The only entry that must be given. -- `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. -- `dscr`: string, description of the snippet, -separated or table - for multiple lines. -- `wordTrig`: boolean, if true, the snippet is only expanded if the word - (`[%w_]+`) before the cursor matches the trigger entirely. - True by default. -- `regTrig`: boolean, whether the trigger should be interpreted as a - lua pattern. False by default. -- `docstring`: string, textual representation of the snippet, specified like - `dscr`. Overrides docstrings loaded from json. -- `docTrig`: string, for snippets triggered using a lua pattern: define the - trigger that is used during docstring-generation. -- `hidden`: hint for completion-engines, if set, the snippet should not show - up when querying snippets. -- `priority`: Priority of the snippet, a positive number, 1000 by default. - Snippets with high priority will be matched to a trigger before those with a - lower one. - The priority for multiple snippets can also be set in `add_snippets`. -- `snippetType`: string, should be either `snippet` or `autosnippet` (ATTENTION: - singular form is used), decides whether this snippet has to be triggered by - `ls.expand()` or whether is triggered automatically (don’t forget to set - `ls.config.setup({ enable_autosnippets = true })` if you want to use this - feature). If unset it depends on how the snippet is added of which type the - snippet will be. - - -`s` can also be a single string, in which case it is used instead of `trig`, -all other values being defaulted: - -> - s("trigger", {}) -< - - -The second argument to `s` is a table containing all nodes that belong to the -snippet. If the table only has a single node, it can be passed directly without -wrapping it in a table. +(This snippet is useless beyond serving as a minimal example) -The third argument (`opts`) is a table with the following valid keys: +`s(context, nodes, opts) -> snippet` -- `condition`: the condition-function `fn(line_to_cursor, matched_trigger, - captures) -> bool`. The snippet will be expanded only if it returns true - (default is a function that just returns `true`). The function is called before - the text is modified in any way. Some parameters are passed to the function: - The line up to the cursor, the matched trigger, and the captures (table). -- `show_condition`: Function with signature `f(line_to_cursor) -> bool`. It is a - hint for completion-engines, indicating when the snippet should be included in - current completion candidates. Defaults to a function returning `true`. This is - different from `condition` because `condition` is evaluated by LuaSnip on - snippet expansion (and thus has access to the matched trigger and captures), - while `show_condition` is evaluated by the completion-engine when scanning for - available snippet candidates. -- `callbacks`: Contains functions that are called upon enterin/leaving a node of - this snippet. To print text upon entering the _second_ node of a snippet, - `callbacks` should be set as follows: +- `context`: Either table or a string. Passing a string is equivalent to passing > { - -- position of the node, not the jump-position!! - -- s("trig", {t"first node", t"second node", i(1, "third node")}). - [2] = { - [events.enter] = function(node, _event_args) print("2!") end - } + trig = context } < - To register a callback for the snippets’ own events, the key `[-1]` may be - used. More info on events |luasnip-here| -- `child_ext_opts`, `merge_child_ext_opts`: `ext_opts` applied to the children of - this snippet. More info |luasnip-here|. + The following keys are valid: + - `trig`: string, plain text by default. The only entry that must be given. + - `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. + - `dscr`: string, description of the snippet, -separated or table + for multiple lines. + - `wordTrig`: boolean, if true, the snippet is only expanded if the word + (`[%w_]+`) before the cursor matches the trigger entirely. + True by default. + - `regTrig`: boolean, whether the trigger should be interpreted as a + lua pattern. False by default. + - `docstring`: string, textual representation of the snippet, specified like + `dscr`. Overrides docstrings loaded from json. + - `docTrig`: string, for snippets triggered using a lua pattern: define the + trigger that is used during docstring-generation. + - `hidden`: boolean, hint for completion-engines. + If set, the snippet should not show up when querying snippets. + - `priority`: positive number, Priority of the snippet, 1000 by default. + Snippets with high priority will be matched to a trigger before those with a + lower one. + The priority for multiple snippets can also be set in `add_snippets`. + - `snippetType`: string, should be either `snippet` or `autosnippet` (ATTENTION: + singular form is used), decides whether this snippet has to be triggered by + `ls.expand()` or whether is triggered automatically (don’t forget to set + `ls.config.setup({ enable_autosnippets = true })` if you want to use this + feature). If unset it depends on how the snippet is added of which type the + snippet will be. +- `nodes`: A single node, or a list of nodes. The nodes that make up the snippet. +- `opts`: A table, with the following valid keys: + - `condition`: `fn(line_to_cursor, matched_trigger, captures) -> bool`, where + - `line_to_cursor`: `string`, the line up to the cursor. + - `matched_trigger`: `string`, the fully matched trigger (can be retrieved + from `line_to_cursor`, but we already have that info here :D) + - `captures`: if the trigger is pattern, this list contains the + capture-groups. Again, could be computed from `line_to_cursor`, but we + already did so. + The snippet will be expanded only if this function returns true (default is a + function that just returns `true`). The function is called before the text on + the line is modified in any way. + - `show_condition`: `f(line_to_cursor) -> bool`. + - `line_to_cursor`: `string`, the line up to the cursor. + This function is (should be) evaluated by completion-engines, indicating + whether the snippet should be included in current completion candidates. + Defaults to a function returning `true`. This is different from `condition` + because `condition` is evaluated by LuaSnip on snippet expansion (and thus has + access to the matched trigger and captures), while `show_condition` is (should + be) evaluated by the completion-engine when scanning for available snippet + candidates. + - `callbacks`: Contains functions that are called upon enterin/leaving a node of + this snippet. For example: to print text upon entering the _second_ node of a + snippet, `callbacks` should be set as follows: + > + { + -- position of the node, not the jump-index!! + -- s("trig", {t"first node", t"second node", i(1, "third node")}). + [2] = { + [events.enter] = function(node, _event_args) print("2!") end + } + } + < + To register a callback for the snippets’ own events, the key `[-1]` may be + used. More info on events |luasnip-here| + - `child_ext_opts`, `merge_child_ext_opts`: Control `ext_opts` applied to the + children of this snippet. More info on those |luasnip-here|. -This `opts`-table can also be passed to e.g. `snippetNode` or +The `opts`-table can also be passed to e.g. `snippetNode` and `indentSnippetNode`, but only `callbacks` and the `ext_opts`-related options are used there. -Snippets contain some interesting tables, e.g. `snippet.env` contains -variables used in the LSP-protocol like `TM_CURRENT_LINE` or `TM_FILENAME` or -`snippet.captures`, where capture-groups of regex-triggers are stored. -Additionally, the string that was used to trigger the snippet is stored in -`snippet.trigger`. These variables/tables are primarily useful in -dynamic/functionNodes, where the snippet can be accessed through the immediate -parent (`parent.snippet`), which is passed to the function. +SNIPPET-DATA ~ + +Snippets contain some interesting tables during runtime: - `snippet.env`: +Contains variables used in the LSP-protocol, for example `TM_CURRENT_LINE` or +`TM_FILENAME`. It’s possible to add customized variables here too, check +|luasnip-environment-namespaces| - `snippet.captures`: If the snippet was +triggered by a pattern (`regTrig`), and the pattern contained capture-groups, +they can be retrieved here. - `snippet.trigger`: The string that triggered this +snippet. Again, only interesting if the snippet was triggered through +`regTrig`, for getting the full match. + +These variables/tables primarily come in handy in `dynamic/functionNodes`, +where the snippet can be accessed through the immediate parent +(`parent.snippet`), which is passed to the function. (in most cases `parent == +parent.snippet`, but the `parent` of the dynamicNode is not always the +surrounding snippet, it could be a `snippetNode`). API: *luasnip-api:* @@ -270,11 +318,14 @@ lines rather than a string: < +`t(text, node_opts)`: - `text`: `string` or `string[]` - `node_opts`: `table`, +see |luasnip-here| + ============================================================================== 5. INSERTNODE *luasnip-insertnode* These Nodes contain editable text and can be jumped to- and from (e.g. -traditional placeholders, like `$1` in textmate-snippets). +traditional placeholders and tabstops, like `$1` in textmate-snippets). The functionality is best demonstrated with an example: @@ -287,20 +338,20 @@ The functionality is best demonstrated with an example: < -The InsertNodes are jumped over in order from `1 to n`. The 0-th node is -special as it’s always the last one. So the order of InsertNode jump is as +The InsertNodes are visited in order `1,2,3,..,n,0`. (The jump-index 0 also +_has_ to belong to an `insertNode`!) So the order of InsertNode-jumps is as follows: -1. After expansion, we will be at InsertNode 1. -2. After jumping forward, we will be at InsertNode 2. -3. After jumping forward again, we will be at InsertNode 0. +1. After expansion, the cursor is at InsertNode 1, +2. after jumping forward once at InsertNode 2, +3. and after jumping forward again at InsertNode 0. If no 0-th InsertNode is found in a snippet, one is automatically inserted after all other nodes. -The jumping-order doesn’t have to follow the "textual" order of the nodes: +The jump-order doesn’t have to follow the "textual" order of the nodes: > s("trigger", { @@ -320,7 +371,7 @@ The above snippet will behave as follows: An **important** (because here luasnip differs from other snippet-engines) -detail is that the jump-positions restart at 1 in nested snippets: +detail is that the jump-indices restart at 1 in nested snippets: > s("trigger", { @@ -344,21 +395,20 @@ as opposed to e.g. the textmate-syntax, where tabstops are snippet-global: (this is not exactly the same snippet of course, but as close as possible) (the restart-rule only applies when defining snippets in lua, the above -textmate-snippet will expand correctly). +textmate-snippet will expand correctly when parsed). -It’s possible to have initial text inside an InsertNode, which is comfortable -for potentially keeping some default-value: +`i(jump_index, text, node_opts)` -> - s("trigger", i(1, "This text is SELECTed after expanding the snippet.")) -< +- |luasnip-`jump_index`|: `number`, this determines when this node will be jumped to. +- `text`: `string|string[]`, a single string for just one line, a list with >1 + entries for multiple lines. + This text will be SELECTed when the `insertNode` is jumped into. +- `node_opts`: `table`, see |luasnip-here| -This initial text is defined the same way as textNodes, e.g. can be multiline. -`i(0)`s can have initial text, but do note that when the SELECTed text is -replaced, its’ replacement won’t end up in the `i(0)`, but behind it (for -reasons, check out Luasnip#110). +If the `jump_index` is `0`, replacing its’ `text` will leave it outside the +`insertNode` (for reasons, check out Luasnip#110). ============================================================================== 6. FUNCTIONNODE *luasnip-functionnode* @@ -386,204 +436,111 @@ user-defined function: < -The first parameter of `f` is the function. Its parameters are: - - -1. A table of the text of currently contained in the argnodes. (e.g. `{{line1}, -{line1, line2}}`). The snippet-indent will be removed from all lines following -the first. -2. The immediate parent of the `functionNode`. It is included here as it allows -easy access to anything that could be useful in functionNodes (i.e. -`parent.snippet.env` or `parent.snippet.captures`, which contains capture -groups of regex-triggered snippets). In most cases `parent.env` works, but if a -`functionNode` is nested within a `snippetNode`, the immediate parent (a -`snippetNode`) will contain neither `captures` nor `env`. Those are only stored -in the `snippet`, which can be accessed as `parent.snippet`. -3. The `user_args` passed in `opts`. Note that there may be multiple user_args -(e.g. `user_args1, ..., user_argsn`). - - -The function shall return a string, which will be inserted as-is, or a table of -strings for multiline-string, here all lines following the first will be -prefixed with the snippets’ indentation. - -The second parameter is a table of indices of jumpable nodes whose text is -passed to the function. The table may be empty, in this case the function is -evaluated once upon snippet-expansion. If the table only has a single node, it -can be passed directly without wrapping it in a table. The indices can be -specified either as relative to the functionNodes’ parent using numbers or as -absolute, using the |luasnip-`absolute_indexer`|. - -The last parameter is, as with any node, `opts`. `functionNode` accepts one -additional option: `user_args`, a table of values passed to the function. These -exist to more easily reuse functionNode-functions, when applicable: - -> - local function reused_func(_,_, user_arg1) - return user_arg1 - end - - s("trig", { - f(reused_func, {}, { - user_args = {"text"} - }), - f(reused_func, {}, { - user_args = {"different text"} - }), - }) -< - - -Examples: Use captures from the regex-trigger using a functionNode: +`f(fn, argnode_references, node_opts)`: - `fn`: `function(argnode_text, parent, +user_args1,...,user_argsn) -> text` - `argnode_text`: `string[][]`, the text +currently contained in the argnodes (e.g. `{{line1}, {line1, line2}}`). The +snippet-indent will be removed from all lines following the first. -> - s({trig = "b(%d)", regTrig = true}, - f(function(args, snip) return - "Captured Text: " .. snip.captures[1] .. "." end, {}) - ) -< +- `parent`: The immediate parent of the `functionNode`. It is included here as it + allows easy access to some information that could be useful in functionNodes + (see |luasnip-here| for some examples). Many snippets access the surrounding + snippet just as `parent`, but if the `functionNode` is nested within a + `snippetNode`, the immediate parent is a `snippetNode`, not the surrounding + snippet (only the surrounding snippet contains data like `env` or `captures`). +- `user_args`: The `user_args` passed in `opts`. Note that there may be multiple + user_args (e.g. `user_args1, ..., user_argsn`). -The table passed to functionNode: -> - s("trig", { - i(1, "text_of_first"), - i(2, {"first_line_of_second", "second_line_of_second"}), - -- order is 2,1, not 1,2!! - f(function(args, snip) - --here - end, {2, 1} )}) -< +`fn` shall return a string, which will be inserted as-is, or a table of strings +for multiline-string, here all lines following the first will be prefixed with +the snippets’ indentation. -At `--here`, `args` would look as follows (provided no text was changed after -expansion): +- `argnode_references`: `node_reference[]|node_refernce|nil`. Either no, a + single, or multiple |luasnip-node-references|. Changing any of these will + trigger a re-evaluation of `fn`, and insertion of the updated text. If no + node-reference is passed, the `functionNode` is evaluated once upon expansion. +- `node_opts`: `table`, see |luasnip-here|. One additional key is supported: + - `user_args`: `any[]`, these will be passed to `fn` as `user_arg1`-`user_argn`. + These make it easier to reuse similar functions, for example a functionNode + that wraps some text in different delimiters (`()`, `[]`, …). + > + local function reused_func(_,_, user_arg1) + return user_arg1 + end + + s("trig", { + f(reused_func, {}, { + user_args = {"text"} + }), + f(reused_func, {}, { + user_args = {"different text"} + }), + }) + < -> - args = { - {"first_line_of_second", "second_line_of_second"}, - {"text_of_first"} - } -< +**Examples**: -One more example to show usage of `absolute_indexer`: -> - s("trig", { - i(1, "text_of_first"), - i(2, {"first_line_of_second", "second_line_of_second"}), - f(function(args, snip) - -- just concat first lines of both. - return args[1][1] .. args[2][1] - end, {ai[2], ai[1]} )}) -< +- Use captures from the regex-trigger using a functionNode: + > + s({trig = "b(%d)", regTrig = true}, + f(function(args, snip) return + "Captured Text: " .. snip.captures[1] .. "." end, {}) + ) + < +- `argnodes_text` during function-evaluation: + > + s("trig", { + i(1, "text_of_first"), + i(2, {"first_line_of_second", "second_line_of_second"}), + f(function(args, snip) + --here + -- order is 2,1, not 1,2!! + end, {2, 1} )}) + < + At `--here`, `args` would look as follows (provided no text was changed after + expansion): + > + args = { + {"first_line_of_second", "second_line_of_second"}, + {"text_of_first"} + } + < +- |luasnip-`absolute_indexer`|: + > + s("trig", { + i(1, "text_of_first"), + i(2, {"first_line_of_second", "second_line_of_second"}), + f(function(args, snip) + -- just concat first lines of both. + return args[1][1] .. args[2][1] + end, {ai[2], ai[1]} )}) + < If the function only performs simple operations on text, consider using the `lambda` from |luasnip-`luasnip.extras`| -============================================================================== -7. POSTFIX SNIPPET *luasnip-postfix-snippet* - -Postfix snippets, famously used in rust analyzer - and various IDEs, are a type of snippet, -which alters text before the snippets trigger. While these can be implemented -using regTrig snippets, this helper makes the process easier in most cases. - -The simplest example, which surrounds the text preceeding the `.br` with -brackets `[]`, looks like: - -> - - postfix(".br", { - f(function(_, parent) - return "[" .. parent.snippet.env.POSTFIX_MATCH .. "]" - end, {}), - }) -< - - -and is triggered with `xxx.br` and expands to `[xxx]`. - -Note the `parent.snippet.env.POSTFIX_MATCH` in the function node. This is -additional field generated by the postfix snippet. This field is generated by -extracting the text matched (using a configurable matching string, see below) -from before the trigger. In the case above, the field would equal `"xxx"`. This -is also usable within dynamic nodes. - -This field can also be used within lambdas and dynamic nodes. - -> - postfix(".br", { - l("[" .. l.POSTFIX_MATCH .. "]"), - }) -< - - -> - postfix(".brd", { - d(1, function (_, parent) - return sn(nil, {t("[" .. parent.env.POSTFIX_MATCH .. "]")}) - end) - }), -< - - -The arguments to `postfix` are identical to the arguments to `s` but with a few -extra options. - -The first argument can be either a string or a table. If it is a string, that -string will act as the trigger, and if it is a table it has the same valid keys -as the table in the same position for `s` except: - - -- `wordTrig`: This key will be ignored if passed in, as it must always be - false for postfix snippets. -- `match_pattern`: The pattern that the line before the trigger is matched - against. The default match pattern is `"[%w%.%_%-]+$"`. Note the `$`. This - matches since only the line _up until_ the beginning of the trigger is - matched against the pattern, which makes the character immediately - preceeding the trigger match as the end of the string. - - -Some other match strings, including the default, are available from the postfix -module. `require("luasnip.extras.postfix).matches`: - - -- `default`: [%w%.%_%-%“%’]+$ -- 'line': `^.+$` - - -The second argument is identical to the second argument for `s`, that is, a -table of nodes. - -The optional third argument is the same as the third (`opts`) argument to the -`s` function, but with one difference: - -The postfix snippet works using a callback on the pre_expand event of the -snippet. If you pass a callback on the pre_expand event (structure example -below) it will get run after the builtin callback. This means that your -callback will have access to the `POSTFIX_MATCH` field as well. - -> - - { - callbacks = { - [-1] = { - [events.pre_expand] = function(snippet, event_args) - -- function body to match before the dot - -- goes here - end - } - } - } -< - +NODE_REFERENCE *luasnip-node_reference* + +Node-references are used to refer to other nodes in various parts of +luasnip’s API. For example, argnodes in functionNode, dynamicNode or lambda +are node-references. These references can be either of: - `number`: the +jump-index of the node. This will be resolved relative to the parent of the +node this is passed to. (So, only nodes with the same parent can be referenced. +This is very easy to grasp, but also limiting) - |luasnip-`absolute_indexer`|: +the absolute position of the node. This will come in handy if the referred-to +node is not in the same snippet/snippetNode as the one this node-reference is +passed to. - `node`: just the node. Usage of this is discouraged, since it can +lead to subtle errors (for example if the node passed here is captured in a +closure and therefore not copied with the remaining tables in the snippet +(there’s a big comment about just this in commit 8bfbd61)). ============================================================================== -8. CHOICENODE *luasnip-choicenode* +7. CHOICENODE *luasnip-choicenode* ChoiceNodes allow choosing between multiple nodes. @@ -596,29 +553,27 @@ ChoiceNodes allow choosing between multiple nodes. < -`c()` expects as its first arg, as with any jumpable node, its position in the -jumplist, and as its second a table with nodes, the choices. This table can -either contain a single node or a table of nodes. In the latter case the table -will be converted into a `snippetNode`. The third parameter is a table of -options with the following keys: - - -- `restore_cursor`: `false` by default. If it is set, and the node that was - being edited also appears in the switched-to choice (can be the case if a - `restoreNode` is present in both choice) the cursor is restored relative to - that node. - The default is `false` as enabling might lead to worse performance. It’s - possible to override the default by wrapping the `choiceNode`-constructor - in another function that sets `opts.restore_cursor` to `true` and then using - that to construct `choiceNode`s: - `lua local function restore_cursor_choice(pos, choices, opts) if opts then opts.restore_cursor = true else opts = {restore_cursor = true} end return c(pos, choices, opts) end` - +`c(jump_index, choices, node_opts)` - |luasnip-`jump_index`|: `number`, since +choiceNodes can be jumped to, they need their jump-indx. - `choices`: +`node[]|node`, the choices. The first will be initialliy active. A list of +nodes will be turned into a `snippetNode`. - `node_opts`: `table`. `choiceNode` +supports the keys common to all nodes described |luasnip-here|, and one +additional key: - `restore_cursor`: `false` by default. If it is set, and the +node that was being edited also appears in the switched-to choice (can be the +case if a `restoreNode` is present in both choice) the cursor is restored +relative to that node. The default is `false` as enabling might lead to +decreased performance. It’s possible to override the default by wrapping the +`choiceNode`-constructor in another function that sets `opts.restore_cursor` to +`true` and then using that to construct `choiceNode`s: `lua local function +restore_cursor_choice(pos, choices, opts) if opts then opts.restore_cursor = +true else opts = {restore_cursor = true} end return c(pos, choices, opts) end` Jumpable nodes that normally expect an index as their first parameter don’t -need one inside a choiceNode; their index is the same as the choiceNodes’. +need one inside a choiceNode; their jump-index is the same as the +choiceNodes’. As it is only possible (for now) to change choices from within the choiceNode, -make sure that all of the choices have some place for the cursor to stop at. +make sure that all of the choices have some place for the cursor to stop at! This means that in `sn(nil, {...nodes...})` `nodes` has to contain e.g. an `i(1)`, otherwise luasnip will just "jump through" the nodes, making it impossible to change the choice. @@ -633,9 +588,10 @@ impossible to change the choice. < -The active choice for a choiceNode can be changed by calling -`ls.change_choice(1)` (forwards) or `ls.change_choice(-1)` (backwards), for -example via +The active choice for a choiceNode can be changed by either calling one of +`ls.change_choice(1)` (forwards) or `ls.change_choice(-1)` (backwards), or by +calling `ls.set_choice(choice_indx)`. One way to easily interact with +choiceNodes is binding `change_choice(1/-1)` to keys: > -- set keybinds for both INSERT and VISUAL. @@ -650,15 +606,15 @@ Apart from this, there is also a |luasnip-picker| where no cycling is necessary and any choice can be selected right away, via `vim.ui.select`. ============================================================================== -9. SNIPPETNODE *luasnip-snippetnode* +8. SNIPPETNODE *luasnip-snippetnode* SnippetNodes directly insert their contents into the surrounding snippet. This -is useful for choiceNodes, which only accept one child, or dynamicNodes, where -nodes are created at runtime and inserted as a snippetNode. +is useful for `choiceNode`s, which only accept one child, or `dynamicNode`s, +where nodes are created at runtime and inserted as a `snippetNode`. -Syntax is similar to snippets, however, where snippets require a table -specifying when to expand, snippetNodes, similar to insertNodes, expect a -number, as they too are jumpable: +Their syntax is similar to `s`, however, where snippets require a table +specifying when to expand, `snippetNode`s, similar to `insertNode`s, expect a +jump-index. > s("trig", sn(1, { @@ -668,10 +624,22 @@ number, as they too are jumpable: < -Note that snippetNodes don’t expect an `i(0)`. +`sn(jump_index, nodes, node_opts)` + + +- |luasnip-`jump_index`|: `number`, the usual. +- `nodes`: `node[]|node`, just like for `s`. + Note that `snippetNode`s don’t accept an `i(0)`, so the jump-indices of the nodes + inside them have to be in `1,2,...,n`. +- `node_opts`: `table`: again, all |luasnip-common| keys are supported, but also + - `callbacks`, + - `child_ext_opts` and + - `merge_child_ext_opts`, + which are further explained in |luasnip-`snippets`|. + ============================================================================== -10. INDENTSNIPPETNODE *luasnip-indentsnippetnode* +9. INDENTSNIPPETNODE *luasnip-indentsnippetnode* By default, all nodes are indented at least as deep as the trigger. With these nodes it’s possible to override that behaviour: @@ -705,50 +673,60 @@ comment- string before the nodes of the snippet: Here the `//` before `This is` is important, once again, because indent is only applied after linebreaks. To enable such usage, `$PARENT_INDENT` in the -indentstring is replaced by the parents’ indent (duh). - -============================================================================== -11. DYNAMICNODE *luasnip-dynamicnode* - -Very similar to functionNode, but returns a snippetNode instead of just text, -which makes them very powerful as parts of the snippet can be changed based on -user-input. +indentstring is replaced by the parent’s indent (duh). -The prototype for the dynamicNodes’ constructor is `d(position:int, function, -argnodes:table of nodes, opts: table)`: +`isn(jump_index, nodes, indentstring, node_opts)` +All of these except `indentstring` are exactly the same as +|luasnip-`snippetnode`|. -1. `position`: just like all jumpable nodes, when this node will be jumped into. -2. `function`: `fn(args, parent, old_state, user_args1, ..., user_argsn) -> snippetNode` -This function is called when the argnodes’ text changes. It generates and -returns (wrapped inside a `snippetNode`) the nodes that should be inserted -at the dynamicNodes place. -`args`, `parent` and `user_args` are also explained in -|luasnip-functionnode| +- `indentstring`: `string`, will be used to indent the nodes inside this + `snippetNode`. + All occurences of `"$PARENT_INDENT"` are replaced with the actual indent of + the parent. -- `args`: `table of text` (`{{"node1line1", "node1line2"}, {"node2line1"}}`) - from nodes the dynamicNode depends on. -- `parent`: the immediate parent of the `dynamicNode`). -- `old_state`: a user-defined table. This table may contain - anything, its intended usage is to preserve information from the previously - generated `snippetNode`: If the `dynamicNode` depends on other nodes it may - be reconstructed, which means all user input (text inserted in `insertNodes`, - changed choices) to the previous dynamicNode is lost. - The `old_state` table must be stored in `snippetNode` returned by - the function (`snippetNode.old_state`). - The second example below illustrates the usage of `old_state`. -- `user_args1, ..., user_argsn`: passed through from `dynamicNode`-opts. -3. `argnodes`: Indices of nodes the dynamicNode depends on: if any of these trigger an -update, the `dynamicNode`s’ function will be executed, and the result inserted at -the `dynamicNodes` place. -Can be a single index or a table of indices. -4. `opts`: Just like `functionNode`, `dynamicNode` also accepts `user_args` in -addition to options common to all nodes. +============================================================================== +10. DYNAMICNODE *luasnip-dynamicnode* +Very similar to functionNode, but returns a snippetNode instead of just text, +which makes them very powerful as parts of the snippet can be changed based on +user-input. -Examples: +`d(jump_index, function, node-references, opts)`: + + +- |luasnip-`jump_index`|: `number`, just like all jumpable nodes, its’ position + in the jump-list. +- `function`: `fn(args, parent, old_state, user_args) -> snippetNode` This + function is called when the argnodes’ text changes. It should generate and + returns (wrapped inside a `snippetNode`) nodes, these will be inserted at the + dynamicNodes place. `args`, `parent` and `user_args` are also explained in + |luasnip-functionnode| + - `args`: `table of text` (`{{"node1line1", "node1line2"}, {"node2line1"}}`) + from nodes the `dynamicNode` depends on. + - `parent`: the immediate parent of the `dynamicNode`. + - `old_state`: a user-defined table. This table may contain anything, its + intended usage is to preserve information from the previously generated + `snippetNode`: If the `dynamicNode` depends on other nodes it may be + reconstructed, which means all user input (text inserted in `insertNodes`, + changed choices) to the previous dynamicNode is lost. + The `old_state` table must be stored in `snippetNode` returned by + the function (`snippetNode.old_state`). + The second example below illustrates the usage of `old_state`. + - `user_args`: passed through from `dynamicNode`-opts, may be more than one + argument. +- `node_references`: `node_reference[]|node_references|nil`, |luasnip-references| + to the nodes the dynamicNode depends on: if any of these trigger an update (for + example if the text inside them changes), the `dynamicNode`s’ function will + be executed, and the result inserted at the `dynamicNodes` place. + (`dynamicNode` behaves exactly the same as `functionNode` in this regard). +- `opts`: In addition to the |luasnip-usual| keys, there is, again, + - `user_args`, which is described in |luasnip-functionnode|. + + +**Examples**: This `dynamicNode` inserts an `insertNode` which copies the text inside the first `insertNode`. @@ -805,11 +783,10 @@ As with `functionNode`, `user_args` can be used to reuse similar `dynamicNode`- functions. ============================================================================== -12. RESTORENODE *luasnip-restorenode* +11. RESTORENODE *luasnip-restorenode* -This node can store and restore a snippetNode that was modified (changed -choices, inserted text) by the user. Its usage is best demonstrated by an -example: +This node can store and restore a snippetNode as-is. This includes changed +choices and changed text. Its’ usage is best demonstrated by an example: > s("paren_change", { @@ -820,7 +797,8 @@ example: }), }, { stored = { - user_text = i(1, "default_text") + -- key passed to restoreNodes. + ["user_text"] = i(1, "default_text") } }) < @@ -828,16 +806,22 @@ example: 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`. +`r(jump_index, key, nodes, node_opts)`: + + +- |luasnip-`jump_index`|, when to jump to this node. +- `key`, `string`: `restoreNode`s with the same key share their content. +- `nodes`, `node[]|node`: the content 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 for a given key may be defined multiple times, but if the + contents differ, it’s undefined which will actually be used. + If a key’s content is defined in a `dynamicNode`, it will not be initially + 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 @@ -848,7 +832,7 @@ An important-to-know limitation of `restoreNode` is that, for a given key, only one may be visible at a time. See this issue for details. -The `restoreNode` is also useful for storing user-input across updates of a +The `restoreNode` is especially useful for storing input across updates of a `dynamicNode`. Consider this: > @@ -885,12 +869,15 @@ Now the entered text is stored. that really bothers you feel free to open an issue. ============================================================================== -13. ABSOLUTE_INDEXER *luasnip-absolute_indexer* +12. ABSOLUTE_INDEXER *luasnip-absolute_indexer* -The `absolute_indexer` can be used to pass text of nodes to a -function/dynamicNode that it doesn’t share a parent with. Normally, accessing -the outer `i(1)` isn’t possible from inside e.g. a snippetNode (nested -inside a choiceNode to make this example more practical): +A more capable way of |luasnip-referencing-nodes|! Using only +|luasnip-`jump-indices`|, accessing an outer `i(1)` isn’t possible from +inside e.g. a snippetNode, since only nodes with the same parent can be +referenced (jump indices are interpreted relative to the parent of the node +that’s passed the reference). The `absolute_indexer` can be used to reference +nodes based on their absolute position in the snippet, which lifts this +restriction. > s("trig", { @@ -922,7 +909,7 @@ There are some quirks in addressing nodes: > s("trig", { - i(2), -- ai[2]: indices based on insert-order, not position. + i(2), -- ai[2]: indices based on jump-index, not position. sn(1, { -- ai[1] i(1), -- ai[1][1] t"lel", -- not addressable. @@ -964,120 +951,150 @@ to be accessed as `ai[restoreNodeIndx][0][1]`. ============================================================================== -14. EXTRAS *luasnip-extras* - -The module `"luasnip.extras"` contains nodes that ease writing snippets (This -is only a short outline, their usage is shown more expansively in -`Examples/snippets.lua`): - - -- `lambda`: A shortcut for `functionNode`s that only do very basic string- - manipulation. For example, to replace all occurences of "a" in the nth insert - with "e", one could use `lambda(lambda._1:gsub("a", "e"), n)` (signature is - similar to that of `functionNode`). If a node has multiple lines, they will be - concatenated using "". -- `match`: Can insert text based on a predicate (shorthand for `functionNode`s). - The complete signature for the node is `match(argnodes, condition, then, - else)`, where - - `argnodes` can be specified as in `functionNode`, - - `condition` may be a - - string: interpreted as a lua-pattern. Matched on the `\n`-joined (in case - it’s multiline) text of the first argnode (`args[1]:match(condition)`). - - function: `fn(args, snip) -> bool`: takes the same parameters as the - `functionNode`-function, any value other than nil or false is interpreted - as a match. - - lambda: `l._n` is the `\n`-joined text of the nth argnode. - Useful if string-manipulations have to be performed before the string is matched. - - `then` is inserted if the condition matches, `else` if it doesn’t. They can - both be either text, lambda or function (with the same parameters as - specified above). - If `then` is not given, the `then`-value depends on what was specified as the - `condition`: - - pattern: Simply the return value from the `match`, e.g. the entire match, - or, if there were capture groups, the first capture group. - - function: the return value of the function if it is either a string, or a - table (if there is no `then`, the function cannot return a table containing - something other than strings). - - lambda: Simply the first value returned by the lambda. - Examples: - - `match(n, "^ABC$", "A")` inserts "A" if the `n`th jumpable node matches "ABC" - exactly, nothing otherwise. - - `match(n, lambda._1:match(lambda._1:reverse()), "PALINDROME")` inserts - "PALINDROME" if the nth jumpable node is a palindrome. - > - s("trig", { - i(1), t":", - i(2), t"::", - m({1, 2}, l._1:match("^"..l._2.."$"), l._1:gsub("a", "e")) - }) - < - This inserts the text of the first insertNode, with all occurences of `a` - replaced with `e` if the second insertNode matches the first exactly. -- `rep`: repeats the node with the passed index. `rep(1)` to repeat the content - of the first insert. -- `partial`: directly inserts the output of a function. Useful for e.g. - `partial(os.date, "%Y")` (arguments passed after the function are passed to - it). -- `nonempty`: inserts text if the insert at the given index doesn’t contain any - text. `nonempty(n, "not empty!", "empty!")` inserts "empty!" if insert n is - empty, "not empty!" if it isn’t. -- `dynamic_lambda`: Operates almost exactly like `lambda`, only that it can be - jumped to, and it’s contents therefore be easily overridden. - `dynamic_lambda(2, lambda._1..lambda._1, 1)` will first contain the content of - insert 1 appended to itself, but the second jump will lead to it, making it - easy to override the generated text. The text will only be changed when a - argnode updates it. +13. EXTRAS *luasnip-extras* +LAMBDA *luasnip-lambda* + +A shortcut for `functionNode`s that only do very basic string- manipulation. +`l(lambda, argnodes)`: - `lambda`: An object created by applying +string-operations to `l._n`, objects representing the `n`th argnode. For +example: - `l._1:gsub("a", "e")` replaces all occurences of "a" in the text of +the first argnode with "e", or - `l._1 .. l._2` concats text of the first and +second argnode. If an argnode contains multiple lines of text, they are +concatenated with `"\n"` prior to any operation. - `argnodes`, +|luasnip-`node-references`|, just like in function- and dynamicNode. + +There are many examples for `lamda` in `Examples/snippets.lua` + +MATCH *luasnip-match* + +`match` can insert text based on a predicate (again, a shorthand for +`functionNode`). + +`match(argnodes, condition, then, else)`, where * `argnode`: A single +|luasnip-`node-reference`|. May not be nil, or a table. * `condition` may be +either of * `string`: interpreted as a lua-pattern. Matched on the `\n`-joined +(in case it’s multiline) text of the first argnode +(`args[1]:match(condition)`). * `function`: `fn(args, snip) -> bool`: takes the +same parameters as the `functionNode`-function, any value other than nil or +false is interpreted as a match. * `lambda`: `l._n` is the `\n`-joined text of +the nth argnode. Useful if string-manipulations have to be performed before the +string is matched. Should end with `match`, but any other truthy result will be +interpreted as matching. + + +- `then` is inserted if the condition matches, +- `else` if it does not. + + +Both `then` and `else` can be either text, lambda or function (with the same +parameters as specified above). `then`’s default-value depends on the +`condition`: * `pattern`: Simply the return value from the `match`, e.g. the +entire match, or, if there were capture groups, the first capture group. * +`function`: the return value of the function if it is either a string, or a +table (if there is no `then`, the function cannot return a table containing +something other than strings). * `lambda`: Simply the first value returned by +the lambda. + +Examples: * `match(n, "^ABC$", "A")`. * `match(n, +lambda._1:match(lambda._1:reverse()), "PALINDROME")` > - ls.add_snippets("all", { - s("extras1", { - i(1), t { "", "" }, m(1, "^ABC$", "A") - }), - s("extras2", { - i(1, "INPUT"), t { "", "" }, m(1, l._1:match(l._1:reverse()), "PALINDROME") - }), - s("extras3", { - i(1), t { "", "" }, i(2), t { "", "" }, - m({ 1, 2 }, l._1:match("^" .. l._2 .. "$"), l._1:gsub("a", "e")) - }), - s("extras4", { i(1), t { "", "" }, extras.rep(1) }), - s("extras5", { extras.partial(os.date, "%Y") }), - s("extras6", { i(1, ""), t { "", "" }, extras.nonempty(1, "not empty!", "empty!") }), - s("extras7", { i(1), t { "", "" }, extras.dynamic_lambda(2, l._1 .. l._1, 1) }), + s("trig", { + i(1), t":", + i(2), t"::", + m({1, 2}, l._1:match("^"..l._2.."$"), l._1:gsub("a", "e")) }) < -- `conditions.show`: Contains typical predicates/functions used as - `show`-condition. Currently this is just `line_end` -- `conditions.expand`: Contains typical predicates/functions used as - `expand`-condition. Currently this is just `line_begin` Contains everything - from `conditions.show` as well. -- `conditions`: Provides a function `make_condition(foo)` which takes a function - as argument and returns a _condition object_ for which several operators are - defined: - - `c1 + c2 -> c1 or c2` - - `c1 * c2 -> c1 and c2` - - `-c1 -> not c1` - - `c1 ^ c2 -> c1 xor/!= c2` - - `c1 % c2 -> c1 xnor/== c2`: This decision may look weird but as we weren’t - able to use `==`, we decided to take something that makes one scratch ones - head (and thus avoid making false assumptions). - For more details look at this comment . - `conditions.show`s and `conditions.expand`s members all are also condition - objects so you can work with those too. - Thus you can easily combine existing predicates. Like in - `conditions.expand.line_end + conditions.expand.line_begin` instead of doing - something like `function(...) return conditions.expand.line_end(...) or - conditions.expand.line_begin(...) end`. +- > + s("extras1", { + i(1), t { "", "" }, m(1, "^ABC$", "A") + }), + < + Inserts "A" if the node with jump-index `n` matches "ABC" exactly, nothing + otherwise. +- > + s("extras2", { + i(1, "INPUT"), t { "", "" }, m(1, l._1:match(l._1:reverse()), "PALINDROME") + }), + < + Inserts `"PALINDROME"` if i(1) contains a palindrome. +- > + s("extras3", { + i(1), t { "", "" }, i(2), t { "", "" }, + m({ 1, 2 }, l._1:match("^" .. l._2 .. "$"), l._1:gsub("a", "e")) + }), + < + This inserts the text of the node with jump-index 1, with all occurences of `a` + replaced with `e`, if the second insertNode matches the first exactly. + + +REPEAT *luasnip-repeat* + +Inserts the text of the passed node. + +`rep(node_reference)` - `node_reference`, a single |luasnip-`node-reference`|. + +> + s("extras4", { i(1), t { "", "" }, extras.rep(1) }), +< + + +PARTIAL *luasnip-partial* + +Evaluates a function on expand and inserts its’ value. + +`partial(fn, params...)` - `fn`: any function - `params`: varargs, any, will be +passed to `fn`. + +For example `partial(os.date, "%Y")` inserts the current year on expansion. + +> + s("extras5", { extras.partial(os.date, "%Y") }), +< + + +NONEMPTY *luasnip-nonempty* + +Inserts text if the referenced node doesn’t contain any text. +`nonempty(node_reference, not_empty, empty)` - `node_reference`, a single +|luasnip-node-reference|. - `not_empty`, `string`: inserted if the node is not +empty. - `empty`, `string`: inserted if the node is empty. + +> + s("extras6", { i(1, ""), t { "", "" }, extras.nonempty(1, "not empty!", "empty!") }), +< + + +DYNAMIC LAMBDA *luasnip-dynamic-lambda* + +Pretty much the same as lambda, it just inserts the resulting text as an +insertNode, and, as such, it can be quickly overridden. + +`dynamic_lambda(jump_indx, lambda, node_references)` - `jump_indx`, as usual, +the jump-indx. + +The remaining arguments carry over from lambda. + +> + s("extras7", { i(1), t { "", "" }, extras.dynamic_lambda(2, l._1 .. l._1, 1) }), +< FMT *luasnip-fmt* -`require("luasnip.extras.fmt").fmt` can be used to create snippets in a more -readable way. +Authoring snippets can be quite clunky, especially since every second node is +probably a `textNode`, inserting a small number of characters between two more +complicated nodes. `fmt` can be used to define snippets in a much more readable +way. This is achieved by borrowing (as the name implies) from +`format`-functionality (our syntax is very similar to python’s +). `fmt` accepts a +string and a table of nodes. Each occurrence of a delimiter-pair in the string +is replaced by one node from the table, while text outside the delimiters is +turned into textNodes. Simple example: @@ -1109,6 +1126,9 @@ Simple example: < +One important detail here is that the position of the delimiters does not, in +any way, correspond to the jump-index of the nodes! + `fmt(format:string, nodes:table of nodes, opts:table|nil) -> table of nodes` @@ -1137,26 +1157,94 @@ Simple example: There is also `require("luasnip.extras.fmt").fmta`. This only differs from `fmt` by using angle-brackets (`<>`) as the default-delimiter. -ON THE FLY SNIPPETS *luasnip-on-the-fly-snippets* +CONDITIONS *luasnip-conditions* + +This module (`luasnip.extras.condition`) contains function that can be passed +to a snippet’s `condition` or `show_condition`. These are grouped accordingly +into `luasnip.extras.conditions.expand` and `luasnip.extras.conditions.show`: + +**`expand`**: - `line_begin`: only expand if the cursor is at the beginning of +the line. + +**`show`**: - `line_end`: only expand at the end of the line. + +`expand` contains, additionally, all conditions provided by `show`. + +CONDITION-OBJECTS ~ -You can create snippets that are not for being used all the time but only in a -single session. +`luasnip.extras.conditions` also contains condition-objects. These can, just +like functions, be passed to `condition` or `show_condition`, but can also be +combined with each other into logical expressions: -This behaves as an "operator" takes what is in a register and transforms it -into a snippet using words prefixed as $ as inputs or copies (depending on if -the same word appears more than once). You can escape $ by repeating it. -In order to use, add something like this to your config: +- `c1 + c2 -> c1 or c2` +- `c1 * c2 -> c1 and c2` +- `-c1 -> not c1` +- `c1 ^ c2 -> c1 xor(!=) c2` +- `c1 % c2 -> c1 xnor(==) c2`: This decision may seem weird, considering how + there is an overload for the `==`-operator. Unfortunately, it’s not possible + to use this for our purposes (some info + here ), + so we decided to make use of a more obscure symbol (which will hopefully avoid + false assumptions about its meaning). + + +This makes logical combinations of conditions very readable. Compare > - vnoremap "eclua require('luasnip.extras.otf').on_the_fly() + condition = conditions.expand.line_end + conditions.expand.line_begin +< + + +with the more verbose + +> + condition = function(...) return conditions.expand.line_end(...) or conditions.expand.line_begin(...) end +< + + +The conditions provided in `show` and `expand` are already condition-objects. +To create new ones, use +`require("luasnip.extras.conditions").make_condition(condition_fn)` + +ON THE FLY SNIPPETS *luasnip-on-the-fly-snippets* + +Sometimes it’s desirable to create snippets tailored for exactly the current +situation. For example inserting repetitive, but just slightly different +invocations of some function, or supplying data in some schema. On The +Fly-snippets enable exactly this usecase: they can be quickly created and +expanded, with as little disruption as possible. + +Since they should mainly fast to write, and don’t necessarily need all bells +and whistles, they don’t make use of lsp/textmate-syntax, but a more +simplistic one: * `$anytext` denotes a placeholder (`insertNode`) with text +"anytext". The text also serves as a unique key: if there are multiple +placeholders with the same key, only the first will be editable, the others +will just mirror it. * … That’s it. `$` can be escaped by preceding it with +a second `$`, all other symbols will be interpreted literally. + +There is currently only one way to expand On The Fly-snippets: +`require('luasnip.extras.otf').on_the_fly("")` will interpret +whatever text is in the register `` as a snippet, and expand it +immediately. The idea behind this mechanism is that it enables a very immediate +way of supplying and retrieving (expanding) the snippet: write the snippet-body +into the buffer, cut/yank it into some register, and call +`on_the_fly("")` to expand the snippet. + +Here’s one set of example-keybindings: + +> + " in the first call: passing the register is optional since `on_the_fly` + " defaults to the unnamed register, which will always contain the previously cut + " text. + vnoremap "eclua require('luasnip.extras.otf').on_the_fly("e") inoremap lua require('luasnip.extras.otf').on_the_fly("e") < -Notice that you can use your own mapping instead of , and you can pick -another register instead of `"p`. You can even use it several times, as if it -where a macro if you add several mapppings like: +Obviously, `` is arbritary, and can be changed to any other key-combo. +Another interesting application is allowing multiple on the fly-snippets at the +same time, by retrieving snippets from multiple registers: > " For register a @@ -1173,16 +1261,17 @@ where a macro if you add several mapppings like: SELECT_CHOICE *luasnip-select_choice* It’s possible to leverage `vim.ui.select` for selecting a choice directly, -without cycling through choices. All that is needed for this is calling -`require("luasnip.extras.select_choice")`, preferably via some keybind, e.g. +without cycling through the available choices. All that is needed for this is +calling `require("luasnip.extras.select_choice")`, most likely via some +keybind, e.g. > inoremap lua require("luasnip.extras.select_choice")() < -, while inside a choiceNode. The `opts.kind` hint for `vim.ui.select` will be -set to `luasnip`. +while inside a choiceNode. The `opts.kind` hint for `vim.ui.select` will be set +to `luasnip`. FILETYPE_FUNCTIONS *luasnip-filetype_functions* @@ -1218,8 +1307,105 @@ Contains some utility-functions that can be passed to the `ft_func` or < +POSTFIX SNIPPET *luasnip-postfix-snippet* + +Postfix snippets, famously used in rust analyzer + and various IDEs, are a type of snippet, +which alters text before the snippets trigger. While these can be implemented +using regTrig snippets, this helper makes the process easier in most cases. + +The simplest example, which surrounds the text preceeding the `.br` with +brackets `[]`, looks like: + +> + + postfix(".br", { + f(function(_, parent) + return "[" .. parent.snippet.env.POSTFIX_MATCH .. "]" + end, {}), + }) +< + + +and is triggered with `xxx.br` and expands to `[xxx]`. + +Note the `parent.snippet.env.POSTFIX_MATCH` in the function node. This is +additional field generated by the postfix snippet. This field is generated by +extracting the text matched (using a configurable matching string, see below) +from before the trigger. In the case above, the field would equal `"xxx"`. This +is also usable within dynamic nodes. + +This field can also be used within lambdas and dynamic nodes. + +> + postfix(".br", { + l("[" .. l.POSTFIX_MATCH .. "]"), + }) +< + + +> + postfix(".brd", { + d(1, function (_, parent) + return sn(nil, {t("[" .. parent.env.POSTFIX_MATCH .. "]")}) + end) + }), +< + + +The arguments to `postfix` are identical to the arguments to `s` but with a few +extra options. + +The first argument can be either a string or a table. If it is a string, that +string will act as the trigger, and if it is a table it has the same valid keys +as the table in the same position for `s` except: + + +- `wordTrig`: This key will be ignored if passed in, as it must always be + false for postfix snippets. +- `match_pattern`: The pattern that the line before the trigger is matched + against. The default match pattern is `"[%w%.%_%-]+$"`. Note the `$`. This + matches since only the line _up until_ the beginning of the trigger is + matched against the pattern, which makes the character immediately + preceeding the trigger match as the end of the string. + + +Some other match strings, including the default, are available from the postfix +module. `require("luasnip.extras.postfix).matches`: + + +- `default`: [%w%.%_%-%“%’]+$ +- 'line': `^.+$` + + +The second argument is identical to the second argument for `s`, that is, a +table of nodes. + +The optional third argument is the same as the third (`opts`) argument to the +`s` function, but with one difference: + +The postfix snippet works using a callback on the pre_expand event of the +snippet. If you pass a callback on the pre_expand event (structure example +below) it will get run after the builtin callback. This means that your +callback will have access to the `POSTFIX_MATCH` field as well. + +> + + { + callbacks = { + [-1] = { + [events.pre_expand] = function(snippet, event_args) + -- function body to match before the dot + -- goes here + end + } + } + } +< + + ============================================================================== -15. EXTEND_DECORATOR *luasnip-extend_decorator* +14. EXTEND_DECORATOR *luasnip-extend_decorator* Most of luasnip’s functions have some arguments to control their behaviour. Examples include `s`, where `wordTrig`, `regTrig`, … can be set in the first @@ -1289,7 +1475,7 @@ One more example for registering a new function: ============================================================================== -16. LSP-SNIPPETS *luasnip-lsp-snippets* +15. LSP-SNIPPETS *luasnip-lsp-snippets* Luasnip is capable of parsing lsp-style snippets using `ls.parser.parse_snippet(context, snippet_string, opts)`: @@ -1369,7 +1555,7 @@ Alternatively, `jsregexp` can be cloned locally, `make`d, and the resulting If `jsregexp` is not available, transformation are replaced by a simple copy. ============================================================================== -17. VARIABLES *luasnip-variables* +16. VARIABLES *luasnip-variables* All `TM_something`-variables are supported with two additions: `LS_SELECT_RAW` and `LS_SELECT_DEDENT`. These were introduced because `TM_SELECTED_TEXT` is @@ -1484,7 +1670,7 @@ and `unset` if it returns an empty string. Consider this when adding env-variables which might be used in lsp-snippets. ============================================================================== -18. LOADERS *luasnip-loaders* +17. LOADERS *luasnip-loaders* Luasnip is capable of loading snippets from different formats, including both the well-established vscode- and snipmate-format, as well as plain lua-files @@ -1818,7 +2004,7 @@ One comfortable way to call this function is registering it as a command: ============================================================================== -19. SNIPPETPROXY *luasnip-snippetproxy* +18. SNIPPETPROXY *luasnip-snippetproxy* `SnippetProxy` is used internally to alleviate the upfront-cost of loading snippets from e.g. a snipmate-library or a vscode-package. This is achieved by @@ -1849,7 +2035,7 @@ lsp-snippets), an alternative is the parser for snipmate-snippets (`ls.parser.parse_snipmate`). ============================================================================== -20. EXT_OPTS *luasnip-ext_opts* +19. EXT_OPTS *luasnip-ext_opts* `ext_opts` can be used to set the `opts` (see `nvim_buf_set_extmark`) of the extmarks used for marking node-positions, either globally, per-snippet or @@ -2065,7 +2251,7 @@ Here the highlight of an insertNode nested directly inside a choiceNode is always visible on top of it. ============================================================================== -21. DOCSTRING *luasnip-docstring* +20. DOCSTRING *luasnip-docstring* Snippet-docstrings can be queried using `snippet:get_docstring()`. The function evaluates the snippet as if it was expanded regularly, which can be problematic @@ -2118,7 +2304,7 @@ A better example to understand `docTrig` and `docstring` can refer to #515 . ============================================================================== -22. DOCSTRING-CACHE *luasnip-docstring-cache* +21. DOCSTRING-CACHE *luasnip-docstring-cache* Although generation of docstrings is pretty fast, it’s preferable to not redo it as long as the snippets haven’t changed. Using @@ -2135,7 +2321,7 @@ The cache is located at `stdpath("cache")/luasnip/docstrings.json` (probably `~/.cache/nvim/luasnip/docstrings.json`). ============================================================================== -23. EVENTS *luasnip-events* +22. EVENTS *luasnip-events* Events can be used to react to some action inside snippets. These callbacks can be defined per-snippet (`callbacks`-key in snippet constructor) or globally @@ -2230,14 +2416,14 @@ or some information about expansions ============================================================================== -24. CLEANUP *luasnip-cleanup* +23. CLEANUP *luasnip-cleanup* The function ls.cleanup() triggers the `LuasnipCleanup` user-event, that you can listen to do some kind of cleaning in your own snippets, by default it will empty the snippets table and the caches of the lazy_load. ============================================================================== -25. API-REFERENCE *luasnip-api-reference* +24. API-REFERENCE *luasnip-api-reference* `require("luasnip")`: