Skip to content
L3MON4D3 edited this page Aug 27, 2023 · 16 revisions

Hint node-type with virtual text

Using ext_opts it's possible to add virtual text to nodes:

local types = require("luasnip.util.types")

require'luasnip'.config.setup({
	ext_opts = {
		[types.choiceNode] = {
			active = {
				virt_text = {{"", "GruvboxOrange"}}
			}
		},
		[types.insertNode] = {
			active = {
				virt_text = {{"", "GruvboxBlue"}}
			}
		}
	},
})

This adds an orange dot to the end of the line if inside a choiceNode, and a blue one if inside an insertNode:

showcase

Imitate vscodes' behaviour for nested placeholders

local util = require("luasnip.util.util")
local node_util = require("luasnip.nodes.util")

ls.setup({
	parser_nested_assembler = function(_, snippet)
		local select = function(snip, no_move)
			snip:focus()
			-- upon deletion, extmarks of inner nodes should shift to end of
			-- placeholder-text.
			snip:subtree_set_rgrav(true)

			-- SELECT all text inside the snippet.
			if not no_move then
				vim.api.nvim_feedkeys(
					vim.api.nvim_replace_termcodes("<Esc>", true, false, true),
					"n",
					true
				)
				node_util.select_node(snip)
			end
		end
		function snippet:jump_into(dir, no_move)
			if self.active then
				-- inside snippet, but not selected.
				if dir == 1 then
					self:input_leave()
					return self.next:jump_into(dir, no_move)
				else
					select(self, no_move)
					return self
				end
			else
				-- jumping in from outside snippet.
				self:input_enter()
				if dir == 1 then
					select(self, no_move)
					return self
				else
					return self.inner_last:jump_into(dir, no_move)
				end
			end
		end
		-- this is called only if the snippet is currently selected.
		function snippet:jump_from(dir, no_move)
			if dir == 1 then
				return self.inner_first:jump_into(dir, no_move)
			else
				self:input_leave()
				return self.prev:jump_into(dir, no_move)
			end
		end
		return snippet
	end
})

The main reason for this not being the default is that it

  • requires a lot of modification to the resulting snippets
  • Doesn't work perfectly(yet): if the entire placeholder is replaced, the nested tabstops won't be skipped.

Example with "test: ${1: this is ${2:nested} ${3:text}} ${4:yay!}": showcase

Only jump in the current snippet

A slight change to the recommended nvim-cmp setup where we replace luasnip.expand_or_jumpable() with luasnip.expand_or_locally_jumpable() to only jump to a snippet field if we are currently in a snippet. example setup:

cmp.setup({

  -- ... Your other configuration ...

  mapping = {

    -- ... Your other mappings ...

    ["<Tab>"] = cmp.mapping(function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      elseif luasnip.expand_or_locally_jumpable() then
        luasnip.expand_or_jump()
      elseif has_words_before() then
        cmp.complete()
      else
        fallback()
      end
    end, { "i", "s" }),
  }
}

"Super-Tab"-like setup for Change Choice and External Update Dynamic Node

Similar to the above, but this is for setting the same keymap for changing choice if there's an active choicenode or using external update dynamic node. Since there are a lot of keymaps already, it might be nice to overload them and use the same keymap for mutually exclusive events. Set it up as follows:

-- feel free to change the keys to new ones, those are just my current mappings
vim.keymap.set("i", "<C-f>", function ()
    if ls.choice_active() then
        return ls.change_choice(1)
    else
        return _G.dynamic_node_external_update(1) -- feel free to update to any index i
    end
end, { noremap = true })
vim.keymap.set("s", "<C-f>", function ()
    if ls.choice_active() then
        return ls.change_choice(1)
    else
        return _G.dynamic_node_external_update(1)
    end
end, { noremap = true })
vim.keymap.set("i", "<C-d>", function ()
    if ls.choice_active() then
        return ls.change_choice(-1)
    else
        return _G.dynamic_node_external_update(2)
    end
end, { noremap = true })
vim.keymap.set("s", "<C-d>", function ()
    if ls.choice_active() then
        return ls.change_choice(-1)
    else
        return _G.dynamic_node_external_update(2)
    end
end, { noremap = true })

Also see:

Clone this wiki locally