local M = {} ---@alias rc.utils.cmp.Action fun():boolean? ---@type table M.actions = { -- Native Snippets snippet_forward = function() if vim.snippet.active { direction = 1 } then vim.schedule(function() vim.snippet.jump(1) end) return true end end, snippet_backward = function() if vim.snippet.active { direction = -1 } then vim.schedule(function() vim.snippet.jump(-1) end) return true end end, snippet_stop = function() if vim.snippet then vim.snippet.stop() end end, } ---@param actions string[] ---@param fallback? string|fun() function M.map(actions, fallback) return function() for _, name in ipairs(actions) do if M.actions[name] then local ret = M.actions[name]() if ret then return true end end end return type(fallback) == "function" and fallback() or fallback end end ---@alias Placeholder {n:number, text:string} ---@param snippet string ---@param fn fun(placeholder:Placeholder):string ---@return string M.snippet_replace = function(snippet, fn) return snippet:gsub("%$%b{}", function(m) local n, name = m:match "^%${(%d+):(.+)}$" return n and fn { n = n, text = name } or m end) or snippet end -- This function resolves nested placeholders in a snippet. ---@param snippet string ---@return string M.snippet_preview = function(snippet) local ok, parsed = pcall(function() return vim.lsp._snippet_grammar.parse(snippet) end) return ok and tostring(parsed) or M.snippet_replace(snippet, function(placeholder) return M.snippet_preview(placeholder.text) end):gsub("%$0", "") end -- This function replaces nested placeholders in a snippet with LSP placeholders. function M.snippet_fix(snippet) local texts = {} ---@type table return M.snippet_replace(snippet, function(placeholder) texts[placeholder.n] = texts[placeholder.n] or M.snippet_preview(placeholder.text) return "${" .. placeholder.n .. ":" .. texts[placeholder.n] .. "}" end) end M.snippet_expand = function(snippet) -- Native sessions don't support nested snippet sessions. -- Always use the top-level session. -- Otherwise, when on the first placeholder and selecting a new completion, -- the nested session will be used instead of the top-level session. -- See: https://github.com/LazyVim/LazyVim/issues/3199 local session = vim.snippet.active() and vim.snippet._session or nil local ok, err = pcall(vim.snippet.expand, snippet) if not ok then local fixed = M.snippet_fix(snippet) ok = pcall(vim.snippet.expand, fixed) local msg = ok and "Failed to parse snippet,\nbut was able to fix it automatically." or ("Failed to parse snippet.\n" .. err) require("rc.utils")[ok and "warn" or "error"]( ([[%s ```%s %s ```]]):format(msg, vim.bo.filetype, snippet), { title = "vim.snippet" } ) end -- Restore top-level session when needed if session then vim.snippet._session = session end end -- Utility for setting Super-Tab like mapping. ---@return boolean M.has_words_before = function() local line, col = unpack(vim.api.nvim_win_get_cursor(0)) return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match "%s" == nil end return M