Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
14 changes: 7 additions & 7 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 All @@ -785,7 +785,7 @@ the latter is special-cased to have a jump-point at the beginning of its text.
end
```
Consider passing this override into `snip_env`.
* `node_callbacks?: { [("change_choice"|"enter"...)]: fun(...) -> ... }?`
* `node_callbacks?: { [LuaSnip.EventType]: 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)
Expand Down
17 changes: 9 additions & 8 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 18

==============================================================================
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 All @@ -806,7 +807,7 @@ have a jump-point at the beginning of its text.
end
<
Consider passing this override into `snip_env`.
- `node_callbacks?: { [("change_choice"|"enter"...)]: fun(...) -> ... }?`
- `node_callbacks?: { [LuaSnip.EventType]: 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
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
10 changes: 6 additions & 4 deletions lua/luasnip/extras/conditions/expand.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local function line_begin(line_to_cursor, matched_trigger)
-- +1 because `string.sub("abcd", 1, -2)` -> abc
return line_to_cursor:sub(1, -(#matched_trigger + 1)):match("^%s*$")
end
--- A condition obj that is true when the trigger is at start of line (maybe after indent).
M.line_begin = cond_obj.make_condition(line_begin)

--- The wordTrig flag will only expand the snippet if
Expand Down Expand Up @@ -41,16 +42,17 @@ M.line_begin = cond_obj.make_condition(line_begin)
--- I think the character wordTrig=true uses should be customized
--- A condtion seems like the best way to do it
---
--- @param pattern string should be a character class eg `[%w]`
---@param pattern string should be a character class eg `[%w]`
---@return LuaSnip.SnipContext.ConditionObj
function M.trigger_not_preceded_by(pattern)
local condition = function(line_to_cursor, matched_trigger)
local line_to_trigger_len = #line_to_cursor - #matched_trigger
if line_to_trigger_len == 0 then
return true
end
return not string
.sub(line_to_cursor, line_to_trigger_len, line_to_trigger_len)
:match(pattern)
local char_before_trigger =
line_to_cursor:sub(line_to_trigger_len, line_to_trigger_len)
return not char_before_trigger:match(pattern)
end
return cond_obj.make_condition(condition)
end
Expand Down
180 changes: 131 additions & 49 deletions lua/luasnip/extras/conditions/init.lua
Original file line number Diff line number Diff line change
@@ -1,55 +1,137 @@
local M = {}

-----------------------
-- CONDITION OBJECTS --
-----------------------
local condition_mt = {
-- logic operators
-- not '-'
__unm = function(o1)
return M.make_condition(function(...)
return not o1(...)
end)
end,
-- or '+'
__add = function(o1, o2)
return M.make_condition(function(...)
return o1(...) or o2(...)
end)
end,
__sub = function(o1, o2)
return M.make_condition(function(...)
return o1(...) and not o2(...)
end)
end,
-- and '*'
__mul = function(o1, o2)
return M.make_condition(function(...)
return o1(...) and o2(...)
end)
end,
-- xor '^'
__pow = function(o1, o2)
return M.make_condition(function(...)
return o1(...) ~= o2(...)
end)
end,
-- xnor '%'
-- might be counter intuitive, but as we can't use '==' (must return bool)
-- it's best to use something weird (doesn't have to be used)
__mod = function(o1, o2)
return function(...)
return o1(...) == o2(...)
end
end,
--- A composable condition object. It can be used for `condition` in a snippet
--- context but can also be logically combined with other condition
--- function/object to build complex conditions.
---
--- This makes logical combinations of conditions very readable.
---
--- Compare
--- ```lua
--- local conds = require"luasnip.extras.conditions.expand"
--- ...
--- -- using combinator functions:
--- condition = conds.line_end:or_(conds.line_begin)
--- -- using operators:
--- condition = conds.line_end + conds.line_begin
--- ```
---
--- with the more verbose
---
--- ```lua
--- local conds = require"luasnip.extras.conditions.expand"
--- ...
--- condition = function(...) return conds.line_end(...) or conds.line_begin(...) end
--- ```
---
--- The conditions provided in `show` and `expand` are already condition objects.
--- To create new ones, use:
--- ```lua
--- require("luasnip.extras.conditions").make_condition(condition_fn)
--- ```
---
---@class LuaSnip.SnipContext.ConditionObj
---@field func LuaSnip.SnipContext.ConditionFn|LuaSnip.SnipContext.ShowConditionFn
---
---@overload fun(line_to_cursor: string, matched_trigger: string, captures: string[]): boolean
--- (note: same signature as `func` field)
---
---@operator unm: LuaSnip.SnipContext.ConditionObj
---@operator add(LuaSnip.SnipContext.Condition): LuaSnip.SnipContext.ConditionObj
---@operator sub(LuaSnip.SnipContext.Condition): LuaSnip.SnipContext.ConditionObj
---@operator mul(LuaSnip.SnipContext.Condition): LuaSnip.SnipContext.ConditionObj
---@operator pow(LuaSnip.SnipContext.Condition): LuaSnip.SnipContext.ConditionObj
---@operator mod(LuaSnip.SnipContext.Condition): LuaSnip.SnipContext.ConditionObj
local ConditionObj = {}
local ConditionObj_mt = {
__index = ConditionObj,
-- use table like a function by overloading __call
__call = function(tab, line_to_cursor, matched_trigger, captures)
return tab.func(line_to_cursor, matched_trigger, captures)
__call = function(self, line_to_cursor, matched_trigger, captures)
return self.func(line_to_cursor, matched_trigger, captures)
end,
}

function M.make_condition(func)
return setmetatable({ func = func }, condition_mt)
--- Wrap the given `condition` function into a composable condition object.
---@param func LuaSnip.SnipContext.ConditionFn
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj.make_condition(func)
return setmetatable({ func = func }, ConditionObj_mt)
end

--- Returns a condition object equivalent to `not self(...)`
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj:inverted()
return ConditionObj.make_condition(function(...)
return not self(...)
end)
end
--- (e.g. `-cond`, implemented as `cond:inverted()`)
ConditionObj_mt.__unm = ConditionObj.inverted

--- Returns a condition object equivalent to `self(...) or other(...)`
---@param other LuaSnip.SnipContext.Condition
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj:or_(other)
return ConditionObj.make_condition(function(...)
return self(...) or other(...)
end)
end
--- (e.g. `cond1 + cond2`, implemented as `cond1:or_(cond2)`)
ConditionObj_mt.__add = ConditionObj.or_

--- Returns a condition object equivalent to `self(...) and not other(...)`
---
--- 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)`.
---
---@param other LuaSnip.SnipContext.Condition
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj:and_not(other)
return ConditionObj.make_condition(function(...)
return self(...) and not other(...)
end)
end
--- (e.g. `cond1 - cond2`, implemented as `cond1:and_not(cond2)`)
ConditionObj_mt.__sub = ConditionObj.and_not

