local M = {} -- see luasnip/util/types.lua local types = { textNode = 1, insertNode = 2, functionNode = 3, snippetNode = 4, choiceNode = 5, dynamicNode = 6, snippet = 7, exitNode = 8, restoreNode = 9, node_types = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, } function M.config() lvim.builtin.luasnip = { sources = { friendly_snippets = true, }, config = { history = false, updateevents = "TextChanged,TextChangedI", ext_opts = { -- Will be populated within config function }, }, ext_opts = { -- Show virtual text to signal when you are inside an sippets [types.insertNode] = { active = { virt_text = { { "<-- snip insert", "BufferInactiveIndex" } }, }, }, -- Helps to notice when you are within a choice node [types.choiceNode] = { active = { virt_text = { { "<-- choice", "BufferInactiveIndex" } }, }, }, }, } end function M.setup() local utils = require "lvim.utils" local paths = {} if lvim.builtin.luasnip.sources.friendly_snippets then paths[#paths + 1] = utils.join_paths(get_runtime_dir(), "site", "pack", "packer", "start", "friendly-snippets") end local user_snippets = utils.join_paths(get_config_dir(), "snippets") if utils.is_directory(user_snippets) then paths[#paths + 1] = user_snippets end local luasnip = require "luasnip" luasnip.config.set_config(lvim.builtin.luasnip.config) -- When no paths are provided, luasnip will search in the runtimepath require("luasnip.loaders.from_lua").lazy_load() require("luasnip.loaders.from_vscode").lazy_load { paths = paths } require("luasnip.loaders.from_snipmate").lazy_load() end ---when inside a snippet, seeks to the nearest luasnip field if possible, and checks if it is jumpable ---@param dir number 1 for forward, -1 for backward; defaults to 1 ---@return boolean true if a jumpable luasnip field is found while inside a snippet local function jumpable(dir) local luasnip_ok, luasnip = pcall(require, "luasnip") if not luasnip_ok then return false end local win_get_cursor = vim.api.nvim_win_get_cursor local get_current_buf = vim.api.nvim_get_current_buf ---sets the current buffer's luasnip to the one nearest the cursor ---@return boolean true if a node is found, false otherwise local function seek_luasnip_cursor_node() -- TODO(kylo252): upstream this -- for outdated versions of luasnip if not luasnip.session.current_nodes then return false end local node = luasnip.session.current_nodes[get_current_buf()] if not node then return false end local snippet = node.parent.snippet local exit_node = snippet.insert_nodes[0] local pos = win_get_cursor(0) pos[1] = pos[1] - 1 -- exit early if we're past the exit node if exit_node then local exit_pos_end = exit_node.mark:pos_end() if (pos[1] > exit_pos_end[1]) or (pos[1] == exit_pos_end[1] and pos[2] > exit_pos_end[2]) then snippet:remove_from_jumplist() luasnip.session.current_nodes[get_current_buf()] = nil return false end end node = snippet.inner_first:jump_into(1, true) while node ~= nil and node.next ~= nil and node ~= snippet do local n_next = node.next local next_pos = n_next and n_next.mark:pos_begin() local candidate = n_next ~= snippet and next_pos and (pos[1] < next_pos[1]) or (pos[1] == next_pos[1] and pos[2] < next_pos[2]) -- Past unmarked exit node, exit early if n_next == nil or n_next == snippet.next then snippet:remove_from_jumplist() luasnip.session.current_nodes[get_current_buf()] = nil return false end if candidate then luasnip.session.current_nodes[get_current_buf()] = node return true end local ok ok, node = pcall(node.jump_from, node, 1, true) -- no_move until last stop if not ok then snippet:remove_from_jumplist() luasnip.session.current_nodes[get_current_buf()] = nil return false end end -- No candidate, but have an exit node if exit_node then -- to jump to the exit node, seek to snippet luasnip.session.current_nodes[get_current_buf()] = snippet return true end -- No exit node, exit from snippet snippet:remove_from_jumplist() luasnip.session.current_nodes[get_current_buf()] = nil return false end if dir == -1 then return luasnip.in_snippet() and luasnip.jumpable(-1) else return luasnip.in_snippet() and seek_luasnip_cursor_node() and luasnip.jumpable(1) end end M.methods = {} M.methods.jumpable = jumpable return M