local ls = require("luasnip") -- auto_pairs {{{ local get_visual = function(args, parent) if #parent.snippet.env.SELECT_RAW > 0 then return sn(nil, i(1, parent.snippet.env.SELECT_RAW)) else return sn(nil, i(1, "")) end end 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 -- This makes creation of pair-type snippets easier. local function pair(pair_begin, pair_end, file_types, condition_function) -- FIXME(@Soispha): This only works if file_types == nil, otherwise the snippet does not expand. -- It would be nice, if it would support both an empty array (`{}`) and nil <2023-08-27> -- file_types = file_types or {}; return s( { trig = pair_begin, wordTrig = false, snippetType = "autosnippet" }, { t({ pair_begin }), d(1, get_visual), t({ pair_end }) }, { condition = function() local filetype_check = true if file_types ~= nil then filetype_check = file_types[vim.bo.filetype] or false end return (not condition_function(pair_begin, pair_end)) and filetype_check end, } ) end local auto_pairs = { pair("(", ")", nil, char_count_same), pair("{", "}", nil, char_count_same), pair("[", "]", nil, char_count_same), pair("<", ">", { ["rust"] = true, ["tex"] = true }, char_count_same), pair("'", "'", nil, even_count), pair('"', '"', nil, even_count), pair("`", "`", nil, even_count), } ls.add_snippets("all", auto_pairs, { type = "snippets", key = "auto_pairs" }) -- }}} -- todo_comments {{{ local calculate_comment_string = require("Comment.ft").calculate local utils = require("Comment.utils") local read_git_config = function(config_value) local command = string.format('git config "%s"', config_value) local handle = io.popen(command) if handle == nil then return error(string.format("Failed to call `%s`.", command)) end local result = handle:read("*a") handle:close() -- stripped = string.gsub(str, '%s+', '') return string.gsub(result, '\n', '') end local name_to_handle = function(name) -- from: https://stackoverflow.com/a/7615129 local split = function(inputstr, sep) local t = {} for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do table.insert(t, str) end return t end local parts = split(name, "%s") local output_name = "" if #parts > 2 then for _, val in ipairs(parts) do output_name = string.format("%s%s", output_name, val:sub(1, 1)) end elseif #parts == 2 then output_name = string.format("%s%s", parts[1]:sub(1, 1), parts[2]) elseif #parts == 1 then output_name = parts[1] else -- parts is 0 output_name = "" end return string.format("@%s", output_name:lower()) end _G.luasnip = {} _G.luasnip.vars = { username = function() return name_to_handle(read_git_config("user.name")) end, email = function() return read_git_config("user.email") end, } --- 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 --- Options for marks to be used in a TODO comment ---@return table,table: The first table contains a node for the date, the second for the signature local marks = { signature = function() return t("(" .. _G.luasnip.vars:username() .. ")"), t("") end, date_signature = function() return t("<" .. os.date("%Y-%m-%d") .. ">"), t("(" .. _G.luasnip.vars:username() .. ")") end, date = function() return t("<" .. os.date("%Y-%m-%d") .. ">"), t("") end, empty = function() return t(""), t("") end, } ---@param alias string ---@param opts table ---@param mark_function function: This function should return two nodes ---@return table: Returns the comment node local todo_snippet_nodes = function(alias, opts, mark_function) local date_node, signature_node = mark_function() -- format them into the actual snippet local comment_node = fmta("<> <><>: <> <> <>", { f(function() return get_cstring(opts.ctype)[1] -- get end), t(alias), -- [name-of-comment] signature_node, i(0), -- {comment-text} date_node, f(function() return get_cstring(opts.ctype)[2] -- get end), }) return comment_node end --- 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 alias string of aliases for the todo comment (ex.: {FIX, ISSUE, FIXIT, BUG}) ---@param opts table merged with the snippet opts table ---@param mark_function function: The function used to get the marks local todo_snippet = function(context, alias, opts, mark_function) opts = opts or {} 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 = alias -- `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(alias, opts, mark_function) return s(context, comment_node, opts) -- the final todo-snippet constructed from our parameters end ---@param context table: The luasnip context ---@param opts table: The luasnip opts table, needs to have a ctype set ---@param aliases string: All aliases for a name ---@param marks table: Possible marks to account in snipped generation ---@return table: All possible snippets build from the marks local process_marks = function(context, aliases, opts, marks) local output = {} for mark_name, mark_function in pairs(marks) do local contex_trig_local = context.trig context.trig = context.trig .. "-" .. mark_name output[#output + 1] = todo_snippet(context, aliases, opts, mark_function) context.trig = contex_trig_local end return output end local todo_snippet_specs = { { { trig = "todo" }, { "TODO" }, { ctype = 1 } }, { { trig = "fix" }, { "FIXME", "ISSUE" }, { ctype = 1 } }, { { trig = "hack" }, { "HACK" }, { ctype = 1 } }, { { trig = "warn" }, { "WARNING" }, { ctype = 1 } }, { { trig = "perf" }, { "PERFORMANCE", "OPTIMIZE" }, { ctype = 1 } }, { { trig = "note" }, { "NOTE", "INFO" }, { ctype = 1 } }, -- NOTE: Block commented todo-comments { { trig = "todob" }, { "TODO" }, { ctype = 2 } }, { { trig = "fixb" }, { "FIXME", "ISSUE" }, { ctype = 2 } }, { { trig = "hackb" }, { "HACK" }, { ctype = 2 } }, { { trig = "warnb" }, { "WARNING" }, { 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 local snippets = process_marks(v[1], v[2][1], v[3], marks) for _, value in pairs(snippets) do table.insert(todo_comment_snippets, value) end end ls.add_snippets("all", todo_comment_snippets, { type = "snippets", key = "todo_comments" }) -- }}}