Add luasnip and use it to load language and project specific snippets,

also add a bunch of mess to get a nice unwrap(), this is committed on
purpose as a reference for future me, will be cleaned up in the next
commit. It also touches a bunch of blink.cmp to achieve this.
This commit is contained in:
2026-06-24 02:40:25 +03:00
parent 43299925b4
commit da84043692
2 changed files with 204 additions and 27 deletions

View File

@@ -5,10 +5,26 @@ return {
version = '1.*',
dependencies = {
{ 'Kaiser-Yang/blink-cmp-git', dependencies = { 'nvim-lua/plenary.nvim' } },
{ 'L3MON4D3/LuaSnip', version = 'v2.*' },
},
---@module 'blink.cmp'
---@type blink.cmp.Config
opts = {
snippets = { preset = 'luasnip' },
-- snippets = {
-- expand = function(snippet)
-- require('luasnip').lsp_expand(snippet)
-- end,
-- active = function(filter)
-- if filter and filter.direction then
-- return require('luasnip').jumpable(filter.direction)
-- end
-- return require('luasnip').in_snippet()
-- end,
-- jump = function(direction)
-- require('luasnip').jump(direction)
-- end,
-- },
keymap = {
preset = 'enter',
['<C-e>'] = {},
@@ -18,6 +34,36 @@ return {
['<C-f>'] = {},
['<C-u>'] = { 'scroll_documentation_up', 'fallback' },
['<C-d>'] = { 'scroll_documentation_down', 'fallback' },
['<cr>'] = {
function(cmp)
local item = cmp.get_selected_item()
if item and item.label == '.unwrap' then
vim.schedule(function()
local ctx = cmp.get_context()
if not ctx or not ctx.bounds then
return cmp.accept()
end
local partial = ctx.get_keyword() or ''
local full = cmp.get_selected_item().label
local rest = full:sub(#partial + 1 + 1)
local row = ctx.bounds.line_number - 1
local col = ctx.bounds.start_col + ctx.bounds.length
local line = vim.api.nvim_buf_get_lines(ctx.bufnr, row, row + 1, false)[1]
local cursor_at_eol = col > #line
vim.api.nvim_put({ rest }, 'c', cursor_at_eol, true)
require('luasnip').expand()
end)
return true
end
return cmp.accept()
end,
'fallback',
},
},
completion = {
@@ -31,7 +77,16 @@ return {
keyword = { range = 'full' },
-- Preselect first one, don't complete until confirmation
list = { selection = { preselect = true, auto_insert = false } },
list = {
selection = {
preselect = true,
-- auto_insert = false,
auto_insert = function(ctx)
-- vim.notify()
return ctx.get_keyword() == 'unwrap'
end,
},
},
menu = {
draw = {
@@ -82,31 +137,7 @@ return {
},
snippets = {
-- score_offset = 200, -- make snippets highest priority
transform_items = function(_, items)
return vim.tbl_filter(function(item)
-- vim.print(item)
if item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet then
return true
end
local name = item.description
-- vim.print(name)
local parts = vim.split(name, ' ', { trimempty = false })
local namespace = #parts > 1 and parts[1] or nil
-- vim.print(namespace)
if not namespace then
return true
end
-- vim.print(vim.fn.getcwd())
local path = vim.split(vim.fn.getcwd(), '/')
local dir = path[#path]
-- vim.print(dir)
return dir == namespace
end, items)
end,
score_offset = 200, -- make snippets highest priority
},
lsp = {
@@ -181,7 +212,6 @@ return {
opts_extend = { 'sources.default' },
-- TODO: pressing backk (deleting), should re-show completion menu
-- TODO: ghost text only for LLMs?
}
-- From LazyVim

View File

@@ -0,0 +1,147 @@
return {
'L3MON4D3/LuaSnip',
version = 'v2.*', -- follow latest release.
build = 'make install_jsregexp',
opts = {},
config = function(_, opts)
require('luasnip').setup(opts)
-- Load language specific snippets
require('luasnip.loaders.from_vscode').load({ paths = './snippets' })
-- load project specific snippets
local project = vim.fn.fnamemodify(vim.fn.getcwd(), ':t')
local project_snippets = {
eko = 'eko.json',
['carbonight.nvim'] = 'carbonight.json',
}
local file = project_snippets[project]
if file then
require('luasnip.loaders.from_vscode').load_standalone({ path = './snippets/' .. file })
end
-- some shorthands...
local ls = require('luasnip')
local s = ls.snippet
local sn = ls.snippet_node
local isn = ls.indent_snippet_node
local t = ls.text_node
local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local events = require('luasnip.util.events')
local ai = require('luasnip.nodes.absolute_indexer')
local opt = require('luasnip.nodes.optional_arg')
local extras = require('luasnip.extras')
local l = extras.lambda
local rep = extras.rep
local p = extras.partial
local m = extras.match
local n = extras.nonempty
local dl = extras.dynamic_lambda
local fmt = require('luasnip.extras.fmt').fmt
local fmta = require('luasnip.extras.fmt').fmta
local conds = require('luasnip.extras.expand_conditions')
local postfix = require('luasnip.extras.postfix').postfix
local ts_postfix = require('luasnip.extras.treesitter_postfix').treesitter_postfix
local types = require('luasnip.util.types')
local parse = require('luasnip.util.parser').parse_snippet
local ms = ls.multi_snippet
local k = require('luasnip.nodes.key_indexer').new_key
local postfix_builtin = require('luasnip.extras.treesitter_postfix').builtin
ls.add_snippets('all', {
ts_postfix({
matchTSNode = postfix_builtin.tsnode_matcher.find_topmost_types({
'identifier',
'method_invocation',
'field_access',
'type_identifier',
'class_literal',
'this',
'scoped_type_identifier',
}),
trig = '.unwrap',
}, {
f(function(_, parent)
-- Use POSTFIX_MATCH instead of LS_TSMATCH
local match = parent.snippet.env.LS_TSMATCH
-- vim.notify('postfix: "' .. vim.inspect(match) .. '"')
if not match then
return { 'unwrap()' }
end
-- If LuaSnip provides the match as a table, concat it
if type(match) == 'table' then
match = table.concat(match, '\n')
end
local wrapped = string.format('unwrap(%s)', match)
-- Split it back into a table of strings to safely handle any newlines
return vim.split(wrapped, '\n', { plain = true })
end),
}),
-- ts_postfix({
-- trig = 'unwrap',
-- matchTSNode = {
-- query = [[
-- (field_access
-- object: (
-- field_access
-- method_invocation
-- identifier
-- ) @my_object
-- ) @my_field
-- ]],
-- query_lang = 'java',
-- select = 'longest',
-- },
-- }, {
-- f(function(_, parent)
-- -- Use POSTFIX_MATCH instead of LS_TSMATCH
-- local match = parent.snippet.env.POSTFIX_MATCH
-- vim.notify('postfix: "' .. vim.inspect(match) .. '"')
--
-- if not match then
-- return { 'unwrap()' }
-- end
--
-- -- If LuaSnip provides the match as a table, concat it
-- if type(match) == 'table' then
-- match = table.concat(match, '\n')
-- end
--
-- -- Format the string
-- local wrapped = string.format('unwrap(%s)', match)
--
-- -- Split it back into a table of strings to safely handle any newlines
-- return vim.split(wrapped, '\n', { plain = true })
-- end),
-- }, { condition = require('luasnip.util.util').yes }),
})
-- postfix({ trig = 'unwrap', match_pattern = [[[%w%.%_%-%"%']+$]] }, {
-- f(function(_, parent)
-- local match = parent.snippet.env.POSTFIX_MATCH
-- if match == nil then
-- return 'bunger'
-- end
-- -- match = match:sub(1, -2)
-- return 'unwrap(' .. match .. ')'
-- end, {}),
-- }, { condition = require('luasnip.util.util').yes }),
-- postfix({ trig = '.unwrap', snippetType = 'snippet' }, {
-- f(function(_, parent)
-- local match = parent.snippet.env.POSTFIX_MATCH
-- if match == nil then
-- return 'bunger'
-- end
-- return '[' .. match .. ']'
-- end, {}),
-- }),
end,
}