-
-
Notifications
You must be signed in to change notification settings - Fork 245
Cool Snippets
Got a cool or useful snippet that you'd like to show off share with the community?
Do it here! Make sure to include a short description to find snippets for a specific filetype. Other users will also surely appreciate a short explaination of that weird pattern or the 100-line dynamicNode you used ;)
Use Treesitter to determine the name of the variable returned from the module and use that as the function's parent, with a choice node to swap between static function (Module.fn_name
) vs. instance method (Module:fn_name
) syntax.
s(
'mfn',
c(1, {
fmt('function {}.{}({})\n {}\nend', {
f(get_returned_mod_name, {}),
i(1),
i(2),
i(3),
}),
fmt('function {}:{}({})\n {}\nend', {
f(get_returned_mod_name, {}),
i(1),
i(2),
i(3),
}),
})
)
Of course, this is just a recommendation :)
local rec_ls
rec_ls = function()
return sn(nil, {
c(1, {
-- important!! Having the sn(...) as the first choice will cause infinite recursion.
t({""}),
-- The same dynamicNode as in the snippet (also note: self reference).
sn(nil, {t({"", "\t\\item "}), i(1), d(2, rec_ls, {})}),
}),
});
end
...
s("ls", {
t({"\\begin{itemize}",
"\t\\item "}), i(1), d(2, rec_ls, {}),
t({"", "\\end{itemize}"}), i(0)
})
This snippet first expands to
\begin{itemize}
\item $1
\end{itemize}
upon jumping into the dynamicNode
and changing the choice, another dynamicNode
(and with it, \item
) will be inserted.
As the dynamicNode
is recursive, it's possible to insert as many \item
s' as desired.
(The gif also showcases Luasnips ability to preserve input to collapsed choices!)
local tex = {}
tex.in_mathzone = function()
return vim.fn['vimtex#syntax#in_mathzone']() == 1
end
tex.in_text = function()
return not tex.in_mathzone()
end
...
s("dm", {
t({ "\\[", "\t" }),
i(1),
t({ "", "\\]" }),
}, { condition = tex.in_text })
This snippet will only expand in text so that you may type dm
safely in math environments. For other environments, use this multipurpose function:
Note: requires VimTeX for this to work.
local function env(name)
local is_inside = vim.fn['vimtex#env#is_inside'](name)
return (is_inside[1] > 0 and is_inside[2] > 0)
end
-- Example: use only in TikZ environments. Note a helper for each environment has to be defined.
local function tikz()
return env("tikzpicture")
end
Moreover, using the following condition,
the snippet will only be expanded in beamer
documents.
tex.in_beamer = function()
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for _, line in ipairs(lines) do
if line:match("\\documentclass{beamer}") then
return true
end
end
return false
end
-- or, use `vimtex` to detect
-- tex.in_beamer = function()
-- return vim.b.vimtex["documentclass"] == "beamer"
-- end
...
s("bfr", {
t({ "\\begin{frame}", "\t\\frametitle{" }),
i(1, "frame title"),
t({ "}", "\t" }),
i(0),
t({ "", "\\end{frame}" }),
}, { condition = tex.in_beamer })
Indeed, you may combine some of the conditions to build your own context-aware snippets.
A possible use case is using snippets of the corresponding language in minted
environments (which also uses Condition Objects and requires VimTeX).
-- Imports, Define Conditions
local conds = require("luasnip.extras.expand_conditions")
local make_condition = require("luasnip.extras.conditions").make_condition
local function tex()
return vim.bo.filetype == "tex"
end
local function python()
return vim.bo.filetype == "python"
end
local function mintedPython()
return vim.fn['vimtex#syntax#in']("texMintedZonePython") == 1
end
local pyfile = make_condition(python)
local texfile = make_condition(tex)
local pymint = make_condition(mintedPython)
-- example snippet
autosnippet(
"pytest",
{ t("this triggers only in python files, or in tex files with minted enabled with python") },
{ condition = pyfile + (texfile * pymint), show_condition = pyfile + (texfile * pymint) }
),
With this snippet you can dynamically generate a table. It contain two main function. The first function return a snipnode which includes an insert node for each column defined by the user.
table_node= function(args)
local tabs = {}
local count
table = args[1][1]:gsub("%s",""):gsub("|","")
count = table:len()
for j=1, count do
local iNode
iNode = i(j)
tabs[2*j-1] = iNode
if j~=count then
tabs[2*j] = t" & "
end
end
return sn(nil, tabs)
end
The second one it is based on the rec_ls
function from endless list entry.
rec_table = function ()
return sn(nil, {
c(1, {
t({""}),
sn(nil, {t{"\\\\",""} ,d(1,table_node, {ai[1]}), d(2, rec_table, {ai[1]})})
}),
});
end
Based on those functions, the snips will be
s("table", {
t"\\begin{tabular}{",
i(1,"0"),
t{"}",""},
d(2, table_node, {1}, {}),
d(3, rec_table, {1}),
t{"","\\end{tabular}"}
}),
A snippet to support multiple integrals with support for surface/volume integrals as well, which uses dynamic nodes with choice nodes.
-- integral functions
-- generate \int_{<>}^{<>}
local int1 = function(args, snip)
local vars = tonumber(snip.captures[1])
local nodes = {}
for j = 1, vars do
table.insert(nodes, t("\\int_{"))
table.insert(nodes, r(2*j-1, "lb" .. tostring(j), i(1))) -- thanks L3MON4D3 for finding the index issue
table.insert(nodes, t("}^{"))
table.insert(nodes, r(2*j, "ub" .. tostring(j), i(1))) -- please remember to count and don't be like me
table.insert(nodes, t("} "))
end
return sn(nil, nodes)
end
-- generate \dd <>
local int2 = function(args, snip)
local vars = tonumber(snip.captures[1])
local nodes = {}
for j = 1, vars do
table.insert(nodes, t(" \\dd "))
table.insert(nodes, r(j, "var" .. tostring(j), i(1)))
end
return sn(nil, nodes)
end
autosnippet(
{ trig = "(%d)int", name = "multi integrals", dscr = "please work", regTrig = true, hidden = false },
fmt([[
<> <> <> <>
]],{c(1, { fmta([[
\<><>nt_{<>}
]], {c(1, { t(""), t("o") }),
f(function(_, parent, snip)
inum = tonumber(parent.parent.captures[1]) -- this guy's lineage looking like a research lab's
res = string.rep("i", inum)
return res
end), i(2),}), d(nil, int1),}),
i(2), d(3, int2), i(0),},
{ delimiters = "<>" }),
{ condition = math, show_condition = math }),
Use choice and dynamic nodes with user_args
to generate concise and clear labels to be referenced in math notes. Additional second argument supports a personal preamble setup with tcolorbox
and newenvironmentx, xargs
.
-- personal util
local function isempty(s) --util
return s == nil or s == ''
end
-- label util
local generate_label = function(args, parent, _, user_arg1, user_arg2)
if user_arg2 ~= "xargs" then
delims = {"\\label{", "}"} -- chooses surrounding environment based on arg2 - tcolorboxes use xargs
else
delims = {"[", "]"}
end
if isempty(user_arg1) then
return sn(nil, fmta([[
\label{<>} -- creates a general label
]], {i(1)}))
else
return sn(nil, fmta([[
<><>:<><> -- creates a specialized label
]], {t(delims[1]), t(user_arg1), i(1), t(delims[2])}))
end
end
-- generates \section{$1}(\label{sec:$1})?
s(
{ trig = "#", hidden = true, priority = 250 },
fmt(
[[
\section{<>}<>
<>]],
{ i(1), c(2, {t(""), d(1, generate_label, {}, {user_args={"sec"}} )
}), i(0) },
{ delimiters = "<>" }
)
),
-- generates \begin{definition}[$1]([def:$2])?{ ...
s(
{ trig = "adef", name = "add definition", dscr = "add definition box" },
fmt(
[[
\begin{definition}[<>]<>{<>
}
\end{definition}]],
{ i(1), c(2, {t(""), d(1, generate_label, {}, {user_args={"def", "xargs"}})}), i(0) },
{ delimiters = "<>" }
)
),
local function char_count_same(c1, c2)
local line = vim.api.nvim_get_current_line()
-- '%'-escape chars to force explicit match (gsub accepts patterns).
-- second return value is number of substitutions.
local _, ct1 = string.gsub(line, '%'..c1, '')
local _, ct2 = string.gsub(line, '%'..c2, '')
return ct1 == ct2
end
local function even_count(c)
local line = vim.api.nvim_get_current_line()
local _, ct = string.gsub(line, c, '')
return ct % 2 == 0
end
local function neg(fn, ...)
return not fn(...)
end
local function part(fn, ...)
local args = {...}
return function() return fn(unpack(args)) end
end
-- This makes creation of pair-type snippets easier.
local function pair(pair_begin, pair_end, expand_func, ...)
-- triggerd by opening part of pair, wordTrig=false to trigger anywhere.
-- ... is used to pass any args following the expand_func to it.
return s({trig = pair_begin, wordTrig=false},{
t({pair_begin}), i(1), t({pair_end})
}, {
condition = part(expand_func, part(..., pair_begin, pair_end))
})
end
...
-- these should be inside your snippet-table.
pair("(", ")", neg, char_count_same),
pair("{", "}", neg, char_count_same),
pair("[", "]", neg, char_count_same),
pair("<", ">", neg, char_count_same),
pair("'", "'", neg, even_count),
pair('"', '"', neg, even_count),
pair("`", "`", neg, even_count),
These snippets make use of the expand-condition to only trigger if a closing end of the pair is missing (lots of false positives and negatives, eg. if the closing part from a paren higher up in the text is on the current line, but it works more oft than not).
Another nice thing is using a function to create similar snippets.
s("mk", {
t("$"), i(1), t("$")
},{
callbacks = {
-- index `-1` means the callback is on the snippet as a whole
[-1] = {
[events.leave] = function ()
vim.cmd([[
autocmd InsertCharPre <buffer> ++once lua _G.if_char_insert_space()
]])
end
}
}
}),
_G.if_char_insert_space = function ()
if string.find(vim.v.char, "%a") then
vim.v.char = " " .. vim.v.char
end
end
This snippet has the following behavior after expansion: |
represents the cursor
-
$|$
jump ->$$|
insert any letter ->$$ a
-
$$|
insert anything else ->$$.
-
see :h InsertCharPre
if you want to understand how it works.
Here are some Rust snippets to make your life a bit easier.
-
derivedebug expands to the outer attribute:
#[derive(Debug)]
-
deadcode expands to the outer attribute:
#[allow(dead_code)]
-
allowfreedom expands to the inner attribute:
#![allow(clippy::disallowed_names, unused_variables, dead_code)]
-
clippypedantic expands to the inner attribute:
#![warn(clippy::all, clippy::pedantic)]
print expands to:
-
println!("(1) {:?}", (0));
or: -
println!("(1) {(0):?}");
(Comment one out)
:turbofish expands to:
::<$0>
for expands to:
for (1) in (2) {
(0)
}
struct expands to:
#[derive(Debug)]
struct (1) {
(0)
}
test expands to:
#[test]
fn (1) () {
assert (0)
}
testcfg expands to:
#[cfg(test)]
mod (1) {
#[test]
fn (2) () {
assert (0)
}
}
rust = {
s('derivedebug', t '#[derive(Debug)]'),
s('deadcode', t '#[allow(dead_code)]'),
s('allowfreedom', t '#![allow(clippy::disallowed_names, unused_variables, dead_code)]'),
s('clippypedantic', t '#![warn(clippy::all, clippy::pedantic)]'),
s(':turbofish', { t {'::<'}, i(0), t {'>'} }),
s('print', {
-- t {'println!("'}, i(1), t {' {:?}", '}, i(0), t {');'}}),
t {'println!("'}, i(1), t {' {'}, i(0), t {':?}");'}}),
s('for',
{
t {'for '}, i(1), t {' in ' }, i(2), t {' {', ''},
i(0),
t {'}', ''},
}),
s('struct',
{
t {'#[derive(Debug)]', ''},
t {'struct '}, i(1), t {' {', ''},
i(0),
t {'}', ''},
}),
s('test',
{
t {'#[test]', ''},
t {'fn '}, i(1), t {'() {', ''},
t {' assert'}, i(0), t {'', ''},
t {'}'},
}),
s('testcfg',
{
t {'#[cfg(test)]', ''},
t {'mod '}, i(1), t {' {', ''},
t {' #[test]', ''},
t {' fn '}, i(2), t {'() {', ''},
t {' assert'}, i(0), t {'', ''},
t {' }', ''},
t {'}'},
}),
s('if',
{
t {'if '}, i(1), t {' {', ''},
i(0),
t {'}'},
}),
},
The following snippet expands to a function definition in Python and has choiceNode
s for if a return declaration should be used and what doc-string to use:
- None.
- Single line.
- Multiline with
Args
/Returns
.
The choiceNode
of the doc-string is manipulated to show different virtual text for the different choices to make it easy to now which one your in. This way of doing it is a sort of prototype and will be easier in the future. See discussion on #291.
The full snippet can be improved a lot to for example add the correct arguments from the function to the mulitline doc-string automatically.
local ls = require('luasnip')
local s = ls.snippet
local i = ls.insert_node
local t = ls.text_node
local c = ls.choice_node
local sn = ls.snippet_node
local isn = ls.indent_snippet_node
local fmt = require('luasnip.extras.fmt').fmt
local types = require("luasnip.util.types")
local function node_with_virtual_text(pos, node, text)
local nodes
if node.type == types.textNode then
node.pos = 2
nodes = {i(1), node}
else
node.pos = 1
nodes = {node}
end
return sn(pos, nodes, {
node_ext_opts = {
active = {
-- override highlight here ("GruvboxOrange").
virt_text = {{text, "GruvboxOrange"}}
}
}
})
end
local function nodes_with_virtual_text(nodes, opts)
if opts == nil then
opts = {}
end
local new_nodes = {}
for pos, node in ipairs(nodes) do
if opts.texts[pos] ~= nil then
node = node_with_virtual_text(pos, node, opts.texts[pos])
end
table.insert(new_nodes, node)
end
return new_nodes
end
local function choice_text_node(pos, choices, opts)
choices = nodes_with_virtual_text(choices, opts)
return c(pos, choices, opts)
end
local ct = choice_text_node
ls.add_snippets("python", {
s('d', fmt([[
def {func}({args}){ret}:
{doc}{body}
]], {
func = i(1),
args = i(2),
ret = c(3, {
t(''),
sn(nil, {
t(' -> '),
i(1),
}),
}),
doc = isn(4, {ct(1, {
t(''),
-- NOTE we need to surround the `fmt` with `sn` to make this work
sn(1, fmt([[
"""{desc}"""
]], {desc = i(1)})),
sn(2, fmt([[
"""{desc}
Args:
{args}
Returns:
{returns}
"""
]], {
desc = i(1),
args = i(2), -- TODO should read from the args in the function
returns = i(3),
})),
}, {
texts = {
"(no docstring)",
"(single line docstring)",
"(full docstring)",
}
})}, "$PARENT_INDENT\t"),
body = i(0),
}))
})
This is just the solution I decided to use. With help of @L3MON4D3 we developed multiple solutions see this issue including one using a similar functionality like the latex tabular macro (see dynamicnode with user input).
local ls = require"luasnip"
local s = ls.snippet
local sn = ls.snippet_node
local t = ls.text_node
local i = ls.insert_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local fmt = require("luasnip.extras.fmt").fmt
-- see latex infinite list for the idea. Allows to keep adding arguments via choice nodes.
local function py_init()
return
sn(nil, c(1, {
t(""),
sn(1, {
t(", "),
i(1),
d(2, py_init)
})
}))
end
-- splits the string of the comma separated argument list into the arguments
-- and returns the text-/insert- or restore-nodes
local function to_init_assign(args)
local tab = {}
local a = args[1][1]
if #(a) == 0 then
table.insert(tab, t({"", "\tpass"}))
else
local cnt = 1
for e in string.gmatch(a, " ?([^,]*) ?") do
if #e > 0 then
table.insert(tab, t({"","\tself."}))
-- use a restore-node to be able to keep the possibly changed attribute name
-- (otherwise this function would always restore the default, even if the user
-- changed the name)
table.insert(tab, r(cnt, tostring(cnt), i(nil,e)))
table.insert(tab, t(" = "))
table.insert(tab, t(e))
cnt = cnt+1
end
end
end
return
sn(nil, tab)
end
-- create the actual snippet
s("pyinit", fmt(
[[def __init__(self{}):{}]],
{
d(1, py_init),
d(2, to_init_assign, {1})
}))
(Note about the keys: tab is my jump-key, ctrl+j/k is my change_choice-key)
(Being in a choice node is indicated by a blue dot, insert nodes by a white dot at the end of the line)
All - todo-comments.nvim
snippets
As some people using honza/vim-snippets
may know, a convenient use for snippets is to annotate your code with named comments. These comments may include additional information. Typical examples of useful information marks for the comment:
- GitHub username in a repository that has more contributors
- date when browsing code after some time has passed after the writing
- email if you have multiple remotes for the repository and anticipate some other developer needing to contact you
As stated above, some implementations of this are already made for other snippet engines. However, we have LuaSnip
and can supercharge this idea using choice_node
s and lua
.
Instead of inventing the wheel, we will be using the amazing numToStr/Comment.nvim
plugin, which also has added benefits that if the plugin adds compatibility with more languages, we will get them too without any work and that the calculation of the comment-string is based on TreeSitter therefore is also correct for injected languages. Here is a function that fetches comment-strings for us to use in the snippet:
local calculate_comment_string = require('Comment.ft').calculate
local utils = require('Comment.utils')
--- Get the comment string {beg,end} table
---@param ctype integer 1 for `line`-comment and 2 for `block`-comment
---@return table comment_strings {begcstring, endcstring}
local get_cstring = function(ctype)
-- use the `Comments.nvim` API to fetch the comment string for the region (eq. '--%s' or '--[[%s]]' for `lua`)
local cstring = calculate_comment_string { ctype = ctype, range = utils.get_region() } or vim.bo.commentstring
-- as we want only the strings themselves and not strings ready for using `format` we want to split the left and right side
local left, right = utils.unwrap_cstr(cstring)
-- create a `{left, right}` table for it
return { left, right }
end
Now we can use this function in any snippet we want. Next we will want some kind of snippet generation based on the input strings, but first, let us define some useful marks that can be placed after the comment.
We will eventually want to place the marks into a choice_node
therefore we want them to return nodes from LuaSnip
. First lets define some variables that we can use in the marks:
_G.luasnip = {}
_G.luasnip.vars = {
username = 'kunzaatko',
email = 'martinkunz@email.cz',
github = 'https://github.com/kunzaatko',
real_name = 'Martin Kunz',
}
Now we can use these in the marks and lets create a variety of them to choose from:
--- Options for marks to be used in a TODO comment
local marks = {
signature = function()
return fmt('<{}>', i(1, _G.luasnip.vars.username))
end,
signature_with_email = function()
return fmt('<{}{}>', { i(1, _G.luasnip.vars.username), i(2, ' ' .. _G.luasnip.vars.email) })
end,
date_signature_with_email = function()
return fmt(
'<{}{}{}>',
{ i(1, os.date '%d-%m-%y'), i(2, ', ' .. _G.luasnip.vars.username), i(3, ' ' .. _G.luasnip.vars.email) }
)
end,
date_signature = function()
return fmt('<{}{}>', { i(1, os.date '%d-%m-%y'), i(2, ', ' .. _G.luasnip.vars.username) })
end,
date = function()
return fmt('<{}>', i(1, os.date '%d-%m-%y'))
end,
empty = function()
return t ''
end,
}
Finally we will want to generate the snippets that we will use for TODO comments. As you can see in the documentation of todo-comments.nvim
, you can create some aliases and we will use them in our snippets too. We want to create snippets that look like this:
<comment-string[1]> [name-of-comment]: {comment-text} [comment-mark] <comment-string[2]>
where <>
are generated strings, []
are choices and {}
are inputs.
Here is an implementation of generating the nodes for the snippet.
local todo_snippet_nodes = function(aliases, opts)
local aliases_nodes = vim.tbl_map(function(alias)
return i(nil, alias) -- generate choices for [name-of-comment]
end, aliases)
local sigmark_nodes = {} -- choices for [comment-mark]
for _, mark in pairs(marks) do
table.insert(sigmark_nodes, mark())
end
-- format them into the actual snippet
local comment_node = fmta('<> <>: <> <> <><>', {
f(function()
return get_cstring(opts.ctype)[1] -- get <comment-string[1]>
end),
c(1, aliases_nodes), -- [name-of-comment]
i(3), -- {comment-text}
c(2, sigmark_nodes), -- [comment-mark]
f(function()
return get_cstring(opts.ctype)[2] -- get <comment-string[2]>
end),
i(0),
})
return comment_node
end
As a cherry on-top, lets make some logic to generate the name
, docstring
and dscr
keys of the context
for the final snippet:
--- Generate a TODO comment snippet with an automatic description and docstring
---@param context table merged with the generated context table `trig` must be specified
---@param aliases string[]|string of aliases for the todo comment (ex.: {FIX, ISSUE, FIXIT, BUG})
---@param opts table merged with the snippet opts table
local todo_snippet = function(context, aliases, opts)
opts = opts or {}
aliases = type(aliases) == 'string' and { aliases } or aliases -- if we do not have aliases, be smart about the function parameters
context = context or {}
if not context.trig then
return error("context doesn't include a `trig` key which is mandatory", 2) -- all we need from the context is the trigger
end
opts.ctype = opts.ctype or 1 -- comment type can be passed in the `opts` table, but if it is not, we have to ensure, it is defined
local alias_string = table.concat(aliases, '|') -- `choice_node` documentation
context.name = context.name or (alias_string .. ' comment') -- generate the `name` of the snippet if not defined
context.dscr = context.dscr or (alias_string .. ' comment with a signature-mark') -- generate the `dscr` if not defined
context.docstring = context.docstring or (' {1:' .. alias_string .. '}: {3} <{2:mark}>{0} ') -- generate the `docstring` if not defined
local comment_node = todo_snippet_nodes(aliases, opts) -- nodes from the previously defined function for their generation
return s(context, comment_node, opts) -- the final todo-snippet constructed from our parameters
end
Now we can create a todo-snippet using only one function with the right parameters. Lets create a few of them:
local todo_snippet_specs = {
{ { trig = 'todo' }, 'TODO' },
{ { trig = 'fix' }, { 'FIX', 'BUG', 'ISSUE', 'FIXIT' } },
{ { trig = 'hack' }, 'HACK' },
{ { trig = 'warn' }, { 'WARN', 'WARNING', 'XXX' } },
{ { trig = 'perf' }, { 'PERF', 'PERFORMANCE', 'OPTIM', 'OPTIMIZE' } },
{ { trig = 'note' }, { 'NOTE', 'INFO' } },
-- NOTE: Block commented todo-comments <kunzaatko>
{ { trig = 'todob' }, 'TODO', { ctype = 2 } },
{ { trig = 'fixb' }, { 'FIX', 'BUG', 'ISSUE', 'FIXIT' }, { ctype = 2 } },
{ { trig = 'hackb' }, 'HACK', { ctype = 2 } },
{ { trig = 'warnb' }, { 'WARN', 'WARNING', 'XXX' }, { ctype = 2 } },
{ { trig = 'perfb' }, { 'PERF', 'PERFORMANCE', 'OPTIM', 'OPTIMIZE' }, { ctype = 2 } },
{ { trig = 'noteb' }, { 'NOTE', 'INFO' }, { ctype = 2 } },
}
local todo_comment_snippets = {}
for _, v in ipairs(todo_snippet_specs) do
-- NOTE: 3rd argument accepts nil
table.insert(todo_comment_snippets, todo_snippet(v[1], v[2], v[3]))
end
ls.add_snippets('all', todo_comment_snippets, { type = 'snippets', key = 'todo_comments' })
And a video of the final result:
see issue #151
local function create_box(opts)
local pl = opts.padding_length or 4
local function pick_comment_start_and_end()
-- because lua block comment is unlike other language's,
-- so handle lua ctype
local ctype = 2
if vim.opt.ft:get() == 'lua' then
ctype = 1
end
local cs = get_cstring(ctype)[1]
local ce = get_cstring(ctype)[2]
if ce == '' or ce == nil then
ce = cs
end
return cs, ce
end
return {
-- top line
f(function (args)
local cs, ce = pick_comment_start_and_end()
return cs .. string.rep(string.sub(cs, #cs, #cs), string.len(args[1][1]) + 2 * pl ) .. ce
end, { 1 }),
t{"", ""},
f(function()
local cs = pick_comment_start_and_end()
return cs .. string.rep(' ', pl)
end),
i(1, 'box'),
f(function()
local cs, ce = pick_comment_start_and_end()
return string.rep(' ', pl) .. ce
end),
t{"", ""},
-- bottom line
f(function (args)
local cs, ce = pick_comment_start_and_end()
return cs .. string.rep(string.sub(ce, 1, 1), string.len(args[1][1]) + 2 * pl ) .. ce
end, { 1 }),
}
end
return {
s({trig = 'box'}, create_box{ padding_length = 8 }),
s({trig = 'bbox'}, create_box{ padding_length = 20 }),
}
It is a drag to add basic using directives in csharp, let's fix that with luasnip!
This snippet requires this helper function to add the using directives. This function should be called from the luasnip pre_expand event to avoid node locations getting messed up.
---Function for luasnip to add using directives needed for snippets
---@param required_using_directive_list string|table
local function add_csharp_using_statement_if_needed(required_using_directive_list)
if type(required_using_directive_list) == 'string' then
local temp = required_using_directive_list
required_using_directive_list = { temp }
end
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for _, line in ipairs(lines) do
for _, using_directive in ipairs(required_using_directive_list) do
if line:match(using_directive) ~= nil then
table.remove(required_using_directive_list, using_directive)
end
end
end
-- Add all using directives that remain in the list to be written to top of file
if #required_using_directive_list > 0 then
local using_directives_to_write = {}
for _, using_directive in ipairs(required_using_directive_list) do
table.insert(using_directives_to_write, string.format('using %s;', using_directive))
end
vim.api.nvim_buf_set_lines(0, 0, 0, false, using_directives_to_write)
end
end
And here are two of my snippets that use this function.
s(
'regex match',
fmt(
[[
if(Regex.IsMatch({}, @"{}"))
{{
{}
}}
]] ,
{
i(1, '"source"'),
i(2, '.*'),
i(3),
}
),
{
callbacks = {
[-1] = {
-- Write needed using directives before expanding snippet so positions are not messed up
[events.pre_expand] = function()
add_csharp_using_statement_if_needed('System.Text.RegularExpressions')
end,
},
},
}
),
s(
'regex matches',
fmt(
[[
var matches = Regex.Matches({}, @"{}")
.Cast<Match>()
.Select(match => {})
.Distinct();
]] ,
{
i(1),
i(2, '.*'),
i(3, 'match'),
}
),
{
callbacks = {
[-1] = {
-- Write needed using directives before expanding snippet so positions are not messed up
[events.pre_expand] = function()
add_csharp_using_statement_if_needed({
'System.Linq',
'System.Text.RegularExpressions',
})
end,
},
},
}
),