--- Returns a condition object equivalent to `self(...) and other(...)`
---@param other LuaSnip.SnipContext.Condition
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj:and_(other)
return ConditionObj.make_condition(function(...)
return self(...) and other(...)
end)
end
--- (e.g. `cond1 * cond2`, implemented as `cond1:and_(cond2)`)
ConditionObj_mt.__mul = ConditionObj.and_

--- Returns a condition object equivalent to `self(...) ~= other(...)` (xor)
---@param other LuaSnip.SnipContext.Condition
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj:not_same_as(other)
return ConditionObj.make_condition(function(...)
return self(...) ~= other(...)
end)
end
--- (e.g. `cond1 ^ cond2`, implemented as `cond1:not_same_as(cond2)`)
ConditionObj_mt.__pow = ConditionObj.not_same_as

--- Returns a condition object equivalent to `self(...) == other(...)` (xnor)
---@param other LuaSnip.SnipContext.Condition
---@return LuaSnip.SnipContext.ConditionObj
function ConditionObj:same_as(other)
return ConditionObj.make_condition(function(...)
return self(...) == other(...)
end)
end
--- (e.g. `cond1 % cond2`, implemented as `cond1:same_as(cond2)`)
---
--- Using `%` might be counter intuitive, considering the `==`-operator exists,
--- unfortunately, it's not possible to use this for our purposes (some info
--- [here](https://github.com/L3MON4D3/LuaSnip/pull/612#issuecomment-1264487743)).
--- We decided instead to make use of a more obscure symbol (which will
--- hopefully avoid false assumptions about its meaning).
ConditionObj_mt.__mod = ConditionObj.same_as

return M
return ConditionObj
Loading