Skip to content

Commit 08b59ed

Browse files
Saghenleiserfgsoifou
committed
feat: native luasnip source
Thanks to @leiserfg and @soifou! Closes #378 Closes #401 Closes #432 Co-authored-by: leiserfg <[email protected]> Co-authored-by: soifou <[email protected]>
1 parent f4e53f2 commit 08b59ed

File tree

4 files changed

+147
-26
lines changed

4 files changed

+147
-26
lines changed

README.md

+3-25
Original file line numberDiff line numberDiff line change
@@ -699,17 +699,11 @@ MiniDeps.add({
699699

700700
### Luasnip
701701

702-
There's currently no `blink.cmp` native source for [luasnip](https://github.com/L3MON4D3/LuaSnip). You may use [blink.compat](https://github.com/saghen/blink.compat) plugin with the [cmp_luasnip](https://github.com/saadparwaiz1/cmp_luasnip) nvim-cmp source in the meantime.
703-
704702
```lua
705703
{
706704
'saghen/blink.cmp',
707-
version = '0.*',
708-
dependencies = {
709-
'L3MON4D3/LuaSnip',
710-
'saadparwaiz1/cmp_luasnip',
711-
-- lock compat to tagged versions, if you've also locked blink.cmp to tagged versions
712-
{ 'saghen/blink.compat', version = '*', opts = { impersonate_nvim_cmp = true } } },
705+
version = 'v0.*',
706+
dependencies = 'L3MON4D3/LuaSnip',
713707
opts = {
714708
snippets = {
715709
expand = function(snippet) require('luasnip').lsp_expand(snippet) end,
@@ -723,23 +717,7 @@ There's currently no `blink.cmp` native source for [luasnip](https://github.com/
723717
},
724718
sources = {
725719
completion = {
726-
-- WARN: add the rest of your providers here, unless you're using `opts_extend`
727-
-- and defining this outside of your primary `blink.cmp` config
728-
-- see the default configuration for the default providers
729-
enabled_providers = { 'luasnip' },
730-
},
731-
providers = {
732-
luasnip = {
733-
name = 'luasnip',
734-
module = 'blink.compat.source',
735-
736-
score_offset = -3,
737-
738-
opts = {
739-
use_show_condition = false,
740-
show_autosnippets = true,
741-
},
742-
},
720+
enabled_providers = { 'lsp', 'path', 'luasnip', 'buffer' },
743721
},
744722
},
745723
}

lua/blink/cmp/config/sources.lua

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ local sources = {
5656
module = 'blink.cmp.sources.snippets',
5757
score_offset = -3,
5858
},
59+
luasnip = {
60+
name = 'Luasnip',
61+
module = 'blink.cmp.sources.luasnip',
62+
score_offset = -3,
63+
},
5964
buffer = {
6065
name = 'Buffer',
6166
module = 'blink.cmp.sources.buffer',

lua/blink/cmp/sources/lib/types.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
--- @field items blink.cmp.CompletionItem[]
1010

1111
--- @class blink.cmp.Source
12-
--- @field new fun(config: blink.cmp.SourceProviderConfig): blink.cmp.Source
12+
--- @field new fun(opts: table, config: blink.cmp.SourceProviderConfig): blink.cmp.Source
1313
--- @field enabled? fun(self: blink.cmp.Source, context: blink.cmp.Context): boolean
1414
--- @field get_trigger_characters? fun(self: blink.cmp.Source): string[]
1515
--- @field get_completions? fun(self: blink.cmp.Source, context: blink.cmp.Context, callback: fun(response?: blink.cmp.CompletionResponse)): (fun(): nil) | nil

lua/blink/cmp/sources/luasnip.lua

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---@class blink.cmp.LuasnipSourceOptions
2+
---@field use_show_condition? boolean Whether to use show_condition for filtering snippets
3+
---@field show_autosnippets? boolean Whether to show autosnippets in the completion list
4+
5+
--- @class blink.cmp.LuasnipSource : blink.cmp.Source
6+
--- @field config blink.cmp.LuasnipSourceOptions
7+
--- @field items_cache table<string, blink.cmp.CompletionItem[]>
8+
9+
--- @type blink.cmp.LuasnipSource
10+
--- @diagnostic disable-next-line: missing-fields
11+
local source = {}
12+
13+
local defaults_config = {
14+
use_show_condition = true,
15+
show_autosnippets = true,
16+
}
17+
18+
function source.new(opts)
19+
local config = vim.tbl_deep_extend('keep', opts or {}, defaults_config)
20+
vim.validate({
21+
use_show_condition = { config.use_show_condition, 'boolean' },
22+
show_autosnippets = { config.show_autosnippets, 'boolean' },
23+
})
24+
local self = setmetatable({}, { __index = source })
25+
self.config = config
26+
self.items_cache = {}
27+
return self
28+
end
29+
30+
function source:enabled()
31+
local ok, _ = pcall(require, 'luasnip')
32+
return ok
33+
end
34+
35+
function source:get_completions(ctx, callback)
36+
local ft = vim.bo.filetype
37+
38+
if not self.items_cache[ft] then
39+
--- @type blink.cmp.CompletionItem[]
40+
local items = {}
41+
42+
-- Gather filetype snippets and, optionally, autosnippets
43+
local snippets = require('luasnip').get_snippets(ft, { type = 'snippets' })
44+
if self.config.show_autosnippets then
45+
local autosnippets = require('luasnip').get_snippets(ft, { type = 'autosnippets' })
46+
snippets = require('blink.cmp.lib.utils').shallow_copy(snippets)
47+
vim.list_extend(snippets, autosnippets)
48+
end
49+
snippets = vim.tbl_filter(function(snip) return not snip.hidden end, snippets)
50+
51+
-- Get the max priority for use with sortText
52+
local max_priority = 0
53+
for _, snip in ipairs(snippets) do
54+
if not snip.hidden then max_priority = math.max(max_priority, snip.effective_priority or 0) end
55+
end
56+
57+
for _, snip in ipairs(snippets) do
58+
-- Convert priority of 1000 (with max of 8000) to string like "00007000|||asd" for sorting
59+
-- This will put high priority snippets at the top of the list, and break ties based on the trigger
60+
local inversed_priority = max_priority - (snip.effective_priority or 0)
61+
local sort_text = ('0'):rep(8 - tostring(inversed_priority), '') .. inversed_priority .. '|||' .. snip.trigger
62+
63+
--- @type lsp.CompletionItem
64+
local item = {
65+
kind = require('blink.cmp.types').CompletionItemKind.Snippet,
66+
label = snip.trigger,
67+
insertText = snip.trigger,
68+
insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText,
69+
sortText = sort_text,
70+
data = { snip_id = snip.id, show_condition = snip.show_condition },
71+
}
72+
table.insert(items, item)
73+
end
74+
75+
self.items_cache[ft] = items
76+
end
77+
78+
local items = self.items_cache[ft] or {}
79+
80+
-- Filter items based on show_condition, if configured
81+
if self.config.use_show_condition then
82+
local line_to_cursor = ctx.line:sub(0, ctx.cursor[2] - 1)
83+
items = vim.tbl_filter(function(item) return item.data.show_condition(line_to_cursor) end, items)
84+
end
85+
86+
callback({
87+
is_incomplete_forward = false,
88+
is_incomplete_backward = false,
89+
items = items,
90+
context = ctx,
91+
})
92+
end
93+
94+
function source:resolve(item, callback)
95+
local snip = require('luasnip').get_id_snippet(item.data.snip_id)
96+
97+
local resolved_item = vim.deepcopy(item)
98+
resolved_item.detail = snip:get_docstring()
99+
resolved_item.documentation = {
100+
kind = 'markdown',
101+
value = table.concat(vim.lsp.util.convert_input_to_markdown_lines(item.data.documentation or ''), '\n'),
102+
}
103+
104+
callback(resolved_item)
105+
end
106+
107+
function source:execute(_, item)
108+
local luasnip = require('luasnip')
109+
local snip = luasnip.get_id_snippet(item.data.snip_id)
110+
111+
-- if trigger is a pattern, expand "pattern" instead of actual snippet.
112+
if snip.regTrig then snip = snip:get_pattern_expand_helper() end
113+
114+
-- get (0, 0) indexed cursor position
115+
local cursor = vim.api.nvim_win_get_cursor(0)
116+
cursor[1] = cursor[1] - 1
117+
118+
local expand_params = snip:matches(require('luasnip.util.util').get_current_line_to_cursor())
119+
120+
local clear_region = {
121+
from = { cursor[1], cursor[2] - #item.insertText },
122+
to = cursor,
123+
}
124+
if expand_params ~= nil and expand_params.clear_region ~= nil then
125+
clear_region = expand_params.clear_region
126+
elseif expand_params ~= nil and expand_params.trigger ~= nil then
127+
clear_region = {
128+
from = { cursor[1], cursor[2] - #expand_params.trigger },
129+
to = cursor,
130+
}
131+
end
132+
133+
luasnip.snip_expand(snip, { expand_params = expand_params, clear_region = clear_region })
134+
end
135+
136+
function source:reload() self.items_cache = {} end
137+
138+
return source

0 commit comments

Comments
 (0)