Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,21 +752,21 @@ ChoiceNodes allow choosing between multiple nodes.

<!-- panvimdoc-ignore-end -->

`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.
`c(pos?, choices, node_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))
* `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.
* `node_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
Expand Down
15 changes: 8 additions & 7 deletions doc/luasnip.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*luasnip.txt* For NeoVim 0.7-0.11 Last change: 2025 November 03
*luasnip.txt* For NeoVim 0.7-0.11 Last change: 2025 November 08

==============================================================================
Table of Contents *luasnip-table-of-contents*
Expand Down Expand Up @@ -772,23 +772,24 @@ ChoiceNodes allow choosing between multiple nodes.
}))
<

`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.
`c(pos?, choices, node_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|)
- `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.
- `node_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
Expand Down
3 changes: 3 additions & 0 deletions lua/luasnip/_types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
---@class LuaSnip.BufferRegion
---@field from LuaSnip.BytecolBufferPosition Starting position, included.
---@field to LuaSnip.BytecolBufferPosition Ending position, excluded.

---@alias LuaSnip.NormalizedNodeRef LuaSnip.KeyIndexer|LuaSnip.AbsoluteIndexer|LuaSnip.OptionalNodeRef
---@alias LuaSnip.NodeRef LuaSnip.KeyIndexer|LuaSnip.AbsoluteIndexer|LuaSnip.OptionalNodeRef|number
100 changes: 45 additions & 55 deletions lua/luasnip/extras/fmt.lua
Original file line number Diff line number Diff line change
@@ -1,53 +1,39 @@
local text_node = require("luasnip.nodes.textNode").T
local wrap_nodes = require("luasnip.util.util").wrap_nodes
local util = require("luasnip.util.util")
local extend_decorator = require("luasnip.util.extend_decorator")
local Str = require("luasnip.util.str")
local rp = require("luasnip.extras").rep

-- https://gist.github.com/tylerneylon/81333721109155b2d244
local function copy3(obj, seen)
-- Handle non-tables and previously-seen tables.
if type(obj) ~= "table" then
return obj
end
if seen and seen[obj] then
return seen[obj]
end

-- New table; mark it as seen an copy recursively.
local s = seen or {}
local res = {}
s[obj] = res
for k, v in next, obj do
res[copy3(k, s)] = copy3(v, s)
end
return setmetatable(res, getmetatable(obj))
end

-- Interpolate elements from `args` into format string with placeholders.
--
-- The placeholder syntax for selecting from `args` is similar to fmtlib and
-- Python's .format(), with some notable differences:
-- * no format options (like `{:.2f}`)
-- * 1-based indexing
-- * numbered/auto-numbered placeholders can be mixed; numbered ones set the
-- current index to new value, so following auto-numbered placeholders start
-- counting from the new value (e.g. `{} {3} {}` is `{1} {3} {4}`)
--
-- Arguments:
-- fmt: string with placeholders
-- args: table with list-like and/or map-like keys
-- opts:
-- delimiters: string, 2 distinct characters (left, right), default "{}"
-- strict: boolean, set to false to allow for unused `args`, default true
-- repeat_duplicates: boolean, repeat nodes which have jump_index instead of copying them, default false
-- Returns: a list of strings and elements of `args` inserted into placeholders
---@class LuaSnip.Opts.Extra.FmtInterpolate
---@field delimiters? string String of 2 distinct characters (left, right).
--- Defaults to "{}".
---@field strict? boolean Whether to allow error out on unused `args`.
--- Defaults to true.
---@field repeat_duplicates? boolean Repeat nodes which have the same jump_index
--- instead of copying them. Default to false.

--- Interpolate elements from `args` into format string with placeholders.
---
--- The placeholder syntax for selecting from `args` is similar to fmtlib and
--- Python's .format(), with some notable differences:
--- * no format options (like `{:.2f}`)
--- * 1-based indexing
--- * numbered/auto-numbered placeholders can be mixed; numbered ones set the
--- current index to new value, so following auto-numbered placeholders start
--- counting from the new value (e.g. `{} {3} {}` is `{1} {3} {4}`)
---
---@param fmt string String with placeholders
---@param args {[integer|string]: LuaSnip.Node} Table with list-like and/or map-like keys
---@param opts? LuaSnip.Opts.Extra.FmtInterpolate
---@return (string|LuaSnip.Node)[] _ A list of strings & elements of `args`
--- inserted into placeholders.
local function interpolate(fmt, args, opts)
local defaults = {
delimiters = "{}",
strict = true,
repeat_duplicates = false,
}
---@type LuaSnip.Opts.Extra.FmtInterpolate
opts = vim.tbl_extend("force", defaults, opts or {})

-- sanitize delimiters
Expand Down Expand Up @@ -102,7 +88,7 @@ local function interpolate(fmt, args, opts)
if used_keys[key] then
local jump_index = args[key]:get_jump_index() -- For nodes that don't have a jump index, copy it instead
if not opts.repeat_duplicates or jump_index == nil then
table.insert(elements, copy3(args[key]))
table.insert(elements, util.copy3(args[key]))
else
table.insert(elements, rp(jump_index))
end
Expand Down Expand Up @@ -175,30 +161,34 @@ local function interpolate(fmt, args, opts)
return elements
end

