-- Author : swytch -- Created : Friday Nov. 19, 2021 23:27:24 CET -- License : GPLv3 -- Description : luasnip config file local ls = require("luasnip") -- some shorthands... local s = ls.snippet local sn = ls.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 l = require("luasnip.extras").lambda local r = require("luasnip.extras").rep local p = require("luasnip.extras").partial local m = require("luasnip.extras").match local n = require("luasnip.extras").nonempty local dl = require("luasnip.extras").dynamic_lambda local fmt = require("luasnip.extras.fmt").fmt local fmta = require("luasnip.extras.fmt").fmta local types = require("luasnip.util.types") local conds = require("luasnip.extras.expand_conditions") -- Every unspecified option will be set to the default. ls.config.set_config({ history = true, -- Update more often, :h events for more info. updateevents = "TextChanged,TextChangedI", ext_opts = { [types.choiceNode] = { active = { virt_text = { { "choiceNode", "Comment" } }, }, }, }, -- treesitter-hl has 100, use something higher (default is 200). ext_base_prio = 200, -- minimal increase in priority. ext_prio_increase = 1, enable_autosnippets = true, }) -- Make sure to not pass an invalid command, as io.popen() may write over nvim-text. local function bash(_, _, command) local file = io.popen(command, "r") local res = {} for line in file:lines() do table.insert(res, line) end return res end -- Returns a snippet_node wrapped around an insert_node whose initial -- text value is set to the current date in the desired format. local function date_input(fmt) local fmt = fmt or "%Y-%m-%d" return sn(nil, i(1, os.date(fmt))) end ls.snippets = { -- When trying to expand a snippet, luasnip first searches the tables for -- each filetype specified in 'filetype' followed by 'all'. -- If ie. the filetype is 'lua.c' -- - luasnip.lua -- - luasnip.c -- - luasnip.all -- are searched in that order. all = { -- trigger is fn. s("fn", { -- Simple static text. t("//Parameters: "), -- function, first parameter is the function, second the Placeholders -- whose text it gets as input. f(copy, 2), t({ "", "function " }), -- Placeholder/Insert. i(1), t("("), -- Placeholder with initial text. i(2, "int foo"), -- Linebreak t({ ") {", "\t" }), -- Last Placeholder, exit Point of the snippet. EVERY 'outer' SNIPPET NEEDS Placeholder 0. i(0), t({ "", "}" }), }), s("class", { -- Choice: Switch between two different Nodes, first parameter is its position, second a list of nodes. c(1, { t("public "), t("private "), }), t("class "), i(2), t(" "), c(3, { t("{"), -- sn: Nested Snippet. Instead of a trigger, it has a position, just like insert-nodes. !!! These don't expect a 0-node!!!! -- Inside Choices, Nodes don't need a position as the choice node is the one being jumped to. sn(nil, { t("extends "), i(1), t(" {"), }), sn(nil, { t("implements "), i(1), t(" {"), }), }), t({ "", "\t" }), i(0), t({ "", "}" }), }), -- Use a dynamic_node to interpolate the output of a -- function (see date_input above) into the initial -- value of an insert_node. s("novel", { t("It was a dark and stormy night on "), d(1, date_input, {}, "%A, %B %d of %Y"), t(" and the clocks were striking thirteen."), }), -- Parsing snippets: First parameter: Snippet-Trigger, Second: Snippet body. -- Placeholders are parsed into choices with 1. the placeholder text(as a snippet) and 2. an empty string. -- This means they are not SELECTed like in other editors/Snippet engines. ls.parser.parse_snippet( "lspsyn", "Wow! This ${1:Stuff} really ${2:works. ${3:Well, a bit.}}" ), -- When wordTrig is set to false, snippets may also expand inside other words. ls.parser.parse_snippet( { trig = "te", wordTrig = false }, "${1:cond} ? ${2:true} : ${3:false}" ), -- When regTrig is set, trig is treated like a pattern, this snippet will expand after any number. ls.parser.parse_snippet({ trig = "%d", regTrig = true }, "A Number!!"), -- Using the condition, it's possible to allow expansion only in specific cases. s("cond", { t("will only expand in c-style comments"), }, { condition = function(line_to_cursor, matched_trigger, captures) -- optional whitespace followed by // return line_to_cursor:match("%s*//") end, }), -- there's some built-in conditions in "luasnip.extras.expand_conditions". s("cond2", { t("will only expand at the beginning of the line"), }, { condition = conds.line_begin, }), -- The last entry of args passed to the user-function is the surrounding snippet. s( { trig = "a%d", regTrig = true }, f(function(_, snip) return "Triggered with " .. snip.trigger .. "." end, {}) ), -- It's possible to use capture-groups inside regex-triggers. s( { trig = "b(%d)", regTrig = true }, f(function(_, snip) return "Captured Text: " .. snip.captures[1] .. "." end, {}) ), s({ trig = "c(%d+)", regTrig = true }, { t("will only expand for even numbers"), }, { condition = function(line_to_cursor, matched_trigger, captures) return tonumber(captures[1]) % 2 == 0 end, }), -- Use a function to execute any shell command and print its text. s("bash", f(bash, {}, "ls")), -- Short version for applying String transformations using function nodes. s("transform", { i(1, "initial text"), t({ "", "" }), -- lambda nodes accept an l._1,2,3,4,5, which in turn accept any string transformations. -- This list will be applied in order to the first node given in the second argument. l(l._1:match("[^i]*$"):gsub("i", "o"):gsub(" ", "_"):upper(), 1), }), s("transform2", { i(1, "initial text"), t("::"), i(2, "replacement for e"), t({ "", "" }), -- Lambdas can also apply transforms USING the text of other nodes: l(l._1:gsub("e", l._2), { 1, 2 }), }), s({ trig = "trafo(%d+)", regTrig = true }, { -- env-variables and captures can also be used: l(l.CAPTURE1:gsub("1", l.TM_FILENAME), {}), }), -- Set store_selection_keys = "" (for example) in your -- luasnip.config.setup() call to access TM_SELECTED_TEXT. In -- this case, select a URL, hit Tab, then expand this snippet. s("link_url", { t(''), i(1), t(""), i(0), }), -- Shorthand for repeating the text in a given node. s("repeat", { i(1, "text"), t({ "", "" }), r(1) }), -- Directly insert the ouput from a function evaluated at runtime. s("part", p(os.date, "%Y")), -- use matchNodes to insert text based on a pattern/function/lambda-evaluation. s("mat", { i(1, { "sample_text" }), t(": "), m(1, "%d", "contains a number", "no number :("), }), -- The inserted text defaults to the first capture group/the entire -- match if there are none s("mat2", { i(1, { "sample_text" }), t(": "), m(1, "[abc][abc][abc]"), }), -- It is even possible to apply gsubs' or other transformations -- before matching. s("mat3", { i(1, { "sample_text" }), t(": "), m( 1, l._1:gsub("[123]", ""):match("%d"), "contains a number that isn't 1, 2 or 3!" ), }), -- `match` also accepts a function, which in turn accepts a string -- (text in node, \n-concatted) and returns any non-nil value to match. -- If that value is a string, it is used for the default-inserted text. s("mat4", { i(1, { "sample_text" }), t(": "), m(1, function(text) return (#text % 2 == 0 and text) or nil end), }), -- The nonempty-node inserts text depending on whether the arg-node is -- empty. s("nempty", { i(1, "sample_text"), n(1, "i(1) is not empty!"), }), -- dynamic lambdas work exactly like regular lambdas, except that they -- don't return a textNode, but a dynamicNode containing one insertNode. -- This makes it easier to dynamically set preset-text for insertNodes. s("dl1", { i(1, "sample_text"), t({ ":", "" }), dl(2, l._1, 1), }), -- Obviously, it's also possible to apply transformations, just like lambdas. s("dl2", { i(1, "sample_text"), i(2, "sample_text_2"), t({ "", "" }), dl(3, l._1:gsub("\n", " linebreak ") .. l._2, { 1, 2 }), }), -- Alternative printf-like notation for defining snippets. It uses format -- string with placeholders similar to the ones used with Python's .format(). s( "fmt1", fmt("To {title} {} {}.", { i(2, "Name"), i(3, "Surname"), title = c(1, { t("Mr."), t("Ms.") }), }) ), -- To escape delimiters use double them, e.g. `{}` -> `{{}}`. -- Multi-line format strings by default have empty first/last line removed. -- Indent common to all lines is also removed. Use the third `opts` argument -- to control this behaviour. s( "fmt2", fmt( [[ foo({1}, {3}) {{ return {2} * {4} }} ]], { i(1, "x"), r(1), i(2, "y"), r(2), } ) ), -- Empty placeholders are numbered automatically starting from 1 or the last -- value of a numbered placeholder. Named placeholders do not affect numbering. s( "fmt3", fmt("{} {a} {} {1} {}", { t("1"), t("2"), a = t("A"), }) ), -- The delimiters can be changed from the default `{}` to something else. s( "fmt4", fmt("foo() { return []; }", i(1, "x"), { delimiters = "[]" }) ), -- `fmta` is a convenient wrapper that uses `<>` instead of `{}`. s("fmt5", fmta("foo() { return <>; }", i(1, "x"))), -- By default all args must be used. Use strict=false to disable the check s( "fmt6", fmt("use {} only", { t("this"), t("not this") }, { strict = false }) ), }, } -- autotriggered snippets have to be defined in a separate table, luasnip.autosnippets. ls.autosnippets = { all = { s("autotrigger", { t("autosnippet"), }), }, } -- in a lua file: search lua-, then c-, then all-snippets. ls.filetype_extend("lua", { "c" }) -- in a cpp file: search c-snippets, then all-snippets only (no cpp-snippets!!). ls.filetype_set("cpp", { "c" }) --[[ -- Beside defining your own snippets you can also load snippets from "vscode-like" packages -- that expose snippets in json files, for example . -- Mind that this will extend `ls.snippets` so you need to do it after your own snippets or you -- will need to extend the table yourself instead of setting a new one. ]] require("luasnip/loaders/from_vscode").load({ include = { "python" } }) -- Load only python snippets -- The directories will have to be structured like eg. (include -- a similar `package.json`) require("luasnip/loaders/from_vscode").load({ paths = { "./my-snippets" } }) -- Load snippets from my-snippets folder -- You can also use lazy loading so you only get in memory snippets of languages you use require("luasnip/loaders/from_vscode").lazy_load() -- You can pass { paths = "./my-snippets/"} as well