diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 242dc298c..0d868dd9b 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -14,16 +14,8 @@ jobs: with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.head_ref }} - - name: panvimdoc - uses: kdheepak/panvimdoc@main - with: - vimdoc: luasnip - pandoc: DOC.md - - run: | - sudo add-apt-repository ppa:neovim-ppa/stable - sudo apt-get update - sudo apt-get install -y neovim - nvim +"helptags doc | exit" + - uses: nixbuild/nix-quick-install-action@v30 + - run: nix develop -i -c bash -c 'make doc' - uses: stefanzweifel/git-auto-commit-action@v4 with: diff --git a/.github/workflows/spell.yaml b/.github/workflows/spell.yaml index 34073549a..01f594a78 100644 --- a/.github/workflows/spell.yaml +++ b/.github/workflows/spell.yaml @@ -1,4 +1,3 @@ ---- name: Check spelling on: @@ -9,4 +8,9 @@ jobs: spellcheck: runs-on: ubuntu-latest steps: - - uses: gevhaz/word-warden@v1.0.0 + - uses: actions/checkout@v3 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.head_ref }} + - uses: nixbuild/nix-quick-install-action@v30 + - run: nix develop -i -c bash -c 'make spellcheck' diff --git a/.gitignore b/.gitignore index a9b760352..a65772417 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /deps/luasnip-jsregexp.so /lua/luasnip-jsregexp.lua /.aspell.en.prepl +/doc.json diff --git a/DOC.md b/DOC.md index a377878d3..f7fcb284e 100644 --- a/DOC.md +++ b/DOC.md @@ -165,8 +165,8 @@ Common opts: * `node_ext_opts` and `merge_node_ext_opts`: Control `ext_opts` (most likely highlighting) of the node. Described in detail in [ext_opts](#ext_opts) -* `key`: The node can be referred to by this key. Useful for either [Key - Indexer](#key-indexer) or for finding the node at runtime (See +* `key`: The node can be referred to by this key. Useful for either + [Key Indexer](#key-indexer) or for finding the node at runtime (See [Snippets-API](#snippets-api)), for example inside a `dynamicNode`. The keys do not have to be unique across the entire lifetime of the snippet, but at any point in time, the snippet may contain each key only once. This means it is @@ -578,6 +578,7 @@ s("trig", { `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 @@ -747,43 +748,53 @@ ChoiceNodes allow choosing between multiple nodes. -`c(jump_index, choices, node_opts)` - -- `jump_index`: `number`, since choiceNodes can be jumped to, they need a - jump-index (Info in [Basics-Jump-Index](#jump-index)). -- `choices`: `node[]|node`, the choices. The first will be initially active. - A list of nodes will be turned into a `snippetNode`. -- `node_opts`: `table`. `choiceNode` supports the keys common to all nodes - described in [Node](#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: +`c(pos, choices, opts?): LuaSnip.ChoiceNode`: Create a new choiceNode from a list of choices. +The first item in this list is the initial choice, and it can be changed while any node of a choice +is active. So, if all choices should be reachable, every choice has to have a place for the cursor +to stop at. + +If the choice is a snippetNode like `sn(nil, {...nodes...})` the given `nodes` have to contain an +`insertNode` (e.g. `i(1)`). Using an `insertNode` or `textNode` directly as a choice is also fine, +the latter is special-cased to have a jump-point at the beginning of its text. + +* `pos: integer` Jump-index of the node. (See [Basics-Jump-Index](#jump-index)) +* `choices: (LuaSnip.Node|LuaSnip.Node[])[]` A list of nodes that can be switched between + interactively. If a list of nodes is passed as a choice, it will be turned into a snippetNode. + Jumpable nodes that generally need a jump-index don't need one when used as a choice since they + inherit the choiceNode's jump-index anyway. +* `opts?: LuaSnip.Opts.ChoiceNode?` Additional optional arguments. + Valid keys are: + + * `restore_cursor?: boolean?` If set, the currently active node is looked up in the switched-to + choice, and the cursor restored to preserve the current position relative to that node. The node + may be found if a `restoreNode` is present in both choice. Defaults to `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 + opts = opts or {} + opts.restore_cursor = true 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 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! - -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. - + Consider passing this override into `snip_env`. + * `node_callbacks?: { [("change_choice"|"enter"...)]: fun(...) -> ... }?` + * `node_ext_opts?: LuaSnip.NodeExtOpts?` Pass these opts through to the underlying extmarks + representing the node. Notably, this enables highlighting the nodes, and allows the highlight to + be different based on the state of the node/snippet. See [ext_opts](#ext_opts) + * `merge_node_ext_opts?: boolean?` Whether to use the parents' `ext_opts` to compute this nodes' + `ext_opts`. + * `key: any` Some unique value (strings seem useful) to identify this node. This is useful for + [Key Indexer](#key-indexer) or for finding the node at runtime (See + [Snippets-API](#snippets-api) These keys don't have to be unique across the entire lifetime of + the snippet, but every key should occur only once at the same time. This means it is fine to + return a keyed node from a dynamicNode, because even if it will be generated multiple times, the + same key not occur twice at the same time. + +**Examples:** ```lua c(1, { t"some text", -- textNodes are just stopped at. @@ -1167,12 +1178,12 @@ s("trig", { # Absolute Indexer `absolute_indexer` allows accessing nodes by their unique jump-index path from -the snippet-root. This makes it almost as powerful as [Key -Indexer](#key-indexer), but again removes the possibility of referring to -non-jumpable nodes and makes it all a bit more error-prone since the jump-index -paths are hard to follow, and (unfortunately) have to be a bit verbose (see the -long example of `absolute_indexer`-positions below). Consider just using [Key -Indexer](#key-indexer) instead. +the snippet-root. This makes it almost as powerful as [Key Indexer](#key-indexer), +but again removes the possibility of referring to non-jumpable nodes and makes +it all a bit more error-prone since the jump-index paths are hard to follow, and +(unfortunately) have to be a bit verbose (see the long example of +`absolute_indexer`-positions below). Consider just using [Key Indexer](#key-indexer) +instead. (The solution-snippet from [Key Indexer](#key-indexer), but using `ai` instead.) ```lua @@ -2699,8 +2710,8 @@ the `luasnip`-table: } ``` -Files with the extension `jsonc` will be parsed as `jsonc`, [`json` with -comments](https://code.visualstudio.com/docs/languages/json#_json-with-comments), +Files with the extension `jsonc` will be parsed as `jsonc`, +[`json` with comments](https://code.visualstudio.com/docs/languages/json#_json-with-comments), while `*.json` are parsed with a regular `json` parser, where comments are disallowed. (the `json` parser is a bit faster, so don't default to `jsonc` if it's not necessary). @@ -2964,6 +2975,7 @@ One side-effect of the injected globals is that language servers, for example may have many diagnostics about missing symbols. There are a few ways to fix this + * Add all variables in `snip_env` to `Lua.diagnostic.globals`: ```lua -- wherever your lua-language-server lsp settings are defined: @@ -2989,8 +3001,8 @@ There are a few ways to fix this `snip_env`, but all variables, like local variable names that may be mistyped. * A more complete, and only slightly more complicated solution is using - `lua-language-server`'s [definition - files](https://luals.github.io/wiki/definition-files/). + `lua-language-server`'s + [definition files](https://luals.github.io/wiki/definition-files/). Add a file with the line `---@meta`, followed by the variables defined by the `snip_env` to any directory listed in the `workspace.library`-settings for `lua-langue-server` (one likely directory is `vim.fn.stdpath("config")/lua`, @@ -3606,6 +3618,7 @@ before expanding. By default, this is disabled (as to not pollute keybindings which may be used for something else), so one has to + * either set `cut_selection_keys` in `setup` (see [Config-Options](#config-options)). * or map `ls.cut_keys` as the right-hand-side of a mapping @@ -3777,252 +3790,390 @@ These are the settings you can provide to `luasnip.setup()`: was defined after the normal snippet `s`, then adding `priority=1001` to the `postfix` snippet will cause it to expand as if it were defined before the normal snippet `s`. Snippet `priority` is discussed in the - [Snippets section](https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#snippets) of the documentation. + [Snippets section](#snippets) of the documentation. # API `require("luasnip")`: -- `add_snippets(ft:string or nil, snippets:list or table, opts:table or nil)`: - Makes `snippets` (list of snippets) available in `ft`. - If `ft` is `nil`, `snippets` should be a table containing lists of snippets, - the keys are corresponding filetypes. - `opts` may contain the following keys: - - `type`: type of `snippets`, `"snippets"` or `"autosnippets"` (ATTENTION: - plural form used here). This serves as default value for the `snippetType` - key of each snippet added by this call see [Snippets](#snippets). - - `key`: Key that identifies snippets added via this call. - If `add_snippets` is called with a key that was already used, the snippets - from that previous call will be removed. - This can be used to reload snippets: pass an unique key to each - `add_snippets` and just redo the `add_snippets`-call when the snippets have - changed. - - `override_priority`: set priority for all snippets. - - `default_priority`: set priority only for snippets without snippet priority. - -- `clean_invalidated(opts: table or nil) -> bool`: clean invalidated snippets - from internal snippet storage. - Invalidated snippets are still stored; it might be useful to actually remove - them as they still have to be iterated during expansion. - - `opts` may contain: - - - `inv_limit`: how many invalidated snippets are allowed. If the number of - invalid snippets doesn't exceed this threshold, they are not yet cleaned up. - - A small number of invalidated snippets (<100) probably doesn't affect - runtime at all, whereas recreating the internal snippet storage might. - -- `get_id_snippet(id)`: returns snippet corresponding to id. - -- `in_snippet()`: returns true if the cursor is inside the current snippet. - -- `jumpable(direction)`: returns true if the current node has a - next(`direction` = 1) or previous(`direction` = -1), e.g. whether it's - possible to jump forward or backward to another node. - -- `jump(direction)`: returns true if the jump was successful. - -- `expandable()`: true if a snippet can be expanded at the current cursor position. - -- `expand(opts)`: expands the snippet at(before) the cursor. - `opts` may contain: - - `jump_into_func` passed through to `ls.snip_expand`, check its' doc for a - description. - -- `expand_or_jumpable()`: returns `expandable() or jumpable(1)` (exists only - because commonly, one key is used to both jump forward and expand). - -- `expand_or_locally_jumpable()`: same as `expand_or_jumpable()` except jumpable - is ignored if the cursor is not inside the current snippet. - -- `locally_jumpable(direction)`: same as `jumpable()` except it is ignored if the cursor - is not inside the current snippet. - -- `expand_or_jump()`: returns true if jump/expand was successful. - -- `expand_auto()`: expands the autosnippets before the cursor (not necessary - to call manually, will be called via `autocmd` if `enable_autosnippets` is set - in the config). - -- `snip_expand(snip, opts)`: expand `snip` at the current cursor position. - `opts` may contain the following keys: - - `clear_region`: A region of text to clear after expanding (but before - jumping into) snip. It has to be at this point (and therefore passed to - this function) as clearing before expansion will populate `TM_CURRENT_LINE` - and `TM_CURRENT_WORD` with wrong values (they would miss the snippet trigger) - and clearing after expansion may move the text currently under the cursor - and have it end up not at the `i(1)`, but a `#trigger` chars to its right. - The actual values used for clearing are `from` and `to`, both (0,0)-indexed - byte-positions. - If the variables don't have to be populated with the correct values, it's - safe to remove the text manually. - - `expand_params`: table, override `trigger`, `captures` or environment of - the snippet. - This is useful for manually expanding snippets where the trigger passed - via `trig` is not the text triggering the snippet, or those which expect - `captures` (basically, snippets with a non-plaintext `trigEngine`). - - One example: - ```lua - snip_expand(snip, { - trigger = "override_trigger", - captures = {"first capture", "second capture"}, - env_override = { this_key = "some value", other_key = {"multiple", "lines"}, TM_FILENAME = "some_other_filename.lua" } - }) - ``` - - `pos`: position (`{line, col}`), (0,0)-indexed (in bytes, as returned by - `nvim_win_get_cursor()`), where the snippet should be expanded. The - snippet will be put between `(line,col-1)` and `(line,col)`. The snippet - will be expanded at the current cursor if `pos` is nil. - - `jump_into_func`: fn(snippet) -> node: - Callback responsible for jumping into the snippet. The returned node is - set as the new active node, i.e. it is the origin of the next jump. - The default is basically this: - ```lua - function(snip) - -- jump_into set the placeholder of the snippet, 1 - -- to jump forwards. - return snip:jump_into(1) - ``` - while this can be used to only insert the snippet: - ```lua - function(snip) - return snip.insert_nodes[0] - end - ``` - - `indent`: `bool?`, defaults to `true`. Whether LuaSnip will try to add - additional indents to fit current indent level in snippet expanding. This - option is useful when some LSP server already take indents into - consideration. In such cases, LuaSnip should not try to add additional - indents. If you are using `nvim-cmp`, sample config: - - ```lua - require("cmp").setup { +#### `get_active_snip(): LuaSnip.Snippet?` + +Get the currently active snippet. + +This function returns: + +* `LuaSnip.Snippet?` The active snippet if one exists, or `nil`. + +#### `get_snippets(ft?, opts?): (LuaSnip.Snippet[]|{ [string]: LuaSnip.Snippet[] })` + +Retrieve snippets from luasnip. + +* `ft?: string?` Filetype, if not given returns snippets for all filetypes. +* `opts?: LuaSnip.Opts.GetSnippets?` Optional arguments. + Valid keys are: + + * `type?: ("snippets"|"autosnippets")?` Whether to get snippets or autosnippets. Defaults to + "snippets". + +This function returns: + +* `(LuaSnip.Snippet[]|{ [string]: LuaSnip.Snippet[] })` Flat array when `ft` is non-nil, otherwise a + table mapping filetypes to snippets. + +#### `available(snip_info?): { [string]: T[] }` + +Retrieve information about snippets available in the current file/at the current position (in case +treesitter-based filetypes are enabled). + +* `snip_info?: fun(LuaSnip.Snippet) -> T?` Optionally pass a function that, given a snippet, returns + the data that is returned by this function in the snippets' stead. + By default, this function is + ```lua + function(snip) + return { + name = snip.name, + trigger = snip.trigger, + description = snip.description, + wordTrig = snip.wordTrig and true or false, + regTrig = snip.regTrig and true or false, + } + end + ``` + +This function returns: + +* `{ [string]: T[] }` Table mapping filetypes to list of data returned by snip_info function. + +#### `unlink_current()` + +Removes the current snippet from the jumplist (useful if LuaSnip fails to automatically detect e.g. +deletion of a snippet) and sets the current node behind the snippet, or, if not possible, before it. + +#### `jump(dir): boolean` + +Jump forwards or backwards + +* `dir: (1|-1)` Jump forward for 1, backward for -1. + +This function returns: + +* `boolean` `true` if a jump was performed, `false` otherwise. + +#### `jump_destination(dir): LuaSnip.Node` + +Find the node the next jump will end up at. This will not work always, because we will not update +the node before jumping, so if the jump would e.g. insert a new node between this node and its +pre-update jump target, this would not be registered. Thus, it currently only works for simple +cases. + +* `dir: (1|-1)` `1`: find the next node, `-1`: find the previous node. + +This function returns: + +* `LuaSnip.Node` The destination node. + +#### `jumpable(dir): boolean` + +Return whether jumping forwards or backwards will actually jump, or if there is no node in that +direction. + +* `dir: (1|-1)` `1` forward, `-1` backward. + +#### `expandable(): boolean` + +Return whether there is an expandable snippet at the current cursor position. Does not consider +autosnippets since those would already be expanded at this point. + +#### `expand_or_jumpable(): boolean` + +Return whether it's possible to expand a snippet at the current cursor-position, or whether it's +possible to jump forward from the current node. + +#### `in_snippet(): boolean` + +Determine whether the cursor is within a snippet. + +#### `expand_or_locally_jumpable(): boolean` + +Return whether a snippet can be expanded at the current cursor position, or whether the cursor is +inside a snippet and the current node can be jumped forward from. + +#### `locally_jumpable(dir): boolean` + +Return whether the cursor is inside a snippet and the current node can be jumped forward from. + +* `dir: (1|-1)` Test jumping forwards/backwards. + +#### `snip_expand(snippet, opts?): LuaSnip.ExpandedSnippet` + +Expand a snippet in the current buffer. + +* `snippet: LuaSnip.Snippet` The snippet. +* `opts?: LuaSnip.Opts.SnipExpand?` Optional additional arguments. + Valid keys are: + + * `clear_region?: LuaSnip.BufferRegion?` A region of text to clear after populating env-variables, + but before jumping into `snip`. If `nil`, no clearing is performed. Being able to remove text at + this point is useful as clearing before calling this function would populate `TM_CURRENT_LINE` + and `TM_CURRENT_WORD` with wrong values (they would miss the snippet trigger). The actual values + used for clearing are `region.from` and `region.to`, both (0,0)-indexed byte-positions in the + buffer. + * `expand_params?: LuaSnip.Opts.SnipExpandExpandParams?` Override various fields of the expanded + snippet. Don't override anything by default. This is useful for manually expanding snippets + where the trigger passed via `trig` is not the text triggering the snippet, or those which + expect `captures` (basically, snippets with a non-plaintext `trigEngine`). + + One Example: + ```lua + snip_expand(snip, { + trigger = "override_trigger", + captures = {"first capture", "second capture"}, + env_override = { this_key = "some value", other_key = {"multiple", "lines"}, TM_FILENAME = "some_other_filename.lua" } + }) + ``` + + Valid keys are: + * `trigger?: string?` What to set as the expanded snippets' trigger (Defaults to + `snip.trigger`). + * `captures?: string[]?` Set as the expanded snippets' captures (Defaults to `{}`). + * `env_override?: { [string]: string }?` Set or override environment variables of the expanded + snippet (Defaults to `{}`). + * `pos?: (integer,integer)?` Position at which the snippet should be inserted. Pass as + `(row,col)`, both 0-based, the `col` given in bytes. + * `indent?: boolean?` Whether to prepend the current lines' indent to all lines of the snippet. + (Defaults to `true`) + Turning this off is a good idea when a LSP server already takes indents into consideration. In + such cases, LuaSnip should not add additional indents. If you are using `nvim-cmp`, this could + be used as follows: + ```lua + require("cmp").setup { snippet = { - expand = function(args) - local indent_nodes = true - if vim.api.nvim_get_option_value("filetype", { buf = 0 }) == "dart" then - indent_nodes = false - end - require("luasnip").lsp_expand(args.body, { - indent = indent_nodes, - }) - end, + expand = function(args) + local indent_nodes = true + if vim.api.nvim_get_option_value("filetype", { buf = 0 }) == "dart" then + indent_nodes = false + end + require("luasnip").lsp_expand(args.body, { + indent = indent_nodes, + }) + end, }, - } - ``` - - `opts` and any of its parameters may be nil. - -- `get_active_snip()`: returns the currently active snippet (not node!). - -- `choice_active()`: true if inside a `choiceNode`. - -- `change_choice(direction)`: changes the choice in the innermost currently - active `choiceNode` forward (`direction` = 1) or backward (`direction` = -1). - -- `unlink_current()`: removes the current snippet from the jumplist (useful - if LuaSnip fails to automatically detect e.g. deletion of a snippet) and - sets the current node behind the snippet, or, if not possible, before it. - -- `lsp_expand(snip_string, opts)`: expands the LSP snippet defined via - `snip_string` at the cursor. - `opts` can have the same options as `opts` in `snip_expand`. - -- `active_update_dependents()`: update all function/dynamicNodes that have the - current node as an argnode (will actually only update them if the text in any - of the argnodes changed). - -- `available(snip_info)`: returns a table of all snippets defined for the - current filetypes(s) (`{ft1={snip1, snip2}, ft2={snip3, snip4}}`). - The structure of the snippet is defined by `snip_info` which is a function - (`snip_info(snip)`) that takes in a snippet (`snip`), finds the desired - information on it, and returns it. - `snip_info` is an optional argument as a default has already been defined. - You can use it for more granular control over the table of snippets that is - returned. - -- `exit_out_of_region(node)`: checks whether the cursor is still within the - range of the root-snippet `node` belongs to. If yes, no change occurs; if no, the - root-snippet is exited and its `$0` will be the new active node. - If a jump causes an error (happens mostly because the text of a snippet was - deleted), the snippet is removed from the jumplist and the current node set to - the end/beginning of the next/previous snippet. - -- `store_snippet_docstrings(snippet_table)`: Stores the docstrings of all - snippets in `snippet_table` to a file - (`stdpath("cache")/luasnip/docstrings.json`). - Calling `store_snippet_docstrings(snippet_table)` after adding/modifying - snippets and `load_snippet_docstrings(snippet_table)` on startup after all - snippets have been added to `snippet_table` is a way to avoid regenerating - the (unchanged) docstrings on each startup. - (Depending on when the docstrings are required and how LuaSnip is loaded, - it may be more sensible to let them load lazily, e.g. just before they are - required). - `snippet_table` should be laid out just like `luasnip.snippets` (it will - most likely always _be_ `luasnip.snippets`). - -- `load_snippet_docstrings(snippet_table)`: Load docstrings for all snippets - in `snippet_table` from `stdpath("cache")/luasnip/docstrings.json`. - The docstrings are stored and restored via trigger, meaning if two - snippets for one filetype have the same (very unlikely to happen in actual - usage), bugs could occur. - `snippet_table` should be laid out as described in `store_snippet_docstrings`. - -- `unlink_current_if_deleted()`: Checks if the current snippet was deleted; - if so, it is removed from the jumplist. This is not 100% reliable as - LuaSnip only sees the extmarks and their beginning/end may not be on the same - position, even if all the text between them was deleted. - -- `filetype_extend(filetype:string, extend_filetypes:table of string)`: Tells - LuaSnip that for a buffer with `ft=filetype`, snippets from - `extend_filetypes` should be searched as well. `extend_filetypes` is a - Lua array (`{ft1, ft2, ft3}`). - `luasnip.filetype_extend("lua", {"c", "cpp"})` would search and expand C and - C++ snippets for Lua files. - -- `filetype_set(filetype:string, replace_filetypes:table of string)`: Similar - to `filetype_extend`, but where _append_ appended filetypes, _set_ sets them: - `filetype_set("lua", {"c"})` causes only c snippets to be expanded in - Lua files; Lua snippets aren't even searched. - -- `cleanup()`: clears all snippets. Not useful for regular usage, only when - authoring and testing snippets. - -- `refresh_notify(ft:string)`: Triggers an `autocmd` that other plugins can hook - into to perform various cleanup for the refreshed filetype. - Useful for signaling that new snippets were added for the filetype `ft`. - -- `set_choice(indx:number)`: Changes to the `indx`th choice. - If no `choiceNode` is active, an error is thrown. - If the active `choiceNode` doesn't have an `indx`th choice, an error is - thrown. - -- `get_current_choices() -> string[]`: Returns a list of multiline-strings - (themselves lists, even if they have only one line), the `i`th string - corresponding to the `i`th choice of the currently active `choiceNode`. - If no `choiceNode` is active, an error is thrown. - -- `setup_snip_env()`: Adds the variables defined (during `setup`) in `snip_env` - to the callers environment. - -- `get_snip_env()`: Returns `snip_env`. - -- `jump_destination(direction)`: Returns the node the next jump in `direction` - (either -1 or 1, for backwards, forwards respectively) leads to, or `nil` if - the destination could not be determined (most likely because there is no node - that can be jumped to in the given direction, or there is no active node). - -- `activate_node(opts)`: Activate a node in any snippet. - `opts` contains the following options: - * `pos`, `{[1]: row, [2]: byte-column}?`: The position at which a node should - be activated. Defaults to the position of the cursor. - * `strict`, `bool?`: If set, throw an error if the node under the cursor can't - be jumped into. If not set, fall back to any node of the snippet and enter - that instead. - * `select`, `bool?`: Whether the text inside the node should be selected. - Defaults to true. + } + ``` + * `jump_into_func?: fun(snip: LuaSnip.Snippet) -> LuaSnip.Node?` + +This function returns: + +* `LuaSnip.ExpandedSnippet` The snippet that was inserted into the buffer. + +#### `expand(opts?): boolean` + +Find a snippet whose trigger matches the text before the cursor and expand it. + +* `opts?: LuaSnip.Opts.Expand?` Subset of opts accepted by `snip_expand`. + Valid keys are: + + * `jump_into_func?: fun(snip: LuaSnip.Snippet) -> LuaSnip.Node?` + +This function returns: + +* `boolean` Whether a snippet was expanded. + +#### `expand_auto()` + +Find an autosnippet matching the text at the cursor-position and expand it. + +#### `expand_repeat()` + +Repeat the last performed `snip_expand`. Useful for dot-repeat. + +#### `expand_or_jump(): boolean` + +Expand at the cursor, or jump forward. + +This function returns: + +* `boolean` Whether an action was performed. + +#### `lsp_expand(body, opts?)` + +Expand a snippet specified in lsp-style. + +* `body: string` A string specifying a lsp-snippet, e.g. `"[${1:text}](${2:url})"` +* `opts?: LuaSnip.Opts.SnipExpand?` Optional args passed through to `snip_expand`. + +#### `choice_active(): boolean` + +Return whether the current node is inside a choiceNode. + +#### `change_choice(val)` + +Change the currently active choice. + +* `val: (1|-1)` Move one choice forward or backward. + +#### `set_choice(choice_indx)` + +Set the currently active choice. + +* `choice_indx: integer` Index of the choice to switch to. + +#### `get_current_choices(): string[]` + +Get a string-representation of all the current choiceNode's choices. + +This function returns: + +* `string[]` \n-concatenated lines of every choice. + +#### `active_update_dependents()` + +Update all nodes that depend on the currently-active node. + +#### `store_snippet_docstrings(snippet_table)` + +Generate and store the docstrings for a list of snippets as generated by `get_snippets()`. +The docstrings are stored at `stdpath("cache") .. "/luasnip/docstrings.json"`, are indexed by their +trigger, and should be updated once any snippet changes. + +* `snippet_table: { [string]: LuaSnip.Snippet[] }` A table mapping some keys to lists of snippets + (keys are most likely filetypes). + +#### `load_snippet_docstrings(snippet_table)` + +Provide all passed snippets with a previously-stored (via `store_snippet_docstrings`) docstring. +This prevents a somewhat costly computation which is performed whenever a snippets' docstring is +first retrieved, but may cause larger delays when `snippet_table` contains many of snippets. +Utilize this function by calling `ls.store_snippet_docstrings(ls.get_snippets())` whenever snippets +are modified, and `ls.load_snippet_docstrings(ls.get_snippets())` on startup. + +* `snippet_table: { [string]: LuaSnip.Snippet[] }` List of snippets, should contain the same keys + (filetypes) as the table that was passed to `store_snippet_docstrings`. Again, most likely the + result of `get_snippets`. + +#### `unlink_current_if_deleted()` + +Checks whether (part of) the current snippet's text was deleted, and removes it from the jumplist if +it was (it cannot be jumped back into). + +#### `exit_out_of_region(node)` + +Checks whether the cursor is still within the range of the root-snippet `node` belongs to. If yes, +no change occurs; if no, the root-snippet is exited and its `i(0)` will be the new active node. +If a jump causes an error (happens mostly because the text of a snippet was deleted), the snippet is +removed from the jumplist and the current node set to the end/beginning of the next/previous +snippet. + +* `node: LuaSnip.Node` + +#### `filetype_extend(ft, extend_ft)` + +Add `extend_ft` filetype to inherit its snippets from `ft`. + +Example: +```lua +ls.filetype_extend("sh", {"zsh"}) +ls.filetype_extend("sh", {"bash"}) +``` +This makes all `sh` snippets available in `sh`/`zsh`/`bash` buffers. + +* `ft: string` +* `extend_ft: string[]` + +#### `filetype_set(ft, fts)` + +Set `fts` filetypes as inheriting their snippets from `ft`. + +Example: +```lua +ls.filetype_set("sh", {"sh", "zsh", "bash"}) +``` +This makes all `sh` snippets available in `sh`/`zsh`/`bash` buffers. + +* `ft: string` +* `fts: string[]` + +#### `cleanup()` + +Clear all loaded snippets. Also sends the `User LuasnipCleanup` autocommand, so plugins that depend +on luasnip's snippet-state can clean up their now-outdated state. + +#### `refresh_notify(ft)` + +Trigger the `User LuasnipSnippetsAdded` autocommand that signifies to other plugins that a filetype +has received new snippets. + +* `ft: string` The filetype that has new snippets. Code that listens to this event can retrieve this + filetype from `require("luasnip").session.latest_load_ft`. + +#### `setup_snip_env()` + +Injects the fields defined in `snip_env`, in `setup`, into the callers global environment. + +This means that variables like `s`, `sn`, `i`, `t`, ... (by default) work, and are useful for +quickly testing snippets in a buffer: +```lua +local ls = require("luasnip") +ls.setup_snip_env() + +ls.add_snippets("all", { + s("choicetest", { + t":", c(1, { + t("asdf", {node_ext_opts = {active = { virt_text = {{"asdf", "Comment"}} }}}), + t("qwer", {node_ext_opts = {active = { virt_text = {{"qwer", "Comment"}} }}}), + }) + }) +}, { key = "3d9cd211-c8df-4270-915e-bf48a0be8a79" }) +``` +where the `key` makes it easy to reload the snippets on changes, since the previously registered +snippets will be replaced when the buffer is re-sourced. + +#### `get_snip_env(): table` + +Return the currently active snip_env. + +#### `get_id_snippet(id): LuaSnip.Snippet` + +Get the snippet corresponding to some id. + +* `id: LuaSnip.SnippetID` + +#### `add_snippets(ft?, snippets, opts?)` + +Add snippets to luasnip's snippet-collection. + +NOTE: Calls `refresh_notify` as needed if enabled via `opts.refresh_notify`. + +* `ft?: string?` The filetype to add the snippets to, or nil if the filetype is specified in + `snippets`. +* `snippets: (LuaSnip.Addable[]|{ [string]: LuaSnip.Addable[] })` If `ft` is nil a table mapping a + filetype to a list of snippets, otherwise a flat table of snippets. + `LuaSnip.Addable` are objects created by e.g. the functions `s`, `ms`, or `sp`. +* `opts?: LuaSnip.Opts.AddSnippets?` Optional arguments. + +#### `clean_invalidated(opts?)` + +Clean invalidated snippets from internal snippet storage. Invalidated snippets are still stored; it +might be useful to actually remove them as they still have to be iterated during expansion. + +* `opts?: LuaSnip.Opts.CleanInvalidated?` Additional, optional arguments. + Valid keys are: + + * `inv_limit?: integer?` If set, invalidated snippets are only cleared if their number exceeds + `inv_limit`. + +#### `activate_node(opts?)` + +Lookup a node by position and activate (ie. jump into) it. + +* `opts?: LuaSnip.Opts.ActivateNode?` Additional, optional arguments. + Valid keys are: + + * `strict?: boolean?` Only activate nodes one could usually jump to. (Defaults to false) + * `select?: boolean?` Whether to select the entire node, or leave the cursor at the position it is + currently at. (Defaults to true) + * `pos?: LuaSnip.BytecolBufferPosition?` Where to look for the node. (Defaults to the position of + the cursor) Not covered in this section are the various node-constructors exposed by the module, their usage is shown either previously in this file or in diff --git a/Makefile b/Makefile index 201b9a389..3e18abe5f 100644 --- a/Makefile +++ b/Makefile @@ -137,6 +137,28 @@ test_nix: nvim install_jsregexp if ${TEST_MASTER}; then nix develop .#test_nvim_master -c make test; fi; spellcheck: - # grabbed from word-warden. - aspell --home-dir . --mode markdown --lang en --personal ./.github/data/project-dictionary.txt check DOC.md - aspell --home-dir . --mode markdown --lang en --personal ./.github/data/project-dictionary.txt check README.md +# grabbed from word-warden. + # Misspelled words will appear inside the following tests: + if [ -n "$(shell aspell --home-dir . --encoding=utf-8 --mode markdown --lang en_US --personal ./data/project-dictionary.txt list < README.md)" ]; then exit 1; fi; + if [ -n "$(shell aspell --home-dir . --encoding=utf-8 --mode markdown --lang en_US --personal ./data/project-dictionary.txt list < DOC.md)" ]; then exit 1; fi; + +spellcheck_interactive: + aspell --home-dir . --encoding=utf-8 --mode markdown --lang en_US --personal ./data/project-dictionary.txt check DOC.md + aspell --home-dir . --encoding=utf-8 --mode markdown --lang en_US --personal ./data/project-dictionary.txt check README.md + +.PHONY: doc +doc: +# Have to generate this (temporarily) into DOC.md. +# This is because all links in annotations refer to that +# file, and luals-mdgen will produce links like [xxx](DOC.md#some-section), +# which panvimdoc cannot deal with, it only knows [xxx](#some-section). +# Use 80 as width, panvimdoc has some weird behaviour when rendering lists, +# where sometimes the textwidth-limit is not applied, and the text from the +# markdown is used verbatim. Rendering the markdown with width 80 is a good +# solution because indentation is identical for lists. + emmylua_doc_cli -f json -i lua/luasnip/ -o ./ + luals-mdgen data/DOC-template.md DOC.md --width 80 --mode vimdoc + panvimdoc --project-name luasnip --input-file DOC.md --vim-version "NeoVim 0.7-0.11" --doc-mapping true + nvim --clean -es +"helptags doc | exit" +# again, this time without vimdoc, overwrite previous DOC.md. + luals-mdgen data/DOC-template.md DOC.md --width 100 diff --git a/data/DOC-template.md b/data/DOC-template.md new file mode 100644 index 000000000..d0692324e --- /dev/null +++ b/data/DOC-template.md @@ -0,0 +1,3819 @@ +``` + __ ____ + /\ \ /\ _`\ __ + \ \ \ __ __ __ \ \,\L\_\ ___ /\_\ _____ + \ \ \ __/\ \/\ \ /'__`\\/_\__ \ /' _ `\/\ \/\ '__`\ + \ \ \L\ \ \ \_\ \/\ \L\.\_/\ \L\ \/\ \/\ \ \ \ \ \L\ \ + \ \____/\ \____/\ \__/.\_\ `\____\ \_\ \_\ \_\ \ ,__/ + \/___/ \/___/ \/__/\/_/\/_____/\/_/\/_/\/_/\ \ \/ + \ \_\ + \/_/ +``` + +LuaSnip is a snippet engine written entirely in Lua. It has some great +features like inserting text (`luasnip-function-node`) or nodes +(`luasnip-dynamic-node`) based on user input, parsing LSP syntax and switching +nodes (`luasnip-choice-node`). +For basic setup like mappings and installing, check the README. + +All code snippets in this help assume the following: + +```lua +local ls = require("luasnip") +local s = ls.snippet +local sn = ls.snippet_node +local isn = ls.indent_snippet_node +local t = ls.text_node +local i = ls.insert_node +local f = ls.function_node +local c = ls.choice_node +local d = ls.dynamic_node +local r = ls.restore_node +local events = require("luasnip.util.events") +local ai = require("luasnip.nodes.absolute_indexer") +local extras = require("luasnip.extras") +local l = extras.lambda +local rep = extras.rep +local p = extras.partial +local m = extras.match +local n = extras.nonempty +local dl = extras.dynamic_lambda +local fmt = require("luasnip.extras.fmt").fmt +local fmta = require("luasnip.extras.fmt").fmta +local conds = require("luasnip.extras.expand_conditions") +local postfix = require("luasnip.extras.postfix").postfix +local types = require("luasnip.util.types") +local parse = require("luasnip.util.parser").parse_snippet +local ms = ls.multi_snippet +local k = require("luasnip.nodes.key_indexer").new_key +``` + +As noted in the [Loaders-Lua](#lua)-section: + +> By default, the names from [`luasnip.config.snip_env`][snip-env-src] will be used, but it's possible to customize them by setting `snip_env` in `setup`. + +Furthermore, note that while this document assumes you have defined `ls` to be `require("luasnip")`, it is **not** provided in the default set of variables. + + + +Note: the source code of snippets in GIFs is actually +[here](https://github.com/zjp-CN/neovim0.6-blogs/commit/2bff84ef53f8da5db9dcf2c3d97edb11b2bf68cd), +and it's slightly different from the code below. + + + +# Basics +In LuaSnip, snippets are made up of `nodes`. These can contain either + +- static text (`textNode`) +- text that can be edited (`insertNode`) +- text that can be generated from the contents of other nodes (`functionNode`) +- other nodes + - `choiceNode`: allows choosing between two nodes (which might contain more + nodes) + - `restoreNode`: store and restore input to nodes +- or nodes that can be generated based on input (`dynamicNode`). + +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 +`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`. +```lua +ls.add_snippets("all", { + s("ternary", { + -- equivalent to "${1:cond} ? ${2:then} : ${3:else}" + i(1, "cond"), t(" ? "), i(2, "then"), t(" : "), i(3, "else") + }) +}) +``` +It is possible to make snippets from one filetype available to another using +`ls.filetype_extend`, more info on that in the section [API](#api-2). + +## Snippet Insertion +When a new snippet is expanded, it can be connected with the snippets that have +already been expanded in the buffer in various ways. +First of all, Luasnip distinguishes between root-snippets and child-snippets. +The latter are nested inside other snippets, so when jumping through a snippet, +one may also traverse the child-snippets expanded inside it, more or less as if +the child just contains more nodes of the parent. +Root-snippets are of course characterized by not being child-snippets. +When expanding a new snippet, it becomes a child of the snippet whose region it +is expanded inside, and a root if it is not inside any snippet's region. +If it is inside another snippet, the specific node it is inside is determined, +and the snippet then nested inside that node. + +* If that node is interactive (for example, an `insertNode`), the new snippet + will be traversed when the node is visited, as long as the + configuration-option `link_children` is enabled. If it is not enabled, it is + possible to jump from the snippet to the node, but not the other way around. +* If that node is not interactive, the snippet will be linked to the currently + active node, also such that it will not be jumped to again once it is left. + This is to prevent jumping large distances across the buffer as much as + possible. There may still be one large jump from the snippet back to the + current node it is nested inside, but that seems hard to avoid. + Thus, one should design snippets such that the regions where other snippets + may be expanded are inside `insertNodes`. + +If the snippet is not a child, but a root, it can be linked up with the roots +immediately adjacent to it by enabling `link_roots` in `setup`. +Since by default only one root is remembered, one should also set `keep_roots` +if `link_roots` is enabled. The two are separate options, since roots that are +not linked can still be reached by `ls.activate_node()`. This setup (remember +roots, but don't jump to them) is useful for a super-tab like mapping (`` +and jump on the same key), where one would like to still enter previous roots. +Since there would almost always be more jumps if the roots are linked, regular +`` would not work almost all the time, and thus `link_roots` has to stay +disabled. + +# Node + +Every node accepts, as its last parameter, an optional table of arguments. +There are some common ones (which are listed here), and some that only apply to +some nodes (`user_args` for function/dynamicNode). These `opts` are +only mentioned if they accept options that are not common to all nodes. + +Common opts: + +* `node_ext_opts` and `merge_node_ext_opts`: Control `ext_opts` (most likely + highlighting) of the node. Described in detail in [ext_opts](#ext_opts) +* `key`: The node can be referred to by this key. Useful for either + [Key Indexer](#key-indexer) or for finding the node at runtime (See + [Snippets-API](#snippets-api)), for example inside a `dynamicNode`. The keys + do not have to be unique across the entire lifetime of the snippet, but at any + point in time, the snippet may contain each key only once. This means it is + fine to return a keyed node from a `dynamicNode`, because even if it will be + generated multiple times, those will not be valid at the same time. +* `node_callbacks`: Define event-callbacks for this node (see + [events](#events)). + Accepts a table that maps an event, e.g. `events.enter` to the callback + (essentially the same as `callbacks` passed to `s`, only that there is no + first mapping from jump-index to the table of callbacks). + +## API + +- `get_jump_index()`: this method returns the jump-index of a node. If a node + doesn't have a jump-index, this method returns `nil` instead. +- `get_buf_position(opts) -> {from_position, to_position}`: + Determines the range of the buffer occupied by this node. `from`- and + `to_position` are `row,column`-tuples, `0,0`-indexed (first line is 0, first + column is 0) and end-inclusive (see `:h api-indexing`, this is extmarks + indexing). + - `opts`: `table|nil`, options, valid keys are: + - `raw`: `bool`, default `true`. This can be used to switch between + byte-columns (`raw=true`) and visual columns (`raw=false`). This makes a + difference if the line contains characters represented by multiple bytes + in UTF, for example `ÿ`. + +# Snippets + +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 + + ```lua + { + trig = context + } + ``` + + The following keys are valid: + - `trig`: string, the trigger of the snippet. If the text in front of (to the + left of) the cursor when `ls.expand()` is called matches it, the snippet + will be expanded. + By default, "matches" means the text in front of the cursor matches the + trigger exactly, this behavior can be modified through `trigEngine` + - `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. + - `desc` (or `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. + Consider setting `trigEngine` to `"pattern"` instead, it is more expressive, + and in line with other settings. + - `trigEngine`: (function|string), determines how `trig` is interpreted, and + what it means for it to "match" the text in front of the cursor. + This behavior can be completely customized by passing a function, but the + predefined ones, which are accessible by passing their identifier, should + suffice in most cases: + * `"plain"`: the default-behavior, the trigger has to match the text before + the cursor exactly. + * `"pattern"`: the trigger is interpreted as a Lua pattern, and is a match if + `trig .. "$"` matches the line up to the cursor. Capture-groups will be + accessible as `snippet.captures`. + * `"ecma"`: the trigger is interpreted as an ECMAscript-regex, and is a + match if `trig .. "$"` matches the line up to the cursor. Capture-groups + will be accessible as `snippet.captures`. + This `trigEngine` requires `jsregexp` (see + [LSP-snippets-transformations](#transformations)) to be installed, if it + is not, this engine will behave like `"plain"`. + * `"vim"`: the trigger is interpreted as a vim-regex, and is a match if + `trig .. "$"` matches the line up to the cursor. As with the other + regex/pattern-engines, captures will be available as `snippet.captures`, + but there is one caveat: the matching is done using `matchlist`, so for + now empty-string submatches will be interpreted as unmatched, and the + corresponding `snippet.capture[i]` will be `nil` (this will most likely + change, don't rely on this behavior). + + Besides these predefined engines, it is also possible to create new ones: + Instead of a string, pass a function which satisfies + `trigEngine(trigger, opts) -> (matcher(line_to_cursor, trigger) -> + whole_match, captures)` + (i.e. the function receives `trig` and `trigEngineOpts` can, for example, + precompile a regex, and then returns a function responsible for determining + whether the current cursor-position (represented by the line up to the + cursor) matches the trigger (it is passed again here so engines which don't + do any trigger-specific work (like compilation) can just return a static + `matcher`), and what the capture-groups are). + The `lua`-engine, for example, can be implemented like this: + ```lua + local function matcher(line_to_cursor, trigger) + -- look for match which ends at the cursor. + -- put all results into a list, there might be many capture-groups. + local find_res = { line_to_cursor:find(trigger .. "$") } + + if #find_res > 0 then + -- if there is a match, determine matching string, and the + -- capture-groups. + local captures = {} + -- find_res[1] is `from`, find_res[2] is `to` (which we already know + -- anyway). + local from = find_res[1] + local match = line_to_cursor:sub(from, #line_to_cursor) + -- collect capture-groups. + for i = 3, #find_res do + captures[i - 2] = find_res[i] + end + return match, captures + else + return nil + end + end + + local function engine(trigger) + -- don't do any special work here, can't precompile lua-pattern. + return matcher + end + ``` + The predefined engines are defined in + [`trig_engines.lua`](https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/nodes/util/trig_engines.lua), + read it for more examples. + + - `trigEngineOpts`: `table`, options for the used `trigEngine`. + The valid options are: + * `max_len`: number, upper bound on the length of the trigger. + If this is set, the `line_to_cursor` will be truncated (from the cursor of + course) to `max_len` characters before performing the match. + This is implemented because feeding long `line_to_cursor` into e.g. the + pattern-`trigEngine` will hurt performance quite a bit (see issue + Luasnip#1103). + This option is implemented for all `trigEngines`. + + - `docstring`: string, textual representation of the snippet, specified like + `desc`. Overrides docstrings loaded from `json`. + - `docTrig`: string, used as `line_to_cursor` during docstring-generation. + This might be relevant if the snippet relies on specific values in the + capture-groups (for example, numbers, which won't work with the default + `$CAPTURESN` 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. + - `resolveExpandParams`: `fn(snippet, line_to_cursor, matched_trigger, captures) -> table|nil`, where + - `snippet`: `Snippet`, the expanding snippet object + - `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`: `captures` as returned by `trigEngine`. + + This function will be evaluated in `Snippet:matches()` to decide whether + the snippet can be expanded or not. + Returns a table if the snippet can be expanded, `nil` if can not. The + returned table can contain any of these fields: + - `trigger`: `string`, the fully matched trigger. + - `captures`: `table`, this list could update the capture-groups from + parameter in snippet expansion. + Both `trigger` and `captures` can override the values returned via + `trigEngine`. + - `clear_region`: `{ "from": {, }, "to": {, } }`, + both (0, 0)-indexed, the region where text has to be cleared before + inserting the snippet. + - `env_override`: `map string->(string[]|string)`, override or extend + the snippet's environment (`snip.env`) + + If any of these is `nil`, the default is used (`trigger` and `captures` as + returned by `trigEngine`, `clear_region` such that exactly the trigger is + deleted, no overridden environment-variables). + + A good example for the usage of `resolveExpandParams` can be found in + the implementation of + [`postfix`](https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/extras/postfix.lua). + - `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. + - `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 engines when scanning for available snippet candidates. + - `filetype`: `string`, the filetype of the snippet. + This overrides the filetype the snippet is added (via `add_snippet`) as. + +- `nodes`: A single node or a list of nodes. The nodes that make up the + snippet. + +- `opts`: A table with the following valid keys: + - `callbacks`: Contains functions that are called upon entering/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 in [events](#events) + - `child_ext_opts`, `merge_child_ext_opts`: Control `ext_opts` applied to the + children of this snippet. More info on those in the + [ext_opts](#ext_opts)-section. + +The `opts`-table, as described here, can also be passed to e.g. `snippetNode` +and `indentSnippetNode`. +It is also possible to set `condition` and `show_condition` (described in the +documentation of the `context`-table) from `opts`. They should, however, not be +set from both. + +## 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 [Variables-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 + +- `invalidate()`: call this method to effectively remove the snippet. The + snippet will no longer be able to expand via `expand` or `expand_auto`. It + will also be hidden from lists (at least if the plugin creating the list + respects the `hidden`-key), but it might be necessary to call + `ls.refresh_notify(ft)` after invalidating snippets. +- `get_keyed_node(key)`: Returns the currently visible node associated with + `key`. + + +# TextNode + +The most simple kind of node; just text. +```lua +s("trigger", { t("Wow! Text!") }) +``` +This snippet expands to + +``` + Wow! Text!⎵ +``` +where ⎵ is the cursor. + +Multiline strings can be defined by passing a table of lines rather than a +string: + +```lua +s("trigger", { + t({"Wow! Text!", "And another line."}) +}) +``` + +`t(text, node_opts)`: + +- `text`: `string` or `string[]` +- `node_opts`: `table`, see [Node](#node) + +# InsertNode + +These Nodes contain editable text and can be jumped to- and from (e.g. +traditional placeholders and tabstops, like `$1` in TextMate-snippets). + +The functionality is best demonstrated with an example: + +```lua +s("trigger", { + t({"After expanding, the cursor is here ->"}), i(1), + t({"", "After jumping forward once, cursor is here ->"}), i(2), + t({"", "After jumping once more, the snippet is exited there ->"}), i(0), +}) +``` + + + +![InsertNode](https://user-images.githubusercontent.com/25300418/184359293-7248c2af-81b4-4754-8a85-7a2459f69cfc.gif) + + + +The Insert Nodes 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, 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 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), + t({"", "After expanding, the cursor is here ->"}), i(1), + t({"", "After jumping once more, the snippet is exited there ->"}), i(0), +}) +``` +The above snippet will behave 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. + +An **important** (because here Luasnip differs from other snippet engines) detail +is that the jump-indices restart at 1 in nested snippets: +```lua +s("trigger", { + i(1, "First jump"), + t(" :: "), + sn(2, { + i(1, "Second jump"), + t" : ", + i(2, "Third jump") + }) +}) +``` + + + +![InsertNode2](https://user-images.githubusercontent.com/25300418/184359299-c813b3d2-5445-47c9-af88-d9106e78fa77.gif) + + + +as opposed to e.g. the TextMate syntax, where tabstops are snippet-global: +```snippet +${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 when parsed). + +`i(jump_index, text, node_opts)` + +- `jump_index`: `number`, this determines when this node will be jumped to (see + [Basics-Jump-Index](#jump-index)). +- `text`: `string|string[]`, a single string for just one line, a list with >1 + entries for multiple lines. + This text will be `SELECT`ed when the `insertNode` is jumped into. +- `node_opts`: `table`, described in [Node](#node) + +If the `jump_index` is `0`, replacing its' `text` will leave it outside the +`insertNode` (for reasons, check out Luasnip#110). + + +# FunctionNode + +Function Nodes insert text based on the content of other nodes using a +user-defined function: + +```lua +local function fn( + args, -- text from i(2) in this example i.e. { { "456" } } + parent, -- parent snippet or parent node + user_args -- user_args from opts.user_args +) + return '[' .. args[1][1] .. user_args .. ']' +end + +s("trig", { + i(1), t '<-i(1) ', + f(fn, -- callback (args, parent, user_args) -> string + {2}, -- node indice(s) whose text is passed to fn, i.e. i(2) + { user_args = { "user_args_value" }} -- opts + ), + t ' i(2)->', i(2), t '<-i(2) i(0)->', i(0) +}) +``` + + + +![f_node_example](https://user-images.githubusercontent.com/3051781/185458218-5aad8099-c808-4772-95ed-febac0b5c5ff.gif) + + + +`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 [Snippets-Data](#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 strings, where 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 Reference](#node-reference)s. + 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 [Node](#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 + 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: + + ```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) + + + +- `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) + + + +- [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` (See [Extras-Lambda](#lambda)) + +# 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) + - `key_indexer`: the key of the node, if it is present. This will come in + handy if the node that is being referred to is not in the same + snippet/snippetNode as the one the node reference is passed to. + Also, it is the proper way to refer to a non-interactive node (a + functionNode, for example) + - `absolute_indexer`: the absolute position of the node. Just like + `key_indexer`, it allows addressing non-sibling nodes, but is a bit more + awkward to handle since a path from root to node has to be determined, + whereas `key_indexer` just needs the key to match. + Due to this, `key_indexer` should be generally preferred. + (More information in [Absolute Indexer](#absolute-indexer)). + - `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 + +ChoiceNodes allow choosing between multiple nodes. + +```lua + s("trig", c(1, { + t("Ugh boring, a text node"), + i(nil, "At least I can edit something now..."), + f(function(args) return "Still only counts as text!!" end, {}) + })) +``` + + + +![ChoiceNode](https://user-images.githubusercontent.com/25300418/184359378-09d83ec0-2580-4a0e-8f75-61bd168903ba.gif) + + + +```lua render_region +fn_doc({ + typename = "LuaSnip.ChoiceNode", + funcname = "C", + display_fname = "c", + pre_list_linebreak = true, + type_expand = { + ["LuaSnip.Opts.ChoiceNode?"] = {explain_type = "LuaSnip.Opts.ChoiceNode"} + }}) +``` + +**Examples:** +```lua +c(1, { + t"some text", -- textNodes are just stopped at. + i(nil, "some text"), -- likewise. + sn(nil, {t"some text"}) -- this will not work! + sn(nil, {i(1), t"some text"}) -- this will. +}) +``` + +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. +vim.api.nvim_set_keymap("i", "", "luasnip-next-choice", {}) +vim.api.nvim_set_keymap("s", "", "luasnip-next-choice", {}) +vim.api.nvim_set_keymap("i", "", "luasnip-prev-choice", {}) +vim.api.nvim_set_keymap("s", "", "luasnip-prev-choice", {}) +``` + +Apart from this, there is also a picker (see [select_choice](#select_choice) +where no cycling is necessary and any choice can be selected right away, via +`vim.ui.select`. + +# SnippetNode + +SnippetNodes directly insert their contents into the surrounding snippet. +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. + +```lua + s("trig", sn(1, { + t("basically just text "), + i(1, "And an insertNode.") + })) +``` + + + +![SnippetNode](https://user-images.githubusercontent.com/25300418/184359349-2127147e-2f57-4612-bdb5-4c9eafc93fad.gif) + + + +`sn(jump_index, nodes, node_opts)` + +- `jump_index`: `number`, the usual [Jump-Index](#jump-index). +- `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, the keys common to all nodes (documented in + [Node](#node)) 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 +nodes it's possible to override that behavior: + +```lua +s("isn", { + isn(1, { + t({"This is indented as deep as the trigger", + "and this is at the beginning of the next line"}) + }, "") +}) +``` + + + +![IndentSnippetNode](https://user-images.githubusercontent.com/25300418/184359281-acc62f04-f130-48b6-9ad8-c0775726507a.gif) + + + +(Note the empty string passed to `isn`). + +Indent is only applied after line breaks, so it's not possible to remove indent +on the line where the snippet was triggered using `ISN` (That is possible via +regex triggers where the entire line before the trigger is matched). + +Another nice use case for `ISN` is inserting text, e.g. `//` or some other comment +string before the nodes of the snippet: + +```lua +s("isn2", { + isn(1, t({"//This is", "A multiline", "comment"}), "$PARENT_INDENT//") +}) +``` + + + +![IndentSnippetNode2](https://user-images.githubusercontent.com/25300418/184359286-e29ba70e-4ccc-472a-accb-af849ca1a68d.gif) + + + +Here the `//` before `This is` is important, once again, because indent is only +applied after line breaks. + +To enable such usage, `$PARENT_INDENT` in the `indentstring` is replaced by the +parent's indent. + +`isn(jump_index, nodes, indentstring, node_opts)` + +All of these parameters except `indentstring` are exactly the same as in +[SnippetNode](#snippetnode). + +- `indentstring`: `string`, will be used to indent the nodes inside this + `snippetNode`. + All occurrences of `"$PARENT_INDENT"` are replaced with the actual indent of + the parent. + +# 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. + +`d(jump_index, function, node-references, opts)`: + +- `jump_index`: `number`, just like all jumpable nodes, its' position in the + jump-list ([Basics-Jump-Index](#jump-index)). +- `function`: `fn(args, parent, old_state, user_args) -> snippetNode` + This function is called when the argnodes' text changes. It should generate + and return (wrapped inside a `snippetNode`) nodes, which will be inserted at + the dynamicNode's 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. + 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 have more than one + argument. +- `node_references`: `node_reference[]|node_references|nil`, + [Node 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 `dynamicNode`s place. + (`dynamicNode` behaves exactly the same as `functionNode` in this regard). + +- `opts`: In addition to the common [Node](#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`. +```lua +s("trig", { + t"text: ", i(1), t{"", "copy: "}, + d(2, function(args) + -- the returned snippetNode doesn't need a position; it's inserted + -- "inside" the dynamicNode. + return sn(nil, { + -- jump-indices are local to each snippetNode, so restart at 1. + i(1, args[1]) + }) + end, + {1}) +}) +``` + + + +![DynamicNode](https://user-images.githubusercontent.com/25300418/184359404-c1081b6c-99e5-4eb1-85c7-7f2e875d7296.gif) + + + +This snippet makes use of `old_state` to count the number of updates. + +To store/restore values generated by the `dynamicNode` or entered into +`insert/choiceNode`, consider using the shortly-introduced `restoreNode` instead +of `old_state`. + +```lua +local function count(_, _, old_state) + old_state = old_state or { + updates = 0 + } + + old_state.updates = old_state.updates + 1 + + local snip = sn(nil, { + t(tostring(old_state.updates)) + }) + + snip.old_state = old_state + return snip +end + +ls.add_snippets("all", + s("trig", { + i(1, "change to update"), + d(2, count, {1}) + }) +) +``` + + + +![DynamicNode2](https://user-images.githubusercontent.com/25300418/184359408-8d6df582-2a9e-4e6c-8937-5424bf7f6ecb.gif) + + + +As with `functionNode`, `user_args` can be used to reuse similar `dynamicNode`- +functions. + +# RestoreNode + +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", { + c(1, { + sn(nil, { t("("), r(1, "user_text"), t(")") }), + sn(nil, { t("["), r(1, "user_text"), t("]") }), + sn(nil, { t("{"), r(1, "user_text"), t("}") }), + }), +}, { + stored = { + -- key passed to restoreNodes. + ["user_text"] = i(1, "default_text") + } +}) +``` + + + +![RestoreNode](https://user-images.githubusercontent.com/25300418/184359328-3715912a-8a32-43b6-91b7-6b012c9c3ccd.gif) + + + +Here the text entered into `user_text` is preserved upon changing choice. + +`r(jump_index, key, nodes, node_opts)`: + +- `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 +the same values as the `nodes`-parameter passed to `r`. +If no content is defined for a key, it defaults to the empty `insertNode`. + +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 especially useful for storing input across updates of a +`dynamicNode`. Consider this: + +```lua +local function simple_restore(args, _) + return sn(nil, {i(1, args[1]), i(2, "user_text")}) +end + +s("rest", { + i(1, "preset"), t{"",""}, + d(2, simple_restore, 1) +}) +``` + + + +![RestoreNode2](https://user-images.githubusercontent.com/25300418/184359337-0962dd5e-a18b-4df1-8c74-3d04a17998ab.gif) + + + +Every time the `i(1)` in the outer snippet is changed, the text inside the +`dynamicNode` is reset to `"user_text"`. This can be prevented by using a +`restoreNode`: + +```lua +local function simple_restore(args, _) + return sn(nil, {i(1, args[1]), r(2, "dyn", i(nil, "user_text"))}) +end + +s("rest", { + i(1, "preset"), t{"",""}, + d(2, simple_restore, 1) +}) +``` +Now the entered text is stored. + +`restoreNode`s indent is not influenced by `indentSnippetNodes` right now. If +that really bothers you feel free to open an issue. + + + +![RestoreNode3](https://user-images.githubusercontent.com/25300418/184359340-35c24160-10b0-4f72-849e-1015f59ed599.gif) + + + +# Key Indexer + +A very flexible way of referencing nodes ([Node Reference](#node-reference)). +While the straightforward way of addressing nodes via their +[Jump-Index](#jump-index) suffices in most cases, a `dynamic/functionNode` can +only depend on nodes in the same snippet(Node), its siblings (since the index is +interpreted as relative to their parent). Accessing a node with a different +parent is thus not possible. Secondly, and less relevant, only nodes that +actually have a jump-index can be referred to (a `functionNode`, for example, +cannot be depended on). +Both of these restrictions are lifted with `key_indexer`: +It allows addressing nodes by their key, which can be set when the node is +constructed, and is wholly independent of the nodes' position in the snippet, +thus enabling descriptive labeling. + +The following snippets demonstrate the issue and the solution by using +`key_indexer`: + +First, the addressed problem of referring to nodes outside the `functionNode`s +parent: +```lua +s("trig", { + i(1), c(2, { + sn(nil, { + t"cannot access the argnode :(", + f(function(args) + return args[1] + end, {???}) -- can't refer to i(1), since it isn't a sibling of `f`. + }), + t"sample_text" + }) +}) +``` + +And the solution: first give the node we want to refer to a key, and then pass +the same to the `functionNode`. +```lua +s("trig", { + i(1, "", {key = "i1-key"}), c(2, { + sn(nil, { i(1), + t"can access the argnode :)", + f(function(args) + return args[1] + end, k("i1-key") ) + }), + t"sample_text" + }) +}) +``` + + + +![Key/AbsoluteIndexer](https://user-images.githubusercontent.com/25300418/184359369-3bbd2b30-33d1-4a5d-9474-19367867feff.gif) + + + + +# Absolute Indexer + +`absolute_indexer` allows accessing nodes by their unique jump-index path from +the snippet-root. This makes it almost as powerful as [Key Indexer](#key-indexer), +but again removes the possibility of referring to non-jumpable nodes and makes +it all a bit more error-prone since the jump-index paths are hard to follow, and +(unfortunately) have to be a bit verbose (see the long example of +`absolute_indexer`-positions below). Consider just using [Key Indexer](#key-indexer) +instead. + +(The solution-snippet from [Key Indexer](#key-indexer), but using `ai` instead.) +```lua +s("trig", { + i(1), c(2, { + sn(nil, { i(1), + t"can access the argnode :)", + f(function(args) + return args[1] + end, ai(1) ) + }), + t"sample_text" + }) +}) +``` + +There are some quirks in addressing nodes: +```lua +s("trig", { + i(2), -- ai[2]: indices based on jump-index, not position. + sn(1, { -- ai[1] + i(1), -- ai[1][1] + t"lel", -- not addressable. + i(2) -- ai[1][2] + }), + c(3, { -- ai[3] + i(nil), -- ai[3][1] + t"lel", -- ai[3][2]: choices are always addressable. + }), + d(4, function() -- ai[4] + return sn(nil, { -- ai[4][0] + i(1), -- ai[4][0][1] + }) + end, {}), + r(5, "restore_key", -- ai[5] + i(1) -- ai[5][0][1]: restoreNodes always store snippetNodes. + ), + r(6, "restore_key_2", -- ai[6] + sn(nil, { -- ai[6][0] + i(1) -- ai[6][0][1] + }) + ) +}) +``` + +Note specifically that the index of a dynamicNode differs from that of the +generated snippetNode, and that restoreNodes (internally) always store a +snippetNode, so even if the restoreNode only contains one node, that node has +to be accessed as `ai[restoreNodeIndx][0][1]`. + +`absolute_indexer`s' can be constructed in different ways: + +* `ai[1][2][3]` +* `ai(1, 2, 3)` +* `ai{1, 2, 3}` + +are all the same node. + +# MultiSnippet + +There are situations where it might be comfortable to access a snippet in +different ways. For example, one might want to enable auto-triggering in regions +where the snippets usage is common, while leaving it manual-only in others. +This is where `ms` should be used: A single snippet can be associated with multiple +`context`s (the `context`-table determines the conditions under which a snippet +may be triggered). +This has the advantage (compared with just registering copies) that all +`context`s are backed by a single snippet, and not multiple, and it's (at least +should be :D) more comfortable to use. + +`ms(contexts, nodes, opts) -> addable`: + +- `contexts`: table containing list of `contexts`, and some keywords. + `context` are described in [Snippets](#snippets), here they may also be tables + or strings. + So far, there is only one valid keyword: + - `common`: Accepts yet another context. + The options in `common` are applied to (but don't override) the other + contexts specified in `contexts`. +- `nodes`: List of nodes, exactly like in [Snippets](#snippets). +- `opts`: Table, options for this function: + - `common_opts`: The snippet-options (see also [Snippets](#snippets)) applied + to the snippet generated from `nodes`. + +The returned object is an `addable`, something which can be passed to +`add_snippets`, or returned from the `lua-loader`. + +**Examples**: +```lua +ls.add_snippets("all", { + ms({"a", "b"}, {t"a or b"}) +}) +``` + +```lua +ls.add_snippets("all", { + ms({ + common = {snippetType = "autosnippet"}, + "a", + "b" + }, { + t"a or b (but autotriggered!!)" + }) +}) +``` + +```lua +ls.add_snippets("all", { + ms({ + common = {snippetType = "autosnippet"}, + {trig = "a", snippetType = "snippet"}, + "b", + {trig = "c", condition = function(line_to_cursor) + return line_to_cursor == "" + end} + }, { + t"a or b (but autotriggered!!)" + }) +}) +``` + +# Extras + +## 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 occurrences of "a" in the text of the + first argnode with "e", or + - `l._1 .. l._2` concatenates 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`, a [Node Reference](#node-reference), just like in function- and + dynamicNode. + +There are many examples for `lambda` in `Examples/snippets.lua` + +## Match +`match` can insert text based on a predicate (again, a shorthand for `functionNode`). + +`match(argnodes, condition, then, else)`: + +* `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 + `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")` + + ```lua + s("extras1", { + i(1), t { "", "" }, m(1, "^ABC$", "A") + }) + ``` + Inserts "A" if the node with jump-index `n` matches "ABC" exactly, nothing otherwise. + + + + ![extras1](https://user-images.githubusercontent.com/25300418/184359431-50f90599-3db0-4df0-a3a9-27013e663649.gif) + + + +* `match(n, lambda._1:match(lambda._1:reverse()), "PALINDROME")` + + ```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) + + +* `match(n, lambda._1:match("^" .. lambda._2 .. "$"), lambda._1:gsub("a", "e"))` + + ```lua + 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 occurrences 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) }) +``` + + + +![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!") }) +``` + + + +![extras6](https://user-images.githubusercontent.com/25300418/184359213-79a71d1e-079c-454d-a092-c231ac5a98f9.gif) + + + +## Dynamic Lambda + +Pretty much the same as lambda, but it 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-index. + +The remaining arguments carry over from lambda. + +```lua +s("extras7", { i(1), t { "", "" }, extras.dynamic_lambda(2, l._1 .. l._1, 1) }) +``` + + + +![extras7](https://user-images.githubusercontent.com/25300418/184359221-1f090895-bc59-44b0-a984-703bf8d278a3.gif) + + + +## `fmt` + +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: + +```lua +ls.add_snippets("all", { + -- important! fmt does not return a snippet, it returns a table of nodes. + s("example1", fmt("just an {iNode1}", { + iNode1 = i(1, "example") + })), + s("example2", fmt([[ + if {} then + {} + end + ]], { + -- i(1) is at nodes[1], i(2) at nodes[2]. + i(1, "not now"), i(2, "when") + })), + s("example3", fmt([[ + if <> then + <> + end + ]], { + -- i(1) is at nodes[1], i(2) at nodes[2]. + i(1, "not now"), i(2, "when") + }, { + delimiters = "<>" + })), + s("example4", fmt([[ + repeat {a} with the same key {a} + ]], { + a = i(1, "this will be repeat") + }, { + repeat_duplicates = true + })) + s("example5", fmt([[ + line1: no indent + + line3: 2 space -> 1 indent ('\t') + line4: 4 space -> 2 indent ('\t\t') + ]], {}, { + indent_string = " " + })) + -- NOTE: [[\t]] means '\\t' + s("example6", fmt([[ + line1: no indent + + \tline3: '\\t' -> 1 indent ('\t') + \t\tline4: '\\t\\t' -> 2 indent ('\t\t') + ]], {}, { + indent_string = [[\t]] + })) +}) +``` + + + +![`fmt`](https://user-images.githubusercontent.com/25300418/184359228-d30df745-0fe8-49df-b28d-662e7eb050ec.gif) + + + +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. Occurrences of `{}` ( `{}` are customizable; more + on that later) are replaced with `content[]` (which should be a + node), while surrounding text becomes `textNode`s. + To escape a delimiter, repeat it (`"{{"`). + If no key is given (`{}`) are numbered automatically: + `"{} ? {} : {}"` becomes `"{1} ? {2} : {3}"`, while + `"{} ? {3} : {}"` becomes `"{1} ? {3} : {4}"` (the count restarts at each + numbered placeholder). + If a key appears more than once in `format`, the node in + `content[]` is inserted for the first, and copies of it for + subsequent occurrences. +* `nodes`: just a table of nodes. +* `opts`: optional arguments: + * `delimiters`: string, two characters. Change `{}` to some other pair, e.g. + `"<>"`. + * `strict`: Warn about unused nodes (default true). + * `trim_empty`: remove empty (`"%s*"`) first and last line in `format`. Useful + when passing multiline strings via `[[]]` (default true). + * `dedent`: remove indent common to all lines in `format`. Again, makes + passing multiline-strings a bit nicer (default true). + * `indent_string`: convert `indent_string` at beginning of each line to unit + indent ('\t'). This is applied after `dedent`. Useful when using + multiline string in `fmt`. (default empty string, disabled) + * `repeat_duplicates`: repeat nodes when a key is reused instead of copying + the node if it has a jump-index, refer to [Basics-Jump-Index](#jump-index) to + know which nodes have a jump-index (default false). + +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 functions 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. +- `trigger_not_preceded_by(pattern)`: only expand if the character before the + trigger does not match `pattern`. This is a generalization of `wordTrig`, + which can be implemented as `trigger_not_preceded_by("[%w_]")`, and is + available as `word_trig_condition`. + +**`show`**: + +- `line_end`: only expand at the end of the line. +- `has_selected_text`: only expand if there's selected text stored after pressing + `store_selection_keys`. + +Additionally, `expand` contains 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 -> not c1` +- `c1 * c2 -> c1 and c2` +- `c1 + c2 -> c1 or c2` +- `c1 - c2 -> c1 and not c2`: This is similar to set differences: + `A \ B = {a in A | a not in B}`. This makes `-(a + b) = -a - b` an identity + representing de Morgan's law: `not (a or b) = not a and not b`. However, + since boolean algebra lacks an additive inverse, `a + (-b) = a - b` does not + hold. Thus, this is NOT the same as `c1 + (-c2)`. +- `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 + +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 use case: 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: + +```vim +" 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") +``` + +Obviously, `` is arbitrary 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() +inoremap a lua require('luasnip.extras.otf').on_the_fly("a") + + +" For register b +vnoremap a "bc:lua require('luasnip.extras.otf').on_the_fly() +inoremap b lua require('luasnip.extras.otf').on_the_fly("b") +``` + + + +![otf](https://user-images.githubusercontent.com/25300418/184359312-8e368393-7be3-4dc4-ae08-1ff1bf17b309.gif) + + + +## select_choice + +It's possible to leverage `vim.ui.select` for selecting a choice directly, +without cycling through the available choices. +All that is needed for this is calling +`require("luasnip.extras.select_choice")`, most likely via some keybinding, 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`. + + + +![select_choice](https://user-images.githubusercontent.com/25300418/184359342-c8d79d50-103c-44b7-805f-fe75294e62df.gif) + + + +## Filetype-Functions + +Contains some utility functions that can be passed to the `ft_func` or +`load_ft_func`-settings. + +* `from_filetype`: the default for `ft_func`. Simply returns the filetype(s) of + the buffer. +* `from_cursor_pos`: uses tree-sitter to determine the filetype at the cursor. + With that, it's possible to expand snippets in injected regions, as long as + the tree-sitter parser supports them. + If this is used in conjunction with `lazy_load`, extra care must be taken that + all the filetypes that can be expanded in a given buffer are also returned by + `load_ft_func` (otherwise their snippets may not be loaded). + This can easily be achieved with `extend_load_ft`. +* `extend_load_ft`: `fn(extend_ft:map) -> fn` + A simple solution to the problem described above is loading more filetypes + than just that of the target buffer when `lazy_load`ing. This can be done + ergonomically via `extend_load_ft`: calling it with a table where the keys are + filetypes, and the values are the filetypes that should be loaded additionally + returns a function that can be passed to `load_ft_func` and takes care of + extending the filetypes properly. + + ```lua + ls.setup({ + load_ft_func = + -- Also load both lua and json when a markdown-file is opened, + -- javascript for html. + -- Other filetypes just load themselves. + require("luasnip.extras.filetype_functions").extend_load_ft({ + markdown = {"lua", "json"}, + html = {"javascript"} + }) + }) + ``` + +## 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 snippet's 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 preceding 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 + preceding 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. + +```lua +{ + callbacks = { + [-1] = { + [events.pre_expand] = function(snippet, event_args) + -- function body to match before the dot + -- goes here + end + } + } +} +``` + +## Treesitter-Postfix-Snippet + +Instead of triggering a postfix-snippet when some pattern matches in front of +the trigger, it might be useful to match if some specific tree-sitter nodes +surround/are in front of the trigger. +While this functionality can also be implemented by a custom +`resolveExpandParams`, this helper simplifies the common cases. + +This matching of tree-sitter nodes can be done either + +* by providing a query and the name of the capture that should be in front of + the trigger (in most cases, the complete match, but requiring specific nodes + before/after the matched node may be useful as well), or +* by providing a function that manually walks the node-tree, and returns the + node in front of the trigger on success (for increased flexibility). + +A simple example, which surrounds the previous node's text preceding the `.mv` +with `std::move()` in C++ files, looks like: + +```lua +local treesitter_postfix = require("luasnip.extras.treesitter_postfix").treesitter_postfix + +treesitter_postfix({ + trig = ".mv", + matchTSNode = { + query = [[ + [ + (call_expression) + (identifier) + (template_function) + (subscript_expression) + (field_expression) + (user_defined_literal) + ] @prefix + ]] + query_lang = "cpp" + }, +},{ + f(function(_, parent) + local node_content = table.concat(parent.snippet.env.LS_TSMATCH, '\n') + local replaced_content = ("std::move(%s)"):format(node_content) + return vim.split(ret_str, "\n", { trimempty = false }) + end) +}) +``` + +`LS_TSMATCH` is the tree-sitter-postfix equivalent to `POSTFIX_MATCH`, and is +populated with the match (in this case the text of a tree-sitter-node) in front +of the trigger. + + + +![tree-sitter-postfix](https://user-images.githubusercontent.com/6359934/260666471-a60589aa-4454-4a9c-a103-87775c2cdf04.gif) + + + +The arguments to `treesitter_postfix` are identical to the arguments to `s` but +with a few extra options. + +The first argument has to be a table, which defines at least `trig` and +`matchTSNode`. All keys from the regular `s` may be set here (except for +`wordTrig`, which will be ignored), and additionally the following: + +- `reparseBuffer`, `string?`: Sometimes the trigger may interfere with + tree-sitter recognizing queries correctly. With this option, the trigger may + either be removed from the live-buffer (`"live"`), from a copy of the buffer + (`"copy"`), or not at all (`nil`). +- `matchTSNode`: How to determine whether there is a matching node in front of + the cursor. There are two options: + * `fun(parser: LuaSnip.extra.TSParser, pos: { [1]: number, [2]: number }): LuaSnip.extra.NamedTSMatch?, TSNode?` + Manually determine whether there is a matching node that ends just before + `pos` (the beginning of the trigger). + Return `nil,nil` if there is no match, otherwise first return a table + mapping names to nodes (the text, position and type of these will be + provided via `snip.env`), and second the node that is the matched node. + * `LuaSnip.extra.MatchTSNodeOpts`, which represents a query and provides all + captures of the matched pattern in `NamedTSMatch`. It contains the following + options: + * `query`, `string`: The query, in textual form. + * `query_name`, `string`: The name of the runtime-query to be used (passed + to `query.get()`), defaults to `"luasnip"` (so one could create a + file which only contains queries used by luasnip, like + `$CONFDIR/queries//luasnip.scm`, which might make sense to define + general concepts independent of a single snippet). + `query` and `query_name` are mutually exclusive, only one of both shall be + defined. + * `query_lang`, `string`: The language of the query. This is the only + required parameter to this function, since there's no sufficiently + straightforward way to determine the language of the query for us. + Consider using `extend_override` to define a `ts_postfix`-function that + automatically fills in the language for the filetype of the snippet-file. + * `match_captures`, `string|string[]`: The capture(s) to use for determining + the actual prefix (so the node that should be immediately in front of the + trigger). This defaults to just `"prefix"`. + * `select`, `string?|fun(): LuaSnip.extra.MatchSelector`: Since there may be + multiple matching captures in front of the cursor, there has to be some + way to select the node that will actually be used. + If this is a string, it has to be one of "any", "shortest", or "longest", + which mean that any, the shortest, or the longest match is used. + If it is a function, it must return a table with two fields, `record` and + `retrieve`. `record` is called with a `TSMatch` and a potential node for the + `TSMatch`, and may return `true` to abort the selection-procedure. + `retrieve` must return either a `TSMatch`-`TSNode`-tuple (which is used as the + match) or `nil`, to signify that there is no match. + `lua/luasnip/extras/_treesitter.lua` contains the table + `builtin_tsnode_selectors`, which contains the implementations for + any/shortest/longest, which can be used as examples for more complicated + custom-selectors. + +The text of the matched node can be accessed as `snip.env.LS_TSMATCH`. +The text of the nodes returned as `NamedTSMatch` can be accessed as +`snip.env.LS_TSCAPTURE_`, and their range and type as +`snip.env.LS_TSDATA..range/type` (where range is a +tuple of row-col-tuples, both 0-indexed). + +For a query like +```scm +(function_declaration + name: (identifier) @fname + parameters: (parameters) @params + body: (block) @body +) @prefix +``` + +matched against + +```lua +function add(a, b) + return a + b +end +``` + +`snip.env` would contain: + +* `LS_TSMATCH`: `{ "function add(a, b)", "\treturn a + b", "end" }` +* `LS_TSDATA`: + ```lua + { + body = { + range = { { 1, 1 }, { 1, 13 } }, + type = "block" + }, + fname = { + range = { { 0, 9 }, { 0, 12 } }, + type = "identifier" + }, + params = { + range = { { 0, 12 }, { 0, 18 } }, + type = "parameters" + }, + prefix = { + range = { { 0, 0 }, { 2, 3 } }, + type = "function_declaration" + } + } + ``` +* `LS_TSCAPTURE_FNAME`: `{ "add" }` +* `LS_TSCAPTURE_PARAMS`: `{ "(a, b)" }` +* `LS_TSCAPTURE_BODY`: `{ "return a + b" }` +* `LS_TSCAPTURE_PREFIX`: `{ "function add(a, b)", "\treturn a + b", "end" }` + +(note that all variables containing text of nodes are string-arrays, one entry +for each line) + +There is one important caveat when accessing `LS_TSDATA` in +function/dynamicNodes: It won't contain the values as specified here while +generating docstrings (in fact, it won't even be a table). +Since docstrings have to be generated without any runtime-information, we just +have to provide dummy-data in `env`, which will be some kind of string related +to the name of the environment variable. +Since the structure of `LS_TSDATA` obviously does not fit that model, we can't +really handle it in a nice way (at least yet). So, for now, best include a check +like `local static_evaluation = type(env.LS_TSDATA) == "string"`, and behave +accordingly if `static_evaluation` is true (for example, return some value +tailored for displaying it in a docstring). + +One more example, which actually uses a few captures: +```lua +ts_post({ + matchTSNode = { + query = [[ + (function_declaration + name: (identifier) @fname + parameters: (parameters) @params + body: (block) @body + ) @prefix + ]], + query_lang = "lua", + }, + trig = ".var" +}, fmt([[ + local {} = function{} + {} + end +]], { + l(l.LS_TSCAPTURE_FNAME), + l(l.LS_TSCAPTURE_PARAMS), + l(l.LS_TSCAPTURE_BODY), +})) +``` + + +![tree-sitter-postfix-2](https://github.com/L3MON4D3/LuaSnip/assets/41961280/37868d75-3240-4a47-bd80-5e8666778b71) + + + +The module `luasnip.extras.treesitter_postfix` contains a few functions that may +be useful for creating more efficient ts-postfix-snippets. +Nested in `builtin.tsnode_matcher` are: + +* `fun find_topmost_types(types: string[]): MatchTSNodeFunc`: Generates + a `LuaSnip.extra.MatchTSNodeFunc` which returns the last parent whose type + is in `types`. +* `fun find_first_types(types: string[]): MatchTSNodeFunc`: Similar to + `find_topmost_types`, only this one matches the first parent whose type is in + types. +* `find_nth_parent(n: number): MatchTSNodeFunc`: Simply matches the `n`-th + parent of the innermost node in front of the trigger. + +With `find_topmost_types`, the first example can be implemented more +efficiently (without needing a whole query): +```lua +local postfix_builtin = require("luasnip.extras.treesitter_postfix").builtin + +ls.add_snippets("all", { + ts_post({ + matchTSNode = postfix_builtin.tsnode_matcher.find_topmost_types({ + "call_expression", + "identifier", + "template_function", + "subscript_expression", + "field_expression", + "user_defined_literal" + }), + trig = ".mv" + }, { + l(l_str.format("std::move(%s)", l.LS_TSMATCH)) + }) +}, {key = "asdf"}) +``` + +## Snippet List + +```lua +local sl = require("luasnip.extras.snippet_list") +``` + +Makes an `open` function available to use to open currently available snippets +in a different buffer/window/tab. + +`sl.open(opts:table|nil)` + +* `opts`: optional arguments: + * `snip_info`: `snip_info(snippet) -> table representation of snippet` + * `printer`: `printer(snippets:table) -> any` + * `display`: `display(snippets:any)` + +Benefits include: syntax highlighting, searching, and customizability. + +Simple Example: +```lua +sl.open() +``` + + + +![default](https://user-images.githubusercontent.com/43832900/204893019-3a83d6bc-9e01-4750-bdf4-f6af967af807.png) + + + +Customization Examples: +```lua +-- making our own snip_info +local function snip_info(snippet) + return { name = snippet.name } +end + +-- using it +sl.open({snip_info = snip_info}) +``` + + + +![snip_info](https://user-images.githubusercontent.com/43832900/204893340-c7296a70-370a-4ad3-8997-23887f311b74.png) + + + +```lua +-- making our own printer +local function printer(snippets) + local res = "" + + for ft, snips in pairs(snippets) do + res = res .. ft .. "\n" + for _, snip in pairs(snips) do + res = res .. " " .. "Name: " .. snip.name .. "\n" + res = res .. " " .. "Desc: " .. snip.description[1] .. "\n" + res = res .. " " .. "Trigger: " .. snip.trigger .. "\n" + res = res .. " ----" .. "\n" + end + end + + return res +end + + +-- using it +sl.open({printer = printer}) +``` + + + +![printer](https://user-images.githubusercontent.com/43832900/204893406-4fc397e2-6d42-43f3-b52d-59ac448e764c.png) + + + +```lua +-- making our own display +local function display(printer_result) + -- right vertical split + vim.cmd("botright vnew") + + -- get buf and win handle + local buf = vim.api.nvim_get_current_buf() + local win = vim.api.nvim_get_current_win() + + -- setting window and buffer options + vim.api.nvim_win_set_option(win, "foldmethod", "manual") + vim.api.nvim_buf_set_option(buf, "filetype", "javascript") + + vim.api.nvim_buf_set_option(buf, "buftype", "nofile") + vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe") + vim.api.nvim_buf_set_option(buf, "buflisted", false) + + vim.api.nvim_buf_set_name(buf, "Custom Display buf " .. buf) + + -- dump snippets + local replacement = vim.split(printer_result) + vim.api.nvim_buf_set_lines(buf, 0, 0, false, replacement) +end + +-- using it +sl.open({display = display}) +``` + + + +![display](https://user-images.githubusercontent.com/43832900/205133425-a3fffa1c-bbec-4aea-927b-5faed14856d7.png) + + + +There is a **caveat** with implementing your own printer and/or display +function. The **default** behavior for the printer function is to return a +string representation of the snippets. The display function uses the results +from the printer function, therefore by **default** the display function is +expecting that result to be a string. + +However, this doesn't have to be the case. For example, you can implement your +own printer function that returns a table representation of the snippets +**but** you would have to then implement your own display function or some +other function in order to return the result as a string. + +An `options` table, which has some core functionality that can be used +to customize 'common' settings, is provided. + +* `sl.options`: options table: + * `display`: `display(opts:table|nil) -> function(printer_result:string)` + +You can see from the example above that making a custom display is a fairly +involved process. What if you just wanted to change a buffer option like the +name or just the filetype? This is where `sl.options.display` comes in. It +allows you to customize buffer and window options while keeping the default +behavior. + +`sl.options.display(opts:table|nil) -> function(printer_result:string)` + +* `opts`: optional arguments: + * `win_opts`: `table which has a {window_option = value} form` + * `buf_opts`: `table which has a {buffer_option = value} form` + * `get_name`: `get_name(buf) -> string` + +Let's recreate the custom display example above: +```lua +-- keeping the default display behavior but modifying window/buffer +local modified_default_display = sl.options.display({ + buf_opts = {filetype = "javascript"}, + win_opts = {foldmethod = "manual"}, + get_name = function(buf) return "Custom Display buf " .. buf end + }) + +-- using it +sl.open({display = modified_default_display}) +``` + + + +![modified display](https://user-images.githubusercontent.com/43832900/205133441-f4363bab-bdab-4c60-af9d-7285d59eca03.png) + + + +## Snippet Location + +This module can consume a snippets [source](#source), more specifically, jump to +the location referred by it. +This is primarily implemented for snippet which got their source from one of the +loaders, but might also work for snippets where the source was set manually. + +`require("luasnip.extras.snip_location")`: + +* `snip_location.jump_to_snippet(snip, opts)` + Jump to the definition of `snip`. + * `snip`: a snippet with attached source-data. + * `opts`: `nil|table`, optional arguments, valid keys are: + * `hl_duration_ms`: `number`, duration for which the definition should be highlighted, + in milliseconds. 0 disables the highlight. + * `edit_fn`: `function(file)`, this function will be called with the file + the snippet is located in, and is responsible for jumping to it. + We assume that after it has returned, the current buffer contains `file`. +* `snip_location.jump_to_active_snippet(opts)` + Jump to definition of active snippet. + * `opts`: `nil|table`, accepts the same keys as the `opts`-parameter of + `jump_to_snippet`. + + +# Extend Decorator + +Most of LuaSnip's functions have some arguments to control their behavior. +Examples include `s`, where `wordTrig`, `regTrig`, ... can be set in the first +argument to the function, or `fmt`, where the delimiter can be set in the third +argument. +This is all good and well, but if these functions are often used with +non-default settings, it can become cumbersome to always explicitly set them. + +This is where the `extend_decorator` comes in: +it can be used to create decorated functions which always extend the arguments +passed directly with other previously defined ones. + +An example: +```lua +local fmt = require("luasnip.extras.fmt").fmt + +fmt("{}", {i(1)}) -- -> list of nodes, containing just the i(1). + +-- when authoring snippets for some filetype where `{` and `}` are common, they +-- would always have to be escaped in the format-string. It might be preferable +-- to use other delimiters, like `<` and `>`. + +fmt("<>", {i(1)}, {delimiters = "<>"}) -- -> same as above. + +-- but it's quite annoying to always pass the `{delimiters = "<>"}`. + +-- with extend_decorator: +local fmt_angle = ls.extend_decorator.apply(fmt, {delimiters = "<>"}) +fmt_angle("<>", {i(1)}) -- -> same as above. + +-- the same also works with other functions provided by luasnip, for example all +-- node/snippet-constructors and `parse_snippet`. +``` + +`extend_decorator.apply(fn, ...)` requires that `fn` is previously registered +via `extend_decorator.register`. +(This is not limited to LuaSnip's functions; although, for usage outside of +LuaSnip, best copy the source file: `/lua/luasnip/util/extend_decorator.lua`). + +`register(fn, ...)`: + +* `fn`: the function. +* `...`: any number of tables. Each specifies how to extend an argument of `fn`. + The tables accept: + * `arg_indx`, `number` (required): the position of the parameter to override. + * `extend`, `fn(arg, extend_value) -> effective_arg` (optional): this function + is used to extend the arguments passed to the decorated function. + It defaults to a function which just extends the arguments table with the + extend table (accepts `nil`). + This extend behavior is adaptable to accommodate `s`, where the first + argument may be string or table. + +`apply(fn, ...) -> decorated_fn`: + +* `fn`: the function to decorate. +* `...`: The values to extend with. These should match the descriptions passed + in `register` (the argument first passed to `register` will be extended with + the first value passed here). + +One more example for registering a new function: +```lua +local function somefn(arg1, arg2, opts1, opts2) + -- not important +end + +-- note the reversed arg_indx!! +extend_decorator.register(somefn, {arg_indx=4}, {arg_indx=3}) +local extended = extend_decorator.apply(somefn, + {key = "opts2 is extended with this"}, + {key = "and opts1 with this"}) +extended(...) +``` + +# LSP-Snippets + +LuaSnip is capable of parsing LSP-style snippets using +`ls.parser.parse_snippet(context, snippet_string, opts)`: +```lua +ls.parser.parse_snippet({trig = "lsp"}, "$1 is ${2|hard,easy,challenging|}") +``` + + + +![LSP](https://user-images.githubusercontent.com/25300418/184359304-eb9c9eb4-bd38-4db9-b412-792391e9c21d.gif) + + + +`context` can be: + - `string|table`: treated like the first argument to `ls.s`, `parse_snippet` + returns a snippet. + - `number`: `parse_snippet` returns a snippetNode, with the position + `context`. + - `nil`: `parse_snippet` returns a flat table of nodes. This can be used + like `fmt`. + +Nested placeholders(`"${1:this is ${2:nested}}"`) will be turned into +choiceNodes with: + - the given snippet(`"this is ${1:nested}"`) and + - an empty insertNode + + + +![`lsp2`](https://user-images.githubusercontent.com/25300418/184359306-c669d3fa-7ae5-4c07-b11a-34ae8c4a17ac.gif) + + + +This behavior can be modified by changing `parser_nested_assembler` in +`ls.setup()`. + + +LuaSnip will also modify some snippets that it is incapable of representing +accurately: + + - if the `$0` is a placeholder with something other than just text inside + - if the `$0` is a choice + - if the `$0` is not an immediate child of the snippet (it could be inside a + placeholder: `"${1: $0 }"`) + +To remedy those incompatibilities, the invalid `$0` will be replaced with a +tabstop/placeholder/choice which will be visited just before the new `$0`. This +new `$0` will be inserted at the (textually) earliest valid position behind the +invalid `$0`. + +`opts` can contain the following keys: + - `trim_empty`: boolean, remove empty lines from the snippet. Default true. + - `dedent`: boolean, remove common indent from the snippet's lines. + Default true. + +Both `trim_empty` and `dedent` will be disabled for snippets parsed via +`ls.lsp_expand`: it might prevent correct expansion of snippets sent by LSP. + +## SnipMate Parser + +It is furthermore possible to parse SnipMate snippets (this includes support for +Vim script-evaluation!!) + +SnipMate snippets need to be parsed with a different function, +`ls.parser.parse_snipmate`: +```lua +ls.parser.parse_snipmate("year", "The year is `strftime('%Y')`") +``` + +`parse_snipmate` accepts the same arguments as `parse_snippet`, only the +snippet body is parsed differently. + +## Transformations + +To apply +[Variable/Placeholder-transformations](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variable-transforms), +LuaSnip needs to apply ECMAScript regular expressions. +This is implemented by relying on [jsregexp](https://github.com/kmarius/jsregexp). + +The easiest (but potentially error-prone) way to install it is by calling `make +install_jsregexp` in the repository root. + +This process can be automated by `packer.nvim`: +```lua +use { "L3MON4D3/LuaSnip", run = "make install_jsregexp" } +``` + +If this fails, first open an issue :P, and then try installing the +`jsregexp`-LuaRock. This is also possible via +`packer.nvim`, although actual usage may require a small workaround, see +[here](https://github.com/wbthomason/packer.nvim/issues/593) or +[here](https://github.com/wbthomason/packer.nvim/issues/358). + +Alternatively, `jsregexp` can be cloned locally, `make`d, and the resulting +`jsregexp.so` placed in some place where Neovim can find it (probably +`~/.config/nvim/lua/`). + +If `jsregexp` is not available, transformations are replaced by a simple copy. + +# 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 designed to be compatible with VSCode's behavior, which +can be counterintuitive when the snippet can be expanded at places other than +the point where selection started (or when doing transformations on selected text). +Besides those we also provide `LS_TRIGGER` which contains the trigger of the snippet, +and `LS_CAPTURE_n` (where n is a positive integer) that contains the n-th capture +when using a regex with capture groups as `trig` in the snippet definition. + +All variables can be used outside of LSP parsed snippets as their values are +stored in a snippets' `snip.env`-table: +```lua +s("selected_text", f(function(args, snip) + local res, env = {}, snip.env + table.insert(res, "Selected Text (current line is " .. env.TM_LINE_NUMBER .. "):") + for _, ele in ipairs(env.LS_SELECT_RAW) do table.insert(res, ele) end + return res +end, {})) +``` + +To use any `*SELECT*` variable, the `store_selection_keys` must be set via +`require("luasnip").config.setup({store_selection_keys=""})`. In this case, +hitting `` while in visual mode will populate the `*SELECT*`-vars for the next +snippet and then clear them. + + + +![variable](https://user-images.githubusercontent.com/25300418/184359360-17cc75cd-a8a0-4385-a6cb-8fa321c14558.gif) + + + +## Environment Namespaces + +You can also add your own variables by using the `ls.env_namespace(name, opts)` where: + +* `name`: `string` the names the namespace, can't contain the character "_" +* `opts` is a table containing (in every case `EnvVal` is the same as `string|list[string]`: + * `vars`: `(fn(name:string)->EnvVal) | map[string, EnvVal]` + Is a function that receives a string and returns a value for the var with that name + or a table from var name to a value + (in this case, if the value is a function it will be executed lazily once per snippet expansion). + * `init`: `fn(info: table)->map[string, EnvVal]` Returns + a table of variables that will set to the environment of the snippet on expansion, + use this for vars that have to be calculated in that moment or that depend on each other. + The `info` table argument contains `pos` (0-based position of the cursor on expansion), + the `trigger` of the snippet and the `captures` list. + * `eager`: `list[string]` names of variables that will be taken from `vars` and appended eagerly (like those in `init`) + * `multiline_vars`: `(fn(name:string)->bool)|map[string, bool]|bool|string[]` Says if certain vars are a table or just a string, + can be a function that get's the name of the var and returns true if the var is a key, + a list of vars that are tables or a boolean for the full namespace, it's false by default. Refer to + [issue#510](https://github.com/L3MON4D3/LuaSnip/issues/510#issuecomment-1209333698) for more information. + +The four fields of `opts` are optional but you need to provide either `init` or `vars`, and `eager` can't be without `vars`. +Also, you can't use namespaces that override default vars. + + +A simple example to make it more clear: + +```lua +local function random_lang() + return ({"LUA", "VIML", "VIML9"})[math.floor(math.random()/2 + 1.5)] +end + +ls.env_namespace("MY", {vars={ NAME="LuaSnip", LANG=random_lang }}) + +-- then you can use $MY_NAME and $MY_LANG in your snippets + +ls.env_namespace("SYS", {vars=os.getenv, eager={"HOME"}}) + +-- then you can use $SYS_HOME which was eagerly initialized but also $SYS_USER (or any other system environment var) in your snippets + +lsp.env_namespace("POS", {init=function(info) return {VAL=vim.inspect(info.pos)} end}) + +-- then you can use $POS_VAL in your snippets + +s("custom_env", d(1, function(args, parent) + local env = parent.snippet.env + return sn(nil, t { + "NAME: " .. env.MY_NAME, + "LANG: " .. env.MY_LANG, + "HOME: " .. env.SYS_HOME, + "USER: " .. env.SYS_USER, + "VAL: " .. env.POS_VAL + }) +end, {})) +``` + + + +![custom_variable](https://user-images.githubusercontent.com/25300418/184359382-2b2a357b-37a6-4cc4-9c8f-930f26457888.gif) + + + +## LSP-Variables + +All variables, even ones added via `env_namespace`, can be accessed in +LSP snippets as `$VAR_NAME`. + +The LSP specification states: + +---- + +With `$name` or `${name:default}` you can insert the value of a variable. +When a variable isn't set, its default or the empty string is inserted. +When a variable is unknown (that is, its name isn't defined) the name of the variable is inserted and it is transformed into a placeholder. + +---- + +The above necessitates a differentiation between `unknown` and `unset` variables: + +For LuaSnip, a variable `VARNAME` is `unknown` when `env.VARNAME` returns `nil` and `unset` +if it returns an empty string. + +Consider this when adding environment variables which might be used in LSP snippets. + +# 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 for +snippets written in Lua. + +All loaders (except the `vscode-standalone-loader`) share a similar interface: +`require("luasnip.loaders.from_{vscode,snipmate,lua}").{lazy_,}load(opts:table|nil)` + +where `opts` can contain the following keys: + +- `paths`: List of paths to load. Can be a table, or a single + comma-separated string. + The paths may begin with `~/` or `./` to indicate that the path is + relative to your `$HOME` or to the directory where your `$MYVIMRC` resides + (useful to add your snippets). + If not set, `runtimepath` is searched for + directories that contain snippets. This procedure differs slightly for + each loader: + - `lua`: the snippet-library has to be in a directory named + `"luasnippets"`. + - `snipmate`: similar to Lua, but the directory has to be `"snippets"`. + - `vscode`: any directory in `runtimepath` that contains a + `package.json` contributing snippets. +- `lazy_paths`: behaves essentially like `paths`, with two exceptions: if it is + `nil`, it does not default to `runtimepath`, and the paths listed here do not + need to exist, and will be loaded on creation. + LuaSnip will do its best to determine the path that this should resolve to, + but since the resolving we do is not very sophisticated it may produce + incorrect paths. Definitely check the log if snippets are not loaded as + expected. +- `exclude`: List of languages to exclude, empty by default. +- `include`: List of languages to include, includes everything by default. +- `{override,default}_priority`: These keys are passed straight to the + `add_snippets`-calls (documented in [API](#api)) and can therefore change the + priority of snippets loaded from some collection (or, in combination with + `{in,ex}clude`, only some of its snippets). +- `fs_event_providers`: `table?`, specifies which mechanisms + should be used to watch files for updates/creation. + If `autocmd` is set to `true`, a `BufWritePost`-hook watches files of this + collection, if `libuv` is set, the `file-watcher-api` exposed by `libuv` is used + to watch for updates. + Use `libuv` if you want snippets to update from other Neovim-instances, and + `autocmd` if the collection resides on a file system where the `libuv`-watchers + may not work correctly. Or, of course, just enable both :D + By default, only `autocmd` is enabled. + +While `load` will immediately load the snippets, `lazy_load` will defer loading until +the snippets are actually needed (whenever a new buffer is created, or the +filetype is changed LuaSnip actually loads `lazy_load`ed snippets for the +filetypes associated with this buffer. This association can be changed by +customizing `load_ft_func` in `setup`: the option takes a function that, passed +a `bufnr`, returns the filetypes that should be loaded (`fn(bufnr) -> filetypes +(string[])`)). + +All of the loaders support reloading, so simply editing any file contributing +snippets will reload its snippets (according to `fs_event_providers` in the +instance where the file was edited, or in other instances as well). + +As an alternative (or addition) to automatic reloading, LuaSnip can also process +manual updates to files: Call `require("luasnip.loaders").reload_file(path)` to +reload the file at `path`. +This may be useful when the collection is controlled by some other plugin, or +when enabling the other reload-mechanisms is for some reason undesirable +(performance? minimalism?). + +For easy editing of these files, LuaSnip provides a `vim.ui.select`-based dialog +([Loaders-edit_snippets](#edit_snippets)) where first the filetype, and then the +file can be selected. + +## Snippet-specific filetypes +Some loaders (`vscode`,`lua`) support giving snippets generated in some file their +own filetype (`vscode` via `scope`, `lua` via the underlying `filetype`-option for +snippets). These snippet-specific filetypes are not considered when determining +which files to `lazy_load` for some filetype, this is exclusively determined by +the `language` associated with a file in `vscodes`' `package.json`, and the +file/directory-name in `lua`. + + * This can be resolved relatively easily in `vscode`, where the `language` + advertised in `package.json` can just be a superset of the `scope`s in the file. + * Another simplistic solution is to set the language to `all` (in `lua`, it might + make sense to create a directory `luasnippets/all/*.lua` to group these files + together). + * Another approach is to modify `load_ft_func` to load a custom filetype if the + snippets should be activated, and store the snippets in a file for that + filetype. This can be used to group snippets by e.g. framework, and load them + once a file belonging to such a framework is edited. + +**Example**: +`react.lua` +```lua +return { + s({filetype = "css", trig = ...}, ...), + s({filetype = "html", trig = ...}, ...), + s({filetype = "js", trig = ...}, ...), +} +``` + +`luasnip_config.lua` +```lua +load_ft_func = function(bufnr) + if "" then + -- will load `react.lua` for this buffer + return {"react"} + else + return require("luasnip.extras.filetype_functions").from_filetype_load + end +end +``` + +See the [Troubleshooting-Adding Snippets-Loaders](#troubleshooting-adding-snippets-loaders) +section if one is having issues adding snippets via loaders. + +## VS-Code + +As a reference on the structure of these snippet libraries, see +[friendly-snippets](https://github.com/rafamadriz/friendly-snippets). + +We support a small extension: snippets can contain LuaSnip-specific options in +the `luasnip`-table: +```json +"example1": { + "prefix": "options", + "body": [ + "whoa! :O" + ], + "luasnip": { + "priority": 2000, + "autotrigger": true, + "wordTrig": false + } +} +``` + +Files with the extension `jsonc` will be parsed as `jsonc`, +[`json` with comments](https://code.visualstudio.com/docs/languages/json#_json-with-comments), +while `*.json` are parsed with a regular `json` parser, where comments are +disallowed. (the `json` parser is a bit faster, so don't default to `jsonc` if +it's not necessary). + +**Example**: + +`~/.config/nvim/my_snippets/package.json`: +```json +{ + "name": "example-snippets", + "contributes": { + "snippets": [ + { + "language": [ + "all" + ], + "path": "./snippets/all.json" + }, + { + "language": [ + "lua" + ], + "path": "./lua.json" + } + ] + } +} +``` +`~/.config/nvim/my_snippets/snippets/all.json`: +```json +{ + "snip1": { + "prefix": "all1", + "body": [ + "expands? jumps? $1 $2 !" + ] + }, + "snip2": { + "prefix": "all2", + "body": [ + "multi $1", + "line $2", + "snippet$0" + ] + } +} +``` + +`~/.config/nvim/my_snippets/lua.json`: +```json +{ + "snip1": { + "prefix": "lua", + "body": [ + "lualualua" + ] + } +} +``` +This collection can be loaded with any of +```lua +-- don't pass any arguments, luasnip will find the collection because it is +-- (probably) in rtp. +require("luasnip.loaders.from_vscode").lazy_load() +-- specify the full path... +require("luasnip.loaders.from_vscode").lazy_load({paths = "~/.config/nvim/my_snippets"}) +-- or relative to the directory of $MYVIMRC +require("luasnip.loaders.from_vscode").load({paths = "./my_snippets"}) +``` + +### Standalone +Beside snippet-libraries provided by packages, `vscode` also supports another +format which can be used for project-local snippets, or user-defined snippets, +`.code-snippets`. + +The layout of these files is almost identical to that of the package-provided +snippets, but there is one additional field supported in the +snippet-definitions, `scope`, with which the filetype of the snippet can be set. +If `scope` is not set, the snippet will be added to the global filetype (`all`). + +`require("luasnip.loaders.from_vscode").load_standalone(opts)` + +- `opts`: `table`, can contain the following keys: + - `path`: `string`, Path to the `*.code-snippets`-file that should be loaded. + Just like the paths in `load`, this one can begin with a `"~/"` to be + relative to `$HOME`, and a `"./"` to be relative to the + Neovim config directory. + - `{override,default}_priority`: These keys are passed straight to the + `add_snippets`-calls (documented in [API](#api)) and can be used to change + the priority of the loaded snippets. + - `lazy`: `boolean`, if it is set, the file does not have to exist when + `load_standalone` is called, and it will be loaded on creation. + `false` by default. + +**Example**: +`a.code-snippets`: +```jsonc +{ + // a comment, since `.code-snippets` may contain jsonc. + "c/cpp-snippet": { + "prefix": [ + "trigger1", + "trigger2" + ], + "body": [ + "this is $1", + "my snippet $2" + ], + "description": "A description of the snippet.", + "scope": "c,cpp" + }, + "python-snippet": { + "prefix": "trig", + "body": [ + "this is $1", + "a different snippet $2" + ], + "description": "Another snippet-description.", + "scope": "python" + }, + "global snippet": { + "prefix": "trigg", + "body": [ + "this is $1", + "the last snippet $2" + ], + "description": "One last snippet-description.", + } +} +``` + +This file can be loaded by calling +```lua +require("luasnip.loaders.from_vscode").load_standalone({path = "a.code-snippets"}) +``` + +## SNIPMATE + +Luasnip does not support the full SnipMate format: Only `./{ft}.snippets` and +`./{ft}/*.snippets` will be loaded. See +[honza/vim-snippets](https://github.com/honza/vim-snippets) for lots of +examples. + +Like VSCode, the SnipMate format is also extended to make use of some of +LuaSnip's more advanced capabilities: +```snippets +priority 2000 +autosnippet options + whoa :O +``` + +**Example**: + +`~/.config/nvim/snippets/c.snippets`: +```snippets +# this is a comment +snippet c c-snippet + c! +``` + +`~/.config/nvim/snippets/cpp.snippets`: +```snippets +extends c + +snippet cpp cpp-snippet + cpp! +``` + +This can, again, be loaded with any of +```lua +require("luasnip.loaders.from_snipmate").load() +-- specify the full path... +require("luasnip.loaders.from_snipmate").lazy_load({paths = "~/.config/nvim/snippets"}) +-- or relative to the directory of $MYVIMRC +require("luasnip.loaders.from_snipmate").lazy_load({paths = "./snippets"}) +``` + +Stuff to watch out for: + +* Using both `extends ` in `.snippets` and + `ls.filetype_extend("", {""})` leads to duplicate snippets. +* `${VISUAL}` will be replaced by `$TM_SELECTED_TEXT` to make the snippets + compatible with LuaSnip +* We do not implement `eval` using \` (backtick). This may be implemented in the + future. + +## Lua + +Instead of adding all snippets via `add_snippets`, it's possible to store them +in separate files and load all of those. +The file-structure here is exactly the supported SnipMate-structure, e.g. +`.lua` or `/*.lua` to add snippets for the filetype ``. + +There are two ways to add snippets: + +* the files may return two lists of snippets, the snippets in the first are all + added as regular snippets, while the snippets in the second will be added as + autosnippets (both are the defaults, if a snippet defines a different + `snippetType`, that will have preference) +* snippets can also be appended to the global (only for these files - they are not + visible anywhere else) tables `ls_file_snippets` and `ls_file_autosnippets`. + This can be combined with a custom `snip_env` to define and add snippets with + one function call: + ```lua + ls.setup({ + snip_env = { + s = function(...) + local snip = ls.s(...) + -- we can't just access the global `ls_file_snippets`, since it will be + -- resolved in the environment of the scope in which it was defined. + table.insert(getfenv(2).ls_file_snippets, snip) + end, + parse = function(...) + local snip = ls.parser.parse_snippet(...) + table.insert(getfenv(2).ls_file_snippets, snip) + end, + -- remaining definitions. + ... + }, + ... + }) + ``` + This is more flexible than the previous approach since the snippets don't have + to be collected; they just have to be defined using the above `s` and `parse`. + +As defining all of the snippet constructors (`s`, `c`, `t`, ...) in every file +is rather cumbersome, LuaSnip will bring some globals into scope for executing +these files. +By default, the names from [`luasnip.config.snip_env`][snip-env-src] will be used, but it's +possible to customize them by setting `snip_env` in `setup`. + +[snip-env-src]: https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/config.lua#L22-L48 + + +**Example**: + +`~/snippets/all.lua`: +```lua +return { + s("trig", t("loaded!!")) +} +``` +`~/snippets/c.lua`: +```lua +return { + s("ctrig", t("also loaded!!")) +}, { + s("autotrig", t("autotriggered, if enabled")) +} +``` + +Load via +```lua +require("luasnip.loaders.from_lua").load({paths = "~/snippets"}) +``` + + +### Snip-Env Diagnostics +One side-effect of the injected globals is that language servers, for example +`lua-language-server`, do not know about them, which means that snippet-files +may have many diagnostics about missing symbols. + +There are a few ways to fix this + +* Add all variables in `snip_env` to `Lua.diagnostic.globals`: + ```lua + -- wherever your lua-language-server lsp settings are defined: + settings = { + Lua = { + ... + diagnostics = { + globals = { + "vim", + "s", + "c", + "t", + ... + } + } + } + } + ``` + This will disable the warnings, but will do so in all files these lsp-settings + are used with. + Similarly, adding `---@diagnostic disable: undefined-global` to the + snippet-files is also possible, but this affects not only the variables in + `snip_env`, but all variables, like local variable names that may be + mistyped. +* A more complete, and only slightly more complicated solution is using + `lua-language-server`'s + [definition files](https://luals.github.io/wiki/definition-files/). + Add a file with the line `---@meta`, followed by the variables defined by the + `snip_env` to any directory listed in the `workspace.library`-settings for + `lua-langue-server` (one likely directory is `vim.fn.stdpath("config")/lua`, + check `:checkhealth lsp` in a lua file to be sure). + ```lua + ---@meta + + s = require("luasnip.nodes.snippet").S + sn = require("luasnip.nodes.snippet").SN + isn = require("luasnip.nodes.snippet").ISN + t = require("luasnip.nodes.textNode").T + i = require("luasnip.nodes.insertNode").I + f = require("luasnip.nodes.functionNode").F + c = require("luasnip.nodes.choiceNode").C + d = require("luasnip.nodes.dynamicNode").D + r = require("luasnip.nodes.restoreNode").R + events = require("luasnip.util.events") + k = require("luasnip.nodes.key_indexer").new_key + ai = require("luasnip.nodes.absolute_indexer") + extras = require("luasnip.extras") + l = require("luasnip.extras").lambda + rep = require("luasnip.extras").rep + p = require("luasnip.extras").partial + m = require("luasnip.extras").match + n = require("luasnip.extras").nonempty + dl = require("luasnip.extras").dynamic_lambda + fmt = require("luasnip.extras.fmt").fmt + fmta = require("luasnip.extras.fmt").fmta + conds = require("luasnip.extras.expand_conditions") + postfix = require("luasnip.extras.postfix").postfix + types = require("luasnip.util.types") + parse = require("luasnip.util.parser").parse_snippet + ms = require("luasnip.nodes.multiSnippet").new_multisnippet + ``` + + While that allows the `snip_env`-variables to resolve correctly in + snippet-files, it also resolves them in other lua files. This can be fixed by + putting the file in a directory that is _not_ in `workspace.library` (create, + for example, `vim.fn.stdpath("state")/luasnip-snip_env/`), and then adding its + path to `workspace.library` only for snippet files, for example by putting a + `.luarc.json` with the following content into all snippet-directories: + ```json + { + "workspace.library": ["/luasnip-snip_env"] + } + ``` + +### Reloading when editing `require`'d files +While the `lua-snippet-files` will be reloaded on edit, this does not +automatically happen if a file the snippet-file depends on (e.g. via `require`) +is changed. +Since this still may still be desirable, there are two functions exposed when a +file is loaded by the `lua-loader`: `ls_tracked_dofile` and +`ls_tracked_dopackage`. They perform like `dofile` and (almost like) `require`, +but both register the loaded file internally as a dependency of the +snippet-file, so it can be reloaded when the loaded file is edited. As stated, +`ls_tracked_dofile` behaves exactly like `dofile`, but does the dependency-work +as well. +`ls_tracked_dopackage` mimics `require` in that it does not take a path, but a +module-name like `"luasnip.loaders.from_lua"`, and then searches the +`runtimepath/lua`-directories, and `path` and `cpath` for the module. +Unlike `require`, the file will not be cached, since that would complicate the +reload-on-edit-behavior. + +## edit_snippets + +To easily edit snippets for the current session, the files loaded by any loader +can be quickly edited via +`require("luasnip.loaders").edit_snippet_files(opts:table|nil)` + +When called, it will open a `vim.ui.select`-dialog to select first a filetype, +and then (if there are multiple) the associated file to edit. + + + +![edit-select](https://user-images.githubusercontent.com/25300418/184359412-e6a1238c-d733-411c-b05d-8334ea993fbf.gif) + + + +`opts` contains four settings: + +* `ft_filter`: `fn(filetype:string) -> bool` + Optionally filter initially listed filetypes. + `true` -> filetype will be listed, `false` -> not listed. + Accepts all filetypes by default. +* `format`: `fn(file:string, source_name:string) -> string|nil` + `file` is simply the path to the file, `source_name` is one of `"lua"`, + `"snipmate"` or `"vscode"`. + If a string is returned, it is used as the title of the item, `nil` on the + other hand will filter out this item. + The default simply replaces some long strings (packer-path and config-path) + in `file` with shorter, symbolic names (`"$PLUGINS"`, `"$CONFIG"`), but + this can be extended to + * filter files from some specific source/path + * more aggressively shorten paths using symbolic names, e.g. + `"$FRIENDLY_SNIPPETS"`. + Example: hide the `*.lua` snippet files, and shorten the path with `$LuaSnip`: + ```lua + require "luasnip.loaders" .edit_snippet_files { + format = function(file, source_name) + if source_name == "lua" then return nil + else return file:gsub("/root/.config/nvim/luasnippets", "$LuaSnip") + end + end + } + ``` + + + ![edit-select-format](https://user-images.githubusercontent.com/25300418/184359420-3bc22d67-1f90-49d9-ac4e-3ea2524bcf0d.gif) + + + +* `edit`: `fn(file:string)` This function is supposed to open the file for + editing. The default is a simple `vim.cmd("edit " .. file)` (replace the + current buffer), but one could open the file in a split, a tab, or a floating + window, for example. +* `extend`: `fn(ft:string, ft_paths:string[]) -> (string,string)[]` + This function can be used to create additional choices for the file-selection. + + * `ft`: The filetype snippet-files are queried for. + * `ft_paths`: list of paths to the known snippet files. + + The function should return a list of `(string,string)`-tuples. The first of + each pair is the label that will appear in the selection-prompt, and the + second is the path that will be passed to the `edit()` function if that item + was selected. + + This can be used to create a new snippet file for the current filetype: +```lua +require("luasnip.loaders").edit_snippet_files { + extend = function(ft, paths) + if #paths == 0 then + return { + { "$CONFIG/" .. ft .. ".snippets", + string.format("%s/%s.snippets", , ft) } + } + end + + return {} + end +} +``` + +One comfortable way to call this function is registering it as a command: +```vim +command! LuaSnipEdit :lua require("luasnip.loaders").edit_snippet_files() +``` + +# 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 only parsing the snippet on expansion, not immediately after reading +it from some file. +`SnippetProxy` may also be used from Lua directly to get the same benefits: + +This will parse the snippet on startup: +```lua +ls.parser.parse_snippet("trig", "a snippet $1!") +``` + +while this will parse the snippet upon expansion: +```lua +local sp = require("luasnip.nodes.snippetProxy") +sp("trig", "a snippet $1") +``` + +`sp(context, body, opts) -> snippetProxy` + +- `context`: exactly the same as the first argument passed to `ls.s`. +- `body`: the snippet body. +- `opts`: accepts the same `opts` as `ls.s`, with some additions: + - `parse_fn`: the function for parsing the snippet. Defaults to + `ls.parser.parse_snippet` (the parser for LSP snippets), an alternative is + the parser for SnipMate snippets (`ls.parser.parse_snipmate`). + +# 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 +per node. +This means that they allow highlighting the text inside of nodes, or adding +virtual text to the line the node begins on. + +This is an example for the `node_ext_opts` used to set `ext_opts` of single nodes: +```lua +local ext_opts = { + -- these ext_opts are applied when the node is active (e.g. it has been + -- jumped into, and not out yet). + active = + -- this is the table actually passed to `nvim_buf_set_extmark`. + { + -- highlight the text inside the node red. + hl_group = "GruvboxRed" + }, + -- these ext_opts are applied when the node is not active, but + -- the snippet still is. + passive = { + -- add virtual text on the line of the node, behind all text. + virt_text = {{"virtual text!!", "GruvboxBlue"}} + }, + -- visited or unvisited are applied when a node was/was not jumped into. + visited = { + hl_group = "GruvboxBlue" + }, + unvisited = { + hl_group = "GruvboxGreen" + }, + -- and these are applied when both the node and the snippet are inactive. + snippet_passive = {} +} + +s("trig", { + i(1, "text1", { + node_ext_opts = ext_opts + }), + i(2, "text2", { + node_ext_opts = ext_opts + }) +}) +``` + + + +![ext_opt](https://user-images.githubusercontent.com/25300418/184359424-f3ae2e85-7863-437b-b360-0e3794c8fa1b.gif) + + + +In the above example, the text inside the insertNodes is highlighted in green if +they were not yet visited, in blue once they were, and red while they are. +The virtual text "virtual text!!" is visible as long as the snippet is active. + +To make defining `ext_opts` less verbose, more specific states inherit from less +specific ones: + +- `passive` inherits from `snippet_passive` +- `visited` and `unvisited` from `passive` +- `active` from `visited` + + + +```mermaid +flowchart TD + visited --> active + passive --> visited + passive --> unvisited + snippet_passive --> passive +``` + + + +To disable a key from a less specific state, it has to be explicitly set to its +default, e.g. to disable highlighting inherited from `passive` when the node is +`active`, `hl_group` should be set to `None`. + +--- + +As stated earlier, these `ext_opts` can also be applied globally or for an +entire snippet. For this, it's necessary to specify which kind of node a given +set of `ext_opts` should be applied to: + +```lua +local types = require("luasnip.util.types") + +ls.setup({ + ext_opts = { + [types.insertNode] = { + active = {...}, + visited = {...}, + passive = {...}, + snippet_passive = {...} + }, + [types.choiceNode] = { + active = {...}, + unvisited = {...} + }, + [types.snippet] = { + passive = {...} + } + } +}) +``` + +The above applies the given `ext_opts` to all nodes of these types, in all +snippets. + +```lua +local types = require("luasnip.util.types") + +s("trig", { i(1, "text1"), i(2, "text2") }, { + child_ext_opts = { + [types.insertNode] = { + passive = { + hl_group = "GruvboxAqua" + } + } + } +}) +``` +However, the `ext_opts` here are only applied to the `insertNodes` inside this +snippet. + +--- + +By default, the `ext_opts` actually used for a node are created by extending the +`node_ext_opts` with the `effective_child_ext_opts[node.type]` of the parent, +which are in turn the parent's `child_ext_opts` extended with the global +`ext_opts` (those set `ls.setup`). + +It's possible to prevent both of these merges by passing +`merge_node/child_ext_opts=false` to the snippet/node-opts: + +```lua +ls.setup({ + ext_opts = { + [types.insertNode] = { + active = {...} + } + } +}) + +s("trig", { + i(1, "text1", { + node_ext_opts = { + active = {...} + }, + merge_node_ext_opts = false + }), + i(2, "text2") +}, { + child_ext_opts = { + [types.insertNode] = { + passive = {...} + } + }, + merge_child_ext_opts = false +}) +``` + +--- + +The `hl_group` of the global `ext_opts` can also be set via standard +highlight groups: + +```lua +vim.cmd("hi link LuasnipInsertNodePassive GruvboxRed") +vim.cmd("hi link LuasnipSnippetPassive GruvboxBlue") + +-- needs to be called for resolving the effective ext_opts. +ls.setup({}) +``` +The names for the used highlight groups are +`"Luasnip{Passive,Active,SnippetPassive}"`, where `` can be any kind of +node in PascalCase (or "Snippet"). + +--- + +One problem that might arise when nested nodes are highlighted is that the +highlight of inner nodes should be visible, e.g. above that of nodes they are +nested inside. + +This can be controlled using the `priority`-key in `ext_opts`. In +`nvim_buf_set_extmark`, that value is an absolute value, but here it is relative +to some base-priority, which is increased for each nesting level of +snippet(Nodes)s. + +Both the initial base-priority and its' increase and can be controlled using +`ext_base_prio` and `ext_prio_increase`: +```lua +ls.setup({ + ext_opts = { + [types.insertNode] = { + active = { + hl_group = "GruvboxBlue", + -- the priorities should be \in [0, ext_prio_increase). + priority = 1 + } + }, + [types.choiceNode] = { + active = { + hl_group = "GruvboxRed" + -- priority defaults to 0 + } + } + } + ext_base_prio = 200, + ext_prio_increase = 2 +}) +``` +Here the highlight of an insertNode nested directly inside a `choiceNode` is +always visible on top of it. + + +# Docstrings + +Snippet docstrings can be queried using `snippet:get_docstring()`. The function +evaluates the snippet as if it was expanded regularly, which can be problematic +if e.g. a dynamicNode in the snippet relies on inputs other than +the argument nodes. +`snip.env` and `snip.captures` are populated with the names of the queried +variable and the index of the capture respectively +(`snip.env.TM_SELECTED_TEXT` -> `'$TM_SELECTED_TEXT'`, `snip.captures[1]` -> + `'$CAPTURES1'`). Although this leads to more expressive docstrings, it can + cause errors in functions that e.g. rely on a capture being a number: + +```lua +s({trig = "(%d)", regTrig = true}, { + f(function(args, snip) + return string.rep("repeatme ", tonumber(snip.captures[1])) + end, {}) +}) +``` + +This snippet works fine because `snippet.captures[1]` is always a number. +During docstring generation, however, `snippet.captures[1]` is `'$CAPTURES1'`, +which will cause an error in the functionNode. +Issues with `snippet.captures` can be prevented by specifying `docTrig` during +snippet-definition: + +```lua +s({trig = "(%d)", regTrig = true, docTrig = "3"}, { + f(function(args, snip) + return string.rep("repeatme ", tonumber(snip.captures[1])) + end, {}) +}) +``` + +`snippet.captures` and `snippet.trigger` will be populated as if actually +triggered with `3`. + +Other issues will have to be handled manually by checking the contents of e.g. +`snip.env` or predefining the docstring for the snippet: + +```lua +s({trig = "(%d)", regTrig = true, docstring = "repeatmerepeatmerepeatme"}, { + f(function(args, snip) + return string.rep("repeatme ", tonumber(snip.captures[1])) + end, {}) +}) +``` + +Refer to [#515](https://github.com/L3MON4D3/LuaSnip/pull/515) for a +better example to understand `docTrig` and `docstring`. + +# 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 +`ls.store_snippet_docstrings(snippets)` and its counterpart +`ls.load_snippet_docstrings(snippets)`, they may be serialized from or +deserialized into the snippets. +Both functions accept a table structured like this: `{ft1={snippets}, +ft2={snippets}}`. Such a table containing all snippets can be obtained via +`ls.get_snippets()`. +`load` should be called before any of the `loader`-functions as snippets loaded +from VSCode style packages already have their `docstring` set (`docstrings` +wouldn't be overwritten, but there'd be unnecessary calls). + +The cache is located at `stdpath("cache")/luasnip/docstrings.json` (probably +`~/.cache/nvim/luasnip/docstrings.json`). + +# Events + +Events can be used to react to some action inside snippets. These callbacks can +be defined per snippet (`callbacks`-key in snippet constructor), per-node by +passing them as `node_callbacks` in `node_opts`, or globally (autocommand). + +`callbacks`: `fn(node[, event_args]) -> event_res` +All callbacks receive the `node` associated with the event and event-specific +optional arguments, `event_args`. +`event_res` is only used in one event, `pre_expand`, where some properties of +the snippet can be changed. If multiple callbacks return `event_res`, we only +guarantee that one of them will be effective, not all of them. + +`autocommand`: +Luasnip uses `User`-events. Autocommands for these can be registered using +```vim +au User SomeUserEvent echom "SomeUserEvent was triggered" +``` + +or +```lua +vim.api.nvim_create_autocommand("User", { + pattern = "SomeUserEvent", + command = "echom SomeUserEvent was triggered" +}) +``` +The node and `event_args` can be accessed through `require("luasnip").session`: + +* `node`: `session.event_node` +* `event_args`: `session.event_args` + +**Events**: + +* `enter/leave`: Called when a node is entered/left (for example when jumping + around in a snippet). + `User-event`: `"Luasnip{Enter,Leave}"`, with `` in + PascalCase, e.g. `InsertNode` or `DynamicNode`. + `event_args`: none +* `change_choice`: When the active choice in a `choiceNode` is changed. + `User-event`: `"LuasnipChangeChoice"` + `event_args`: none +* `pre_expand`: Called before a snippet is expanded. Modifying text is allowed, + the expand-position will be adjusted so the snippet expands at the same + position relative to existing text. + `User-event`: `"LuasnipPreExpand"` + `event_args`: + * `expand_pos`: `{, }`, position at which the snippet will be + expanded. `` and `` are both 0-indexed. + * `expand_pos_mark_id`: `number`, the id of the extmark LuaSnip uses to track + `expand_pos`. This may be moved around freely. + `event_res`: + * `env_override`: `map string->(string[]|string)`, override or extend the + snippet's environment (`snip.env`). + +A pretty useless, beyond serving as an example here, application of these would +be printing e.g. the node's text after entering: + +```lua +vim.api.nvim_create_autocmd("User", { + pattern = "LuasnipInsertNodeEnter", + callback = function() + local node = require("luasnip").session.event_node + print(table.concat(node:get_text(), "\n")) + end +}) +``` + +or some information about expansions: + +```lua +vim.api.nvim_create_autocmd("User", { + pattern = "LuasnipPreExpand", + callback = function() + -- get event-parameters from `session`. + local snippet = require("luasnip").session.event_node + local expand_position = + require("luasnip").session.event_args.expand_pos + + print(string.format("expanding snippet %s at %s:%s", + table.concat(snippet:get_docstring(), "\n"), + expand_position[1], + expand_position[2] + )) + end +}) +``` + +# 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. + +# Logging +Luasnip uses logging to report unexpected program states, and information on +what's going on in general. If something does not work as expected, taking a +look at the log (and potentially increasing the log level) might give some good +hints towards what is going wrong. + +The log is stored in `/luasnip.log` +(`/luasnip.log` for Neovim versions where +`stdpath("log")` does not exist), and can be opened by calling `ls.log.open()`. You can get the log path through `ls.log.log_location()`. +The log level (granularity of reported events) can be adjusted by calling +`ls.log.set_loglevel("error"|"warn"|"info"|"debug")`. `"debug"` has the highest +granularity, `"error"` the lowest, the default is `"warn"`. +You can also adjust the datetime formatting through the `ls.log.time_fmt` variable. By default, it uses the `'%X'` formatting, which results in the full time (hour, minutes and seconds) being shown. + +Once this log grows too large (10MiB, currently not adjustable), it will be +renamed to `luasnip.log.old`, and a new, empty log created in its place. If +there already exists a `luasnip.log.old`, it will be deleted. + +`ls.log.ping()` can be used to verify the log is working correctly: it will +print a short message to the log. + +# Source +It is possible to attach, to a snippet, information about its source. This can +be done either by the various loaders (if it is enabled in `ls.setup` +([Config-Options](#config-options), `loaders_store_source`)), or manually. The +attached data can be used by [Extras-Snippet-Location](#snippet-location) to +jump to the definition of a snippet. + +It is also possible to get/set the source of a snippet via API: + +`ls.snippet_source`: + +* `get(snippet) -> source_data`: + Retrieve the source-data of `snippet`. `source_data` always contains the key + `file`, the file in which the snippet was defined, and may additionally + contain `line` or `line_end`, the first and last line of the definition. +* `set(snippet, source)`: + Set the source of a snippet. + * `snippet`: a snippet which was added via `ls.add_snippets`. + * `source`: a `source`-object, obtained from either `from_debuginfo` or + `from_location`. +* `from_location(file, opts) -> source`: + * `file`: `string`, The path to the file in which the snippet is defined. + * `opts`: `table|nil`, optional parameters for the source. + * `line`: `number`, the first line of the definition. 1-indexed. + * `line_end`: `number`, the final line of the definition. 1-indexed. +* `from_debuginfo(debuginfo) -> source`: + Generates source from the table returned by `debug.getinfo` (from now on + referred to as `debuginfo`). `debuginfo` has to be of a frame of a function + which is backed by a file, and has to contain this information, i.e. has to be + generated by `debug.get_info(*, "Sl")` (at least `"Sl"`, it may also contain + more info). + +# Selection + +Many snippets use the `$TM_SELECTED_TEXT` or (for LuaSnip, preferably +`LS_SELECT_RAW` or `LS_SELECT_DEDENT`) variable, which has to be populated by +selecting and then yanking (and usually also cutting) text from the buffer +before expanding. + +By default, this is disabled (as to not pollute keybindings which may be used +for something else), so one has to + +* either set `cut_selection_keys` in `setup` (see + [Config-Options](#config-options)). +* or map `ls.cut_keys` as the right-hand-side of a mapping +* or manually configure the keybinding. For this, create a new keybinding that + 1. ``es to NORMAL (to populate the `<` and `>`-markers) + 2. calls `luasnip.pre_yank()` + 3. yanks text to some named register `` + 4. calls `luasnip.post_yank()` + Take care that the yanking actually takes place between the two calls. One way + to ensure this is to call the two functions via `lua ...`: + ```lua + vim.keymap.set("v", "", [[lua require("luasnip.util.select").pre_yank("z")gv"zslua require('luasnip.util.select').post_yank("z")]]) + ``` + The reason for this specific order is to allow us to take a snapshot of + registers (in the pre-callback), and then restore them (in the post-callback) + (so that we may get the visual selection directly from the register, which + seems to be the most foolproof way of doing this). + +# Config-Options + +These are the settings you can provide to `luasnip.setup()`: + +- `keep_roots`: Whether snippet-roots should be linked. See + [Basics-Snippet-Insertion](#snippet-insertion) for more context. +- `link_roots`: Whether snippet-roots should be linked. See + [Basics-Snippet-Insertion](#snippet-insertion) for more context. +- `exit_roots`: Whether snippet-roots should exit at reaching at their last + node, `$0`. This setting is only valid for root snippets, not child snippets. + This setting may avoid unexpected behavior by disallowing to jump earlier + (finished) snippets. Check [Basics-Snippet-Insertion](#snippet-insertion) for + more information on snippet-roots. +- `link_children`: Whether children should be linked. See + [Basics-Snippet-Insertion](#snippet-insertion) for more context. +- `history` (deprecated): if not nil, `keep_roots`, `link_roots`, and + `link_children` will be set to the value of `history`, and + `exit_roots` will set to inverse value of `history`. This is just to ensure + backwards-compatibility. +- `update_events`: Choose which events trigger an update of the active nodes' + dependents. Default is just `'InsertLeave'`, `'TextChanged,TextChangedI'` + would update on every change. + These, like all other `*_events` are passed to `nvim_create_autocmd` as + `events`, so they can be wrapped in a table, like + ```lua + ls.setup({ + update_events = {"TextChanged", "TextChangedI"} + }) + ``` +- `region_check_events`: Events on which to leave the current snippet-root if + the cursor is outside its' 'region'. Disabled by default, `'CursorMoved'`, + `'CursorHold'` or `'InsertEnter'` seem reasonable. +- `delete_check_events`: When to check if the current snippet was deleted, and + if so, remove it from the history. Off by default, `'TextChanged'` (perhaps + `'InsertLeave'`, to react to changes done in Insert mode) should work just + fine (alternatively, this can also be mapped using + `luasnip-delete-check`). +- `cut_selection_keys`: Mapping for populating `TM_SELECTED_TEXT` and related + variables (not set by default). + See [Selection](#selection) for more information. +- `store_selection_keys` (deprecated): same as `cut_selection_keys` +- `enable_autosnippets`: Autosnippets are disabled by default to minimize + performance penalty if unused. Set to `true` to enable. +- `ext_opts`: Additional options passed to extmarks. Can be used to add + passive/active highlight on a per-node-basis (more info in `DOC.md`) +- `parser_nested_assembler`: Override the default behavior of inserting a + `choiceNode` containing the nested snippet and an empty `insertNode` for + nested placeholders (`"${1: ${2: this is nested}}"`). For an example + (behavior more similar to VSCode), check + [here](https://github.com/L3MON4D3/LuaSnip/wiki/Nice-Configs#imitate-vscodes-behaviour-for-nested-placeholders) +- `ft_func`: Source of possible filetypes for snippets. Defaults to a function, + which returns `vim.split(vim.bo.filetype, ".", true)`, but check + [filetype_functions](lua/luasnip/extras/filetype_functions.lua) or the + [Extras-Filetype-Functions](#filetype-functions)-section for more options. +- `load_ft_func`: Function to determine which filetypes belong to a given + buffer (used for `lazy_loading`). `fn(bufnr) -> filetypes (string[])`. Again, + there are some examples in + [filetype_functions](lua/luasnip/extras/filetype_functions.lua). +- `snip_env`: The best way to author snippets in Lua involves the + `lua-loader` (see [Loaders-Lua](#lua)). + Unfortunately, this requires that snippets are defined in separate files, + which means that common definitions like `s`, `i`, `sn`, `t`, `fmt`, ... have + to be repeated in each of them, and that adding more customized functions to + ease writing snippets also requires some setup. + `snip_env` can be used to insert variables into exactly the places where + `lua-snippets` are defined (for now only the file loaded by the `lua-loader`). + Setting `snip_env` to `{ some_global = "a value" }` will add (amongst the + defaults stated at the beginning of this documentation) the global variable + `some_global` while evaluating these files. + There are special keys which, when set in `snip_env` change the behavior of + this option, and are not passed through to the `lua-files`: + * `__snip_env_behaviour`, string: either `"set"` or `"extend"` (default + `"extend"`) + If this is `"extend"`, the variables defined in `snip_env` will complement (and + override) the defaults. If this is not desired, `"set"` will not include the + defaults, but only the variables set here. + +- `loaders_store_source`, boolean, whether loaders should store the source of + the loaded snippets. + Enabling this means that the definition of any snippet can be jumped to via + [Extras-Snippet-Location](#snippet-location), but also entails slightly + increased memory consumption (and load-time, but it's not really noticeable). + +# Troubleshooting + +## Adding Snippets + + + +### Loaders + +* **Filetypes**. LuaSnip uses `all` as the global filetype. As most snippet + collections don't explicitly target LuaSnip, they may not provide global + snippets for this filetype, but another, like `_` (`honza/vim-snippets`). + In these cases, it's necessary to extend LuaSnip's global filetype with + the collection's global filetype: + ```lua + ls.filetype_extend("all", { "_" }) + ``` + + In general, if some snippets don't show up when loading a collection, a good + first step is checking the filetype LuaSnip is actually looking into (print + them for the current buffer via + `:lua print(vim.inspect(require("luasnip").get_snippet_filetypes()))`), + against the one the missing snippet is provided for (in the collection). + If there is indeed a mismatch, `filetype_extend` can be used to also search + the collection's filetype: + ```lua + ls.filetype_extend("", { "" }) + ``` + +* **Non-default `ft_func` loading**. As we only load `lazy_load`ed snippets on + some events, `lazy_load` will probably not play nice when a non-default + `ft_func` is used: if it depends on e.g. the cursor position, only the + filetypes for the cursor position when the `lazy_load` events are triggered + will be loaded. Check [Extras-Filetype-Function](#filetype-functions)'s + `extend_load_ft` for a solution. + +### General + +* **Snippets sharing triggers**. If multiple snippets could be triggered at + the current buffer-position, the snippet that was defined first in one's + configuration will be expanded first. As a small, real-world LaTeX math + example, given the following two snippets with triggers `.ov` and `ov`: + + ```lua + postfix( -- Insert over-line command to text via post-fix + { trig = ".ov", snippetType = "autosnippet" }, + { + f(function(_, parent) + return "\\overline{" .. parent.snippet.env.POSTFIX_MATCH .. "}" + end, {}), + } + ), + s( -- Insert over-line command + { trig = "ov", snippetType="autosnippet" }, + fmt( + [[\overline{<>}]], + { i(1) }, + { delimiters = "<>" } + ) + ), + ``` + + If one types `x` followed by `.ov`, the postfix snippet expands producing + `\overline{x}`. However, if the `postfix` snippet above is defined *after* + the normal snippet `s`, then the same key press sequence produces + `x.\overline{}`. + This behavior can be overridden by explicitly providing a priority to + such snippets. For example, in the above code, if the `postfix` snippet + was defined after the normal snippet `s`, then adding `priority=1001` to the + `postfix` snippet will cause it to expand as if it were defined before + the normal snippet `s`. Snippet `priority` is discussed in the + [Snippets section](#snippets) of the documentation. + +# API + +`require("luasnip")`: + +```lua render_region +local api_fieldnames_static = fnames("LuaSnip.API") +local static_doc = {} +local opts_table = { + get_snippets = {type_expand = { + ["LuaSnip.Opts.GetSnippets?"] = {explain_type = "LuaSnip.Opts.GetSnippets"}, + }}, + snip_expand = {type_expand = { + ["LuaSnip.Opts.SnipExpand?"] = {explain_type = "LuaSnip.Opts.SnipExpand"}, + ["LuaSnip.Opts.SnipExpandExpandParams?"] = {explain_type = "LuaSnip.Opts.SnipExpandExpandParams"}, + }}, + expand = {type_expand = { + ["LuaSnip.Opts.Expand?"] = {explain_type = "LuaSnip.Opts.Expand"}, + }}, + add_snippets = {type_expand = { + ["LuaSnip.Opts.AddSnippets?"] = {explain_type = "LuaSnip.Opts.AddSnippets"}, + }}, + add_snippets = {type_expand = { + ["LuaSnip.Opts.CleanInvalidated?"] = {explain_type = "LuaSnip.Opts.CleanInvalidated"}, + }}, + activate_node = {type_expand = { + ["LuaSnip.Opts.ActivateNode?"] = {explain_type = "LuaSnip.Opts.ActivateNode"}, + }}, + clean_invalidated = {type_expand = { + ["LuaSnip.Opts.CleanInvalidated?"] = {explain_type = "LuaSnip.Opts.CleanInvalidated"}, + }}, +} + +local no_doc_functions = {env_namespace = true, setup = true} + +for _, name in ipairs(api_fieldnames_static) do + -- don't render private functions. Those have a leading newline, luckily + -- :). + if not no_doc_functions[name] and name:sub(1,1) ~= "_" then + local opts = vim.tbl_extend("force", { + typename = "LuaSnip.API", + funcname = name, + display_fname = name, + pre_list_linebreak = true + }, opts_table[name] or {}) + + -- put these all in a single line! + append_tokens({ + tokens.combinable_linebreak(2), + "#### " .. + prototype_string(opts) .. + (mode == "vimdoc" and (" {doc=luasnip-api-%s}"):format(opts.funcname) or ""), + tokens.combinable_linebreak(2) }) + func_info(opts) + end +end +``` + +Not covered in this section are the various node-constructors exposed by +the module, their usage is shown either previously in this file or in +`Examples/snippets.lua` (in the repository). diff --git a/.github/data/project-dictionary.txt b/data/project-dictionary.txt similarity index 91% rename from .github/data/project-dictionary.txt rename to data/project-dictionary.txt index 1752db4ca..5ea39ad30 100644 --- a/.github/data/project-dictionary.txt +++ b/data/project-dictionary.txt @@ -1,4 +1,4 @@ -personal_ws-1.1 en 113 utf-8 +personal_ws-1.1 en 123 utf-8 AbsoluteIndexer Autosnippets Cfigure @@ -34,13 +34,16 @@ amongst anytext argnode argnodes +args autocommand autocommands +autosnippet autosnippets autotriggered backtick boolean choiceNode +choiceNode's choiceNodes cmp compe @@ -57,7 +60,9 @@ docstrings dynamicNode dynamicNode's dynamicNodes +eg ejmastnak +env evesdropper extmark extmarks @@ -65,11 +70,13 @@ filetype filetypes fmt fn +fts functionNode functionNodes get's globals honza +ie ing insertNode insertNodes @@ -78,7 +85,9 @@ jumpable jumplist keymaps lsp +lua luasnip +luasnip's mistyped molleweide multiline @@ -109,6 +118,7 @@ textNode textNodes th there'd +treesitter truthy varargs vsnip diff --git a/doc/luasnip.txt b/doc/luasnip.txt index d29e35692..19d28689d 100644 --- a/doc/luasnip.txt +++ b/doc/luasnip.txt @@ -1,4 +1,4 @@ -*luasnip.txt* For NVIM v0.8.0 Last change: 2025 June 10 +*luasnip.txt* For NeoVim 0.7-0.11 Last change: 2025 June 25 ============================================================================== Table of Contents *luasnip-table-of-contents* @@ -65,7 +65,6 @@ Table of Contents *luasnip-table-of-contents* 31. Troubleshooting |luasnip-troubleshooting| - Adding Snippets |luasnip-troubleshooting-adding-snippets| 32. API |luasnip-api| -33. Links |luasnip-links| > __ ____ /\ \ /\ _`\ __ @@ -119,8 +118,8 @@ All code snippets in this help assume the following: As noted in the |luasnip-loaders-lua|-section: - By default, the names from `luasnip.config.snip_env` - + By default, the names from + [`luasnip.config.snip_env`](https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/config.lua#L22-L48) will be used, but it’s possible to customize them by setting `snip_env` in `setup`. Furthermore, note that while this document assumes you have defined `ls` to be @@ -136,16 +135,16 @@ In LuaSnip, snippets are made up of `nodes`. These can contain either - text that can be edited (`insertNode`) - text that can be generated from the contents of other nodes (`functionNode`) - other nodes - - `choiceNode`: allows choosing between two nodes (which might contain more - nodes) - - `restoreNode`: store and restore input to nodes + - `choiceNode`: allows choosing between two nodes (which might contain more + nodes) + - `restoreNode`: store and restore input to nodes - or nodes that can be generated based on input (`dynamicNode`). -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 `ls.expand` is called. +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 `ls.expand` is called. JUMP-INDEX *luasnip-basics-jump-index* @@ -197,27 +196,28 @@ It is possible to make snippets from one filetype available to another using SNIPPET INSERTION *luasnip-basics-snippet-insertion* When a new snippet is expanded, it can be connected with the snippets that have -already been expanded in the buffer in various ways. First of all, Luasnip -distinguishes between root-snippets and child-snippets. The latter are nested -inside other snippets, so when jumping through a snippet, one may also traverse -the child-snippets expanded inside it, more or less as if the child just -contains more nodes of the parent. Root-snippets are of course characterized by -not being child-snippets. When expanding a new snippet, it becomes a child of -the snippet whose region it is expanded inside, and a root if it is not inside -any snippet’s region. If it is inside another snippet, the specific node it -is inside is determined, and the snippet then nested inside that node. +already been expanded in the buffer in various ways. +First of all, Luasnip distinguishes between root-snippets and child-snippets. +The latter are nested inside other snippets, so when jumping through a snippet, +one may also traverse the child-snippets expanded inside it, more or less as if +the child just contains more nodes of the parent. +Root-snippets are of course characterized by not being child-snippets. +When expanding a new snippet, it becomes a child of the snippet whose region it +is expanded inside, and a root if it is not inside any snippet’s region. +If it is inside another snippet, the specific node it is inside is determined, +and the snippet then nested inside that node. - If that node is interactive (for example, an `insertNode`), the new snippet - will be traversed when the node is visited, as long as the - configuration-option `link_children` is enabled. If it is not enabled, it is - possible to jump from the snippet to the node, but not the other way around. + will be traversed when the node is visited, as long as the + configuration-option `link_children` is enabled. If it is not enabled, it is + possible to jump from the snippet to the node, but not the other way around. - If that node is not interactive, the snippet will be linked to the currently - active node, also such that it will not be jumped to again once it is left. - This is to prevent jumping large distances across the buffer as much as - possible. There may still be one large jump from the snippet back to the - current node it is nested inside, but that seems hard to avoid. - Thus, one should design snippets such that the regions where other snippets - may be expanded are inside `insertNodes`. + active node, also such that it will not be jumped to again once it is left. + This is to prevent jumping large distances across the buffer as much as + possible. There may still be one large jump from the snippet back to the + current node it is nested inside, but that seems hard to avoid. + Thus, one should design snippets such that the regions where other snippets + may be expanded are inside `insertNodes`. If the snippet is not a child, but a root, it can be linked up with the roots immediately adjacent to it by enabling `link_roots` in `setup`. Since by @@ -242,34 +242,35 @@ mentioned if they accept options that are not common to all nodes. Common opts: - `node_ext_opts` and `merge_node_ext_opts`: Control `ext_opts` (most likely - highlighting) of the node. Described in detail in |luasnip-ext_opts| -- `key`: The node can be referred to by this key. Useful for either |luasnip-key-indexer| or for finding the node at runtime (See - |luasnip-snippets-api|), for example inside a `dynamicNode`. The keys - do not have to be unique across the entire lifetime of the snippet, but at any - point in time, the snippet may contain each key only once. This means it is - fine to return a keyed node from a `dynamicNode`, because even if it will be - generated multiple times, those will not be valid at the same time. + highlighting) of the node. Described in detail in |luasnip-ext_opts| +- `key`: The node can be referred to by this key. Useful for either + |luasnip-key-indexer| or for finding the node at runtime (See + |luasnip-snippets-api|), for example inside a `dynamicNode`. The keys + do not have to be unique across the entire lifetime of the snippet, but at any + point in time, the snippet may contain each key only once. This means it is + fine to return a keyed node from a `dynamicNode`, because even if it will be + generated multiple times, those will not be valid at the same time. - `node_callbacks`: Define event-callbacks for this node (see - |luasnip-events|). - Accepts a table that maps an event, e.g. `events.enter` to the callback - (essentially the same as `callbacks` passed to `s`, only that there is no - first mapping from jump-index to the table of callbacks). + |luasnip-events|). + Accepts a table that maps an event, e.g. `events.enter` to the callback + (essentially the same as `callbacks` passed to `s`, only that there is no + first mapping from jump-index to the table of callbacks). API *luasnip-node-api* - `get_jump_index()`: this method returns the jump-index of a node. If a node - doesn’t have a jump-index, this method returns `nil` instead. + doesn’t have a jump-index, this method returns `nil` instead. - `get_buf_position(opts) -> {from_position, to_position}`: - Determines the range of the buffer occupied by this node. `from`- and - `to_position` are `row,column`-tuples, `0,0`-indexed (first line is 0, first - column is 0) and end-inclusive (see |api-indexing|, this is extmarks - indexing). - - `opts`: `table|nil`, options, valid keys are: - - `raw`: `bool`, default `true`. This can be used to switch between - byte-columns (`raw=true`) and visual columns (`raw=false`). This makes a - difference if the line contains characters represented by multiple bytes - in UTF, for example `ÿ`. + Determines the range of the buffer occupied by this node. `from`- and + `to_position` are `row,column`-tuples, `0,0`-indexed (first line is 0, first + column is 0) and end-inclusive (see |api-indexing|, this is extmarks + indexing). + - `opts`: `table|nil`, options, valid keys are: + - `raw`: `bool`, default `true`. This can be used to switch between + byte-columns (`raw=true`) and visual columns (`raw=false`). This makes a + difference if the line contains characters represented by multiple bytes + in UTF, for example `ÿ`. ============================================================================== @@ -286,181 +287,188 @@ The most direct way to define snippets is `s`: `s(context, nodes, opts) -> snippet` - `context`: Either table or a string. Passing a string is equivalent to passing + >lua + { + trig = context + } + < + The following keys are valid: + - `trig`: string, the trigger of the snippet. If the text in front of (to the + left of) the cursor when `ls.expand()` is called matches it, the snippet will + be expanded. + By default, "matches" means the text in front of the cursor matches the trigger + exactly, this behavior can be modified through `trigEngine` + - `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. + - `desc` (or `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. + Consider setting `trigEngine` to `"pattern"` instead, it is more expressive, + and in line with other settings. + - `trigEngine`: (function|string), determines how `trig` is interpreted, and what + it means for it to "match" the text in front of the cursor. + This behavior can be completely customized by passing a function, but the + predefined ones, which are accessible by passing their identifier, should + suffice in most cases: + - `"plain"`: the default-behavior, the trigger has to match the text before + the cursor exactly. + - `"pattern"`: the trigger is interpreted as a Lua pattern, and is a match if + `trig .. "$"` matches the line up to the cursor. Capture-groups will be + accessible as `snippet.captures`. + - `"ecma"`: the trigger is interpreted as an ECMAscript-regex, and is a + match if `trig .. "$"` matches the line up to the cursor. Capture-groups + will be accessible as `snippet.captures`. + This `trigEngine` requires `jsregexp` (see + |luasnip-lsp-snippets-transformations|) to be installed, if it + is not, this engine will behave like `"plain"`. + - `"vim"`: the trigger is interpreted as a vim-regex, and is a match if + `trig .. "$"` matches the line up to the cursor. As with the other + regex/pattern-engines, captures will be available as `snippet.captures`, + but there is one caveat: the matching is done using `matchlist`, so for + now empty-string submatches will be interpreted as unmatched, and the + corresponding `snippet.capture[i]` will be `nil` (this will most likely + change, don’t rely on this behavior). + Besides these predefined engines, it is also possible to create new ones: + Instead of a string, pass a function which satisfies + `trigEngine(trigger, opts) -> (matcher(line_to_cursor, trigger) -> whole_match, captures)` + (i.e. the function receives `trig` and `trigEngineOpts` can, for example, + precompile a regex, and then returns a function responsible for determining + whether the current cursor-position (represented by the line up to the cursor) + matches the trigger (it is passed again here so engines which don’t do any + trigger-specific work (like compilation) can just return a static `matcher`), + and what the capture-groups are). + The `lua`-engine, for example, can be implemented like this: >lua - { - trig = context - } - < - The following keys are valid: - - `trig`: string, the trigger of the snippet. If the text in front of (to the - left of) the cursor when `ls.expand()` is called matches it, the snippet will - be expanded. By default, "matches" means the text in front of the cursor - matches the trigger exactly, this behavior can be modified through `trigEngine` - - `name`: string, can be used by e.g. `nvim-compe` to identify the snippet. - - `desc` (or `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. Consider setting `trigEngine` to `"pattern"` instead, it is - more expressive, and in line with other settings. - - `trigEngine`: (function|string), determines how `trig` is interpreted, and what - it means for it to "match" the text in front of the cursor. This behavior can - be completely customized by passing a function, but the predefined ones, which - are accessible by passing their identifier, should suffice in most cases: - - `"plain"`: the default-behavior, the trigger has to match the text before - the cursor exactly. - - `"pattern"`: the trigger is interpreted as a Lua pattern, and is a match if - `trig .. "$"` matches the line up to the cursor. Capture-groups will be - accessible as `snippet.captures`. - - `"ecma"`: the trigger is interpreted as an ECMAscript-regex, and is a - match if `trig .. "$"` matches the line up to the cursor. Capture-groups - will be accessible as `snippet.captures`. - This `trigEngine` requires `jsregexp` (see - |luasnip-lsp-snippets-transformations|) to be installed, if it - is not, this engine will behave like `"plain"`. - - `"vim"`: the trigger is interpreted as a vim-regex, and is a match if - `trig .. "$"` matches the line up to the cursor. As with the other - regex/pattern-engines, captures will be available as `snippet.captures`, - but there is one caveat: the matching is done using `matchlist`, so for - now empty-string submatches will be interpreted as unmatched, and the - corresponding `snippet.capture[i]` will be `nil` (this will most likely - change, don’t rely on this behavior). - Besides these predefined engines, it is also possible to create new ones: - Instead of a string, pass a function which satisfies `trigEngine(trigger, opts) - -> (matcher(line_to_cursor, trigger) -> whole_match, captures)` (i.e. the - function receives `trig` and `trigEngineOpts` can, for example, precompile a - regex, and then returns a function responsible for determining whether the - current cursor-position (represented by the line up to the cursor) matches the - trigger (it is passed again here so engines which don’t do any - trigger-specific work (like compilation) can just return a static `matcher`), - and what the capture-groups are). The `lua`-engine, for example, can be - implemented like this: - >lua - local function matcher(line_to_cursor, trigger) - -- look for match which ends at the cursor. - -- put all results into a list, there might be many capture-groups. - local find_res = { line_to_cursor:find(trigger .. "$") } - - if #find_res > 0 then - -- if there is a match, determine matching string, and the - -- capture-groups. - local captures = {} - -- find_res[1] is `from`, find_res[2] is `to` (which we already know - -- anyway). - local from = find_res[1] - local match = line_to_cursor:sub(from, #line_to_cursor) - -- collect capture-groups. - for i = 3, #find_res do - captures[i - 2] = find_res[i] - end - return match, captures - else - return nil + local function matcher(line_to_cursor, trigger) + -- look for match which ends at the cursor. + -- put all results into a list, there might be many capture-groups. + local find_res = { line_to_cursor:find(trigger .. "$") } + + if #find_res > 0 then + -- if there is a match, determine matching string, and the + -- capture-groups. + local captures = {} + -- find_res[1] is `from`, find_res[2] is `to` (which we already know + -- anyway). + local from = find_res[1] + local match = line_to_cursor:sub(from, #line_to_cursor) + -- collect capture-groups. + for i = 3, #find_res do + captures[i - 2] = find_res[i] end + return match, captures + else + return nil end - - local function engine(trigger) - -- don't do any special work here, can't precompile lua-pattern. - return matcher - end - < - The predefined engines are defined in `trig_engines.lua` - , - read it for more examples. - - `trigEngineOpts`: `table`, options for the used `trigEngine`. The - valid options are: - - `max_len`: number, upper bound on the length of the trigger. - If this is set, the `line_to_cursor` will be truncated (from the cursor of - course) to `max_len` characters before performing the match. - This is implemented because feeding long `line_to_cursor` into e.g. the - pattern-`trigEngine` will hurt performance quite a bit (see issue - Luasnip#1103). - This option is implemented for all `trigEngines`. - - `docstring`: string, textual representation of the snippet, specified like - `desc`. Overrides docstrings loaded from `json`. - - `docTrig`: string, used as `line_to_cursor` during docstring-generation. This - might be relevant if the snippet relies on specific values in the - capture-groups (for example, numbers, which won’t work with the default - `$CAPTURESN` 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. - - `resolveExpandParams`: `fn(snippet, line_to_cursor, matched_trigger, captures) - -> table|nil`, where - - `snippet`: `Snippet`, the expanding snippet object - - `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`: `captures` as returned by `trigEngine`. - This function will be evaluated in `Snippet:matches()` to decide whether the - snippet can be expanded or not. Returns a table if the snippet can be expanded, - `nil` if can not. The returned table can contain any of these fields: - - `trigger`: `string`, the fully matched trigger. - - `captures`: `table`, this list could update the capture-groups from - parameter in snippet expansion. - Both `trigger` and `captures` can override the values returned via - `trigEngine`. - - `clear_region`: `{ "from": {, }, "to": {, } }`, - both (0, 0)-indexed, the region where text has to be cleared before - inserting the snippet. - - `env_override`: `map string->(string[]|string)`, override or extend - the snippet’s environment (`snip.env`) - If any of these is `nil`, the default is used (`trigger` and `captures` as - returned by `trigEngine`, `clear_region` such that exactly the trigger is - deleted, no overridden environment-variables). - A good example for the usage of `resolveExpandParams` can be found in the - implementation of `postfix` - . - - `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. - - `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 engines when scanning for available snippet - candidates. - - `filetype`: `string`, the filetype of the snippet. This overrides the filetype - the snippet is added (via `add_snippet`) as. + end + + local function engine(trigger) + -- don't do any special work here, can't precompile lua-pattern. + return matcher + end + < + The predefined engines are defined in + [`trig_engines.lua`](https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/nodes/util/trig_engines.lua), + read it for more examples. + - `trigEngineOpts`: `table`, options for the used `trigEngine`. + The valid options are: + - `max_len`: number, upper bound on the length of the trigger. + If this is set, the `line_to_cursor` will be truncated (from the cursor of + course) to `max_len` characters before performing the match. + This is implemented because feeding long `line_to_cursor` into e.g. the + pattern-`trigEngine` will hurt performance quite a bit (see issue + Luasnip#1103). + This option is implemented for all `trigEngines`. + - `docstring`: string, textual representation of the snippet, specified like + `desc`. Overrides docstrings loaded from `json`. + - `docTrig`: string, used as `line_to_cursor` during docstring-generation. This + might be relevant if the snippet relies on specific values in the + capture-groups (for example, numbers, which won’t work with the default + `$CAPTURESN` 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. + - `resolveExpandParams`: + `fn(snippet, line_to_cursor, matched_trigger, captures) -> table|nil`, where + - `snippet`: `Snippet`, the expanding snippet object + - `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`: `captures` as returned by `trigEngine`. + This function will be evaluated in `Snippet:matches()` to decide whether the + snippet can be expanded or not. + Returns a table if the snippet can be expanded, `nil` if can not. The returned + table can contain any of these fields: + - `trigger`: `string`, the fully matched trigger. + - `captures`: `table`, this list could update the capture-groups from + parameter in snippet expansion. + Both `trigger` and `captures` can override the values returned via + `trigEngine`. + - `clear_region`: `{ "from": {, }, "to": {, } }`, + both (0, 0)-indexed, the region where text has to be cleared before + inserting the snippet. + - `env_override`: `map string->(string[]|string)`, override or extend + the snippet’s environment (`snip.env`) + If any of these is `nil`, the default is used (`trigger` and `captures` as + returned by `trigEngine`, `clear_region` such that exactly the trigger is + deleted, no overridden environment-variables). + A good example for the usage of `resolveExpandParams` can be found in the + implementation of + [`postfix`](https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/extras/postfix.lua). + - `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. + - `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 engines when + scanning for available snippet candidates. + - `filetype`: `string`, the filetype of the snippet. This overrides the filetype + the snippet is added (via `add_snippet`) as. - `nodes`: A single node or a list of nodes. The nodes that make up the snippet. - `opts`: A table with the following valid keys: - - `callbacks`: Contains functions that are called upon entering/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 - } + - `callbacks`: Contains functions that are called upon entering/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 in |luasnip-events| - - `child_ext_opts`, `merge_child_ext_opts`: Control `ext_opts` applied to the - children of this snippet. More info on those in the |luasnip-ext_opts|-section. + } + < + To register a callback for the snippets’ own events, the key `[-1]` may be + used. More info on events in |luasnip-events| + - `child_ext_opts`, `merge_child_ext_opts`: Control `ext_opts` applied to the + children of this snippet. More info on those in the |luasnip-ext_opts|-section. The `opts`-table, as described here, can also be passed to e.g. `snippetNode` -and `indentSnippetNode`. It is also possible to set `condition` and -`show_condition` (described in the documentation of the `context`-table) from -`opts`. They should, however, not be set from both. +and `indentSnippetNode`. +It is also possible to set `condition` and `show_condition` (described in the +documentation of the `context`-table) from `opts`. They should, however, not be +set from both. DATA *luasnip-snippets-data* @@ -468,29 +476,29 @@ DATA *luasnip-snippets-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-variables-environment-namespaces| + `TM_CURRENT_LINE` or `TM_FILENAME`. It’s possible to add customized variables + here too, check |luasnip-variables-environment-namespaces| - `snippet.captures`: If the snippet was triggered by a pattern (`regTrig`), and - the pattern contained capture-groups, they can be retrieved here. + 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. + 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`). +(`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 - `invalidate()`: call this method to effectively remove the snippet. The - snippet will no longer be able to expand via `expand` or `expand_auto`. It - will also be hidden from lists (at least if the plugin creating the list - respects the `hidden`-key), but it might be necessary to call - `ls.refresh_notify(ft)` after invalidating snippets. + snippet will no longer be able to expand via `expand` or `expand_auto`. It + will also be hidden from lists (at least if the plugin creating the list + respects the `hidden`-key), but it might be necessary to call + `ls.refresh_notify(ft)` after invalidating snippets. - `get_keyed_node(key)`: Returns the currently visible node associated with - `key`. + `key`. ============================================================================== @@ -541,9 +549,9 @@ The functionality is best demonstrated with an example: }) < -The Insert Nodes 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: +The Insert Nodes 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, the cursor is at InsertNode 1, 2. after jumping forward once at InsertNode 2, @@ -596,10 +604,10 @@ TextMate-snippet will expand correctly when parsed). `i(jump_index, text, node_opts)` - `jump_index`: `number`, this determines when this node will be jumped to (see - |luasnip-basics-jump-index|). + |luasnip-basics-jump-index|). - `text`: `string|string[]`, a single string for just one line, a list with >1 - entries for multiple lines. - This text will be `SELECT`ed when the `insertNode` is jumped into. + entries for multiple lines. + This text will be `SELECT`ed when the `insertNode` is jumped into. - `node_opts`: `table`, described in |luasnip-node| If the `jump_index` is `0`, replacing its’ `text` will leave it outside the @@ -631,85 +639,85 @@ user-defined function: }) < -`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 |luasnip-snippets-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 +`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 |luasnip-snippets-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 strings, where 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 |luasnip-node-reference|s. 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. + `fn` shall return a string, which will be inserted as is, or a table of strings + for multiline strings, where 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 |luasnip-node-reference|s. 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-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 - 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"} - }), - }) - < + - `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 + 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: - >lua - s({trig = "b(%d)", regTrig = true}, - f(function(args, snip) return - "Captured Text: " .. snip.captures[1] .. "." end, {}) - ) - < + >lua + s({trig = "b(%d)", regTrig = true}, + f(function(args, snip) return + "Captured Text: " .. snip.captures[1] .. "." end, {}) + ) + < - `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} )}) - < - 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"} - } - < + >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} )}) + < + 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"} + } + < - |luasnip-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]} )}) - < + >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]} )}) + < If the function only performs simple operations on text, consider using the `lambda` from `luasnip.extras` (See |luasnip-extras-lambda|) @@ -719,28 +727,30 @@ If the function only performs simple operations on text, consider using the 7. 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: +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) + 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) - `key_indexer`: the key of the node, if it is present. This will come in - handy if the node that is being referred to is not in the same - snippet/snippetNode as the one the node reference is passed to. - Also, it is the proper way to refer to a non-interactive node (a - functionNode, for example) + handy if the node that is being referred to is not in the same + snippet/snippetNode as the one the node reference is passed to. + Also, it is the proper way to refer to a non-interactive node (a + functionNode, for example) - `absolute_indexer`: the absolute position of the node. Just like - `key_indexer`, it allows addressing non-sibling nodes, but is a bit more - awkward to handle since a path from root to node has to be determined, - whereas `key_indexer` just needs the key to match. - Due to this, `key_indexer` should be generally preferred. - (More information in |luasnip-absolute-indexer|). + `key_indexer`, it allows addressing non-sibling nodes, but is a bit more + awkward to handle since a path from root to node has to be determined, + whereas `key_indexer` just needs the key to match. + Due to this, `key_indexer` should be generally preferred. + (More information in |luasnip-absolute-indexer|). - `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`). + 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`). ============================================================================== @@ -756,43 +766,57 @@ ChoiceNodes allow choosing between multiple nodes. })) < -`c(jump_index, choices, node_opts)` - -- `jump_index`: `number`, since choiceNodes can be jumped to, they need a - jump-index (Info in |luasnip-basics-jump-index|). -- `choices`: `node[]|node`, the choices. The first will be initially active. - A list of nodes will be turned into a `snippetNode`. -- `node_opts`: `table`. `choiceNode` supports the keys common to all nodes - described in |luasnip-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 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! - -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. +`c(pos, choices, opts?): LuaSnip.ChoiceNode`: Create a new choiceNode from a +list of choices. +The first item in this list is the initial choice, and it can be changed while +any node of a choice is active. So, if all choices should be reachable, every +choice has to have a place for the cursor to stop at. + +If the choice is a snippetNode like `sn(nil, {...nodes...})` the given `nodes` +have to contain an `insertNode` (e.g. `i(1)`). Using an `insertNode` or +`textNode` directly as a choice is also fine, the latter is special-cased to +have a jump-point at the beginning of its text. + +- `pos: integer` Jump-index of the node. (See |luasnip-basics-jump-index|) +- `choices: (LuaSnip.Node|LuaSnip.Node[])[]` A list of nodes that can be switched + between interactively. If a list of nodes is passed as a choice, it will be + turned into a snippetNode. + Jumpable nodes that generally need a jump-index don’t need one when used as a + choice since they inherit the choiceNode’s jump-index anyway. +- `opts?: LuaSnip.Opts.ChoiceNode?` Additional optional arguments. + Valid keys are: + - `restore_cursor?: boolean?` If set, the currently active node is looked up in + the switched-to choice, and the cursor restored to preserve the current + position relative to that node. The node may be found if a `restoreNode` is + present in both choice. Defaults to `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) + opts = opts or {} + opts.restore_cursor = true + return c(pos, choices, opts) + end + < + Consider passing this override into `snip_env`. + - `node_callbacks?: { [("change_choice"|"enter"...)]: fun(...) -> ... }?` + - `node_ext_opts?: LuaSnip.NodeExtOpts?` Pass these opts through to the + underlying extmarks representing the node. Notably, this enables highlighting + the nodes, and allows the highlight to be different based on the state of the + node/snippet. See |luasnip-ext_opts| + - `merge_node_ext_opts?: boolean?` Whether to use the parents’ `ext_opts` to + compute this nodes’ `ext_opts`. + - `key: any` Some unique value (strings seem useful) to identify this node. This + is useful for |luasnip-key-indexer| or for finding the node at runtime (See + |luasnip-snippets-api| These keys don’t have to be unique across the entire + lifetime of the snippet, but every key should occur only once at the same time. + This means it is fine to return a keyed node from a dynamicNode, because even + if it will be generated multiple times, the same key not occur twice at the + same time. + +**Examples:** >lua c(1, { @@ -845,14 +869,14 @@ jump-index. - `jump_index`: `number`, the usual |luasnip-jump-index|. - `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`. + 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, the keys common to all nodes (documented in - |luasnip-node|) are supported, but also - - `callbacks`, - - `child_ext_opts` and - - `merge_child_ext_opts`, - which are further explained in |luasnip-snippets|. + |luasnip-node|) are supported, but also + - `callbacks`, + - `child_ext_opts` and + - `merge_child_ext_opts`, + which are further explained in |luasnip-snippets|. ============================================================================== @@ -898,9 +922,9 @@ All of these parameters except `indentstring` are exactly the same as in |luasnip-snippetnode|. - `indentstring`: `string`, will be used to indent the nodes inside this - `snippetNode`. - All occurrences of `"$PARENT_INDENT"` are replaced with the actual indent of - the parent. + `snippetNode`. + All occurrences of `"$PARENT_INDENT"` are replaced with the actual indent of + the parent. ============================================================================== @@ -913,33 +937,33 @@ user input. `d(jump_index, function, node-references, opts)`: - `jump_index`: `number`, just like all jumpable nodes, its’ position in the - jump-list (|luasnip-basics-jump-index|). + jump-list (|luasnip-basics-jump-index|). - `function`: `fn(args, parent, old_state, user_args) -> snippetNode` This - function is called when the argnodes’ text changes. It should generate and - return (wrapped inside a `snippetNode`) nodes, which will be inserted at the - dynamicNode’s 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 have more than one - argument. + function is called when the argnodes’ text changes. It should generate and + return (wrapped inside a `snippetNode`) nodes, which will be inserted at the + dynamicNode’s 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 have more than one + argument. - `node_references`: `node_reference[]|node_references|nil`, - |luasnip-node-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 - `dynamicNode`s place. (`dynamicNode` behaves exactly the same as `functionNode` - in this regard). + |luasnip-node-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 + `dynamicNode`s place. + (`dynamicNode` behaves exactly the same as `functionNode` in this regard). - `opts`: In addition to the common |luasnip-node|-keys, there is, again, - - `user_args`, which is described in |luasnip-functionnode|. + - `user_args`, which is described in |luasnip-functionnode|. **Examples**: @@ -1023,15 +1047,15 @@ Here the text entered into `user_text` is preserved upon changing choice. - `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`. + 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 @@ -1039,8 +1063,8 @@ the same values as the `nodes`-parameter passed to `r`. If no content is defined for a key, it defaults to the empty `insertNode`. 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. +one may be visible at a time. See +[this issue](https://github.com/L3MON4D3/LuaSnip/issues/234) for details. The `restoreNode` is especially useful for storing input across updates of a `dynamicNode`. Consider this: @@ -1080,17 +1104,18 @@ that really bothers you feel free to open an issue. ============================================================================== 13. Key Indexer *luasnip-key-indexer* -A very flexible way of referencing nodes (|luasnip-node-reference|). While the -straightforward way of addressing nodes via their |luasnip-jump-index| suffices -in most cases, a `dynamic/functionNode` can only depend on nodes in the same -snippet(Node), its siblings (since the index is interpreted as relative to -their parent). Accessing a node with a different parent is thus not possible. -Secondly, and less relevant, only nodes that actually have a jump-index can be -referred to (a `functionNode`, for example, cannot be depended on). Both of -these restrictions are lifted with `key_indexer`: It allows addressing nodes by -their key, which can be set when the node is constructed, and is wholly -independent of the nodes’ position in the snippet, thus enabling descriptive -labeling. +A very flexible way of referencing nodes (|luasnip-node-reference|). +While the straightforward way of addressing nodes via their +|luasnip-jump-index| suffices in most cases, a `dynamic/functionNode` can only +depend on nodes in the same snippet(Node), its siblings (since the index is +interpreted as relative to their parent). Accessing a node with a different +parent is thus not possible. Secondly, and less relevant, only nodes that +actually have a jump-index can be referred to (a `functionNode`, for example, +cannot be depended on). +Both of these restrictions are lifted with `key_indexer`: +It allows addressing nodes by their key, which can be set when the node is +constructed, and is wholly independent of the nodes’ position in the snippet, +thus enabling descriptive labeling. The following snippets demonstrate the issue and the solution by using `key_indexer`: @@ -1207,25 +1232,27 @@ are all the same node. There are situations where it might be comfortable to access a snippet in different ways. For example, one might want to enable auto-triggering in regions where the snippets usage is common, while leaving it manual-only in -others. This is where `ms` should be used: A single snippet can be associated -with multiple `context`s (the `context`-table determines the conditions under -which a snippet may be triggered). This has the advantage (compared with just -registering copies) that all `context`s are backed by a single snippet, and not -multiple, and it’s (at least should be :D) more comfortable to use. +others. +This is where `ms` should be used: A single snippet can be associated with +multiple `context`s (the `context`-table determines the conditions under which +a snippet may be triggered). +This has the advantage (compared with just registering copies) that all +`context`s are backed by a single snippet, and not multiple, and it’s (at +least should be :D) more comfortable to use. `ms(contexts, nodes, opts) -> addable`: - `contexts`: table containing list of `contexts`, and some keywords. - `context` are described in |luasnip-snippets|, here they may also be tables - or strings. - So far, there is only one valid keyword: - - `common`: Accepts yet another context. - The options in `common` are applied to (but don’t override) the other - contexts specified in `contexts`. + `context` are described in |luasnip-snippets|, here they may also be tables + or strings. + So far, there is only one valid keyword: + - `common`: Accepts yet another context. + The options in `common` are applied to (but don’t override) the other + contexts specified in `contexts`. - `nodes`: List of nodes, exactly like in |luasnip-snippets|. - `opts`: Table, options for this function: - - `common_opts`: The snippet-options (see also |luasnip-snippets|) applied - to the snippet generated from `nodes`. + - `common_opts`: The snippet-options (see also |luasnip-snippets|) applied + to the snippet generated from `nodes`. The returned object is an `addable`, something which can be passed to `add_snippets`, or returned from the `lua-loader`. @@ -1277,15 +1304,15 @@ 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 occurrences of "a" in the text of the - first argnode with "e", or - - `l._1 .. l._2` concatenates text of the first and second argnode. - If an argnode contains multiple lines of text, they are concatenated with - `"\n"` prior to any operation. + representing the `n`th argnode. + For example: + - `l._1:gsub("a", "e")` replaces all occurrences of "a" in the text of the + first argnode with "e", or + - `l._1 .. l._2` concatenates 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`, a |luasnip-node-reference|, just like in function- and - dynamicNode. + dynamicNode. There are many examples for `lambda` in `Examples/snippets.lua` @@ -1298,57 +1325,57 @@ MATCH *luasnip-extras-match* `match(argnodes, condition, then, else)`: - `argnode`: A single |luasnip-node-reference|. May not be nil, or - a table. + 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. + - `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`: +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. + 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). + 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")` - >lua - s("extras1", { - i(1), t { "", "" }, m(1, "^ABC$", "A") - }) - < - Inserts "A" if the node with jump-index `n` matches "ABC" exactly, nothing - otherwise. -- `match(n, lambda._1:match(lambda._1:reverse()), "PALINDROME")` - >lua - s("extras2", { - i(1, "INPUT"), t { "", "" }, m(1, l._1:match(l._1:reverse()), "PALINDROME") + >lua + s("extras1", { + i(1), t { "", "" }, m(1, "^ABC$", "A") }) - < - Inserts `"PALINDROME"` if i(1) contains a palindrome. + < + Inserts "A" if the node with jump-index `n` matches "ABC" exactly, nothing + otherwise. +- `match(n, lambda._1:match(lambda._1:reverse()), "PALINDROME")` + >lua + s("extras2", { + i(1, "INPUT"), t { "", "" }, m(1, l._1:match(l._1:reverse()), "PALINDROME") + }) + < + Inserts `"PALINDROME"` if i(1) contains a palindrome. - `match(n, lambda._1:match("^" .. lambda._2 .. "$"), lambda._1:gsub("a", "e"))` - >lua - 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 occurrences of - `a` replaced with `e`, if the second insertNode matches the first exactly. + >lua + 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 occurrences of + `a` replaced with `e`, if the second insertNode matches the first exactly. REPEAT *luasnip-extras-repeat* @@ -1414,8 +1441,8 @@ 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 -). +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 @@ -1480,31 +1507,31 @@ 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. Occurrences of `{}` ( `{}` are customizable; more - on that later) are replaced with `content[]` (which should be a - node), while surrounding text becomes `textNode`s. - To escape a delimiter, repeat it (`"{{"`). - If no key is given (`{}`) are numbered automatically: - `"{} ? {} : {}"` becomes `"{1} ? {2} : {3}"`, while - `"{} ? {3} : {}"` becomes `"{1} ? {3} : {4}"` (the count restarts at each - numbered placeholder). - If a key appears more than once in `format`, the node in - `content[]` is inserted for the first, and copies of it for - subsequent occurrences. + on that later) are replaced with `content[]` (which should be a + node), while surrounding text becomes `textNode`s. + To escape a delimiter, repeat it (`"{{"`). + If no key is given (`{}`) are numbered automatically: + `"{} ? {} : {}"` becomes `"{1} ? {2} : {3}"`, while + `"{} ? {3} : {}"` becomes `"{1} ? {3} : {4}"` (the count restarts at each + numbered placeholder). + If a key appears more than once in `format`, the node in + `content[]` is inserted for the first, and copies of it for + subsequent occurrences. - `nodes`: just a table of nodes. - `opts`: optional arguments: - - `delimiters`: string, two characters. Change `{}` to some other pair, e.g. - `"<>"`. - - `strict`: Warn about unused nodes (default true). - - `trim_empty`: remove empty (`"%s*"`) first and last line in `format`. Useful - when passing multiline strings via `[[]]` (default true). - - `dedent`: remove indent common to all lines in `format`. Again, makes - passing multiline-strings a bit nicer (default true). - - `indent_string`: convert `indent_string` at beginning of each line to unit - indent (’). This is applied after `dedent`. Useful when using - multiline string in `fmt`. (default empty string, disabled) - - `repeat_duplicates`: repeat nodes when a key is reused instead of copying - the node if it has a jump-index, refer to |luasnip-basics-jump-index| to - know which nodes have a jump-index (default false). + - `delimiters`: string, two characters. Change `{}` to some other pair, e.g. + `"<>"`. + - `strict`: Warn about unused nodes (default true). + - `trim_empty`: remove empty (`"%s*"`) first and last line in `format`. Useful + when passing multiline strings via `[[]]` (default true). + - `dedent`: remove indent common to all lines in `format`. Again, makes + passing multiline-strings a bit nicer (default true). + - `indent_string`: convert `indent_string` at beginning of each line to unit + indent (’). This is applied after `dedent`. Useful when using + multiline string in `fmt`. (default empty string, disabled) + - `repeat_duplicates`: repeat nodes when a key is reused instead of copying + the node if it has a jump-index, refer to |luasnip-basics-jump-index| to + know which nodes have a jump-index (default false). There is also `require("luasnip.extras.fmt").fmta`. This only differs from `fmt` by using angle brackets (`<>`) as the default delimiter. @@ -1520,15 +1547,15 @@ into `luasnip.extras.conditions.expand` and `luasnip.extras.conditions.show`: - `line_begin`: only expand if the cursor is at the beginning of the line. - `trigger_not_preceded_by(pattern)`: only expand if the character before the - trigger does not match `pattern`. This is a generalization of `wordTrig`, - which can be implemented as `trigger_not_preceded_by("[%w_]")`, and is - available as `word_trig_condition`. + trigger does not match `pattern`. This is a generalization of `wordTrig`, + which can be implemented as `trigger_not_preceded_by("[%w_]")`, and is + available as `word_trig_condition`. **show**: - `line_end`: only expand at the end of the line. - `has_selected_text`: only expand if there’s selected text stored after pressing - `store_selection_keys`. + `store_selection_keys`. Additionally, `expand` contains all conditions provided by `show`. @@ -1543,17 +1570,17 @@ combined with each other into logical expressions: - `c1 * c2 -> c1 and c2` - `c1 + c2 -> c1 or c2` - `c1 - c2 -> c1 and not c2`: This is similar to set differences: - `A \ B = {a in A | a not in B}`. This makes `-(a + b) = -a - b` an identity - representing de Morgan’s law: `not (a or b) = not a and not b`. However, - since boolean algebra lacks an additive inverse, `a + (-b) = a - b` does not - hold. Thus, this is NOT the same as `c1 + (-c2)`. + `A \ B = {a in A | a not in B}`. This makes `-(a + b) = -a - b` an identity + representing de Morgan’s law: `not (a or b) = not a and not b`. However, + since boolean algebra lacks an additive inverse, `a + (-b) = a - b` does not + hold. Thus, this is NOT the same as `c1 + (-c2)`. - `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). + 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 @@ -1586,10 +1613,10 @@ 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. + 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. + 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 @@ -1628,16 +1655,17 @@ same time by retrieving snippets from multiple registers: SELECT_CHOICE *luasnip-extras-select_choice* It’s possible to leverage `vim.ui.select` for selecting a choice directly, -without cycling through the available choices. All that is needed for this is -calling `require("luasnip.extras.select_choice")`, most likely via some -keybinding, 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 keybinding, +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`. FILETYPE-FUNCTIONS *luasnip-extras-filetype-functions* @@ -1646,38 +1674,38 @@ Contains some utility functions that can be passed to the `ft_func` or `load_ft_func`-settings. - `from_filetype`: the default for `ft_func`. Simply returns the filetype(s) of - the buffer. + the buffer. - `from_cursor_pos`: uses tree-sitter to determine the filetype at the cursor. - With that, it’s possible to expand snippets in injected regions, as long as - the tree-sitter parser supports them. If this is used in conjunction with - `lazy_load`, extra care must be taken that all the filetypes that can be - expanded in a given buffer are also returned by `load_ft_func` (otherwise their - snippets may not be loaded). This can easily be achieved with `extend_load_ft`. + With that, it’s possible to expand snippets in injected regions, as long as + the tree-sitter parser supports them. If this is used in conjunction with + `lazy_load`, extra care must be taken that all the filetypes that can be + expanded in a given buffer are also returned by `load_ft_func` (otherwise their + snippets may not be loaded). This can easily be achieved with `extend_load_ft`. - `extend_load_ft`: `fn(extend_ft:map) -> fn` A simple solution to the problem - described above is loading more filetypes than just that of the target buffer - when `lazy_load`ing. This can be done ergonomically via `extend_load_ft`: - calling it with a table where the keys are filetypes, and the values are the - filetypes that should be loaded additionally returns a function that can be - passed to `load_ft_func` and takes care of extending the filetypes properly. - >lua - ls.setup({ - load_ft_func = - -- Also load both lua and json when a markdown-file is opened, - -- javascript for html. - -- Other filetypes just load themselves. - require("luasnip.extras.filetype_functions").extend_load_ft({ - markdown = {"lua", "json"}, - html = {"javascript"} - }) - }) - < + described above is loading more filetypes than just that of the target buffer + when `lazy_load`ing. This can be done ergonomically via `extend_load_ft`: + calling it with a table where the keys are filetypes, and the values are the + filetypes that should be loaded additionally returns a function that can be + passed to `load_ft_func` and takes care of extending the filetypes properly. + >lua + ls.setup({ + load_ft_func = + -- Also load both lua and json when a markdown-file is opened, + -- javascript for html. + -- Other filetypes just load themselves. + require("luasnip.extras.filetype_functions").extend_load_ft({ + markdown = {"lua", "json"}, + html = {"javascript"} + }) + }) + < POSTFIX-SNIPPET *luasnip-extras-postfix-snippet* -Postfix snippets, famously used in rust analyzer - and various IDEs, are a type of snippet -which alters text before the snippet’s trigger. While these can be +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 snippet’s trigger. While these can be implemented using `regTrig` snippets, this helper makes the process easier in most cases. @@ -1724,12 +1752,12 @@ 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. + 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 - preceding the trigger match as the end of the string. + 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 + preceding 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`: @@ -1765,17 +1793,17 @@ TREESITTER-POSTFIX-SNIPPET *luasnip-extras-treesitter-postfix-snippet* Instead of triggering a postfix-snippet when some pattern matches in front of the trigger, it might be useful to match if some specific tree-sitter nodes -surround/are in front of the trigger. While this functionality can also be -implemented by a custom `resolveExpandParams`, this helper simplifies the -common cases. +surround/are in front of the trigger. +While this functionality can also be implemented by a custom +`resolveExpandParams`, this helper simplifies the common cases. This matching of tree-sitter nodes can be done either - by providing a query and the name of the capture that should be in front of - the trigger (in most cases, the complete match, but requiring specific nodes - before/after the matched node may be useful as well), or + the trigger (in most cases, the complete match, but requiring specific nodes + before/after the matched node may be useful as well), or - by providing a function that manually walks the node-tree, and returns the - node in front of the trigger on success (for increased flexibility). + node in front of the trigger on success (for increased flexibility). A simple example, which surrounds the previous node’s text preceding the `.mv` with `std::move()` in C++ files, looks like: @@ -1819,53 +1847,53 @@ The first argument has to be a table, which defines at least `trig` and `wordTrig`, which will be ignored), and additionally the following: - `reparseBuffer`, `string?`: Sometimes the trigger may interfere with - tree-sitter recognizing queries correctly. With this option, the trigger may - either be removed from the live-buffer (`"live"`), from a copy of the buffer - (`"copy"`), or not at all (`nil`). + tree-sitter recognizing queries correctly. With this option, the trigger may + either be removed from the live-buffer (`"live"`), from a copy of the buffer + (`"copy"`), or not at all (`nil`). - `matchTSNode`: How to determine whether there is a matching node in front of - the cursor. There are two options: - - `fun(parser: LuaSnip.extra.TSParser, pos: { [1]: number, [2]: number }): LuaSnip.extra.NamedTSMatch?, TSNode?` - Manually determine whether there is a matching node that ends just before - `pos` (the beginning of the trigger). - Return `nil,nil` if there is no match, otherwise first return a table - mapping names to nodes (the text, position and type of these will be - provided via `snip.env`), and second the node that is the matched node. - - `LuaSnip.extra.MatchTSNodeOpts`, which represents a query and provides all - captures of the matched pattern in `NamedTSMatch`. It contains the following - options: - - `query`, `string`: The query, in textual form. - - `query_name`, `string`: The name of the runtime-query to be used (passed - to `query.get()`), defaults to `"luasnip"` (so one could create a - file which only contains queries used by luasnip, like - `$CONFDIR/queries//luasnip.scm`, which might make sense to define - general concepts independent of a single snippet). - `query` and `query_name` are mutually exclusive, only one of both shall be - defined. - - `query_lang`, `string`: The language of the query. This is the only - required parameter to this function, since there’s no sufficiently - straightforward way to determine the language of the query for us. - Consider using `extend_override` to define a `ts_postfix`-function that - automatically fills in the language for the filetype of the snippet-file. - - `match_captures`, `string|string[]`: The capture(s) to use for determining - the actual prefix (so the node that should be immediately in front of the - trigger). This defaults to just `"prefix"`. - - `select`, `string?|fun(): LuaSnip.extra.MatchSelector`: Since there may be - multiple matching captures in front of the cursor, there has to be some - way to select the node that will actually be used. - If this is a string, it has to be one of "any", "shortest", or "longest", - which mean that any, the shortest, or the longest match is used. - If it is a function, it must return a table with two fields, `record` and - `retrieve`. `record` is called with a `TSMatch` and a potential node for the - `TSMatch`, and may return `true` to abort the selection-procedure. - `retrieve` must return either a `TSMatch`-`TSNode`-tuple (which is used as the - match) or `nil`, to signify that there is no match. - `lua/luasnip/extras/_treesitter.lua` contains the table - `builtin_tsnode_selectors`, which contains the implementations for - any/shortest/longest, which can be used as examples for more complicated - custom-selectors. - -The text of the matched node can be accessed as `snip.env.LS_TSMATCH`. The text -of the nodes returned as `NamedTSMatch` can be accessed as + the cursor. There are two options: + - `fun(parser: LuaSnip.extra.TSParser, pos: { [1]: number, [2]: number }): LuaSnip.extra.NamedTSMatch?, TSNode?` + Manually determine whether there is a matching node that ends just before + `pos` (the beginning of the trigger). + Return `nil,nil` if there is no match, otherwise first return a table + mapping names to nodes (the text, position and type of these will be + provided via `snip.env`), and second the node that is the matched node. + - `LuaSnip.extra.MatchTSNodeOpts`, which represents a query and provides all + captures of the matched pattern in `NamedTSMatch`. It contains the following + options: + - `query`, `string`: The query, in textual form. + - `query_name`, `string`: The name of the runtime-query to be used (passed + to `query.get()`), defaults to `"luasnip"` (so one could create a + file which only contains queries used by luasnip, like + `$CONFDIR/queries//luasnip.scm`, which might make sense to define + general concepts independent of a single snippet). + `query` and `query_name` are mutually exclusive, only one of both shall be + defined. + - `query_lang`, `string`: The language of the query. This is the only + required parameter to this function, since there’s no sufficiently + straightforward way to determine the language of the query for us. + Consider using `extend_override` to define a `ts_postfix`-function that + automatically fills in the language for the filetype of the snippet-file. + - `match_captures`, `string|string[]`: The capture(s) to use for determining + the actual prefix (so the node that should be immediately in front of the + trigger). This defaults to just `"prefix"`. + - `select`, `string?|fun(): LuaSnip.extra.MatchSelector`: Since there may be + multiple matching captures in front of the cursor, there has to be some + way to select the node that will actually be used. + If this is a string, it has to be one of "any", "shortest", or "longest", + which mean that any, the shortest, or the longest match is used. + If it is a function, it must return a table with two fields, `record` and + `retrieve`. `record` is called with a `TSMatch` and a potential node for the + `TSMatch`, and may return `true` to abort the selection-procedure. + `retrieve` must return either a `TSMatch`-`TSNode`-tuple (which is used as the + match) or `nil`, to signify that there is no match. + `lua/luasnip/extras/_treesitter.lua` contains the table + `builtin_tsnode_selectors`, which contains the implementations for + any/shortest/longest, which can be used as examples for more complicated + custom-selectors. + +The text of the matched node can be accessed as `snip.env.LS_TSMATCH`. +The text of the nodes returned as `NamedTSMatch` can be accessed as `snip.env.LS_TSCAPTURE_`, and their range and type as `snip.env.LS_TSDATA..range/type` (where range is a tuple of row-col-tuples, both 0-indexed). @@ -1892,26 +1920,26 @@ matched against - `LS_TSMATCH`: `{ "function add(a, b)", "\treturn a + b", "end" }` - `LS_TSDATA`: - >lua - { - body = { - range = { { 1, 1 }, { 1, 13 } }, - type = "block" - }, - fname = { - range = { { 0, 9 }, { 0, 12 } }, - type = "identifier" - }, - params = { - range = { { 0, 12 }, { 0, 18 } }, - type = "parameters" - }, - prefix = { - range = { { 0, 0 }, { 2, 3 } }, - type = "function_declaration" - } + >lua + { + body = { + range = { { 1, 1 }, { 1, 13 } }, + type = "block" + }, + fname = { + range = { { 0, 9 }, { 0, 12 } }, + type = "identifier" + }, + params = { + range = { { 0, 12 }, { 0, 18 } }, + type = "parameters" + }, + prefix = { + range = { { 0, 0 }, { 2, 3 } }, + type = "function_declaration" } - < + } + < - `LS_TSCAPTURE_FNAME`: `{ "add" }` - `LS_TSCAPTURE_PARAMS`: `{ "(a, b)" }` - `LS_TSCAPTURE_BODY`: `{ "return a + b" }` @@ -1922,15 +1950,16 @@ for each line) There is one important caveat when accessing `LS_TSDATA` in function/dynamicNodes: It won’t contain the values as specified here while -generating docstrings (in fact, it won’t even be a table). Since docstrings -have to be generated without any runtime-information, we just have to provide -dummy-data in `env`, which will be some kind of string related to the name of -the environment variable. Since the structure of `LS_TSDATA` obviously does not -fit that model, we can’t really handle it in a nice way (at least yet). So, -for now, best include a check like `local static_evaluation = -type(env.LS_TSDATA) == "string"`, and behave accordingly if `static_evaluation` -is true (for example, return some value tailored for displaying it in a -docstring). +generating docstrings (in fact, it won’t even be a table). +Since docstrings have to be generated without any runtime-information, we just +have to provide dummy-data in `env`, which will be some kind of string related +to the name of the environment variable. +Since the structure of `LS_TSDATA` obviously does not fit that model, we +can’t really handle it in a nice way (at least yet). So, for now, best +include a check like +`local static_evaluation = type(env.LS_TSDATA) == "string"`, and behave +accordingly if `static_evaluation` is true (for example, return some value +tailored for displaying it in a docstring). One more example, which actually uses a few captures: @@ -1959,17 +1988,17 @@ One more example, which actually uses a few captures: < The module `luasnip.extras.treesitter_postfix` contains a few functions that -may be useful for creating more efficient ts-postfix-snippets. Nested in -`builtin.tsnode_matcher` are: +may be useful for creating more efficient ts-postfix-snippets. +Nested in `builtin.tsnode_matcher` are: - `fun find_topmost_types(types: string[]): MatchTSNodeFunc`: Generates - a `LuaSnip.extra.MatchTSNodeFunc` which returns the last parent whose type - is in `types`. + a `LuaSnip.extra.MatchTSNodeFunc` which returns the last parent whose type + is in `types`. - `fun find_first_types(types: string[]): MatchTSNodeFunc`: Similar to - `find_topmost_types`, only this one matches the first parent whose type is in - types. + `find_topmost_types`, only this one matches the first parent whose type is in + types. - `find_nth_parent(n: number): MatchTSNodeFunc`: Simply matches the `n`-th - parent of the innermost node in front of the trigger. + parent of the innermost node in front of the trigger. With `find_topmost_types`, the first example can be implemented more efficiently (without needing a whole query): @@ -2007,9 +2036,9 @@ in a different buffer/window/tab. `sl.open(opts:table|nil)` - `opts`: optional arguments: - - `snip_info`: `snip_info(snippet) -> table representation of snippet` - - `printer`: `printer(snippets:table) -> any` - - `display`: `display(snippets:any)` + - `snip_info`: `snip_info(snippet) -> table representation of snippet` + - `printer`: `printer(snippets:table) -> any` + - `display`: `display(snippets:any)` Benefits include: syntax highlighting, searching, and customizability. @@ -2098,7 +2127,7 @@ An `options` table, which has some core functionality that can be used to customize 'common' settings, is provided. - `sl.options`: options table: - - `display`: `display(opts:table|nil) -> function(printer_result:string)` + - `display`: `display(opts:table|nil) -> function(printer_result:string)` You can see from the example above that making a custom display is a fairly involved process. What if you just wanted to change a buffer option like the @@ -2109,9 +2138,9 @@ behavior. `sl.options.display(opts:table|nil) -> function(printer_result:string)` - `opts`: optional arguments: - - `win_opts`: `table which has a {window_option = value} form` - - `buf_opts`: `table which has a {buffer_option = value} form` - - `get_name`: `get_name(buf) -> string` + - `win_opts`: `table which has a {window_option = value} form` + - `buf_opts`: `table which has a {buffer_option = value} form` + - `get_name`: `get_name(buf) -> string` Let’s recreate the custom display example above: @@ -2131,25 +2160,26 @@ Let’s recreate the custom display example above: SNIPPET LOCATION *luasnip-extras-snippet-location* This module can consume a snippets |luasnip-source|, more specifically, jump to -the location referred by it. This is primarily implemented for snippet which -got their source from one of the loaders, but might also work for snippets -where the source was set manually. +the location referred by it. +This is primarily implemented for snippet which got their source from one of +the loaders, but might also work for snippets where the source was set +manually. `require("luasnip.extras.snip_location")`: - `snip_location.jump_to_snippet(snip, opts)` - Jump to the definition of `snip`. - - `snip`: a snippet with attached source-data. - - `opts`: `nil|table`, optional arguments, valid keys are: - - `hl_duration_ms`: `number`, duration for which the definition should be highlighted, - in milliseconds. 0 disables the highlight. - - `edit_fn`: `function(file)`, this function will be called with the file - the snippet is located in, and is responsible for jumping to it. - We assume that after it has returned, the current buffer contains `file`. + Jump to the definition of `snip`. + - `snip`: a snippet with attached source-data. + - `opts`: `nil|table`, optional arguments, valid keys are: + - `hl_duration_ms`: `number`, duration for which the definition should be highlighted, + in milliseconds. 0 disables the highlight. + - `edit_fn`: `function(file)`, this function will be called with the file + the snippet is located in, and is responsible for jumping to it. + We assume that after it has returned, the current buffer contains `file`. - `snip_location.jump_to_active_snippet(opts)` - Jump to definition of active snippet. - - `opts`: `nil|table`, accepts the same keys as the `opts`-parameter of - `jump_to_snippet`. + Jump to definition of active snippet. + - `opts`: `nil|table`, accepts the same keys as the `opts`-parameter of + `jump_to_snippet`. ============================================================================== @@ -2158,7 +2188,8 @@ where the source was set manually. Most of LuaSnip’s functions have some arguments to control their behavior. Examples include `s`, where `wordTrig`, `regTrig`, … can be set in the first argument to the function, or `fmt`, where the delimiter can be set in the third -argument. This is all good and well, but if these functions are often used with +argument. +This is all good and well, but if these functions are often used with non-default settings, it can become cumbersome to always explicitly set them. This is where the `extend_decorator` comes in: it can be used to create @@ -2197,21 +2228,21 @@ although, for usage outside of LuaSnip, best copy the source file: - `fn`: the function. - `...`: any number of tables. Each specifies how to extend an argument of `fn`. - The tables accept: - - `arg_indx`, `number` (required): the position of the parameter to override. - - `extend`, `fn(arg, extend_value) -> effective_arg` (optional): this function - is used to extend the arguments passed to the decorated function. - It defaults to a function which just extends the arguments table with the - extend table (accepts `nil`). - This extend behavior is adaptable to accommodate `s`, where the first - argument may be string or table. + The tables accept: + - `arg_indx`, `number` (required): the position of the parameter to override. + - `extend`, `fn(arg, extend_value) -> effective_arg` (optional): this function + is used to extend the arguments passed to the decorated function. + It defaults to a function which just extends the arguments table with the + extend table (accepts `nil`). + This extend behavior is adaptable to accommodate `s`, where the first + argument may be string or table. `apply(fn, ...) -> decorated_fn`: - `fn`: the function to decorate. - `...`: The values to extend with. These should match the descriptions passed - in `register` (the argument first passed to `register` will be extended with - the first value passed here). + in `register` (the argument first passed to `register` will be extended with + the first value passed here). One more example for registering a new function: @@ -2257,7 +2288,7 @@ accurately: - if the `$0` is a placeholder with something other than just text inside - if the `$0` is a choice - if the `$0` is not an immediate child of the snippet (it could be inside a - placeholder: `"${1: $0 }"`) + placeholder: `"${1: $0 }"`) To remedy those incompatibilities, the invalid `$0` will be replaced with a tabstop/placeholder/choice which will be visited just before the new `$0`. This @@ -2290,13 +2321,13 @@ snippet body is parsed differently. TRANSFORMATIONS *luasnip-lsp-snippets-transformations* -To apply Variable/Placeholder-transformations -, +To apply +[Variable/Placeholder-transformations](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variable-transforms), LuaSnip needs to apply ECMAScript regular expressions. This is implemented by -relying on jsregexp . +relying on [jsregexp](https://github.com/kmarius/jsregexp). -The easiest (but potentially error-prone) way to install it is by calling `make -install_jsregexp` in the repository root. +The easiest (but potentially error-prone) way to install it is by calling +`make install_jsregexp` in the repository root. This process can be automated by `packer.nvim`: @@ -2306,9 +2337,9 @@ This process can be automated by `packer.nvim`: If this fails, first open an issue :P, and then try installing the `jsregexp`-LuaRock. This is also possible via `packer.nvim`, although actual -usage may require a small workaround, see here - or here -. +usage may require a small workaround, see +[here](https://github.com/wbthomason/packer.nvim/issues/593) or +[here](https://github.com/wbthomason/packer.nvim/issues/358). Alternatively, `jsregexp` can be cloned locally, `make`d, and the resulting `jsregexp.so` placed in some place where Neovim can find it (probably @@ -2355,20 +2386,20 @@ where: - `name`: `string` the names the namespace, can’t contain the character “_” - `opts` is a table containing (in every case `EnvVal` is the same as `string|list[string]`: - - `vars`: `(fn(name:string)->EnvVal) | map[string, EnvVal]` - Is a function that receives a string and returns a value for the var with that name - or a table from var name to a value - (in this case, if the value is a function it will be executed lazily once per snippet expansion). - - `init`: `fn(info: table)->map[string, EnvVal]` Returns - a table of variables that will set to the environment of the snippet on expansion, - use this for vars that have to be calculated in that moment or that depend on each other. - The `info` table argument contains `pos` (0-based position of the cursor on expansion), - the `trigger` of the snippet and the `captures` list. - - `eager`: `list[string]` names of variables that will be taken from `vars` and appended eagerly (like those in `init`) - - `multiline_vars`: `(fn(name:string)->bool)|map[string, bool]|bool|string[]` Says if certain vars are a table or just a string, - can be a function that get’s the name of the var and returns true if the var is a key, - a list of vars that are tables or a boolean for the full namespace, it’s false by default. Refer to - issue#510 for more information. + - `vars`: `(fn(name:string)->EnvVal) | map[string, EnvVal]` + Is a function that receives a string and returns a value for the var with that name + or a table from var name to a value + (in this case, if the value is a function it will be executed lazily once per snippet expansion). + - `init`: `fn(info: table)->map[string, EnvVal]` Returns + a table of variables that will set to the environment of the snippet on expansion, + use this for vars that have to be calculated in that moment or that depend on each other. + The `info` table argument contains `pos` (0-based position of the cursor on expansion), + the `trigger` of the snippet and the `captures` list. + - `eager`: `list[string]` names of variables that will be taken from `vars` and appended eagerly (like those in `init`) + - `multiline_vars`: `(fn(name:string)->bool)|map[string, bool]|bool|string[]` Says if certain vars are a table or just a string, + can be a function that get’s the name of the var and returns true if the var is a key, + a list of vars that are tables or a boolean for the full namespace, it’s false by default. Refer to + [issue#510](https://github.com/L3MON4D3/LuaSnip/issues/510#issuecomment-1209333698) for more information. The four fields of `opts` are optional but you need to provide either `init` or `vars`, and `eager` can’t be without `vars`. Also, you can’t use namespaces @@ -2414,9 +2445,9 @@ snippets as `$VAR_NAME`. The LSP specification states: ------------------------------------------------------------------------------ -With `$name` or `${name:default}` you can insert the value of a variable. When -a variable isn’t set, its default or the empty string is inserted. When a -variable is unknown (that is, its name isn’t defined) the name of the +With `$name` or `${name:default}` you can insert the value of a variable. +When a variable isn’t set, its default or the empty string is inserted. When +a variable is unknown (that is, its name isn’t defined) the name of the variable is inserted and it is transformed into a placeholder. ------------------------------------------------------------------------------ @@ -2443,48 +2474,48 @@ All loaders (except the `vscode-standalone-loader`) share a similar interface: where `opts` can contain the following keys: - `paths`: List of paths to load. Can be a table, or a single - comma-separated string. - The paths may begin with `~/` or `./` to indicate that the path is - relative to your `$HOME` or to the directory where your `$MYVIMRC` resides - (useful to add your snippets). - If not set, `runtimepath` is searched for - directories that contain snippets. This procedure differs slightly for - each loader: - - `lua`: the snippet-library has to be in a directory named - `"luasnippets"`. - - `snipmate`: similar to Lua, but the directory has to be `"snippets"`. - - `vscode`: any directory in `runtimepath` that contains a - `package.json` contributing snippets. + comma-separated string. + The paths may begin with `~/` or `./` to indicate that the path is + relative to your `$HOME` or to the directory where your `$MYVIMRC` resides + (useful to add your snippets). + If not set, `runtimepath` is searched for + directories that contain snippets. This procedure differs slightly for + each loader: + - `lua`: the snippet-library has to be in a directory named + `"luasnippets"`. + - `snipmate`: similar to Lua, but the directory has to be `"snippets"`. + - `vscode`: any directory in `runtimepath` that contains a + `package.json` contributing snippets. - `lazy_paths`: behaves essentially like `paths`, with two exceptions: if it is - `nil`, it does not default to `runtimepath`, and the paths listed here do not - need to exist, and will be loaded on creation. - LuaSnip will do its best to determine the path that this should resolve to, - but since the resolving we do is not very sophisticated it may produce - incorrect paths. Definitely check the log if snippets are not loaded as - expected. + `nil`, it does not default to `runtimepath`, and the paths listed here do not + need to exist, and will be loaded on creation. + LuaSnip will do its best to determine the path that this should resolve to, + but since the resolving we do is not very sophisticated it may produce + incorrect paths. Definitely check the log if snippets are not loaded as + expected. - `exclude`: List of languages to exclude, empty by default. - `include`: List of languages to include, includes everything by default. - `{override,default}_priority`: These keys are passed straight to the - `add_snippets`-calls (documented in |luasnip-api|) and can therefore change the - priority of snippets loaded from some collection (or, in combination with - `{in,ex}clude`, only some of its snippets). + `add_snippets`-calls (documented in |luasnip-api|) and can therefore change the + priority of snippets loaded from some collection (or, in combination with + `{in,ex}clude`, only some of its snippets). - `fs_event_providers`: `table?`, specifies which mechanisms - should be used to watch files for updates/creation. - If `autocmd` is set to `true`, a `BufWritePost`-hook watches files of this - collection, if `libuv` is set, the `file-watcher-api` exposed by `libuv` is used - to watch for updates. - Use `libuv` if you want snippets to update from other Neovim-instances, and - `autocmd` if the collection resides on a file system where the `libuv`-watchers - may not work correctly. Or, of course, just enable both :D - By default, only `autocmd` is enabled. + should be used to watch files for updates/creation. + If `autocmd` is set to `true`, a `BufWritePost`-hook watches files of this + collection, if `libuv` is set, the `file-watcher-api` exposed by `libuv` is used + to watch for updates. + Use `libuv` if you want snippets to update from other Neovim-instances, and + `autocmd` if the collection resides on a file system where the `libuv`-watchers + may not work correctly. Or, of course, just enable both :D + By default, only `autocmd` is enabled. While `load` will immediately load the snippets, `lazy_load` will defer loading until the snippets are actually needed (whenever a new buffer is created, or the filetype is changed LuaSnip actually loads `lazy_load`ed snippets for the filetypes associated with this buffer. This association can be changed by customizing `load_ft_func` in `setup`: the option takes a function that, passed -a `bufnr`, returns the filetypes that should be loaded (`fn(bufnr) -> filetypes -(string[])`)). +a `bufnr`, returns the filetypes that should be loaded +(`fn(bufnr) -> filetypes (string[])`)). All of the loaders support reloading, so simply editing any file contributing snippets will reload its snippets (according to `fs_event_providers` in the @@ -2512,16 +2543,17 @@ is exclusively determined by the `language` associated with a file in `vscodes`’ `package.json`, and the file/directory-name in `lua`. - This can be resolved relatively easily in `vscode`, where the `language` - advertised in `package.json` can just be a superset of the `scope`s in the file. + advertised in `package.json` can just be a superset of the `scope`s in the file. - Another simplistic solution is to set the language to `all` (in `lua`, it might - make sense to create a directory `luasnippets/all/*.lua` to group these files - together). + make sense to create a directory `luasnippets/all/*.lua` to group these files + together). - Another approach is to modify `load_ft_func` to load a custom filetype if the - snippets should be activated, and store the snippets in a file for that - filetype. This can be used to group snippets by e.g. framework, and load them - once a file belonging to such a framework is edited. + snippets should be activated, and store the snippets in a file for that + filetype. This can be used to group snippets by e.g. framework, and load them + once a file belonging to such a framework is edited. -**Example**: `react.lua` +**Example**: +`react.lua` >lua return { @@ -2551,7 +2583,7 @@ having issues adding snippets via loaders. VS-CODE *luasnip-loaders-vs-code* As a reference on the structure of these snippet libraries, see -friendly-snippets . +[friendly-snippets](https://github.com/rafamadriz/friendly-snippets). We support a small extension: snippets can contain LuaSnip-specific options in the `luasnip`-table: @@ -2570,10 +2602,9 @@ the `luasnip`-table: } < -Files with the extension `jsonc` will be parsed as `jsonc`, `json` with -comments -, while -`*.json` are parsed with a regular `json` parser, where comments are +Files with the extension `jsonc` will be parsed as `jsonc`, +[`json` with comments](https://code.visualstudio.com/docs/languages/json#_json-with-comments), +while `*.json` are parsed with a regular `json` parser, where comments are disallowed. (the `json` parser is a bit faster, so don’t default to `jsonc` if it’s not necessary). @@ -2665,16 +2696,16 @@ set. If `scope` is not set, the snippet will be added to the global filetype `require("luasnip.loaders.from_vscode").load_standalone(opts)` - `opts`: `table`, can contain the following keys: - - `path`: `string`, Path to the `*.code-snippets`-file that should be loaded. - Just like the paths in `load`, this one can begin with a `"~/"` to be - relative to `$HOME`, and a `"./"` to be relative to the - Neovim config directory. - - `{override,default}_priority`: These keys are passed straight to the - `add_snippets`-calls (documented in |luasnip-api|) and can be used to change - the priority of the loaded snippets. - - `lazy`: `boolean`, if it is set, the file does not have to exist when - `load_standalone` is called, and it will be loaded on creation. - `false` by default. + - `path`: `string`, Path to the `*.code-snippets`-file that should be loaded. + Just like the paths in `load`, this one can begin with a `"~/"` to be + relative to `$HOME`, and a `"./"` to be relative to the + Neovim config directory. + - `{override,default}_priority`: These keys are passed straight to the + `add_snippets`-calls (documented in |luasnip-api|) and can be used to change + the priority of the loaded snippets. + - `lazy`: `boolean`, if it is set, the file does not have to exist when + `load_standalone` is called, and it will be loaded on creation. + `false` by default. **Example**: `a.code-snippets`: @@ -2723,8 +2754,9 @@ This file can be loaded by calling SNIPMATE *luasnip-loaders-snipmate* Luasnip does not support the full SnipMate format: Only `./{ft}.snippets` and -`./{ft}/*.snippets` will be loaded. See honza/vim-snippets - for lots of examples. +`./{ft}/*.snippets` will be loaded. See +[honza/vim-snippets](https://github.com/honza/vim-snippets) for lots of +examples. Like VSCode, the SnipMate format is also extended to make use of some of LuaSnip’s more advanced capabilities: @@ -2767,57 +2799,57 @@ This can, again, be loaded with any of Stuff to watch out for: - Using both `extends ` in `.snippets` and - `ls.filetype_extend("", {""})` leads to duplicate snippets. + `ls.filetype_extend("", {""})` leads to duplicate snippets. - `${VISUAL}` will be replaced by `$TM_SELECTED_TEXT` to make the snippets - compatible with LuaSnip + compatible with LuaSnip - We do not implement `eval` using ` (backtick). This may be implemented in the - future. + future. LUA *luasnip-loaders-lua* Instead of adding all snippets via `add_snippets`, it’s possible to store -them in separate files and load all of those. The file-structure here is -exactly the supported SnipMate-structure, e.g. `.lua` or `/*.lua` to -add snippets for the filetype ``. +them in separate files and load all of those. +The file-structure here is exactly the supported SnipMate-structure, e.g. +`.lua` or `/*.lua` to add snippets for the filetype ``. There are two ways to add snippets: - the files may return two lists of snippets, the snippets in the first are all - added as regular snippets, while the snippets in the second will be added as - autosnippets (both are the defaults, if a snippet defines a different - `snippetType`, that will have preference) + added as regular snippets, while the snippets in the second will be added as + autosnippets (both are the defaults, if a snippet defines a different + `snippetType`, that will have preference) - snippets can also be appended to the global (only for these files - they are - not visible anywhere else) tables `ls_file_snippets` and - `ls_file_autosnippets`. This can be combined with a custom `snip_env` to define - and add snippets with one function call: - >lua - ls.setup({ - snip_env = { - s = function(...) - local snip = ls.s(...) - -- we can't just access the global `ls_file_snippets`, since it will be - -- resolved in the environment of the scope in which it was defined. - table.insert(getfenv(2).ls_file_snippets, snip) - end, - parse = function(...) - local snip = ls.parser.parse_snippet(...) - table.insert(getfenv(2).ls_file_snippets, snip) - end, - -- remaining definitions. - ... - }, - ... - }) - < - This is more flexible than the previous approach since the snippets don’t - have to be collected; they just have to be defined using the above `s` and - `parse`. + not visible anywhere else) tables `ls_file_snippets` and + `ls_file_autosnippets`. This can be combined with a custom `snip_env` to define + and add snippets with one function call: + >lua + ls.setup({ + snip_env = { + s = function(...) + local snip = ls.s(...) + -- we can't just access the global `ls_file_snippets`, since it will be + -- resolved in the environment of the scope in which it was defined. + table.insert(getfenv(2).ls_file_snippets, snip) + end, + parse = function(...) + local snip = ls.parser.parse_snippet(...) + table.insert(getfenv(2).ls_file_snippets, snip) + end, + -- remaining definitions. + ... + }, + ... + }) + < + This is more flexible than the previous approach since the snippets don’t + have to be collected; they just have to be defined using the above `s` and + `parse`. As defining all of the snippet constructors (`s`, `c`, `t`, …) in every file is rather cumbersome, LuaSnip will bring some globals into scope for executing -these files. By default, the names from `luasnip.config.snip_env` - +these files. By default, the names from +[`luasnip.config.snip_env`](https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/config.lua#L22-L48) will be used, but it’s possible to customize them by setting `snip_env` in `setup`. @@ -2854,64 +2886,98 @@ One side-effect of the injected globals is that language servers, for example `lua-language-server`, do not know about them, which means that snippet-files may have many diagnostics about missing symbols. -There are a few ways to fix this * Add all variables in `snip_env` to -`Lua.diagnostic.globals`: `lua -- wherever your lua-language-server lsp -settings are defined: settings = { Lua = { ... diagnostics = { globals = { -"vim", "s", "c", "t", ... } } } }` This will disable the warnings, but will do -so in all files these lsp-settings are used with. Similarly, adding -`---@diagnostic disable: undefined-global` to the snippet-files is also -possible, but this affects not only the variables in `snip_env`, but all -variables, like local variable names that may be mistyped. * A more complete, -and only slightly more complicated solution is using `lua-language-server`’s -definition files . Add a file -with the line `---@meta`, followed by the variables defined by the `snip_env` -to any directory listed in the `workspace.library`-settings for -`lua-langue-server` (one likely directory is `vim.fn.stdpath("config")/lua`, -check `:checkhealth lsp` in a lua file to be sure). ```lua —@meta - -s = require("luasnip.nodes.snippet").S sn = require("luasnip.nodes.snippet").SN -isn = require("luasnip.nodes.snippet").ISN t = -require("luasnip.nodes.textNode").T i = require("luasnip.nodes.insertNode").I f -= require("luasnip.nodes.functionNode").F c = -require("luasnip.nodes.choiceNode").C d = -require("luasnip.nodes.dynamicNode").D r = -require("luasnip.nodes.restoreNode").R events = require("luasnip.util.events") -k = require("luasnip.nodes.key_indexer").new_key ai = -require("luasnip.nodes.absolute_indexer") extras = require("luasnip.extras") l -= require("luasnip.extras").lambda rep = require("luasnip.extras").rep p = -require("luasnip.extras").partial m = require("luasnip.extras").match n = -require("luasnip.extras").nonempty dl = -require("luasnip.extras").dynamic_lambda fmt = -require("luasnip.extras.fmt").fmt fmta = require("luasnip.extras.fmt").fmta -conds = require("luasnip.extras.expand_conditions") postfix = -require("luasnip.extras.postfix").postfix types = require("luasnip.util.types") -parse = require("luasnip.util.parser").parse_snippet ms = -require("luasnip.nodes.multiSnippet").new_multisnippet ``` - -While that allows the `snip_env`-variables to resolve correctly in -snippet-files, it also resolves them in other lua files. This can be fixed by -putting the file in a directory that is _not_ in `workspace.library` (create, -for example, `vim.fn.stdpath("state")/luasnip-snip_env/`), and then adding its -path to `workspace.library` only for snippet files, for example by putting a -`.luarc.json` with the following content into all snippet-directories: `json { -"workspace.library": ["/luasnip-snip_env"] }` +There are a few ways to fix this + +- Add all variables in `snip_env` to `Lua.diagnostic.globals`: + >lua + -- wherever your lua-language-server lsp settings are defined: + settings = { + Lua = { + ... + diagnostics = { + globals = { + "vim", + "s", + "c", + "t", + ... + } + } + } + } + < + This will disable the warnings, but will do so in all files these lsp-settings + are used with. + Similarly, adding `---@diagnostic disable: undefined-global` to the + snippet-files is also possible, but this affects not only the variables in + `snip_env`, but all variables, like local variable names that may be mistyped. +- A more complete, and only slightly more complicated solution is using + `lua-language-server`’s + [definition files](https://luals.github.io/wiki/definition-files/). + Add a file with the line `---@meta`, followed by the variables defined by the + `snip_env` to any directory listed in the `workspace.library`-settings for + `lua-langue-server` (one likely directory is `vim.fn.stdpath("config")/lua`, + check `:checkhealth lsp` in a lua file to be sure). + >lua + ---@meta + + s = require("luasnip.nodes.snippet").S + sn = require("luasnip.nodes.snippet").SN + isn = require("luasnip.nodes.snippet").ISN + t = require("luasnip.nodes.textNode").T + i = require("luasnip.nodes.insertNode").I + f = require("luasnip.nodes.functionNode").F + c = require("luasnip.nodes.choiceNode").C + d = require("luasnip.nodes.dynamicNode").D + r = require("luasnip.nodes.restoreNode").R + events = require("luasnip.util.events") + k = require("luasnip.nodes.key_indexer").new_key + ai = require("luasnip.nodes.absolute_indexer") + extras = require("luasnip.extras") + l = require("luasnip.extras").lambda + rep = require("luasnip.extras").rep + p = require("luasnip.extras").partial + m = require("luasnip.extras").match + n = require("luasnip.extras").nonempty + dl = require("luasnip.extras").dynamic_lambda + fmt = require("luasnip.extras.fmt").fmt + fmta = require("luasnip.extras.fmt").fmta + conds = require("luasnip.extras.expand_conditions") + postfix = require("luasnip.extras.postfix").postfix + types = require("luasnip.util.types") + parse = require("luasnip.util.parser").parse_snippet + ms = require("luasnip.nodes.multiSnippet").new_multisnippet + < + While that allows the `snip_env`-variables to resolve correctly in + snippet-files, it also resolves them in other lua files. This can be fixed by + putting the file in a directory that is _not_ in `workspace.library` (create, + for example, `vim.fn.stdpath("state")/luasnip-snip_env/`), and then adding its + path to `workspace.library` only for snippet files, for example by putting a + `.luarc.json` with the following content into all snippet-directories: + >json + { + "workspace.library": ["/luasnip-snip_env"] + } + < RELOADING WHEN EDITING REQUIRE’D FILES ~ While the `lua-snippet-files` will be reloaded on edit, this does not automatically happen if a file the snippet-file depends on (e.g. via -`require`) is changed. Since this still may still be desirable, there are two -functions exposed when a file is loaded by the `lua-loader`: -`ls_tracked_dofile` and `ls_tracked_dopackage`. They perform like `dofile` and -(almost like) `require`, but both register the loaded file internally as a -dependency of the snippet-file, so it can be reloaded when the loaded file is -edited. As stated, `ls_tracked_dofile` behaves exactly like `dofile`, but does -the dependency-work as well. `ls_tracked_dopackage` mimics `require` in that it -does not take a path, but a module-name like `"luasnip.loaders.from_lua"`, and -then searches the `runtimepath/lua`-directories, and `path` and `cpath` for the -module. Unlike `require`, the file will not be cached, since that would -complicate the reload-on-edit-behavior. +`require`) is changed. +Since this still may still be desirable, there are two functions exposed when a +file is loaded by the `lua-loader`: `ls_tracked_dofile` and +`ls_tracked_dopackage`. They perform like `dofile` and (almost like) `require`, +but both register the loaded file internally as a dependency of the +snippet-file, so it can be reloaded when the loaded file is edited. As stated, +`ls_tracked_dofile` behaves exactly like `dofile`, but does the dependency-work +as well. +`ls_tracked_dopackage` mimics `require` in that it does not take a path, but a +module-name like `"luasnip.loaders.from_lua"`, and then searches the +`runtimepath/lua`-directories, and `path` and `cpath` for the module. +Unlike `require`, the file will not be cached, since that would complicate the +reload-on-edit-behavior. EDIT_SNIPPETS *luasnip-loaders-edit_snippets* @@ -2926,54 +2992,56 @@ and then (if there are multiple) the associated file to edit. `opts` contains four settings: - `ft_filter`: `fn(filetype:string) -> bool` Optionally filter initially listed - filetypes. `true` -> filetype will be listed, `false` -> not listed. Accepts - all filetypes by default. -- `format`: `fn(file:string, source_name:string) -> string|nil` `file` is simply - the path to the file, `source_name` is one of `"lua"`, `"snipmate"` or - `"vscode"`. If a string is returned, it is used as the title of the item, `nil` - on the other hand will filter out this item. The default simply replaces some - long strings (packer-path and config-path) in `file` with shorter, symbolic - names (`"$PLUGINS"`, `"$CONFIG"`), but this can be extended to - - filter files from some specific source/path - - more aggressively shorten paths using symbolic names, e.g. - `"$FRIENDLY_SNIPPETS"`. Example: hide the `*.lua` snippet files, and shorten - the path with `$LuaSnip`: - >lua - require "luasnip.loaders" .edit_snippet_files { - format = function(file, source_name) - if source_name == "lua" then return nil - else return file:gsub("/root/.config/nvim/luasnippets", "$LuaSnip") - end - end - } - < -- `edit`: `fn(file:string)` This function is supposed to open the file for - editing. The default is a simple `vim.cmd("edit " .. file)` (replace the - current buffer), but one could open the file in a split, a tab, or a floating - window, for example. -- `extend`: `fn(ft:string, ft_paths:string[]) -> (string,string)[]` This function - can be used to create additional choices for the file-selection. - - `ft`: The filetype snippet-files are queried for. - - `ft_paths`: list of paths to the known snippet files. - The function should return a list of `(string,string)`-tuples. The first of - each pair is the label that will appear in the selection-prompt, and the second - is the path that will be passed to the `edit()` function if that item was - selected. - This can be used to create a new snippet file for the current filetype: + filetypes. `true` -> filetype will be listed, `false` -> not listed. Accepts + all filetypes by default. +- `format`: `fn(file:string, source_name:string) -> string|nil` + `file` is simply the path to the file, `source_name` is one of `"lua"`, + `"snipmate"` or `"vscode"`. + If a string is returned, it is used as the title of the item, `nil` on the + other hand will filter out this item. + The default simply replaces some long strings (packer-path and config-path) in + `file` with shorter, symbolic names (`"$PLUGINS"`, `"$CONFIG"`), but this can + be extended to + - filter files from some specific source/path + - more aggressively shorten paths using symbolic names, e.g. + `"$FRIENDLY_SNIPPETS"`. + Example: hide the `*.lua` snippet files, and shorten the path with `$LuaSnip`: >lua - require("luasnip.loaders").edit_snippet_files { - extend = function(ft, paths) - if #paths == 0 then - return { - { "$CONFIG/" .. ft .. ".snippets", - string.format("%s/%s.snippets", , ft) } - } + require "luasnip.loaders" .edit_snippet_files { + format = function(file, source_name) + if source_name == "lua" then return nil + else return file:gsub("/root/.config/nvim/luasnippets", "$LuaSnip") + end end - - return {} - end } < +- `edit`: `fn(file:string)` This function is supposed to open the file for + editing. The default is a simple `vim.cmd("edit " .. file)` (replace the + current buffer), but one could open the file in a split, a tab, or a floating + window, for example. +- `extend`: `fn(ft:string, ft_paths:string[]) -> (string,string)[]` + This function can be used to create additional choices for the file-selection. + - `ft`: The filetype snippet-files are queried for. + - `ft_paths`: list of paths to the known snippet files. + The function should return a list of `(string,string)`-tuples. The first of + each pair is the label that will appear in the selection-prompt, and the second + is the path that will be passed to the `edit()` function if that item was + selected. + This can be used to create a new snippet file for the current filetype: + >lua + require("luasnip.loaders").edit_snippet_files { + extend = function(ft, paths) + if #paths == 0 then + return { + { "$CONFIG/" .. ft .. ".snippets", + string.format("%s/%s.snippets", , ft) } + } + end + + return {} + end + } + < One comfortable way to call this function is registering it as a command: @@ -3009,9 +3077,9 @@ while this will parse the snippet upon expansion: - `context`: exactly the same as the first argument passed to `ls.s`. - `body`: the snippet body. - `opts`: accepts the same `opts` as `ls.s`, with some additions: - - `parse_fn`: the function for parsing the snippet. Defaults to - `ls.parser.parse_snippet` (the parser for LSP snippets), an alternative is - the parser for SnipMate snippets (`ls.parser.parse_snipmate`). + - `parse_fn`: the function for parsing the snippet. Defaults to + `ls.parser.parse_snippet` (the parser for LSP snippets), an alternative is + the parser for SnipMate snippets (`ls.parser.parse_snipmate`). ============================================================================== @@ -3262,7 +3330,7 @@ Other issues will have to be handled manually by checking the contents of e.g. }) < -Refer to #515 for a better +Refer to [#515](https://github.com/L3MON4D3/LuaSnip/pull/515) for a better example to understand `docTrig` and `docstring`. @@ -3291,12 +3359,12 @@ Events can be used to react to some action inside snippets. These callbacks can be defined per snippet (`callbacks`-key in snippet constructor), per-node by passing them as `node_callbacks` in `node_opts`, or globally (autocommand). -`callbacks`: `fn(node[, event_args]) -> event_res` All callbacks receive the -`node` associated with the event and event-specific optional arguments, -`event_args`. `event_res` is only used in one event, `pre_expand`, where some -properties of the snippet can be changed. If multiple callbacks return -`event_res`, we only guarantee that one of them will be effective, not all of -them. +`callbacks`: `fn(node[, event_args]) -> event_res` +All callbacks receive the `node` associated with the event and event-specific +optional arguments, `event_args`. `event_res` is only used in one event, +`pre_expand`, where some properties of the snippet can be changed. If multiple +callbacks return `event_res`, we only guarantee that one of them will be +effective, not all of them. `autocommand`: Luasnip uses `User`-events. Autocommands for these can be registered using @@ -3322,25 +3390,25 @@ The node and `event_args` can be accessed through `require("luasnip").session`: **Events**: - `enter/leave`: Called when a node is entered/left (for example when jumping - around in a snippet). - `User-event`: `"Luasnip{Enter,Leave}"`, with `` in - PascalCase, e.g. `InsertNode` or `DynamicNode`. - `event_args`: none + around in a snippet). + `User-event`: `"Luasnip{Enter,Leave}"`, with `` in + PascalCase, e.g. `InsertNode` or `DynamicNode`. + `event_args`: none - `change_choice`: When the active choice in a `choiceNode` is changed. - `User-event`: `"LuasnipChangeChoice"` - `event_args`: none + `User-event`: `"LuasnipChangeChoice"` + `event_args`: none - `pre_expand`: Called before a snippet is expanded. Modifying text is allowed, - the expand-position will be adjusted so the snippet expands at the same - position relative to existing text. - `User-event`: `"LuasnipPreExpand"` - `event_args`: - - `expand_pos`: `{, }`, position at which the snippet will be - expanded. `` and `` are both 0-indexed. - - `expand_pos_mark_id`: `number`, the id of the extmark LuaSnip uses to track - `expand_pos`. This may be moved around freely. - `event_res`: - - `env_override`: `map string->(string[]|string)`, override or extend the - snippet’s environment (`snip.env`). + the expand-position will be adjusted so the snippet expands at the same + position relative to existing text. + `User-event`: `"LuasnipPreExpand"` + `event_args`: + - `expand_pos`: `{, }`, position at which the snippet will be + expanded. `` and `` are both 0-indexed. + - `expand_pos_mark_id`: `number`, the id of the extmark LuaSnip uses to track + `expand_pos`. This may be moved around freely. + `event_res`: + - `env_override`: `map string->(string[]|string)`, override or extend the + snippet’s environment (`snip.env`). A pretty useless, beyond serving as an example here, application of these would be printing e.g. the node’s text after entering: @@ -3425,25 +3493,25 @@ It is also possible to get/set the source of a snippet via API: `ls.snippet_source`: - `get(snippet) -> source_data`: - Retrieve the source-data of `snippet`. `source_data` always contains the key - `file`, the file in which the snippet was defined, and may additionally - contain `line` or `line_end`, the first and last line of the definition. + Retrieve the source-data of `snippet`. `source_data` always contains the key + `file`, the file in which the snippet was defined, and may additionally + contain `line` or `line_end`, the first and last line of the definition. - `set(snippet, source)`: - Set the source of a snippet. - - `snippet`: a snippet which was added via `ls.add_snippets`. - - `source`: a `source`-object, obtained from either `from_debuginfo` or - `from_location`. + Set the source of a snippet. + - `snippet`: a snippet which was added via `ls.add_snippets`. + - `source`: a `source`-object, obtained from either `from_debuginfo` or + `from_location`. - `from_location(file, opts) -> source`: - - `file`: `string`, The path to the file in which the snippet is defined. - - `opts`: `table|nil`, optional parameters for the source. - - `line`: `number`, the first line of the definition. 1-indexed. - - `line_end`: `number`, the final line of the definition. 1-indexed. + - `file`: `string`, The path to the file in which the snippet is defined. + - `opts`: `table|nil`, optional parameters for the source. + - `line`: `number`, the first line of the definition. 1-indexed. + - `line_end`: `number`, the final line of the definition. 1-indexed. - `from_debuginfo(debuginfo) -> source`: - Generates source from the table returned by `debug.getinfo` (from now on - referred to as `debuginfo`). `debuginfo` has to be of a frame of a function - which is backed by a file, and has to contain this information, i.e. has to be - generated by `debug.get_info(*, "Sl")` (at least `"Sl"`, it may also contain - more info). + Generates source from the table returned by `debug.getinfo` (from now on + referred to as `debuginfo`). `debuginfo` has to be of a frame of a function + which is backed by a file, and has to contain this information, i.e. has to be + generated by `debug.get_info(*, "Sl")` (at least `"Sl"`, it may also contain + more info). ============================================================================== @@ -3455,21 +3523,25 @@ selecting and then yanking (and usually also cutting) text from the buffer before expanding. By default, this is disabled (as to not pollute keybindings which may be used -for something else), so one has to * either set `cut_selection_keys` in `setup` -(see |luasnip-config-options|). * or map `ls.cut_keys` as the right-hand-side -of a mapping * or manually configure the keybinding. For this, create a new -keybinding that 1. ``es to NORMAL (to populate the `<` and `>`-markers) 2. -calls `luasnip.pre_yank()` 3. yanks text to some named register -`` 4. calls `luasnip.post_yank()` Take care that the -yanking actually takes place between the two calls. One way to ensure this is -to call the two functions via `lua ...`: `lua vim.keymap.set("v", -"", [[lua -require("luasnip.util.select").pre_yank("z")gv"zslua -require('luasnip.util.select').post_yank("z")]])` The reason for this -specific order is to allow us to take a snapshot of registers (in the -pre-callback), and then restore them (in the post-callback) (so that we may get -the visual selection directly from the register, which seems to be the most -foolproof way of doing this). +for something else), so one has to + +- either set `cut_selection_keys` in `setup` (see + |luasnip-config-options|). +- or map `ls.cut_keys` as the right-hand-side of a mapping +- or manually configure the keybinding. For this, create a new keybinding that + 1. ``es to NORMAL (to populate the `<` and `>`-markers) + 2. calls `luasnip.pre_yank()` + 3. yanks text to some named register `` + 4. calls `luasnip.post_yank()` + Take care that the yanking actually takes place between the two calls. One way + to ensure this is to call the two functions via `lua ...`: + >lua + vim.keymap.set("v", "", [[lua require("luasnip.util.select").pre_yank("z")gv"zslua require('luasnip.util.select').post_yank("z")]]) + < + The reason for this specific order is to allow us to take a snapshot of + registers (in the pre-callback), and then restore them (in the post-callback) + (so that we may get the visual selection directly from the register, which + seems to be the most foolproof way of doing this). ============================================================================== @@ -3478,77 +3550,79 @@ foolproof way of doing this). These are the settings you can provide to `luasnip.setup()`: - `keep_roots`: Whether snippet-roots should be linked. See - |luasnip-basics-snippet-insertion| for more context. + |luasnip-basics-snippet-insertion| for more context. - `link_roots`: Whether snippet-roots should be linked. See - |luasnip-basics-snippet-insertion| for more context. + |luasnip-basics-snippet-insertion| for more context. - `exit_roots`: Whether snippet-roots should exit at reaching at their last node, - `$0`. This setting is only valid for root snippets, not child snippets. This - setting may avoid unexpected behavior by disallowing to jump earlier (finished) - snippets. Check |luasnip-basics-snippet-insertion| for more information on - snippet-roots. + `$0`. This setting is only valid for root snippets, not child snippets. This + setting may avoid unexpected behavior by disallowing to jump earlier (finished) + snippets. Check |luasnip-basics-snippet-insertion| for more information on + snippet-roots. - `link_children`: Whether children should be linked. See - |luasnip-basics-snippet-insertion| for more context. + |luasnip-basics-snippet-insertion| for more context. - `history` (deprecated): if not nil, `keep_roots`, `link_roots`, and - `link_children` will be set to the value of `history`, and `exit_roots` will - set to inverse value of `history`. This is just to ensure - backwards-compatibility. + `link_children` will be set to the value of `history`, and `exit_roots` will + set to inverse value of `history`. This is just to ensure + backwards-compatibility. - `update_events`: Choose which events trigger an update of the active nodes’ - dependents. Default is just `'InsertLeave'`, `'TextChanged,TextChangedI'` would - update on every change. These, like all other `*_events` are passed to - `nvim_create_autocmd` as `events`, so they can be wrapped in a table, like - >lua - ls.setup({ - update_events = {"TextChanged", "TextChangedI"} - }) - < + dependents. Default is just `'InsertLeave'`, `'TextChanged,TextChangedI'` would + update on every change. These, like all other `*_events` are passed to + `nvim_create_autocmd` as `events`, so they can be wrapped in a table, like + >lua + ls.setup({ + update_events = {"TextChanged", "TextChangedI"} + }) + < - `region_check_events`: Events on which to leave the current snippet-root if the - cursor is outside its’ 'region'. Disabled by default, `'CursorMoved'`, - `'CursorHold'` or `'InsertEnter'` seem reasonable. + cursor is outside its’ 'region'. Disabled by default, `'CursorMoved'`, + `'CursorHold'` or `'InsertEnter'` seem reasonable. - `delete_check_events`: When to check if the current snippet was deleted, and if - so, remove it from the history. Off by default, `'TextChanged'` (perhaps - `'InsertLeave'`, to react to changes done in Insert mode) should work just fine - (alternatively, this can also be mapped using `luasnip-delete-check`). + so, remove it from the history. Off by default, `'TextChanged'` (perhaps + `'InsertLeave'`, to react to changes done in Insert mode) should work just fine + (alternatively, this can also be mapped using `luasnip-delete-check`). - `cut_selection_keys`: Mapping for populating `TM_SELECTED_TEXT` and related - variables (not set by default). See |luasnip-selection| for more information. + variables (not set by default). + See |luasnip-selection| for more information. - `store_selection_keys` (deprecated): same as `cut_selection_keys` - `enable_autosnippets`: Autosnippets are disabled by default to minimize - performance penalty if unused. Set to `true` to enable. + performance penalty if unused. Set to `true` to enable. - `ext_opts`: Additional options passed to extmarks. Can be used to add - passive/active highlight on a per-node-basis (more info in `DOC.md`) + passive/active highlight on a per-node-basis (more info in `DOC.md`) - `parser_nested_assembler`: Override the default behavior of inserting a - `choiceNode` containing the nested snippet and an empty `insertNode` for nested - placeholders (`"${1: ${2: this is nested}}"`). For an example (behavior more - similar to VSCode), check here - + `choiceNode` containing the nested snippet and an empty `insertNode` for nested + placeholders (`"${1: ${2: this is nested}}"`). For an example (behavior more + similar to VSCode), check + [here](https://github.com/L3MON4D3/LuaSnip/wiki/Nice-Configs#imitate-vscodes-behaviour-for-nested-placeholders) - `ft_func`: Source of possible filetypes for snippets. Defaults to a function, - which returns `vim.split(vim.bo.filetype, ".", true)`, but check - filetype_functions or the - |luasnip-extras-filetype-functions|-section for more options. + which returns `vim.split(vim.bo.filetype, ".", true)`, but check + [filetype_functions](lua/luasnip/extras/filetype_functions.lua) or the + |luasnip-extras-filetype-functions|-section for more options. - `load_ft_func`: Function to determine which filetypes belong to a given buffer - (used for `lazy_loading`). `fn(bufnr) -> filetypes (string[])`. Again, there - are some examples in filetype_functions - . + (used for `lazy_loading`). `fn(bufnr) -> filetypes (string[])`. Again, there + are some examples in + [filetype_functions](lua/luasnip/extras/filetype_functions.lua). - `snip_env`: The best way to author snippets in Lua involves the `lua-loader` - (see |luasnip-loaders-lua|). Unfortunately, this requires that snippets are - defined in separate files, which means that common definitions like `s`, `i`, - `sn`, `t`, `fmt`, … have to be repeated in each of them, and that adding more - customized functions to ease writing snippets also requires some setup. - `snip_env` can be used to insert variables into exactly the places where - `lua-snippets` are defined (for now only the file loaded by the `lua-loader`). - Setting `snip_env` to `{ some_global = "a value" }` will add (amongst the - defaults stated at the beginning of this documentation) the global variable - `some_global` while evaluating these files. There are special keys which, when - set in `snip_env` change the behavior of this option, and are not passed - through to the `lua-files`: - - `__snip_env_behaviour`, string: either `"set"` or `"extend"` (default - `"extend"`) - If this is `"extend"`, the variables defined in `snip_env` will complement (and - override) the defaults. If this is not desired, `"set"` will not include the - defaults, but only the variables set here. + (see |luasnip-loaders-lua|). Unfortunately, this requires that snippets are + defined in separate files, which means that common definitions like `s`, `i`, + `sn`, `t`, `fmt`, … have to be repeated in each of them, and that adding more + customized functions to ease writing snippets also requires some setup. + `snip_env` can be used to insert variables into exactly the places where + `lua-snippets` are defined (for now only the file loaded by the `lua-loader`). + Setting `snip_env` to `{ some_global = "a value" }` will add (amongst the + defaults stated at the beginning of this documentation) the global variable + `some_global` while evaluating these files. + There are special keys which, when set in `snip_env` change the behavior of + this option, and are not passed through to the `lua-files`: + - `__snip_env_behaviour`, string: either `"set"` or `"extend"` (default + `"extend"`) + If this is `"extend"`, the variables defined in `snip_env` will complement (and + override) the defaults. If this is not desired, `"set"` will not include the + defaults, but only the variables set here. - `loaders_store_source`, boolean, whether loaders should store the source of the - loaded snippets. Enabling this means that the definition of any snippet can be - jumped to via |luasnip-extras-snippet-location|, but also entails slightly - increased memory consumption (and load-time, but it’s not really noticeable). + loaded snippets. + Enabling this means that the definition of any snippet can be jumped to via + |luasnip-extras-snippet-location|, but also entails slightly increased memory + consumption (and load-time, but it’s not really noticeable). ============================================================================== @@ -3563,64 +3637,63 @@ ADDING SNIPPETS *luasnip-troubleshooting-adding-snippets* LOADERS ~ - **Filetypes**. LuaSnip uses `all` as the global filetype. As most snippet - collections don’t explicitly target LuaSnip, they may not provide global - snippets for this filetype, but another, like `_` (`honza/vim-snippets`). In - these cases, it’s necessary to extend LuaSnip’s global filetype with the - collection’s global filetype: - >lua - ls.filetype_extend("all", { "_" }) - < - In general, if some snippets don’t show up when loading a collection, a good - first step is checking the filetype LuaSnip is actually looking into (print - them for the current buffer via `:lua - print(vim.inspect(require("luasnip").get_snippet_filetypes()))`), against the - one the missing snippet is provided for (in the collection). If there is indeed - a mismatch, `filetype_extend` can be used to also search the collection’s - filetype: - >lua - ls.filetype_extend("", { "" }) - < + collections don’t explicitly target LuaSnip, they may not provide global + snippets for this filetype, but another, like `_` (`honza/vim-snippets`). In + these cases, it’s necessary to extend LuaSnip’s global filetype with the + collection’s global filetype: + >lua + ls.filetype_extend("all", { "_" }) + < + In general, if some snippets don’t show up when loading a collection, a good + first step is checking the filetype LuaSnip is actually looking into (print + them for the current buffer via + `:lua print(vim.inspect(require("luasnip").get_snippet_filetypes()))`), against + the one the missing snippet is provided for (in the collection). + If there is indeed a mismatch, `filetype_extend` can be used to also search the + collection’s filetype: + >lua + ls.filetype_extend("", { "" }) + < - **Non-default ft_func loading**. As we only load `lazy_load`ed snippets on some - events, `lazy_load` will probably not play nice when a non-default `ft_func` is - used: if it depends on e.g. the cursor position, only the filetypes for the - cursor position when the `lazy_load` events are triggered will be loaded. Check - |luasnip-extras-filetype-function|’s `extend_load_ft` for a solution. + events, `lazy_load` will probably not play nice when a non-default `ft_func` is + used: if it depends on e.g. the cursor position, only the filetypes for the + cursor position when the `lazy_load` events are triggered will be loaded. Check + |luasnip-extras-filetype-function|’s `extend_load_ft` for a solution. GENERAL ~ - **Snippets sharing triggers**. If multiple snippets could be triggered at the - current buffer-position, the snippet that was defined first in one’s - configuration will be expanded first. As a small, real-world LaTeX math - example, given the following two snippets with triggers `.ov` and `ov`: - >lua - postfix( -- Insert over-line command to text via post-fix - { trig = ".ov", snippetType = "autosnippet" }, - { - f(function(_, parent) - return "\\overline{" .. parent.snippet.env.POSTFIX_MATCH .. "}" - end, {}), - } - ), - s( -- Insert over-line command - { trig = "ov", snippetType="autosnippet" }, - fmt( - [[\overline{<>}]], - { i(1) }, - { delimiters = "<>" } - ) - ), - < - If one types `x` followed by `.ov`, the postfix snippet expands producing - `\overline{x}`. However, if the `postfix` snippet above is defined _after_ the - normal snippet `s`, then the same key press sequence produces `x.\overline{}`. - This behavior can be overridden by explicitly providing a priority to such - snippets. For example, in the above code, if the `postfix` snippet was defined - after the normal snippet `s`, then adding `priority=1001` to the `postfix` - snippet will cause it to expand as if it were defined before the normal snippet - `s`. Snippet `priority` is discussed in the Snippets section - of the - documentation. + current buffer-position, the snippet that was defined first in one’s + configuration will be expanded first. As a small, real-world LaTeX math + example, given the following two snippets with triggers `.ov` and `ov`: + >lua + postfix( -- Insert over-line command to text via post-fix + { trig = ".ov", snippetType = "autosnippet" }, + { + f(function(_, parent) + return "\\overline{" .. parent.snippet.env.POSTFIX_MATCH .. "}" + end, {}), + } + ), + s( -- Insert over-line command + { trig = "ov", snippetType="autosnippet" }, + fmt( + [[\overline{<>}]], + { i(1) }, + { delimiters = "<>" } + ) + ), + < + If one types `x` followed by `.ov`, the postfix snippet expands producing + `\overline{x}`. However, if the `postfix` snippet above is defined _after_ the + normal snippet `s`, then the same key press sequence produces `x.\overline{}`. + This behavior can be overridden by explicitly providing a priority to such + snippets. For example, in the above code, if the `postfix` snippet was defined + after the normal snippet `s`, then adding `priority=1001` to the `postfix` + snippet will cause it to expand as if it were defined before the normal snippet + `s`. Snippet `priority` is discussed in the |luasnip-snippets-section| of the + documentation. ============================================================================== @@ -3628,197 +3701,449 @@ GENERAL ~ `require("luasnip")`: -- `add_snippets(ft:string or nil, snippets:list or table, opts:table or nil)`: - Makes `snippets` (list of snippets) available in `ft`. If `ft` is `nil`, - `snippets` should be a table containing lists of snippets, the keys are - corresponding filetypes. `opts` may contain the following keys: - - `type`: type of `snippets`, `"snippets"` or `"autosnippets"` (ATTENTION: - plural form used here). This serves as default value for the `snippetType` - key of each snippet added by this call see |luasnip-snippets|. - - `key`: Key that identifies snippets added via this call. - If `add_snippets` is called with a key that was already used, the snippets - from that previous call will be removed. - This can be used to reload snippets: pass an unique key to each - `add_snippets` and just redo the `add_snippets`-call when the snippets have - changed. - - `override_priority`: set priority for all snippets. - - `default_priority`: set priority only for snippets without snippet priority. -- `clean_invalidated(opts: table or nil) -> bool`: clean invalidated snippets - from internal snippet storage. Invalidated snippets are still stored; it might - be useful to actually remove them as they still have to be iterated during - expansion. - `opts` may contain: - - `inv_limit`: how many invalidated snippets are allowed. If the number of - invalid snippets doesn’t exceed this threshold, they are not yet cleaned up. - A small number of invalidated snippets (<100) probably doesn’t affect runtime - at all, whereas recreating the internal snippet storage might. -- `get_id_snippet(id)`: returns snippet corresponding to id. -- `in_snippet()`: returns true if the cursor is inside the current snippet. -- `jumpable(direction)`: returns true if the current node has a next(`direction` - = 1) or previous(`direction` = -1), e.g. whether it’s possible to jump - forward or backward to another node. -- `jump(direction)`: returns true if the jump was successful. -- `expandable()`: true if a snippet can be expanded at the current cursor - position. -- `expand(opts)`: expands the snippet at(before) the cursor. `opts` may contain: - - `jump_into_func` passed through to `ls.snip_expand`, check its’ doc for a - description. -- `expand_or_jumpable()`: returns `expandable() or jumpable(1)` (exists only - because commonly, one key is used to both jump forward and expand). -- `expand_or_locally_jumpable()`: same as `expand_or_jumpable()` except jumpable - is ignored if the cursor is not inside the current snippet. -- `locally_jumpable(direction)`: same as `jumpable()` except it is ignored if the - cursor is not inside the current snippet. -- `expand_or_jump()`: returns true if jump/expand was successful. -- `expand_auto()`: expands the autosnippets before the cursor (not necessary to - call manually, will be called via `autocmd` if `enable_autosnippets` is set in - the config). -- `snip_expand(snip, opts)`: expand `snip` at the current cursor position. `opts` - may contain the following keys: - - `clear_region`: A region of text to clear after expanding (but before jumping - into) snip. It has to be at this point (and therefore passed to this function) - as clearing before expansion will populate `TM_CURRENT_LINE` and - `TM_CURRENT_WORD` with wrong values (they would miss the snippet trigger) and - clearing after expansion may move the text currently under the cursor and have - it end up not at the `i(1)`, but a `#trigger` chars to its right. The actual - values used for clearing are `from` and `to`, both (0,0)-indexed - byte-positions. If the variables don’t have to be populated with the correct - values, it’s safe to remove the text manually. - - `expand_params`: table, override `trigger`, `captures` or environment of the - snippet. This is useful for manually expanding snippets where the trigger - passed via `trig` is not the text triggering the snippet, or those which expect - `captures` (basically, snippets with a non-plaintext `trigEngine`). - One example: - >lua - snip_expand(snip, { - trigger = "override_trigger", - captures = {"first capture", "second capture"}, - env_override = { this_key = "some value", other_key = {"multiple", "lines"}, TM_FILENAME = "some_other_filename.lua" } - }) - < - - `pos`: position (`{line, col}`), (0,0)-indexed (in bytes, as returned by - `nvim_win_get_cursor()`), where the snippet should be expanded. The snippet - will be put between `(line,col-1)` and `(line,col)`. The snippet will be - expanded at the current cursor if `pos` is nil. - - `jump_into_func`: fn(snippet) -> node: Callback responsible for jumping into - the snippet. The returned node is set as the new active node, i.e. it is the - origin of the next jump. The default is basically this: `lua function(snip) -- - jump_into set the placeholder of the snippet, 1 -- to jump forwards. return - snip:jump_into(1)` while this can be used to only insert the snippet: `lua - function(snip) return snip.insert_nodes[0] end` - - `indent`: `bool?`, defaults to `true`. Whether LuaSnip will try to add - additional indents to fit current indent level in snippet expanding. This - option is useful when some LSP server already take indents into consideration. - In such cases, LuaSnip should not try to add additional indents. If you are - using `nvim-cmp`, sample config: - >lua - require("cmp").setup { - snippet = { + +`get_active_snip(): LuaSnip.Snippet?` *luasnip-api-get_active_snip* + +Get the currently active snippet. + +This function returns: + +- `LuaSnip.Snippet?` The active snippet if one exists, or `nil`. + + +`get_snippets(ft?, opts?): (LuaSnip.Snippet[]|{ [string]: LuaSnip.Snippet[] })` *luasnip-api-get_snippets* + +Retrieve snippets from luasnip. + +- `ft?: string?` Filetype, if not given returns snippets for all filetypes. +- `opts?: LuaSnip.Opts.GetSnippets?` Optional arguments. + Valid keys are: + - `type?: ("snippets"|"autosnippets")?` Whether to get snippets or + autosnippets. Defaults to "snippets". + +This function returns: + +- `(LuaSnip.Snippet[]|{ [string]: LuaSnip.Snippet[] })` Flat array when `ft` is + non-nil, otherwise a table mapping filetypes to snippets. + + +`available(snip_info?): { [string]: T[] }` *luasnip-api-available* + +Retrieve information about snippets available in the current file/at the +current position (in case treesitter-based filetypes are enabled). + +- `snip_info?: fun(LuaSnip.Snippet) -> T?` Optionally pass a function that, given + a snippet, returns the data that is returned by this function in the + snippets’ stead. + By default, this function is + >lua + function(snip) + return { + name = snip.name, + trigger = snip.trigger, + description = snip.description, + wordTrig = snip.wordTrig and true or false, + regTrig = snip.regTrig and true or false, + } + end + < + +This function returns: + +- `{ [string]: T[] }` Table mapping filetypes to list of data returned by + snip_info function. + + +`unlink_current()` *luasnip-api-unlink_current* + +Removes the current snippet from the jumplist (useful if LuaSnip fails to +automatically detect e.g. deletion of a snippet) and sets the current node +behind the snippet, or, if not possible, before it. + + +`jump(dir): boolean` *luasnip-api-jump* + +Jump forwards or backwards + +- `dir: (1|-1)` Jump forward for 1, backward for -1. + +This function returns: + +- `boolean` `true` if a jump was performed, `false` otherwise. + + +`jump_destination(dir): LuaSnip.Node` *luasnip-api-jump_destination* + +Find the node the next jump will end up at. This will not work always, because +we will not update the node before jumping, so if the jump would e.g. insert a +new node between this node and its pre-update jump target, this would not be +registered. Thus, it currently only works for simple cases. + +- `dir: (1|-1)` `1`: find the next node, `-1`: find the previous node. + +This function returns: + +- `LuaSnip.Node` The destination node. + + +`jumpable(dir): boolean` *luasnip-api-jumpable* + +Return whether jumping forwards or backwards will actually jump, or if there is +no node in that direction. + +- `dir: (1|-1)` `1` forward, `-1` backward. + + +`expandable(): boolean` *luasnip-api-expandable* + +Return whether there is an expandable snippet at the current cursor position. +Does not consider autosnippets since those would already be expanded at this +point. + + +`expand_or_jumpable(): boolean` *luasnip-api-expand_or_jumpable* + +Return whether it’s possible to expand a snippet at the current +cursor-position, or whether it’s possible to jump forward from the current +node. + + +`in_snippet(): boolean` *luasnip-api-in_snippet* + +Determine whether the cursor is within a snippet. + + +`expand_or_locally_jumpable(): boolean` *luasnip-api-expand_or_locally_jumpable* + +Return whether a snippet can be expanded at the current cursor position, or +whether the cursor is inside a snippet and the current node can be jumped +forward from. + + +`locally_jumpable(dir): boolean` *luasnip-api-locally_jumpable* + +Return whether the cursor is inside a snippet and the current node can be +jumped forward from. + +- `dir: (1|-1)` Test jumping forwards/backwards. + + +`snip_expand(snippet, opts?): LuaSnip.ExpandedSnippet` *luasnip-api-snip_expand* + +Expand a snippet in the current buffer. + +- `snippet: LuaSnip.Snippet` The snippet. +- `opts?: LuaSnip.Opts.SnipExpand?` Optional additional arguments. + Valid keys are: + - `clear_region?: LuaSnip.BufferRegion?` A region of text to clear after + populating env-variables, but before jumping into `snip`. If `nil`, no clearing + is performed. Being able to remove text at this point is useful as clearing + before calling this function would populate `TM_CURRENT_LINE` and + `TM_CURRENT_WORD` with wrong values (they would miss the snippet trigger). The + actual values used for clearing are `region.from` and `region.to`, both + (0,0)-indexed byte-positions in the buffer. + - `expand_params?: LuaSnip.Opts.SnipExpandExpandParams?` Override various fields + of the expanded snippet. Don’t override anything by default. This is useful + for manually expanding snippets where the trigger passed via `trig` is not the + text triggering the snippet, or those which expect `captures` (basically, + snippets with a non-plaintext `trigEngine`). + One Example: + >lua + snip_expand(snip, { + trigger = "override_trigger", + captures = {"first capture", "second capture"}, + env_override = { this_key = "some value", other_key = {"multiple", "lines"}, TM_FILENAME = "some_other_filename.lua" } + }) + < + Valid keys are: + - `trigger?: string?` What to set as the expanded snippets’ trigger + (Defaults to `snip.trigger`). + - `captures?: string[]?` Set as the expanded snippets’ captures (Defaults to + `{}`). + - `env_override?: { [string]: string }?` Set or override environment + variables of the expanded snippet (Defaults to `{}`). + - `pos?: (integer,integer)?` Position at which the snippet should be inserted. + Pass as `(row,col)`, both 0-based, the `col` given in bytes. + - `indent?: boolean?` Whether to prepend the current lines’ indent to all lines + of the snippet. (Defaults to `true`) + Turning this off is a good idea when a LSP server already takes indents into + consideration. In such cases, LuaSnip should not add additional indents. If you + are using `nvim-cmp`, this could be used as follows: + >lua + require("cmp").setup { + snippet = { expand = function(args) - local indent_nodes = true - if vim.api.nvim_get_option_value("filetype", { buf = 0 }) == "dart" then - indent_nodes = false - end - require("luasnip").lsp_expand(args.body, { - indent = indent_nodes, - }) + local indent_nodes = true + if vim.api.nvim_get_option_value("filetype", { buf = 0 }) == "dart" then + indent_nodes = false + end + require("luasnip").lsp_expand(args.body, { + indent = indent_nodes, + }) end, - }, - } - < - `opts` and any of its parameters may be nil. -- `get_active_snip()`: returns the currently active snippet (not node!). -- `choice_active()`: true if inside a `choiceNode`. -- `change_choice(direction)`: changes the choice in the innermost currently - active `choiceNode` forward (`direction` = 1) or backward (`direction` = -1). -- `unlink_current()`: removes the current snippet from the jumplist (useful if - LuaSnip fails to automatically detect e.g. deletion of a snippet) and sets the - current node behind the snippet, or, if not possible, before it. -- `lsp_expand(snip_string, opts)`: expands the LSP snippet defined via - `snip_string` at the cursor. `opts` can have the same options as `opts` in - `snip_expand`. -- `active_update_dependents()`: update all function/dynamicNodes that have the - current node as an argnode (will actually only update them if the text in any - of the argnodes changed). -- `available(snip_info)`: returns a table of all snippets defined for the current - filetypes(s) (`{ft1={snip1, snip2}, ft2={snip3, snip4}}`). The structure of the - snippet is defined by `snip_info` which is a function (`snip_info(snip)`) that - takes in a snippet (`snip`), finds the desired information on it, and returns - it. `snip_info` is an optional argument as a default has already been defined. - You can use it for more granular control over the table of snippets that is - returned. -- `exit_out_of_region(node)`: checks whether the cursor is still within the range - of the root-snippet `node` belongs to. If yes, no change occurs; if no, the - root-snippet is exited and its `$0` will be the new active node. If a jump - causes an error (happens mostly because the text of a snippet was deleted), the - snippet is removed from the jumplist and the current node set to the - end/beginning of the next/previous snippet. -- `store_snippet_docstrings(snippet_table)`: Stores the docstrings of all - snippets in `snippet_table` to a file - (`stdpath("cache")/luasnip/docstrings.json`). Calling - `store_snippet_docstrings(snippet_table)` after adding/modifying snippets and - `load_snippet_docstrings(snippet_table)` on startup after all snippets have - been added to `snippet_table` is a way to avoid regenerating the (unchanged) - docstrings on each startup. (Depending on when the docstrings are required and - how LuaSnip is loaded, it may be more sensible to let them load lazily, - e.g. just before they are required). `snippet_table` should be laid out just - like `luasnip.snippets` (it will most likely always _be_ `luasnip.snippets`). -- `load_snippet_docstrings(snippet_table)`: Load docstrings for all snippets in - `snippet_table` from `stdpath("cache")/luasnip/docstrings.json`. The docstrings - are stored and restored via trigger, meaning if two snippets for one filetype - have the same (very unlikely to happen in actual usage), bugs could occur. - `snippet_table` should be laid out as described in `store_snippet_docstrings`. -- `unlink_current_if_deleted()`: Checks if the current snippet was deleted; if - so, it is removed from the jumplist. This is not 100% reliable as LuaSnip only - sees the extmarks and their beginning/end may not be on the same position, even - if all the text between them was deleted. -- `filetype_extend(filetype:string, extend_filetypes:table of string)`: Tells - LuaSnip that for a buffer with `ft=filetype`, snippets from `extend_filetypes` - should be searched as well. `extend_filetypes` is a Lua array (`{ft1, ft2, - ft3}`). `luasnip.filetype_extend("lua", {"c", "cpp"})` would search and expand - C and C++ snippets for Lua files. -- `filetype_set(filetype:string, replace_filetypes:table of string)`: Similar to - `filetype_extend`, but where _append_ appended filetypes, _set_ sets them: - `filetype_set("lua", {"c"})` causes only c snippets to be expanded in Lua - files; Lua snippets aren’t even searched. -- `cleanup()`: clears all snippets. Not useful for regular usage, only when - authoring and testing snippets. -- `refresh_notify(ft:string)`: Triggers an `autocmd` that other plugins can hook - into to perform various cleanup for the refreshed filetype. Useful for - signaling that new snippets were added for the filetype `ft`. -- `set_choice(indx:number)`: Changes to the `indx`th choice. If no `choiceNode` - is active, an error is thrown. If the active `choiceNode` doesn’t have an - `indx`th choice, an error is thrown. -- `get_current_choices() -> string[]`: Returns a list of multiline-strings - (themselves lists, even if they have only one line), the `i`th string - corresponding to the `i`th choice of the currently active `choiceNode`. If no - `choiceNode` is active, an error is thrown. -- `setup_snip_env()`: Adds the variables defined (during `setup`) in `snip_env` - to the callers environment. -- `get_snip_env()`: Returns `snip_env`. -- `jump_destination(direction)`: Returns the node the next jump in `direction` - (either -1 or 1, for backwards, forwards respectively) leads to, or `nil` if - the destination could not be determined (most likely because there is no node - that can be jumped to in the given direction, or there is no active node). -- `activate_node(opts)`: Activate a node in any snippet. `opts` contains the - following options: - - `pos`, `{[1]: row, [2]: byte-column}?`: The position at which a node should - be activated. Defaults to the position of the cursor. - - `strict`, `bool?`: If set, throw an error if the node under the cursor can’t - be jumped into. If not set, fall back to any node of the snippet and enter - that instead. - - `select`, `bool?`: Whether the text inside the node should be selected. - Defaults to true. + }, + } + < + - `jump_into_func?: fun(snip: LuaSnip.Snippet) -> LuaSnip.Node?` + +This function returns: + +- `LuaSnip.ExpandedSnippet` The snippet that was inserted into the buffer. + + +`expand(opts?): boolean` *luasnip-api-expand* + +Find a snippet whose trigger matches the text before the cursor and expand it. + +- `opts?: LuaSnip.Opts.Expand?` Subset of opts accepted by `snip_expand`. + Valid keys are: + - `jump_into_func?: fun(snip: LuaSnip.Snippet) -> LuaSnip.Node?` + +This function returns: + +- `boolean` Whether a snippet was expanded. + + +`expand_auto()` *luasnip-api-expand_auto* + +Find an autosnippet matching the text at the cursor-position and expand it. + + +`expand_repeat()` *luasnip-api-expand_repeat* + +Repeat the last performed `snip_expand`. Useful for dot-repeat. + + +`expand_or_jump(): boolean` *luasnip-api-expand_or_jump* + +Expand at the cursor, or jump forward. + +This function returns: + +- `boolean` Whether an action was performed. + + +`lsp_expand(body, opts?)` *luasnip-api-lsp_expand* + +Expand a snippet specified in lsp-style. + +- `body: string` A string specifying a lsp-snippet, e.g. + `"[${1:text}](${2:url})"` +- `opts?: LuaSnip.Opts.SnipExpand?` Optional args passed through to + `snip_expand`. + + +`choice_active(): boolean` *luasnip-api-choice_active* + +Return whether the current node is inside a choiceNode. + + +`change_choice(val)` *luasnip-api-change_choice* + +Change the currently active choice. + +- `val: (1|-1)` Move one choice forward or backward. + + +`set_choice(choice_indx)` *luasnip-api-set_choice* + +Set the currently active choice. + +- `choice_indx: integer` Index of the choice to switch to. + + +`get_current_choices(): string[]` *luasnip-api-get_current_choices* + +Get a string-representation of all the current choiceNode’s choices. + +This function returns: + +- `string[]` -concatenated lines of every choice. + + +`active_update_dependents()` *luasnip-api-active_update_dependents* + +Update all nodes that depend on the currently-active node. + + +`store_snippet_docstrings(snippet_table)` *luasnip-api-store_snippet_docstrings* + +Generate and store the docstrings for a list of snippets as generated by +`get_snippets()`. +The docstrings are stored at `stdpath("cache") .. "/luasnip/docstrings.json"`, +are indexed by their trigger, and should be updated once any snippet changes. + +- `snippet_table: { [string]: LuaSnip.Snippet[] }` A table mapping some keys to + lists of snippets (keys are most likely filetypes). + + +`load_snippet_docstrings(snippet_table)` *luasnip-api-load_snippet_docstrings* + +Provide all passed snippets with a previously-stored (via +`store_snippet_docstrings`) docstring. This prevents a somewhat costly +computation which is performed whenever a snippets’ docstring is first +retrieved, but may cause larger delays when `snippet_table` contains many of +snippets. +Utilize this function by calling +`ls.store_snippet_docstrings(ls.get_snippets())` whenever snippets are +modified, and `ls.load_snippet_docstrings(ls.get_snippets())` on startup. + +- `snippet_table: { [string]: LuaSnip.Snippet[] }` List of snippets, should + contain the same keys (filetypes) as the table that was passed to + `store_snippet_docstrings`. Again, most likely the result of `get_snippets`. + + +`unlink_current_if_deleted()` *luasnip-api-unlink_current_if_deleted* + +Checks whether (part of) the current snippet’s text was deleted, and removes +it from the jumplist if it was (it cannot be jumped back into). + + +`exit_out_of_region(node)` *luasnip-api-exit_out_of_region* + +Checks whether the cursor is still within the range of the root-snippet `node` +belongs to. If yes, no change occurs; if no, the root-snippet is exited and its +`i(0)` will be the new active node. +If a jump causes an error (happens mostly because the text of a snippet was +deleted), the snippet is removed from the jumplist and the current node set to +the end/beginning of the next/previous snippet. + +- `node: LuaSnip.Node` + + +`filetype_extend(ft, extend_ft)` *luasnip-api-filetype_extend* + +Add `extend_ft` filetype to inherit its snippets from `ft`. + +Example: + +>lua + ls.filetype_extend("sh", {"zsh"}) + ls.filetype_extend("sh", {"bash"}) +< + +This makes all `sh` snippets available in `sh`/`zsh`/`bash` buffers. + +- `ft: string` +- `extend_ft: string[]` + + +`filetype_set(ft, fts)` *luasnip-api-filetype_set* + +Set `fts` filetypes as inheriting their snippets from `ft`. + +Example: + +>lua + ls.filetype_set("sh", {"sh", "zsh", "bash"}) +< + +This makes all `sh` snippets available in `sh`/`zsh`/`bash` buffers. + +- `ft: string` +- `fts: string[]` + + +`cleanup()` *luasnip-api-cleanup* + +Clear all loaded snippets. Also sends the `User LuasnipCleanup` autocommand, so +plugins that depend on luasnip’s snippet-state can clean up their +now-outdated state. + + +`refresh_notify(ft)` *luasnip-api-refresh_notify* + +Trigger the `User LuasnipSnippetsAdded` autocommand that signifies to other +plugins that a filetype has received new snippets. + +- `ft: string` The filetype that has new snippets. Code that listens to this + event can retrieve this filetype from + `require("luasnip").session.latest_load_ft`. + + +`setup_snip_env()` *luasnip-api-setup_snip_env* + +Injects the fields defined in `snip_env`, in `setup`, into the callers global +environment. + +This means that variables like `s`, `sn`, `i`, `t`, … (by default) work, and +are useful for quickly testing snippets in a buffer: + +>lua + local ls = require("luasnip") + ls.setup_snip_env() + + ls.add_snippets("all", { + s("choicetest", { + t":", c(1, { + t("asdf", {node_ext_opts = {active = { virt_text = {{"asdf", "Comment"}} }}}), + t("qwer", {node_ext_opts = {active = { virt_text = {{"qwer", "Comment"}} }}}), + }) + }) + }, { key = "3d9cd211-c8df-4270-915e-bf48a0be8a79" }) +< + +where the `key` makes it easy to reload the snippets on changes, since the +previously registered snippets will be replaced when the buffer is re-sourced. + + +`get_snip_env(): table` *luasnip-api-get_snip_env* + +Return the currently active snip_env. + + +`get_id_snippet(id): LuaSnip.Snippet` *luasnip-api-get_id_snippet* + +Get the snippet corresponding to some id. + +- `id: LuaSnip.SnippetID` + + +`add_snippets(ft?, snippets, opts?)` *luasnip-api-add_snippets* + +Add snippets to luasnip’s snippet-collection. + +NOTE: Calls `refresh_notify` as needed if enabled via `opts.refresh_notify`. + +- `ft?: string?` The filetype to add the snippets to, or nil if the filetype is + specified in `snippets`. +- `snippets: (LuaSnip.Addable[]|{ [string]: LuaSnip.Addable[] })` If `ft` is nil + a table mapping a filetype to a list of snippets, otherwise a flat table of + snippets. + `LuaSnip.Addable` are objects created by e.g. the functions `s`, `ms`, or + `sp`. +- `opts?: LuaSnip.Opts.AddSnippets?` Optional arguments. + + +`clean_invalidated(opts?)` *luasnip-api-clean_invalidated* + +Clean invalidated snippets from internal snippet storage. Invalidated snippets +are still stored; it might be useful to actually remove them as they still have +to be iterated during expansion. + +- `opts?: LuaSnip.Opts.CleanInvalidated?` Additional, optional arguments. + Valid keys are: + - `inv_limit?: integer?` If set, invalidated snippets are only cleared if + their number exceeds `inv_limit`. + + +`activate_node(opts?)` *luasnip-api-activate_node* + +Lookup a node by position and activate (ie. jump into) it. + +- `opts?: LuaSnip.Opts.ActivateNode?` Additional, optional arguments. + Valid keys are: + - `strict?: boolean?` Only activate nodes one could usually jump to. (Defaults + to false) + - `select?: boolean?` Whether to select the entire node, or leave the cursor + at the position it is currently at. (Defaults to true) + - `pos?: LuaSnip.BytecolBufferPosition?` Where to look for the node. (Defaults + to the position of the cursor) Not covered in this section are the various node-constructors exposed by the module, their usage is shown either previously in this file or in `Examples/snippets.lua` (in the repository). -============================================================================== -33. Links *luasnip-links* - -1. *@meta*: - Generated by panvimdoc vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/flake.lock b/flake.lock index 202cc07c2..3a9bdd4c1 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,45 @@ { "nodes": { + "emmylua-analyzer-rust": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1750139816, + "narHash": "sha256-Q/ZPCRDU+6OKbSVxcdWdOJsQ1jfFbaKqKcM3u4/Txws=", + "owner": "EmmyLuaLs", + "repo": "emmylua-analyzer-rust", + "rev": "7d56390fbcfc0dddd0c4416a96b77f0f76302c1c", + "type": "github" + }, + "original": { + "owner": "EmmyLuaLs", + "repo": "emmylua-analyzer-rust", + "type": "github" + } + }, + "emmylua-analyzer-rust_2": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay_2" + }, + "locked": { + "lastModified": 1749817903, + "narHash": "sha256-6ltnF1xmGc5OmhWNFmTKzVrOWjsMbeqg55dqfJIIHZI=", + "owner": "EmmyLuaLs", + "repo": "emmylua-analyzer-rust", + "rev": "6abc39a068a9ca9ee9a79a851f9b3d37f8fdc371", + "type": "github" + }, + "original": { + "owner": "EmmyLuaLs", + "repo": "emmylua-analyzer-rust", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -75,6 +115,59 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_4": { "locked": { "lastModified": 1644229661, "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", @@ -89,7 +182,7 @@ "type": "github" } }, - "flake-utils_2": { + "flake-utils_5": { "locked": { "lastModified": 1667395993, "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", @@ -104,6 +197,24 @@ "type": "github" } }, + "flake-utils_6": { + "inputs": { + "systems": "systems_4" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "git-hooks": { "inputs": { "flake-compat": "flake-compat_2", @@ -171,6 +282,27 @@ "type": "github" } }, + "luals-mdgen": { + "inputs": { + "emmylua-analyzer-rust": "emmylua-analyzer-rust_2", + "flake-utils": "flake-utils_3", + "nixpkgs": "nixpkgs_3", + "nlua": "nlua" + }, + "locked": { + "lastModified": 1750805301, + "narHash": "sha256-qFr7zNZsc+cXR0+QD7NEh1c26/Bx/WWI71pXCoaDTjA=", + "owner": "L3MON4D3", + "repo": "luals-mdgen", + "rev": "66ec5d9c19eef77c1ea523a0a8d5396d10b7cea4", + "type": "github" + }, + "original": { + "owner": "L3MON4D3", + "repo": "luals-mdgen", + "type": "github" + } + }, "neovim-src": { "flake": false, "locked": { @@ -189,15 +321,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1646254136, - "narHash": "sha256-8nQx02tTzgYO21BP/dy5BCRopE8OwE8Drsw98j+Qoaw=", - "owner": "nixos", + "lastModified": 1746904237, + "narHash": "sha256-3e+AVBczosP5dCLQmMoMEogM57gmZ2qrVSrmq9aResQ=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "3e072546ea98db00c2364b81491b893673267827", + "rev": "d89fc19e405cb2d55ce7cc114356846a0ee5e956", "type": "github" }, "original": { - "owner": "nixos", + "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" @@ -219,7 +351,87 @@ "type": "github" } }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1750386251, + "narHash": "sha256-1ovgdmuDYVo5OUC5NzdF+V4zx2uT8RtsgZahxidBTyw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "076e8c6678d8c54204abcb4b1b14c366835a58bb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_2": { + "locked": { + "lastModified": 1746904237, + "narHash": "sha256-3e+AVBczosP5dCLQmMoMEogM57gmZ2qrVSrmq9aResQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d89fc19e405cb2d55ce7cc114356846a0ee5e956", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1746328495, + "narHash": "sha256-uKCfuDs7ZM3QpCE/jnfubTg459CnKnJG/LwqEVEdEiw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "979daf34c8cacebcd917d540070b52a3c2b9b16e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1750386251, + "narHash": "sha256-1ovgdmuDYVo5OUC5NzdF+V4zx2uT8RtsgZahxidBTyw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "076e8c6678d8c54204abcb4b1b14c366835a58bb", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_5": { + "locked": { + "lastModified": 1646254136, + "narHash": "sha256-8nQx02tTzgYO21BP/dy5BCRopE8OwE8Drsw98j+Qoaw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "3e072546ea98db00c2364b81491b893673267827", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_6": { "locked": { "lastModified": 1671983799, "narHash": "sha256-Z2Ro6hFPZHkBqkVXY5/aBUzxi5xizQGvuHQ9+T5B/ks=", @@ -235,7 +447,7 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_7": { "locked": { "lastModified": 1745377448, "narHash": "sha256-jhZDfXVKdD7TSEGgzFJQvEEZ2K65UMiqW5YJ2aIqxMA=", @@ -251,10 +463,22 @@ "type": "github" } }, + "nlua": { + "flake": false, + "locked": { + "narHash": "sha256-pF4Kp86wHKcIV5sg+ng9meVrQoHp3EfkwRr2owzZDL0=", + "type": "file", + "url": "https://raw.githubusercontent.com/mfussenegger/nlua/8a2d76043d94ed4130ae703f13f393bb9132d259/nlua" + }, + "original": { + "type": "file", + "url": "https://raw.githubusercontent.com/mfussenegger/nlua/8a2d76043d94ed4130ae703f13f393bb9132d259/nlua" + } + }, "nvim_07": { "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "flake-utils": "flake-utils_4", + "nixpkgs": "nixpkgs_5" }, "locked": { "dir": "contrib", @@ -275,8 +499,8 @@ }, "nvim_09": { "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" + "flake-utils": "flake-utils_5", + "nixpkgs": "nixpkgs_6" }, "locked": { "dir": "contrib", @@ -302,7 +526,7 @@ "git-hooks": "git-hooks", "hercules-ci-effects": "hercules-ci-effects", "neovim-src": "neovim-src", - "nixpkgs": "nixpkgs_3", + "nixpkgs": "nixpkgs_7", "treefmt-nix": "treefmt-nix" }, "locked": { @@ -319,12 +543,138 @@ "type": "github" } }, + "panvimdoc": { + "inputs": { + "flake-utils": "flake-utils_6", + "nixpkgs-unstable": "nixpkgs-unstable" + }, + "locked": { + "lastModified": 1750836320, + "narHash": "sha256-ATjXZL7l5qdd29++I9UhlaIxZa/dX5WhbWFvdyuyhmw=", + "owner": "L3MON4D3", + "repo": "panvimdoc", + "rev": "a2a3fd38c9cead1d082b9f72c6a6cc8b5864fc02", + "type": "github" + }, + "original": { + "owner": "L3MON4D3", + "repo": "panvimdoc", + "type": "github" + } + }, "root": { "inputs": { + "emmylua-analyzer-rust": "emmylua-analyzer-rust", + "luals-mdgen": "luals-mdgen", + "nixpkgs": "nixpkgs_4", "nixpkgs-treesitter": "nixpkgs-treesitter", "nvim_07": "nvim_07", "nvim_09": "nvim_09", - "nvim_master": "nvim_master" + "nvim_master": "nvim_master", + "panvimdoc": "panvimdoc" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "emmylua-analyzer-rust", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747017456, + "narHash": "sha256-C/U12fcO+HEF071b5mK65lt4XtAIZyJSSJAg9hdlvTk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "5b07506ae89b025b14de91f697eba23b48654c52", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_2": { + "inputs": { + "nixpkgs": [ + "luals-mdgen", + "emmylua-analyzer-rust", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747017456, + "narHash": "sha256-C/U12fcO+HEF071b5mK65lt4XtAIZyJSSJAg9hdlvTk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "5b07506ae89b025b14de91f697eba23b48654c52", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } }, "treefmt-nix": { diff --git a/flake.nix b/flake.nix index 9c0259b96..79f1415b9 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,8 @@ { description = "A nix flake for developing LuaSnip."; + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + # has tree-sitter 0.20.8, which is required for neovim 0.9.0. inputs.nixpkgs-treesitter.url = "github:nixos/nixpkgs/7a339d87931bba829f68e94621536cad9132971a"; @@ -11,37 +13,66 @@ # this only has to be updated sporadically, basically whenever the source in # worktree_master no longer builds. inputs.nvim_master.url = "github:nix-community/neovim-nightly-overlay"; - - outputs = { self, nixpkgs-treesitter, nvim_07, nvim_09, nvim_master }: + inputs.luals-mdgen.url = "github:L3MON4D3/luals-mdgen"; + inputs.emmylua-analyzer-rust.url = "github:EmmyLuaLs/emmylua-analyzer-rust"; + inputs.panvimdoc.url = "github:L3MON4D3/panvimdoc"; + + outputs = { + self, + nixpkgs, + nixpkgs-treesitter, + nvim_07, + nvim_09, + nvim_master, + luals-mdgen, + emmylua-analyzer-rust, + panvimdoc + }: let supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forEachSupportedSystem = f: nixpkgs-treesitter.lib.genAttrs supportedSystems (system: f { + pkgs = import nixpkgs { inherit system; }; pkgs-treesitter = import nixpkgs-treesitter { inherit system; }; pkgs-nvim_09 = import nvim_09.inputs.nixpkgs { inherit system; }; pkgs-nvim_07 = import nvim_07.inputs.nixpkgs { inherit system; }; + pkg-luals-mdgen = luals-mdgen.outputs.packages.${system}.default; + pkg-emmylua-doc = emmylua-analyzer-rust.outputs.packages.${system}.emmylua_doc_cli; + pkg-panvimdoc = panvimdoc.outputs.packages.${system}.default; }); in { - devShells = forEachSupportedSystem ({ pkgs-treesitter, pkgs-nvim_09, pkgs-nvim_07 }: let - default_09_devshell = nvim_09.outputs.devShells.${pkgs-treesitter.system}.default; - true_bin = "${pkgs-treesitter.coreutils}/bin/true"; - false_bin = "${pkgs-treesitter.coreutils}/bin/false"; + devShells = forEachSupportedSystem ({ + pkgs, + pkgs-treesitter, + pkgs-nvim_09, + pkgs-nvim_07, + pkg-luals-mdgen, + pkg-emmylua-doc, + pkg-panvimdoc + }: let + default_09_devshell = nvim_09.outputs.devShells.${pkgs.system}.default; + true_bin = "${pkgs.coreutils}/bin/true"; + false_bin = "${pkgs.coreutils}/bin/false"; in { - default = pkgs-treesitter.mkShell { + default = pkgs.mkShell { # use any nixpkgs here. - packages = with pkgs-treesitter; [ - (aspellWithDicts (dicts: with dicts; [en])) - gnumake - - git - nix - which - gnugrep - gcc + packages = [ + (pkgs.aspellWithDicts (dicts: with dicts; [en])) + pkgs.gnumake + + pkgs.git + pkgs.nix + pkgs.which + pkgs.gnugrep + pkgs.gcc + pkg-luals-mdgen + pkg-emmylua-doc + pkg-panvimdoc + pkgs.neovim ]; }; # clang stdenv does not build, and it's used by de. - test_nvim_07 = (nvim_07.outputs.devShell.${pkgs-treesitter.system}.override { stdenv = pkgs-nvim_07.gccStdenv; }).overrideAttrs(attrs: { + test_nvim_07 = (nvim_07.outputs.devShell.${pkgs.system}.override { stdenv = pkgs-nvim_07.gccStdenv; }).overrideAttrs(attrs: { TEST_07=true_bin; TEST_09=false_bin; TEST_MASTER=false_bin; @@ -92,7 +123,7 @@ shellHook = ""; }); - test_nvim_master = nvim_master.outputs.devShells.${pkgs-treesitter.system}.default.overrideAttrs(attrs: { + test_nvim_master = nvim_master.outputs.devShells.${pkgs.system}.default.overrideAttrs(attrs: { TEST_07=false_bin; TEST_09=false_bin; TEST_MASTER=true_bin; diff --git a/lua/luasnip/_types.lua b/lua/luasnip/_types.lua index ec839c875..435b367c0 100644 --- a/lua/luasnip/_types.lua +++ b/lua/luasnip/_types.lua @@ -1,8 +1,19 @@ ---@alias LuaSnip.Cursor {[1]: number, [2]: number} ----@class LuaSnip.MatchRegion 0-based region +--- 0-based region within a single line +---@class LuaSnip.MatchRegion ---@field row integer 0-based row ---@field col_range { [1]: integer, [2]: integer } 0-based column range, from-in, to-exclusive ----@alias LuaSnip.Addable table ----Anything that can be passed to ls.add_snippets(). +--- Specifies a position in a buffer, or some other collection of lines. (0,0) is +--- the first line, first character, and the column-position is specified in +--- bytes, not visual columns (there are characters that are visually one column, +--- but occupy several bytes). +---@class LuaSnip.BytecolBufferPosition +---@field [1] integer +---@field [2] integer + +--- 0-based region in a buffer. +---@class LuaSnip.BufferRegion +---@field from LuaSnip.BytecolBufferPosition Starting position, included. +---@field to LuaSnip.BytecolBufferPosition Ending position, excluded. diff --git a/lua/luasnip/extras/_extra_types.lua b/lua/luasnip/extras/_extra_types.lua index d8d7305cd..e647e50f9 100644 --- a/lua/luasnip/extras/_extra_types.lua +++ b/lua/luasnip/extras/_extra_types.lua @@ -1,5 +1,6 @@ ----@class LuaSnip.extra.MatchTSNodeOpts Passed in by the user, describes how to ----select a node from the tree via a query and captures. +---Passed in by the user, describes how to select a node from the tree via a +---query and captures. +---@class LuaSnip.extra.MatchTSNodeOpts ---@field query? string A query, as text ---@field query_name? string The name of a query (passed to `vim.treesitter.query.get`). ---@field query_lang string The language of the query. @@ -15,7 +16,8 @@ ---| '"shortest"' # Selects the shortest match, return all captures too ---| '"longest"' # Selects the longest match, return all captures too ----Call record repeatedly to record all matches/nodes, retrieve once there are no more matches +---Call record repeatedly to record all matches/nodes, retrieve once there are +---no more matches ---@class LuaSnip.extra.MatchSelector ---@field record fun(ts_match: TSMatch?, node: TSNode): boolean return true if recording can be aborted --- (because the best match has been found) @@ -23,4 +25,4 @@ ---@alias LuaSnip.extra.MatchTSNodeFunc fun(parser: LuaSnip.extra.TSParser, cursor: LuaSnip.Cursor): LuaSnip.extra.NamedTSMatch?,TSNode? ----@alias LuaSnip.extra.NamedTSMatch table +---@alias LuaSnip.extra.NamedTSMatch {[string]: TSNode} diff --git a/lua/luasnip/init.lua b/lua/luasnip/init.lua index 3e0e7778b..bf2470261 100644 --- a/lua/luasnip/init.lua +++ b/lua/luasnip/init.lua @@ -17,7 +17,12 @@ local luasnip_data_dir = vim.fn.stdpath("cache") .. "/luasnip" local log = require("luasnip.util.log").new("main") -local function get_active_snip() +---@class LuaSnip.API +local API = {} + +--- Get the currently active snippet. +---@return LuaSnip.Snippet? _ The active snippet if one exists, or `nil`. +function API.get_active_snip() local node = session.current_nodes[vim.api.nvim_get_current_buf()] if not node then return nil @@ -39,23 +44,23 @@ local function match_snippet(line, type) ) end --- ft: --- * string: interpreted as filetype, return corresponding snippets. --- * nil: return snippets for all filetypes: --- { --- lua = {...}, --- cpp = {...}, --- ... --- } --- opts: optional args, can contain `type`, either "snippets" or "autosnippets". --- --- return table, may be empty. -local function get_snippets(ft, opts) +---@class LuaSnip.Opts.GetSnippets +---@field type? "snippets"|"autosnippets" Whether to get snippets or +--- autosnippets. Defaults to "snippets". + +--- Retrieve snippets from luasnip. +---@param ft? string Filetype, if not given returns snippets for all filetypes. +---@param opts? LuaSnip.Opts.GetSnippets Optional arguments. +---@return LuaSnip.Snippet[]|{[string]: LuaSnip.Snippet[]} _ Flat array when +--- `ft` is non-nil, otherwise a table mapping filetypes to snippets. +function API.get_snippets(ft, opts) opts = opts or {} return snippet_collection.get_snippets(ft, opts.type or "snippets") or {} end +---@param snip LuaSnip.Snippet +---@return any local function default_snip_info(snip) return { name = snip.name, @@ -66,19 +71,40 @@ local function default_snip_info(snip) } end -local function available(snip_info) +--- Retrieve information about snippets available in the current file/at the +--- current position (in case treesitter-based filetypes are enabled). +--- +---@generic T +---@param snip_info? (fun(LuaSnip.Snippet): T) Optionally pass a function that, +--- given a snippet, returns the data that is returned by this function in the +--- snippets' stead. +--- By default, this function is +--- ```lua +--- function(snip) +--- return { +--- name = snip.name, +--- trigger = snip.trigger, +--- description = snip.description, +--- wordTrig = snip.wordTrig and true or false, +--- regTrig = snip.regTrig and true or false, +--- } +--- end +--- ``` +---@return {[string]: T[]} _ Table mapping filetypes to list of data returned by +--- snip_info function. +function API.available(snip_info) snip_info = snip_info or default_snip_info local fts = util.get_snippet_filetypes() local res = {} for _, ft in ipairs(fts) do res[ft] = {} - for _, snip in ipairs(get_snippets(ft)) do + for _, snip in ipairs(API.get_snippets(ft)) do if not snip.invalidated then table.insert(res[ft], snip_info(snip)) end end - for _, snip in ipairs(get_snippets(ft, { type = "autosnippets" })) do + for _, snip in ipairs(API.get_snippets(ft, { type = "autosnippets" })) do if not snip.invalidated then table.insert(res[ft], snip_info(snip)) end @@ -125,7 +151,10 @@ function unlink_set_adjacent_as_current(snippet, reason, ...) unlink_set_adjacent_as_current_no_log(snippet) end -local function unlink_current() +--- Removes the current snippet from the jumplist (useful if LuaSnip fails to +--- automatically detect e.g. deletion of a snippet) and sets the current node +--- behind the snippet, or, if not possible, before it. +function API.unlink_current() local current = session.current_nodes[vim.api.nvim_get_current_buf()] if not current then print("No active Snippet") @@ -156,7 +185,11 @@ local function safe_jump_current(dir, no_move, dry_run) return session.current_nodes[vim.api.nvim_get_current_buf()] end end -local function jump(dir) + +--- Jump forwards or backwards +---@param dir 1|-1 Jump forward for 1, backward for -1. +---@return boolean _ `true` if a jump was performed, `false` otherwise. +function API.jump(dir) local current = session.current_nodes[vim.api.nvim_get_current_buf()] if current then local next_node = util.no_region_check_wrap(safe_jump_current, dir) @@ -176,28 +209,50 @@ local function jump(dir) return false end end -local function jump_destination(dir) + +--- Find the node the next jump will end up at. This will not work always, +--- because we will not update the node before jumping, so if the jump would +--- e.g. insert a new node between this node and its pre-update jump target, +--- this would not be registered. +--- Thus, it currently only works for simple cases. +---@param dir 1|-1 `1`: find the next node, `-1`: find the previous node. +---@return LuaSnip.Node _ The destination node. +function API.jump_destination(dir) -- dry run of jump (+no_move ofc.), only retrieves destination-node. return safe_jump_current(dir, true, { active = {} }) end -local function jumpable(dir) +--- Return whether jumping forwards or backwards will actually jump, or if +--- there is no node in that direction. +---@param dir 1|-1 `1` forward, `-1` backward. +---@return boolean +function API.jumpable(dir) -- node is jumpable if there is a destination. - return jump_destination(dir) + return API.jump_destination(dir) ~= session.current_nodes[vim.api.nvim_get_current_buf()] end -local function expandable() +--- Return whether there is an expandable snippet at the current cursor +--- position. Does not consider autosnippets since those would already be +--- expanded at this point. +---@return boolean +function API.expandable() next_expand, next_expand_params = match_snippet(util.get_current_line_to_cursor(), "snippets") return next_expand ~= nil end -local function expand_or_jumpable() - return expandable() or jumpable(1) +--- Return whether it's possible to expand a snippet at the current +--- cursor-position, or whether it's possible to jump forward from the current +--- node. +---@return boolean +function API.expand_or_jumpable() + return API.expandable() or API.jumpable(1) end -local function in_snippet() +--- Determine whether the cursor is within a snippet. +---@return boolean +function API.in_snippet() -- check if the cursor on a row inside a snippet. local node = session.current_nodes[vim.api.nvim_get_current_buf()] if not node then @@ -215,20 +270,29 @@ local function in_snippet() "Error while getting extmark-position: %s", snip_begin_pos ) - return + return false end local pos = vim.api.nvim_win_get_cursor(0) if pos[1] - 1 >= snip_begin_pos[1] and pos[1] - 1 <= snip_end_pos[1] then return true -- cursor not on row inside snippet end + return false end -local function expand_or_locally_jumpable() - return expandable() or (in_snippet() and jumpable(1)) +--- Return whether a snippet can be expanded at the current cursor position, or +--- whether the cursor is inside a snippet and the current node can be jumped +--- forward from. +---@return boolean +function API.expand_or_locally_jumpable() + return API.expandable() or (API.in_snippet() and API.jumpable(1)) end -local function locally_jumpable(dir) - return in_snippet() and jumpable(dir) +--- Return whether the cursor is inside a snippet and the current node can be +--- jumped forward from. +---@param dir 1|-1 Test jumping forwards/backwards. +---@return boolean +function API.locally_jumpable(dir) + return API.in_snippet() and API.jumpable(dir) end local function _jump_into_default(snippet) @@ -236,7 +300,92 @@ local function _jump_into_default(snippet) end -- opts.clear_region: table, keys `from` and `to`, both (0,0)-indexed. -local function snip_expand(snippet, opts) + +---@class LuaSnip.Opts.SnipExpandExpandParams +---@field trigger? string What to set as the expanded snippets' trigger +--- (Defaults to `snip.trigger`). +---@field captures? string[] Set as the expanded snippets' captures +--- (Defaults to `{}`). +---@field env_override? {[string]: string} Set or override environment +--- variables of the expanded snippet (Defaults to `{}`). + +---@class LuaSnip.Opts.Expand +---@field jump_into_func? (fun(snip: LuaSnip.Snippet): LuaSnip.Node) +--- Callback responsible for jumping into the snippet. The returned node is +--- set as the new active node, i.e. it is the origin of the next jump. +--- The default is basically this: +--- ```lua +--- function(snip) +--- -- jump_into set the placeholder of the snippet, 1 +--- -- to jump forwards. +--- return snip:jump_into(1) +--- end +--- ``` +--- while this can be used to insert the snippet and immediately move the cursor +--- at the `i(0)`: +--- ```lua +--- function(snip) +--- return snip.insert_nodes[0] +--- end +--- ``` + +--This can also use `jump_into_func`. +---@class LuaSnip.Opts.SnipExpand: LuaSnip.Opts.Expand +--- +---@field clear_region? LuaSnip.BufferRegion A region of text to clear after +--- populating env-variables, but before jumping into `snip`. If `nil`, no +--- clearing is performed. +--- Being able to remove text at this point is useful as clearing before calling +--- this function would populate `TM_CURRENT_LINE` and `TM_CURRENT_WORD` with +--- wrong values (they would miss the snippet trigger). +--- The actual values used for clearing are `region.from` and `region.to`, both +--- (0,0)-indexed byte-positions in the buffer. +--- +---@field expand_params? LuaSnip.Opts.SnipExpandExpandParams Override various +--- fields of the expanded snippet. Don't override anything by default. +--- This is useful for manually expanding snippets where the trigger passed +--- via `trig` is not the text triggering the snippet, or those which expect +--- `captures` (basically, snippets with a non-plaintext `trigEngine`). +--- +--- One Example: +--- ```lua +--- snip_expand(snip, { +--- trigger = "override_trigger", +--- captures = {"first capture", "second capture"}, +--- env_override = { this_key = "some value", other_key = {"multiple", "lines"}, TM_FILENAME = "some_other_filename.lua" } +--- }) +--- ``` +--- +---@field pos? [integer, integer] Position at which the snippet should be +--- inserted. Pass as `(row,col)`, both 0-based, the `col` given in bytes. +--- +---@field indent? boolean Whether to prepend the current lines' indent to all +--- lines of the snippet. (Defaults to `true`) +--- Turning this off is a good idea when a LSP server already takes indents into +--- consideration. In such cases, LuaSnip should not add additional indents. If +--- you are using `nvim-cmp`, this could be used as follows: +--- ```lua +--- require("cmp").setup { +--- snippet = { +--- expand = function(args) +--- local indent_nodes = true +--- if vim.api.nvim_get_option_value("filetype", { buf = 0 }) == "dart" then +--- indent_nodes = false +--- end +--- require("luasnip").lsp_expand(args.body, { +--- indent = indent_nodes, +--- }) +--- end, +--- }, +--- } +--- ``` + +--- Expand a snippet in the current buffer. +---@param snippet LuaSnip.Snippet The snippet. +---@param opts? LuaSnip.Opts.SnipExpand Optional additional arguments. +---@return LuaSnip.ExpandedSnippet _ The snippet that was inserted into the +--- buffer. +function API.snip_expand(snippet, opts) local snip = snippet:copy() opts = opts or {} @@ -315,11 +464,11 @@ local function snip_expand(snippet, opts) return snip end ----Find a snippet matching the current cursor-position. ----@param opts table: may contain: ---- - `jump_into_func`: passed through to `snip_expand`. ----@return boolean: whether a snippet was expanded. -local function expand(opts) +--- Find a snippet whose trigger matches the text before the cursor and expand +--- it. +---@param opts? LuaSnip.Opts.Expand Subset of opts accepted by `snip_expand`. +---@return boolean _ Whether a snippet was expanded. +function API.expand(opts) local expand_params local snip -- find snip via next_expand (set from previous expandable()) or manual matching. @@ -334,6 +483,7 @@ local function expand(opts) match_snippet(util.get_current_line_to_cursor(), "snippets") end if snip then + assert(expand_params) -- hint lsp type checker local jump_into_func = opts and opts.jump_into_func local cursor = util.get_cursor_0ind() @@ -348,7 +498,7 @@ local function expand(opts) } -- override snip with expanded copy. - snip = snip_expand(snip, { + snip = API.snip_expand(snip, { expand_params = expand_params, -- clear trigger-text. clear_region = clear_region, @@ -360,10 +510,12 @@ local function expand(opts) return false end -local function expand_auto() +--- Find an autosnippet matching the text at the cursor-position and expand it. +function API.expand_auto() local snip, expand_params = match_snippet(util.get_current_line_to_cursor(), "autosnippets") if snip then + assert(expand_params) -- hint lsp type checker local cursor = util.get_cursor_0ind() local clear_region = expand_params.clear_region or { @@ -373,7 +525,7 @@ local function expand_auto() }, to = cursor, } - snip = snip_expand(snip, { + snip = API.snip_expand(snip, { expand_params = expand_params, -- clear trigger-text. clear_region = clear_region, @@ -381,28 +533,35 @@ local function expand_auto() end end -local function expand_repeat() +--- Repeat the last performed `snip_expand`. Useful for dot-repeat. +function API.expand_repeat() -- prevent clearing text with repeated expand. session.last_expand_opts.clear_region = nil session.last_expand_opts.pos = nil - snip_expand(session.last_expand_snip, session.last_expand_opts) + API.snip_expand(session.last_expand_snip, session.last_expand_opts) end --- return true and expand snippet if expandable, return false if not. -local function expand_or_jump() - if expand() then +--- Expand at the cursor, or jump forward. +---@return boolean _ Whether an action was performed. +function API.expand_or_jump() + if API.expand() then return true end - if jump(1) then + if API.jump(1) then return true end return false end -local function lsp_expand(body, opts) +--- Expand a snippet specified in lsp-style. +---@param body string A string specifying a lsp-snippet, +--- e.g. `"[${1:text}](${2:url})"` +---@param opts? LuaSnip.Opts.SnipExpand Optional args passed through to +--- `snip_expand`. +function API.lsp_expand(body, opts) -- expand snippet as-is. - snip_expand( + API.snip_expand( ls.parser.parse_snippet( "", body, @@ -412,7 +571,9 @@ local function lsp_expand(body, opts) ) end -local function choice_active() +--- Return whether the current node is inside a choiceNode. +---@return boolean +function API.choice_active() return session.active_choice_nodes[vim.api.nvim_get_current_buf()] ~= nil end @@ -435,7 +596,10 @@ local function safe_choice_action(snip, ...) return session.current_nodes[vim.api.nvim_get_current_buf()] end end -local function change_choice(val) + +--- Change the currently active choice. +---@param val 1|-1 Move one choice forward or backward. +function API.change_choice(val) local active_choice = session.active_choice_nodes[vim.api.nvim_get_current_buf()] assert(active_choice, "No active choiceNode") @@ -450,7 +614,9 @@ local function change_choice(val) session.current_nodes[vim.api.nvim_get_current_buf()] = new_active end -local function set_choice(choice_indx) +--- Set the currently active choice. +---@param choice_indx integer Index of the choice to switch to. +function API.set_choice(choice_indx) local active_choice = session.active_choice_nodes[vim.api.nvim_get_current_buf()] assert(active_choice, "No active choiceNode") @@ -467,7 +633,9 @@ local function set_choice(choice_indx) session.current_nodes[vim.api.nvim_get_current_buf()] = new_active end -local function get_current_choices() +--- Get a string-representation of all the current choiceNode's choices. +---@return string[] _ \n-concatenated lines of every choice. +function API.get_current_choices() local active_choice = session.active_choice_nodes[vim.api.nvim_get_current_buf()] assert(active_choice, "No active choiceNode") @@ -482,7 +650,8 @@ local function get_current_choices() return choice_lines end -local function active_update_dependents() +--- Update all nodes that depend on the currently-active node. +function API.active_update_dependents() local active = session.current_nodes[vim.api.nvim_get_current_buf()] -- special case for startNode, cannot focus on those (and they can't -- have dependents) @@ -533,7 +702,14 @@ local function active_update_dependents() end end -local function store_snippet_docstrings(snippet_table) +--- Generate and store the docstrings for a list of snippets as generated by +--- `get_snippets()`. +--- The docstrings are stored at `stdpath("cache") .. "/luasnip/docstrings.json"`, +--- are indexed by their trigger, and should be updated once any snippet +--- changes. +---@param snippet_table {[string]: LuaSnip.Snippet[]} A table mapping some +--- keys to lists of snippets (keys are most likely filetypes). +function API.store_snippet_docstrings(snippet_table) -- ensure the directory exists. -- 493 = 0755 vim.loop.fs_mkdir(luasnip_data_dir, 493) @@ -567,7 +743,19 @@ local function store_snippet_docstrings(snippet_table) vim.loop.fs_write(docstring_cache_fd, util.json_encode(docstrings)) end -local function load_snippet_docstrings(snippet_table) +--- Provide all passed snippets with a previously-stored (via +--- `store_snippet_docstrings`) docstring. This prevents a somewhat costly +--- computation which is performed whenever a snippets' docstring is first +--- retrieved, but may cause larger delays when `snippet_table` contains many of +--- snippets. +--- Utilize this function by calling +--- `ls.store_snippet_docstrings(ls.get_snippets())` whenever snippets are +--- modified, and `ls.load_snippet_docstrings(ls.get_snippets())` on startup. +---@param snippet_table {[string]: LuaSnip.Snippet[]} List of snippets, should +--- contain the same keys (filetypes) as the table that was passed to +--- `store_snippet_docstrings`. Again, most likely the result of +--- `get_snippets`. +function API.load_snippet_docstrings(snippet_table) -- ensure the directory exists. -- 493 = 0755 vim.loop.fs_mkdir(luasnip_data_dir, 493) @@ -604,7 +792,9 @@ local function load_snippet_docstrings(snippet_table) end end -local function unlink_current_if_deleted() +--- Checks whether (part of) the current snippet's text was deleted, and removes +--- it from the jumplist if it was (it cannot be jumped back into). +function API.unlink_current_if_deleted() local node = session.current_nodes[vim.api.nvim_get_current_buf()] if not node then return @@ -623,7 +813,14 @@ local function unlink_current_if_deleted() end end -local function exit_out_of_region(node) +--- Checks whether the cursor is still within the range of the root-snippet +--- `node` belongs to. If yes, no change occurs; if no, the root-snippet is +--- exited and its `i(0)` will be the new active node. +--- If a jump causes an error (happens mostly because the text of a snippet was +--- deleted), the snippet is removed from the jumplist and the current node set +--- to the end/beginning of the next/previous snippet. +---@param node LuaSnip.Node +function API.exit_out_of_region(node) -- if currently jumping via luasnip or no active node: if session.jump_active or not node then return @@ -687,18 +884,40 @@ local function exit_out_of_region(node) end end --- ft string, extend_ft table of strings. -local function filetype_extend(ft, extend_ft) +--- Add `extend_ft` filetype to inherit its snippets from `ft`. +--- +--- Example: +--- ```lua +--- ls.filetype_extend("sh", {"zsh"}) +--- ls.filetype_extend("sh", {"bash"}) +--- ``` +--- This makes all `sh` snippets available in `sh`/`zsh`/`bash` buffers. +--- +---@param ft string +---@param extend_ft string[] +function API.filetype_extend(ft, extend_ft) vim.list_extend(session.ft_redirect[ft], extend_ft) session.ft_redirect[ft] = util.deduplicate(session.ft_redirect[ft]) end --- ft string, fts table of strings. -local function filetype_set(ft, fts) +--- Set `fts` filetypes as inheriting their snippets from `ft`. +--- +--- Example: +--- ```lua +--- ls.filetype_set("sh", {"sh", "zsh", "bash"}) +--- ``` +--- This makes all `sh` snippets available in `sh`/`zsh`/`bash` buffers. +--- +---@param ft string +---@param fts string[] +function API.filetype_set(ft, fts) session.ft_redirect[ft] = util.deduplicate(fts) end -local function cleanup() +--- Clear all loaded snippets. Also sends the `User LuasnipCleanup` +--- autocommand, so plugins that depend on luasnip's snippet-state can clean up +--- their now-outdated state. +function API.cleanup() -- Use this to reload luasnip vim.api.nvim_exec_autocmds( "User", @@ -709,11 +928,37 @@ local function cleanup() loader.cleanup() end -local function refresh_notify(ft) +--- Trigger the `User LuasnipSnippetsAdded` autocommand that signifies to other +--- plugins that a filetype has received new snippets. +--- +---@param ft string The filetype that has new snippets. +--- Code that listens to this event can retrieve this filetype from +--- `require("luasnip").session.latest_load_ft`. +function API.refresh_notify(ft) snippet_collection.refresh_notify(ft) end -local function setup_snip_env() +--- Injects the fields defined in `snip_env`, in `setup`, into the callers +--- global environment. +--- +--- This means that variables like `s`, `sn`, `i`, `t`, ... (by default) work, +--- and are useful for quickly testing snippets in a buffer: +--- ```lua +--- local ls = require("luasnip") +--- ls.setup_snip_env() +--- +--- ls.add_snippets("all", { +--- s("choicetest", { +--- t":", c(1, { +--- t("asdf", {node_ext_opts = {active = { virt_text = {{"asdf", "Comment"}} }}}), +--- t("qwer", {node_ext_opts = {active = { virt_text = {{"qwer", "Comment"}} }}}), +--- }) +--- }) +--- }, { key = "3d9cd211-c8df-4270-915e-bf48a0be8a79" }) +--- ``` +--- where the `key` makes it easy to reload the snippets on changes, since the +--- previously registered snippets will be replaced when the buffer is re-sourced. +function API.setup_snip_env() local combined_table = vim.tbl_extend("force", _G, session.config.snip_env) -- TODO: if desired, take into account _G's __index before looking into -- snip_env's __index. @@ -721,46 +966,108 @@ local function setup_snip_env() setfenv(2, combined_table) end -local function get_snip_env() + +--- Return the currently active snip_env. +---@return table +function API.get_snip_env() return session.get_snip_env() end -local function get_id_snippet(id) +--- Get the snippet corresponding to some id. +---@param id LuaSnip.SnippetID +---@return LuaSnip.Snippet +function API.get_id_snippet(id) return snippet_collection.get_id_snippet(id) end -local function add_snippets(ft, snippets, opts) +---@class LuaSnip.Opts.AddSnippets +--- +---@field type? "snippets"|"autosnippets" What to set `snippetType` to if it is +--- not defined for an individual snippet. Defaults to `"snippets"`. +--- +---@field key? string This key uniquely identifies this call to `add_snippets`. +--- If another call has the same `key`, the snippets added in this call will be +--- removed. +--- This is useful for reloading snippets once they are updated. +--- +---@field override_priority? integer Override the priority of individual +--- snippets. +--- +---@field default_priority? integer Priority of snippets where `priority` is not +--- already set. (Defaults to 1000) +--- +---@field refresh_notify? boolean Whether to call `refresh_notify` once the +--- snippets are added. (Defaults to true) + +--- Add snippets to luasnip's snippet-collection. +--- +--- NOTE: Calls `refresh_notify` as needed if enabled via `opts.refresh_notify`. +--- +---@param ft? string The filetype to add the snippets to, or nil if the filetype +--- is specified in `snippets`. +--- +---@param snippets LuaSnip.Addable[]|{[string]: LuaSnip.Addable[]} If `ft` is +--- nil a table mapping a filetype to a list of snippets, otherwise a flat +--- table of snippets. +--- `LuaSnip.Addable` are objects created by e.g. the functions `s`, `ms`, or +--- `sp`. +--- +---@param opts LuaSnip.Opts.AddSnippets? Optional arguments. +function API.add_snippets(ft, snippets, opts) util.validate("filetype", ft, { "string", "nil" }) util.validate("snippets", snippets, { "table" }) util.validate("opts", opts, { "table", "nil" }) opts = opts or {} opts.refresh_notify = opts.refresh_notify or true - -- alternatively, "autosnippets" - opts.type = opts.type or "snippets" - -- if ft is nil, snippets already has this format. + -- when ft is nil, snippets already use this format. if ft then snippets = { [ft] = snippets, } end - - snippet_collection.add_snippets(snippets, opts) + -- update type. + snippets = snippets --[[@as {[string]: LuaSnip.Snippet[]}]] + + snippet_collection.add_snippets(snippets, { + type = opts.type or "snippets", + key = opts.key, + override_priority = opts.override_priority, + default_priority = opts.default_priority, + }) if opts.refresh_notify then for ft_, _ in pairs(snippets) do - refresh_notify(ft_) + API.refresh_notify(ft_) end end end -local function clean_invalidated(opts) +---@class LuaSnip.Opts.CleanInvalidated +---@field inv_limit? integer If set, invalidated snippets are only cleared if +--- their number exceeds `inv_limit`. + +--- Clean invalidated snippets from internal snippet storage. +--- Invalidated snippets are still stored; it might be useful to actually remove +--- them as they still have to be iterated during expansion. +---@param opts? LuaSnip.Opts.CleanInvalidated Additional, optional arguments. +function API.clean_invalidated(opts) opts = opts or {} snippet_collection.clean_invalidated(opts) end -local function activate_node(opts) +---@class LuaSnip.Opts.ActivateNode +---@field strict? boolean Only activate nodes one could usually jump to. +--- (Defaults to false) +---@field select? boolean Whether to select the entire node, or leave the +--- cursor at the position it is currently at. (Defaults to true) +---@field pos? LuaSnip.BytecolBufferPosition Where to look for the node. +--- (Defaults to the position of the cursor) + +--- Lookup a node by position and activate (ie. jump into) it. +---@param opts? LuaSnip.Opts.ActivateNode Additional, optional arguments. +function API.activate_node(opts) opts = opts or {} local pos = opts.pos or util.get_cursor_0ind() local strict = vim.F.if_nil(opts.strict, false) @@ -851,7 +1158,7 @@ local ls_lazy = { -- It is used to define the type annotation class for all lazy attributes by tricking LuaLS into -- exploring all targeted functions and use their documentation for the class methods. if false then - ---@class LuaSnip_lazy + ---@class LuaSnip.LazyAPI _ = { s = require("luasnip.nodes.snippet").S, sn = require("luasnip.nodes.snippet").SN, @@ -883,53 +1190,13 @@ if false then } end ----@class LuaSnip_static -local ls_static = { - expand_or_jumpable = expand_or_jumpable, - expand_or_locally_jumpable = expand_or_locally_jumpable, - locally_jumpable = locally_jumpable, - jumpable = jumpable, - expandable = expandable, - in_snippet = in_snippet, - expand = expand, - snip_expand = snip_expand, - expand_repeat = expand_repeat, - expand_auto = expand_auto, - expand_or_jump = expand_or_jump, - jump = jump, - get_active_snip = get_active_snip, - choice_active = choice_active, - change_choice = change_choice, - set_choice = set_choice, - get_current_choices = get_current_choices, - unlink_current = unlink_current, - lsp_expand = lsp_expand, - active_update_dependents = active_update_dependents, - available = available, - exit_out_of_region = exit_out_of_region, - load_snippet_docstrings = load_snippet_docstrings, - store_snippet_docstrings = store_snippet_docstrings, - unlink_current_if_deleted = unlink_current_if_deleted, - filetype_extend = filetype_extend, - filetype_set = filetype_set, - add_snippets = add_snippets, - get_snippets = get_snippets, - get_id_snippet = get_id_snippet, - setup_snip_env = setup_snip_env, - get_snip_env = get_snip_env, - clean_invalidated = clean_invalidated, - get_snippet_filetypes = util.get_snippet_filetypes, - jump_destination = jump_destination, - session = session, - cleanup = cleanup, - refresh_notify = refresh_notify, - env_namespace = Environ.env_namespace, - setup = require("luasnip.config").setup, - extend_decorator = extend_decorator, - log = require("luasnip.util.log"), - activate_node = activate_node, -} +API.get_snippet_filetypes = util.get_snippet_filetypes +API.session = session +API.env_namespace = Environ.env_namespace +API.setup = require("luasnip.config").setup +API.extend_decorator = extend_decorator +API.log = require("luasnip.util.log") ----@class LuaSnip: LuaSnip_static, LuaSnip_lazy -ls = lazy_table(ls_static, ls_lazy) +---@class LuaSnip: LuaSnip.API, LuaSnip.LazyAPI +ls = lazy_table(API, ls_lazy) return ls diff --git a/lua/luasnip/loaders/from_vscode.lua b/lua/luasnip/loaders/from_vscode.lua index a42c09f95..54a5e7a95 100644 --- a/lua/luasnip/loaders/from_vscode.lua +++ b/lua/luasnip/loaders/from_vscode.lua @@ -151,7 +151,7 @@ Data.vscode_cache = --- Parse package.json(c), determine all files that contribute snippets, and --- which filetype is associated with them. --- @param manifest string ---- @return table> +--- @return {[string]: {[string]: true|nil}} local function get_snippet_files(manifest) -- if root doesn't contain a package.json, or it contributes no snippets, -- return no snippets. diff --git a/lua/luasnip/loaders/fs_watchers.lua b/lua/luasnip/loaders/fs_watchers.lua index 413444d74..dca63dddc 100644 --- a/lua/luasnip/loaders/fs_watchers.lua +++ b/lua/luasnip/loaders/fs_watchers.lua @@ -20,6 +20,9 @@ local callback_mt = { --- @alias LuaSnip.FSWatcher.Callback fun(full_path: string) +--- The callbacks are called with the full path to the file/directory that is +--- affected. +--- Callbacks that are not set will be replaced by a nop. --- @class LuaSnip.FSWatcher.TreeCallbacks --- @field new_file LuaSnip.FSWatcher.Callback? --- @field new_dir LuaSnip.FSWatcher.Callback? @@ -28,23 +31,20 @@ local callback_mt = { --- @field remove_root LuaSnip.FSWatcher.Callback? --- @field change_file LuaSnip.FSWatcher.Callback? --- @field change_dir LuaSnip.FSWatcher.Callback? ---- The callbacks are called with the full path to the file/directory that is ---- affected. ---- Callbacks that are not set will be replaced by a nop. +--- The callbacks are called with the full path to the file that path-watcher +--- is registered on. +--- Callbacks that are not set will be replaced by a nop. --- @class LuaSnip.FSWatcher.PathCallbacks --- @field add LuaSnip.FSWatcher.Callback? --- @field remove LuaSnip.FSWatcher.Callback? --- @field change LuaSnip.FSWatcher.Callback? ---- The callbacks are called with the full path to the file that path-watcher ---- is registered on. ---- Callbacks that are not set will be replaced by a nop. --- @class LuaSnip.FSWatcher.Options --- @field lazy boolean? --- If set, the watcher will be initialized even if the root/watched path does --- not yet exist, and start notifications once it is created. ---- @field fs_event_providers table? +--- @field fs_event_providers {[LuaSnip.FSWatcher.FSEventProviders]: boolean}? --- Which providers to use for receiving file-changes. local function get_opts(opts) @@ -118,13 +118,13 @@ end --- @class LuaSnip.FSWatcher.Tree --- @field root string --- @field fs_event userdata ---- @field files table ---- @field dir_watchers table +--- @field files {[string]: boolean} +--- @field dir_watchers {[string]: LuaSnip.FSWatcher.Tree} --- @field removed boolean --- @field stopped boolean --- @field callbacks LuaSnip.FSWatcher.TreeCallbacks --- @field depth number How deep the root should be monitored. ---- @field fs_event_providers table +--- @field fs_event_providers {[LuaSnip.FSWatcher.FSEventProviders]: boolean} --- @field root_realpath string? Set as soon as the watcher is started. local TreeWatcher = {} local TreeWatcher_mt = { @@ -507,7 +507,7 @@ end --- @field private stopped boolean --- @field private send_notifications boolean --- @field private callbacks LuaSnip.FSWatcher.TreeCallbacks ---- @field private fs_event_providers table +--- @field private fs_event_providers {[LuaSnip.FSWatcher.FSEventProviders]: boolean} --- @field private realpath string? Set as soon as the watcher is started. local PathWatcher = {} diff --git a/lua/luasnip/loaders/snippet_cache.lua b/lua/luasnip/loaders/snippet_cache.lua index 42a590cb3..a8d9d0a24 100644 --- a/lua/luasnip/loaders/snippet_cache.lua +++ b/lua/luasnip/loaders/snippet_cache.lua @@ -1,21 +1,21 @@ local uv = vim.uv or vim.loop local duplicate = require("luasnip.nodes.duplicate") +--- Stores modified time for a file. --- @class LuaSnip.Loaders.SnippetCache.Mtime --- @field sec number --- @field nsec number ---- Stores modified time for a file. +--- mtime is nil if the file does not currently exist. Since `get_fn` may still +--- return data, there's no need to treat this differently. --- @class LuaSnip.Loaders.SnippetCache.TimeCacheEntry --- @field mtime LuaSnip.Loaders.SnippetCache.Mtime? --- @field data LuaSnip.Loaders.SnippetFileData ---- mtime is nil if the file does not currently exist. Since `get_fn` may still ---- return data, there's no need to treat this differently. ---- @class LuaSnip.Loaders.SnippetCache --- SnippetCache stores snippets and other data loaded by files. +--- @class LuaSnip.Loaders.SnippetCache --- @field private get_fn fun(file: string): LuaSnip.Loaders.SnippetFileData ---- @field private cache table +--- @field private cache {[string]: LuaSnip.Loaders.SnippetCache.TimeCacheEntry} local SnippetCache = {} SnippetCache.__index = SnippetCache diff --git a/lua/luasnip/loaders/types.lua b/lua/luasnip/loaders/types.lua index b1297e143..40e01348d 100644 --- a/lua/luasnip/loaders/types.lua +++ b/lua/luasnip/loaders/types.lua @@ -1,8 +1,8 @@ --- @class LuaSnip.Loaders.LoadOpts ---- @field paths string[]?|string? Either a list of paths, or ","-delimited paths. If nil, searches rtp for snippet-collections. ---- @field lazy_paths string[]?|string? Like paths, but these will be watched, and loaded when creation is detected. ---- @field include string[]? If set, all filetypes not in include will be excluded from loading. ---- @field exclude string[]? Exclude these filetypes, even if they are set in include. ---- @field override_priority number? load all snippets with this priority. ---- @field default_priority number? snippet-priority, unless the snippet sets its own priority. ---- @field fs_event_providers table? How to monitor the filesystem +--- @field paths? string[]|string Either a list of paths, or ","-delimited paths. If nil, searches rtp for snippet-collections. +--- @field lazy_paths? string[]|string Like paths, but these will be watched, and loaded when creation is detected. +--- @field include? string[] If set, all filetypes not in include will be excluded from loading. +--- @field exclude? string[] Exclude these filetypes, even if they are set in include. +--- @field override_priority? number load all snippets with this priority. +--- @field default_priority? number snippet-priority, unless the snippet sets its own priority. +--- @field fs_event_providers? {[LuaSnip.FSWatcher.FSEventProviders]: boolean} How to monitor the filesystem diff --git a/lua/luasnip/nodes/choiceNode.lua b/lua/luasnip/nodes/choiceNode.lua index 22de611b2..fd36d307a 100644 --- a/lua/luasnip/nodes/choiceNode.lua +++ b/lua/luasnip/nodes/choiceNode.lua @@ -1,5 +1,4 @@ local Node = require("luasnip.nodes.node").Node -local ChoiceNode = Node:new() local util = require("luasnip.util.util") local node_util = require("luasnip.nodes.util") local types = require("luasnip.util.types") @@ -9,6 +8,12 @@ local session = require("luasnip.session") local sNode = require("luasnip.nodes.snippet").SN local extend_decorator = require("luasnip.util.extend_decorator") +---@class LuaSnip.ChoiceNode.ItemNode: LuaSnip.Node + +---@class LuaSnip.ChoiceNode: LuaSnip.Node +---@field choices LuaSnip.ChoiceNode.ItemNode[] +local ChoiceNode = Node:new() + function ChoiceNode:init_nodes() for i, choice in ipairs(self.choices) do -- setup jumps @@ -45,7 +50,50 @@ function ChoiceNode:init_nodes() self.active_choice = self.choices[1] end -local function C(pos, choices, opts) +---@class LuaSnip.Opts.ChoiceNode: LuaSnip.Opts.Node +---@field restore_cursor? boolean If set, the currently active node is looked up +--- in the switched-to choice, and the cursor restored to preserve the current +--- position relative to that node. The node may be found if a `restoreNode` is +--- present in both choice. +--- Defaults to `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) +--- opts = opts or {} +--- opts.restore_cursor = true +--- return c(pos, choices, opts) +--- end +--- ``` +--- Consider passing this override into `snip_env`. +--- +---@field node_callbacks? {["change_choice"|"enter"|"leave"]: fun(node:LuaSnip.Node)} +--- Specify functions to call after changing the choice, or entering or leaving +--- the node. The callback receives the `node` the callback was called on. + +--- Create a new choiceNode from a list of choices. +--- The first item in this list is the initial choice, and it can be changed +--- while any node of a choice is active. So, if all choices should be +--- reachable, every choice has to have a place for the cursor to stop at. +--- +--- If the choice is a snippetNode like `sn(nil, {...nodes...})` the given +--- `nodes` have to contain an `insertNode` (e.g. `i(1)`). Using an `insertNode` +--- or `textNode` directly as a choice is also fine, the latter is special-cased +--- to have a jump-point at the beginning of its text. +---@param pos integer Jump-index of the node. +--- (See [Basics-Jump-Index](../../../DOC.md#jump-index)) +--- +---@param choices (LuaSnip.Node|LuaSnip.Node[])[] A list of nodes that can be +--- switched between interactively. If a list of nodes is passed as a choice, +--- it will be turned into a snippetNode. +--- Jumpable nodes that generally need a jump-index don't need one when used as +--- a choice since they inherit the choiceNode's jump-index anyway. +--- +---@param opts? LuaSnip.Opts.ChoiceNode Additional optional arguments. +---@return LuaSnip.ChoiceNode +function ChoiceNode.C(pos, choices, opts) opts = opts or {} if opts.restore_cursor == nil then -- disable by default, can affect performance. @@ -74,7 +122,7 @@ local function C(pos, choices, opts) c:init_nodes() return c end -extend_decorator.register(C, { arg_indx = 3 }) +extend_decorator.register(ChoiceNode.C, { arg_indx = 3 }) function ChoiceNode:subsnip_init() node_util.subsnip_init_children(self.parent, self.choices) @@ -423,5 +471,5 @@ function ChoiceNode:extmarks_valid() end return { - C = C, + C = ChoiceNode.C, } diff --git a/lua/luasnip/nodes/node.lua b/lua/luasnip/nodes/node.lua index b3cce555e..64f1279a1 100644 --- a/lua/luasnip/nodes/node.lua +++ b/lua/luasnip/nodes/node.lua @@ -6,8 +6,30 @@ local events = require("luasnip.util.events") local key_indexer = require("luasnip.nodes.key_indexer") local types = require("luasnip.util.types") +---@class LuaSnip.Node local Node = {} +---@alias LuaSnip.NodeExtOpts {["active"|"passive"|"visited"|"unvisited"|"snippet_passive"]: vim.api.keyset.set_extmark} + +---@class LuaSnip.Opts.Node +---@field node_ext_opts LuaSnip.NodeExtOpts? Pass these opts through to the +---underlying extmarks representing the node. Notably, this enables highlighting +---the nodes, and allows the highlight to be different based on the state of the +---node/snippet. See [ext_opts](../../../DOC.md#ext_opts) +---@field merge_node_ext_opts boolean? Whether to use the parents' `ext_opts` to +---compute this nodes' `ext_opts`. +---@field key any? Some unique value (strings seem useful) to identify this +---node. +---This is useful for [Key Indexer](../../../DOC.md#key-indexer) or for finding the node at +---runtime (See [Snippets-API](../../../DOC.md#snippets-api) +---These keys don't have to be unique across the entire lifetime of the snippet, +---but every key should occur only once at the same time. This means it is fine +---to return a keyed node from a dynamicNode, because even if it will be +---generated multiple times, the same key not occur twice at the same time. +---@field node_callbacks {["enter"|"leave"]: fun(node:LuaSnip.Node)} +---Specify functions to call after changing the choice, or entering or leaving +---the node. The callback receives the `node` the callback was called on. + function Node:new(o, opts) o = o or {} diff --git a/lua/luasnip/nodes/snippet.lua b/lua/luasnip/nodes/snippet.lua index bfd39b5f1..70594a7b8 100644 --- a/lua/luasnip/nodes/snippet.lua +++ b/lua/luasnip/nodes/snippet.lua @@ -66,8 +66,21 @@ local stored_mt = { end, } +---@class LuaSnip.Snippet: LuaSnip.Addable, LuaSnip.ExpandedSnippet +---@field nodes LuaSnip.Node[] local Snippet = node_mod.Node:new() +-- very approximate classes, for now. +---@alias LuaSnip.SnippetID integer + +---Anything that can be passed to ls.add_snippets(). +---@class LuaSnip.Addable + +---Represents an expanded snippet. +---@class LuaSnip.ExpandedSnippet: LuaSnip.Node + +---@class LuaSnip.SnippetNode: LuaSnip.Node + local Parent_indexer = {} function Parent_indexer:new(o) diff --git a/lua/luasnip/session/snippet_collection/init.lua b/lua/luasnip/session/snippet_collection/init.lua index bc6c07fde..4490b606d 100644 --- a/lua/luasnip/session/snippet_collection/init.lua +++ b/lua/luasnip/session/snippet_collection/init.lua @@ -222,9 +222,27 @@ local function invalidate_addables(addables_by_ft) M.clean_invalidated({ inv_limit = 100 }) end +---@class LuaSnip.Opts.SessionAddSnippets +--- +---@field type "snippets"|"autosnippets" What to set `snippetType` to if it is +--- not defined for an individual snippet. +--- +---@field key? string This key uniquely identifies this call to `add_snippets`. +--- If another call has the same `key`, the snippets added in this call will be +--- removed. +--- This is useful for reloading snippets once they are updated. +--- +---@field override_priority? integer Override the priority of individual +--- snippets. +--- +---@field default_priority? integer Default snippets priority. +--- (Defaults to 1000) + local current_id = 0 --- snippets like {ft1={}, ft2={}}, opts should be properly --- initialized with default values. +--- Add snippets to the collection of snippets. +--- +---@param snippets {[string]: LuaSnip.Addable[]} +---@param opts LuaSnip.Opts.SessionAddSnippets function M.add_snippets(snippets, opts) for ft, ft_snippets in pairs(snippets) do for _, addable in ipairs(ft_snippets) do diff --git a/lua/luasnip/util/table.lua b/lua/luasnip/util/table.lua index 317626868..ba0876b5e 100644 --- a/lua/luasnip/util/table.lua +++ b/lua/luasnip/util/table.lua @@ -1,7 +1,7 @@ ---Convert set of values to a list of those values. ---@generic T ----@param tbl T|T[]|table ----@return table +---@param tbl T|T[]|{[T]: boolean} +---@return {[T]: boolean} local function set_to_list(tbl) local ls = {} @@ -14,8 +14,8 @@ end ---Convert value or list of values to a table of booleans for fast lookup. ---@generic T ----@param values T|T[]|table ----@return table +---@param values T|T[]|{[T]: boolean} +---@return {[T]: boolean} local function list_to_set(values) if values == nil then return {} diff --git a/lua/luasnip/util/util.lua b/lua/luasnip/util/util.lua index 4348b6ec1..8601fe1fd 100644 --- a/lua/luasnip/util/util.lua +++ b/lua/luasnip/util/util.lua @@ -293,6 +293,9 @@ local function deduplicate(list) return ret end +---Returns the list of filetypes that are considered for the current buffer, in +---order of priority. +---@return string[] local function get_snippet_filetypes() local config = require("luasnip.session").config local fts = config.ft_func()