-- Use a format string with placeholders to interpolate nodes.
--
-- See `interpolate` documentation for details on the format.
--
-- Arguments:
-- str: format string
-- nodes: snippet node or list of nodes
-- opts: optional table
-- trim_empty: boolean, remove whitespace-only first/last lines, default true
-- dedent: boolean, remove all common indent in `str`, default true
-- indent_string: string, convert `indent_string` at beginning of each line to unit indent ('\t')
-- after applying `dedent`, default empty string (disabled)
-- ... the rest is passed to `interpolate`
-- Returns: list of snippet nodes
---@class LuaSnip.Opts.Extra.Fmt: LuaSnip.Opts.Extra.FmtInterpolate
---@field trim_empty? boolean Whether to remove whitespace-only first/last lines
--- Defaults to true.
---@field dedent? boolean Whether to remove all common indent in `str`.
--- Defaults to true.
---@field indent_string? string When set, will convert `indent_string` at
--- beginning of each line to unit indent ('\t') after applying `dedent`.
--- Defaults to empty string (disabled).

--- Use a format string with placeholders to interpolate nodes.
---
--- See `interpolate` documentation for details on the format.
---
---@param str string The format string
---@param nodes LuaSnip.Node|LuaSnip.Node[]
---@param opts? LuaSnip.Opts.Extra.Fmt
---@return LuaSnip.Node[]
local function format_nodes(str, nodes, opts)
local defaults = {
trim_empty = true,
dedent = true,
indent_string = "",
}
---@type LuaSnip.Opts.Extra.Fmt
opts = vim.tbl_extend("force", defaults, opts or {})

-- allow to pass a single node
nodes = wrap_nodes(nodes)
nodes = util.wrap_nodes(nodes)

-- optimization: avoid splitting multiple times
local lines = nil
Expand Down
13 changes: 10 additions & 3 deletions lua/luasnip/extras/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,18 @@ end
return {
lambda = lambda,
match = match,
-- repeat a node.
rep = function(node_indx)
--- Repeat a node, by inserting the text of the passed node.
---
--- ```lua
--- s("extras4", { i(1), t { "", "" }, extras.rep(1) })
--- ```
---
---@param node_ref LuaSnip.NodeRef a single [Node Reference](../../../DOC.md#node-reference).
---@return LuaSnip.FunctionNode
rep = function(node_ref)
return F(function(args)
return args[1]
end, node_indx)
end, node_ref)
end,
-- Insert the output of a function.
partial = function(func, ...)
Expand Down
20 changes: 14 additions & 6 deletions lua/luasnip/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ local function api_leave()
session.luasnip_changedtick = nil
end

---@generic T: any?
---@param fn (fun(...))|(fun(...): T)
---@param ... any
---@return T
local function api_do(fn, ...)
api_enter()

Expand Down Expand Up @@ -523,6 +527,7 @@ end
---@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)
Expand All @@ -531,15 +536,14 @@ end
--- return snip:jump_into(1)
--- end
--- ```
--- while this can be used to insert the snippet and immediately move the cursor
--- at the `i(0)`:
--- 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
Expand Down Expand Up @@ -590,8 +594,12 @@ end
--- }
--- ```

---@param snippet LuaSnip.Snippet
---@param opts? LuaSnip.Opts.SnipExpand
---@return LuaSnip.ExpandedSnippet
local function _snip_expand(snippet, opts)
local snip = snippet:copy()
---@cast snip LuaSnip.ExpandedSnippet

opts = opts or {}
opts.expand_params = opts.expand_params or {}
Expand Down Expand Up @@ -681,9 +689,8 @@ function API.snip_expand(snippet, opts)
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.
---@param opts? LuaSnip.Opts.Expand
---@return boolean _ Whether a snippet was expanded.
local function _expand(opts)
local expand_params
local snip
Expand Down Expand Up @@ -740,6 +747,7 @@ function API.expand_auto()
local snip, expand_params =
match_snippet(util.get_current_line_to_cursor(), "autosnippets")
if snip then
---@cast expand_params -nil
local cursor = util.get_cursor_0ind()
local clear_region = expand_params.clear_region
or {
Expand Down
1 change: 1 addition & 0 deletions lua/luasnip/loaders/from_lua.lua
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ local function _luasnip_load_file(file)
log.error("Failed to load %s\n: %s", file, error_msg)
error(string.format("Failed to load %s\n: %s", file, error_msg))
end
---@cast func -nil

-- the loaded file may add snippets to these tables, they'll be
-- combined with the snippets returned regularly.
Expand Down
4 changes: 2 additions & 2 deletions lua/luasnip/loaders/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ end

---Get paths of .snippets files
---@param root string @snippet directory path
---@return table @keys are file types, values are paths
---@return table _ @keys are file types, values are paths
local function get_ft_paths(root, extension)
local ft_path = {}
local files, dirs = Path.scandir(root)
Expand Down Expand Up @@ -155,7 +155,7 @@ end
--- directory named `rtp_dirname` in the runtimepath.
---@param extension string: extension of valid snippet-files for the given
--- collection (eg `.lua` or `.snippets`)
---@return table: a list of tables, each of the inner tables contains two
---@return table _ a list of tables, each of the inner tables contains two
--- entries:
--- - collection_paths: ft->files for the entire collection and
--- - load_paths: ft->files for only the files that should be loaded.
Expand Down
4 changes: 4 additions & 0 deletions lua/luasnip/nodes/absolute_indexer.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
-- absolute_indexer[0][1][2][3] -> { absolute_insert_position = {0,1,2,3} }

---@class LuaSnip.AbsoluteIndexer: {[integer]: LuaSnip.AbsoluteIndexer}
---@field absolute_insert_position integer[]

---@return LuaSnip.AbsoluteIndexer
local function new()
return setmetatable({
absolute_insert_position = {},
Expand Down
Loading
Loading