|
@@ -0,0 +1,163 @@
|
|
|
+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
|