Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mini.snippets and snippets presets #877

Merged
merged 12 commits into from
Jan 7, 2025
Merged
3 changes: 3 additions & 0 deletions docs/configuration/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ For more common configurations, see the [recipes](../recipes.md).
cmdline = {},
},

-- Use a preset for snippets, check the snippets documentation for more information
snippets = { preset = 'default' | 'luasnip' | 'mini_snippets' },

-- Experimental signature help support
signature = { enabled = true }
}
Expand Down
17 changes: 13 additions & 4 deletions docs/configuration/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ sources.providers = {
score_offset = 0, -- Boost/penalize the score of the items
override = nil, -- Override the source's functions
},

path = {
name = 'Path',
module = 'blink.cmp.sources.path',
Expand All @@ -447,9 +448,12 @@ sources.providers = {
show_hidden_files_by_default = false,
}
},

snippets = {
name = 'Snippets',
module = 'blink.cmp.sources.snippets',

-- For `snippets.preset == 'default'`
opts = {
friendly_snippets = true,
search_paths = { vim.fn.stdpath('config') .. '/snippets' },
Expand All @@ -462,17 +466,22 @@ sources.providers = {
-- Set to '+' to use the system clipboard, or '"' to use the unnamed register
clipboard_register = nil,
}
},
luasnip = {
name = 'Luasnip',
module = 'blink.cmp.sources.luasnip',

-- For `snippets.preset == 'luasnip'`
opts = {
-- Whether to use show_condition for filtering snippets
use_show_condition = true,
-- Whether to show autosnippets in the completion list
show_autosnippets = true,
}

-- For `snippets.preset == 'mini_snippets'`
opts = {
-- Whether to use a cache for completion items
use_items_cache = true,
}
},

buffer = {
name = 'Buffer',
module = 'blink.cmp.sources.buffer',
Expand Down
28 changes: 18 additions & 10 deletions docs/configuration/snippets.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,26 @@ By default, the `snippets` source will check `~/.config/nvim/snippets` for your
-- `main` does not work at the moment
dependencies = { 'L3MON4D3/LuaSnip', version = 'v2.*' },
opts = {
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,
snippets = { preset = 'luasnip' },
-- ensure you have the `snippets` source (enabled by default)
sources = {
default = { 'lsp', 'path', 'snippets', 'buffer' },
},
}
}
```

## `mini.snippets`

```lua
{
'saghen/blink.cmp',
dependencies = 'echasnovski/mini.snippets',
opts = {
snippets = { preset = 'mini_snippets' },
-- ensure you have the `snippets` source (enabled by default)
sources = {
default = { 'lsp', 'path', 'luasnip', 'buffer' },
default = { 'lsp', 'path', 'snippets', 'buffer' },
},
}
}
Expand Down
49 changes: 44 additions & 5 deletions lua/blink/cmp/config/snippets.lua
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
--- @class (exact) blink.cmp.SnippetsConfig
--- @field preset 'default' | 'luasnip' | 'mini_snippets'
--- @field expand fun(snippet: string) Function to use when expanding LSP provided snippets
--- @field active fun(filter?: { direction?: number }): boolean Function to use when checking if a snippet is active
--- @field jump fun(direction: number) Function to use when jumping between tab stops in a snippet, where direction can be negative or positive

--- @param handlers table<'default' | 'luasnip' | 'mini_snippets', fun(...): any>
local function by_preset(handlers)
return function(...)
local preset = require('blink.cmp.config').snippets.preset
return handlers[preset](...)
end
end

local validate = require('blink.cmp.config.utils').validate
local snippets = {
--- @type blink.cmp.SnippetsConfig
default = {
-- NOTE: we wrap these in functions to reduce startup by 1-2ms
-- when using lazy.nvim
expand = function(snippet) vim.snippet.expand(snippet) end,
active = function(filter) return vim.snippet.active(filter) end,
jump = function(direction) vim.snippet.jump(direction) end,
preset = 'default',
-- NOTE: we wrap `vim.snippet` calls to reduce startup by 1-2ms
expand = by_preset({
default = function(snippet) vim.snippet.expand(snippet) end,
luasnip = function(snippet) require('luasnip').lsp_expand(snippet) end,
mini_snippets = function(snippet)
if not _G.MiniSnippets then error('mini.snippets has not been setup') end
local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
insert(snippet)
end,
}),
active = by_preset({
default = function(filter) return vim.snippet.active(filter) end,
luasnip = function(filter)
if filter and filter.direction then return require('luasnip').jumpable(filter.direction) end
return require('luasnip').in_snippet()
end,
mini_snippets = function()
if not _G.MiniSnippets then error('mini.snippets has not been setup') end
return MiniSnippets.session.get(false) ~= nil
end,
}),
jump = by_preset({
default = function(direction) vim.snippet.jump(direction) end,
luasnip = function(direction) require('luasnip').jump(direction) end,
mini_snippets = function(direction)
if not _G.MiniSnippets then error('mini.snippets has not been setup') end
MiniSnippets.session.jump(direction == -1 and 'prev' or 'next')
end,
}),
},
}

function snippets.validate(config)
validate('snippets', {
preset = {
config.preset,
function(preset) return vim.tbl_contains({ 'default', 'luasnip', 'mini_snippets' }, preset) end,
'one of: "default", "luasnip", "mini_snippets"',
},
expand = { config.expand, 'function' },
active = { config.active, 'function' },
jump = { config.jump, 'function' },
Expand Down
5 changes: 0 additions & 5 deletions lua/blink/cmp/config/sources.lua
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ local sources = {
module = 'blink.cmp.sources.snippets',
score_offset = -3,
},
luasnip = {
name = 'Luasnip',
module = 'blink.cmp.sources.luasnip',
score_offset = -3,
},
buffer = {
name = 'Buffer',
module = 'blink.cmp.sources.buffer',
Expand Down
3 changes: 2 additions & 1 deletion lua/blink/cmp/sources/buffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ end
local buffer = {}

function buffer.new(opts)
opts = opts or {} ---@type blink.cmp.BufferOpts
--- @cast opts blink.cmp.BufferOpts

local self = setmetatable({}, { __index = buffer })
self.get_bufnrs = opts.get_bufnrs
or function()
Expand Down
7 changes: 7 additions & 0 deletions lua/blink/cmp/sources/lib/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ function sources.get_enabled_providers(mode)
end

function sources.get_provider_by_id(provider_id)
-- TODO: remove in v1.0
if not sources.providers[provider_id] and provider_id == 'luasnip' then
error(
"Luasnip has been moved to the `snippets` source, alongside a new preset system (`snippets.preset = 'luasnip'`). See the documentation for more information."
)
end

assert(
sources.providers[provider_id] ~= nil or config.sources.providers[provider_id] ~= nil,
'Requested provider "'
Expand Down
2 changes: 1 addition & 1 deletion lua/blink/cmp/sources/lib/provider/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function source.new(id, config)
self.id = id
self.name = config.name
self.module = require('blink.cmp.sources.lib.provider.override').new(
require(config.module).new(config.opts, config),
require(config.module).new(config.opts or {}, config),
config.override
)
self.config = require('blink.cmp.sources.lib.provider.config').new(config)
Expand Down
2 changes: 1 addition & 1 deletion lua/blink/cmp/sources/path/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function path.new(opts)
local self = setmetatable({}, { __index = path })

--- @type blink.cmp.PathOpts
opts = vim.tbl_deep_extend('keep', opts or {}, {
opts = vim.tbl_deep_extend('keep', opts, {
trailing_slash = true,
label_trailing_slash = true,
get_cwd = function(context) return vim.fn.expand(('#%d:p:h'):format(context.bufnr)) end,
Expand Down
65 changes: 65 additions & 0 deletions lua/blink/cmp/sources/snippets/default/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--- @class blink.cmp.SnippetsOpts
--- @field friendly_snippets? boolean
--- @field search_paths? string[]
--- @field global_snippets? string[]
--- @field extended_filetypes? table<string, string[]>
--- @field ignored_filetypes? string[]
--- @field get_filetype? fun(context: blink.cmp.Context): string
--- @field clipboard_register? string

local snippets = {}

function snippets.new(opts)
--- @cast opts blink.cmp.SnippetsOpts

local self = setmetatable({}, { __index = snippets })
--- @type table<string, blink.cmp.CompletionItem[]>
self.cache = {}
self.registry = require('blink.cmp.sources.snippets.default.registry').new(opts)
self.get_filetype = opts.get_filetype or function() return vim.bo.filetype end
return self
end

function snippets:get_completions(context, callback)
local filetype = self.get_filetype(context)
if vim.tbl_contains(self.registry.config.ignored_filetypes, filetype) then return callback() end

if not self.cache[filetype] then
local global_snippets = self.registry:get_global_snippets()
local extended_snippets = self.registry:get_extended_snippets(filetype)
local ft_snippets = self.registry:get_snippets_for_ft(filetype)
local snips = vim.list_extend({}, global_snippets)
vim.list_extend(snips, extended_snippets)
vim.list_extend(snips, ft_snippets)

self.cache[filetype] = snips
end

local items = vim.tbl_map(
function(item) return self.registry:snippet_to_completion_item(item) end,
self.cache[filetype]
)
callback({
is_incomplete_forward = false,
is_incomplete_backward = false,
items = items,
})
end

function snippets:resolve(item, callback)
local parsed_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(item.insertText)
local snippet = parsed_snippet and tostring(parsed_snippet) or item.insertText

local resolved_item = vim.deepcopy(item)
resolved_item.detail = snippet
resolved_item.documentation = {
kind = 'markdown',
value = item.description,
}
callback(resolved_item)
end

--- For external integrations to force reloading the snippets
function snippets:reload() self.cache = {} end

return snippets
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
--- @field description? string

local registry = {
builtin_vars = require('blink.cmp.sources.snippets.builtin'),
builtin_vars = require('blink.cmp.sources.snippets.default.builtin'),
}

local utils = require('blink.cmp.sources.snippets.utils')
Expand All @@ -33,7 +33,7 @@ function registry.new(config)
if string.match(path, 'friendly.snippets') then table.insert(self.config.search_paths, path) end
end
end
self.registry = require('blink.cmp.sources.snippets.scan').register_snippets(self.config.search_paths)
self.registry = require('blink.cmp.sources.snippets.default.scan').register_snippets(self.config.search_paths)

return self
end
Expand Down
68 changes: 6 additions & 62 deletions lua/blink/cmp/sources/snippets/init.lua
Original file line number Diff line number Diff line change
@@ -1,65 +1,9 @@
--- @class blink.cmp.SnippetsOpts
--- @field friendly_snippets? boolean
--- @field search_paths? string[]
--- @field global_snippets? string[]
--- @field extended_filetypes? table<string, string[]>
--- @field ignored_filetypes? string[]
--- @field get_filetype? fun(context: blink.cmp.Context): string
--- @field clipboard_register? string
local source = {}

local snippets = {}

function snippets.new(opts)
--- @type blink.cmp.SnippetsOpts
opts = opts or {}
local self = setmetatable({}, { __index = snippets })
--- @type table<string, blink.cmp.CompletionItem[]>
self.cache = {}
self.registry = require('blink.cmp.sources.snippets.registry').new(opts)
self.get_filetype = opts.get_filetype or function() return vim.bo.filetype end
return self
end

function snippets:get_completions(context, callback)
local filetype = self.get_filetype(context)
if vim.tbl_contains(self.registry.config.ignored_filetypes, filetype) then return callback() end

if not self.cache[filetype] then
local global_snippets = self.registry:get_global_snippets()
local extended_snippets = self.registry:get_extended_snippets(filetype)
local ft_snippets = self.registry:get_snippets_for_ft(filetype)
local snips = vim.list_extend({}, global_snippets)
vim.list_extend(snips, extended_snippets)
vim.list_extend(snips, ft_snippets)

self.cache[filetype] = snips
end

local items = vim.tbl_map(
function(item) return self.registry:snippet_to_completion_item(item) end,
self.cache[filetype]
)
callback({
is_incomplete_forward = false,
is_incomplete_backward = false,
items = items,
})
function source.new(opts)
local preset = opts.preset or require('blink.cmp.config').snippets.preset
local module = 'blink.cmp.sources.snippets.' .. preset
return require(module).new(opts)
end

function snippets:resolve(item, callback)
local parsed_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(item.insertText)
local snippet = parsed_snippet and tostring(parsed_snippet) or item.insertText

local resolved_item = vim.deepcopy(item)
resolved_item.detail = snippet
resolved_item.documentation = {
kind = 'markdown',
value = item.description,
}
callback(resolved_item)
end

--- For external integrations to force reloading the snippets
function snippets:reload() self.cache = {} end

return snippets
return source
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ local defaults_config = {
}

function source.new(opts)
local config = vim.tbl_deep_extend('keep', opts or {}, defaults_config)
local config = vim.tbl_deep_extend('keep', opts, defaults_config)
require('blink.cmp.config.utils').validate('sources.providers.luasnip', {
use_show_condition = { config.use_show_condition, 'boolean' },
show_autosnippets = { config.show_autosnippets, 'boolean' },
Expand Down
Loading
Loading