summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/config
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
committerMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
commitb77413ff8f59f380612074f0c9bd49093d8db695 (patch)
tree32c39a811ba96ed4ab0a1c81cce9f8d518ed7e31 /lua/blink/cmp/config
Squashed 'mut/neovim/pack/plugins/start/blink.cmp/' content from commit 1cc3b1a
git-subtree-dir: mut/neovim/pack/plugins/start/blink.cmp git-subtree-split: 1cc3b1a908fbcfd15451c4772759549724f38524
Diffstat (limited to 'lua/blink/cmp/config')
-rw-r--r--lua/blink/cmp/config/appearance.lua58
-rw-r--r--lua/blink/cmp/config/completion/accept.lua72
-rw-r--r--lua/blink/cmp/config/completion/documentation.lua98
-rw-r--r--lua/blink/cmp/config/completion/ghost_text.lua19
-rw-r--r--lua/blink/cmp/config/completion/init.lua42
-rw-r--r--lua/blink/cmp/config/completion/keyword.lua27
-rw-r--r--lua/blink/cmp/config/completion/list.lua54
-rw-r--r--lua/blink/cmp/config/completion/menu.lua223
-rw-r--r--lua/blink/cmp/config/completion/trigger.lua42
-rw-r--r--lua/blink/cmp/config/fuzzy.lua66
-rw-r--r--lua/blink/cmp/config/init.lua57
-rw-r--r--lua/blink/cmp/config/keymap.lua174
-rw-r--r--lua/blink/cmp/config/shared.lua2
-rw-r--r--lua/blink/cmp/config/signature.lua72
-rw-r--r--lua/blink/cmp/config/snippets.lua65
-rw-r--r--lua/blink/cmp/config/sources.lua149
-rw-r--r--lua/blink/cmp/config/types_partial.lua78
-rw-r--r--lua/blink/cmp/config/utils.lua36
18 files changed, 1334 insertions, 0 deletions
diff --git a/lua/blink/cmp/config/appearance.lua b/lua/blink/cmp/config/appearance.lua
new file mode 100644
index 0000000..08a97ba
--- /dev/null
+++ b/lua/blink/cmp/config/appearance.lua
@@ -0,0 +1,58 @@
+--- @class (exact) blink.cmp.AppearanceConfig
+--- @field highlight_ns number
+--- @field use_nvim_cmp_as_default boolean Sets the fallback highlight groups to nvim-cmp's highlight groups. Useful for when your theme doesn't support blink.cmp, will be removed in a future release.
+--- @field nerd_font_variant 'mono' | 'normal' Set to 'mono' for 'Nerd Font Mono' or 'normal' for 'Nerd Font'. Adjusts spacing to ensure icons are aligned
+--- @field kind_icons table<string, string>
+
+local validate = require('blink.cmp.config.utils').validate
+local appearance = {
+ --- @type blink.cmp.AppearanceConfig
+ default = {
+ highlight_ns = vim.api.nvim_create_namespace('blink_cmp'),
+ use_nvim_cmp_as_default = false,
+ nerd_font_variant = 'mono',
+ kind_icons = {
+ Text = '󰉿',
+ Method = '󰊕',
+ Function = '󰊕',
+ Constructor = '󰒓',
+
+ Field = '󰜢',
+ Variable = '󰆦',
+ Property = '󰖷',
+
+ Class = '󱡠',
+ Interface = '󱡠',
+ Struct = '󱡠',
+ Module = '󰅩',
+
+ Unit = '󰪚',
+ Value = '󰦨',
+ Enum = '󰦨',
+ EnumMember = '󰦨',
+
+ Keyword = '󰻾',
+ Constant = '󰏿',
+
+ Snippet = '󱄽',
+ Color = '󰏘',
+ File = '󰈔',
+ Reference = '󰬲',
+ Folder = '󰉋',
+ Event = '󱐋',
+ Operator = '󰪚',
+ TypeParameter = '󰬛',
+ },
+ },
+}
+
+function appearance.validate(config)
+ validate('appearance', {
+ highlight_ns = { config.highlight_ns, 'number' },
+ use_nvim_cmp_as_default = { config.use_nvim_cmp_as_default, 'boolean' },
+ nerd_font_variant = { config.nerd_font_variant, 'string' },
+ kind_icons = { config.kind_icons, 'table' },
+ }, config)
+end
+
+return appearance
diff --git a/lua/blink/cmp/config/completion/accept.lua b/lua/blink/cmp/config/completion/accept.lua
new file mode 100644
index 0000000..a81aa55
--- /dev/null
+++ b/lua/blink/cmp/config/completion/accept.lua
@@ -0,0 +1,72 @@
+--- @class (exact) blink.cmp.CompletionAcceptConfig
+--- @field create_undo_point boolean Create an undo point when accepting a completion item
+--- @field auto_brackets blink.cmp.AutoBracketsConfig
+
+--- @class (exact) blink.cmp.AutoBracketsConfig
+--- @field enabled boolean Whether to auto-insert brackets for functions
+--- @field default_brackets string[] Default brackets to use for unknown languages
+--- @field override_brackets_for_filetypes table<string, string[] | fun(item: blink.cmp.CompletionItem): string[]>
+--- @field force_allow_filetypes string[] Overrides the default blocked filetypes
+--- @field blocked_filetypes string[]
+--- @field kind_resolution blink.cmp.AutoBracketResolutionConfig Synchronously use the kind of the item to determine if brackets should be added
+--- @field semantic_token_resolution blink.cmp.AutoBracketSemanticTokenResolutionConfig Asynchronously use semantic token to determine if brackets should be added
+
+--- @class (exact) blink.cmp.AutoBracketResolutionConfig
+--- @field enabled boolean
+--- @field blocked_filetypes string[]
+
+--- @class (exact) blink.cmp.AutoBracketSemanticTokenResolutionConfig
+--- @field enabled boolean
+--- @field blocked_filetypes string[]
+--- @field timeout_ms number How long to wait for semantic tokens to return before assuming no brackets should be added
+
+local validate = require('blink.cmp.config.utils').validate
+local accept = {
+ --- @type blink.cmp.CompletionAcceptConfig
+ default = {
+ create_undo_point = true,
+ auto_brackets = {
+ enabled = true,
+ default_brackets = { '(', ')' },
+ override_brackets_for_filetypes = {},
+ force_allow_filetypes = {},
+ blocked_filetypes = {},
+ kind_resolution = {
+ enabled = true,
+ blocked_filetypes = { 'typescriptreact', 'javascriptreact', 'vue', 'rust' },
+ },
+ semantic_token_resolution = {
+ enabled = true,
+ blocked_filetypes = { 'java' },
+ timeout_ms = 400,
+ },
+ },
+ },
+}
+
+function accept.validate(config)
+ validate('completion.accept', {
+ create_undo_point = { config.create_undo_point, 'boolean' },
+ auto_brackets = { config.auto_brackets, 'table' },
+ }, config)
+ validate('completion.accept.auto_brackets', {
+ enabled = { config.auto_brackets.enabled, 'boolean' },
+ default_brackets = { config.auto_brackets.default_brackets, 'table' },
+ override_brackets_for_filetypes = { config.auto_brackets.override_brackets_for_filetypes, 'table' },
+ force_allow_filetypes = { config.auto_brackets.force_allow_filetypes, 'table' },
+ blocked_filetypes = { config.auto_brackets.blocked_filetypes, 'table' },
+ kind_resolution = { config.auto_brackets.kind_resolution, 'table' },
+ semantic_token_resolution = { config.auto_brackets.semantic_token_resolution, 'table' },
+ }, config.auto_brackets)
+ validate('completion.accept.auto_brackets.kind_resolution', {
+ enabled = { config.auto_brackets.kind_resolution.enabled, 'boolean' },
+ blocked_filetypes = { config.auto_brackets.kind_resolution.blocked_filetypes, 'table' },
+ }, config.auto_brackets.kind_resolution)
+ validate('completion.accept.auto_brackets.semantic_token_resolution', {
+ enabled = { config.auto_brackets.semantic_token_resolution.enabled, 'boolean' },
+ blocked_filetypes = { config.auto_brackets.semantic_token_resolution.blocked_filetypes, 'table' },
+ timeout_ms = { config.auto_brackets.semantic_token_resolution.timeout_ms, 'number' },
+ }, config.auto_brackets.semantic_token_resolution)
+end
+
+return accept
diff --git a/lua/blink/cmp/config/completion/documentation.lua b/lua/blink/cmp/config/completion/documentation.lua
new file mode 100644
index 0000000..71784d1
--- /dev/null
+++ b/lua/blink/cmp/config/completion/documentation.lua
@@ -0,0 +1,98 @@
+--- @class (exact) blink.cmp.CompletionDocumentationConfig
+--- @field auto_show boolean Controls whether the documentation window will automatically show when selecting a completion item
+--- @field auto_show_delay_ms number Delay before showing the documentation window
+--- @field update_delay_ms number Delay before updating the documentation window when selecting a new item, while an existing item is still visible
+--- @field treesitter_highlighting boolean Whether to use treesitter highlighting, disable if you run into performance issues
+--- @field window blink.cmp.CompletionDocumentationWindowConfig
+
+--- @class (exact) blink.cmp.CompletionDocumentationWindowConfig
+--- @field min_width number
+--- @field max_width number
+--- @field max_height number
+--- @field desired_min_width number
+--- @field desired_min_height number
+--- @field border blink.cmp.WindowBorder
+--- @field winblend number
+--- @field winhighlight string
+--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none'
+--- @field direction_priority blink.cmp.CompletionDocumentationDirectionPriorityConfig Which directions to show the window, for each of the possible menu window directions, falling back to the next direction when there's not enough space
+
+--- @class (exact) blink.cmp.CompletionDocumentationDirectionPriorityConfig
+--- @field menu_north ("n" | "s" | "e" | "w")[]
+--- @field menu_south ("n" | "s" | "e" | "w")[]
+
+local validate = require('blink.cmp.config.utils').validate
+local documentation = {
+ --- @type blink.cmp.CompletionDocumentationConfig
+ default = {
+ auto_show = false,
+ auto_show_delay_ms = 500,
+ update_delay_ms = 50,
+ treesitter_highlighting = true,
+ window = {
+ min_width = 10,
+ max_width = 80,
+ max_height = 20,
+ desired_min_width = 50,
+ desired_min_height = 10,
+ border = 'padded',
+ winblend = 0,
+ winhighlight = 'Normal:BlinkCmpDoc,FloatBorder:BlinkCmpDocBorder,EndOfBuffer:BlinkCmpDoc',
+ scrollbar = true,
+ direction_priority = {
+ menu_north = { 'e', 'w', 'n', 's' },
+ menu_south = { 'e', 'w', 's', 'n' },
+ },
+ },
+ },
+}
+
+function documentation.validate(config)
+ validate('completion.documentation', {
+ auto_show = { config.auto_show, 'boolean' },
+ auto_show_delay_ms = { config.auto_show_delay_ms, 'number' },
+ update_delay_ms = { config.update_delay_ms, 'number' },
+ treesitter_highlighting = { config.treesitter_highlighting, 'boolean' },
+ window = { config.window, 'table' },
+ }, config)
+
+ validate('completion.documentation.window', {
+ min_width = { config.window.min_width, 'number' },
+ max_width = { config.window.max_width, 'number' },
+ max_height = { config.window.max_height, 'number' },
+ desired_min_width = { config.window.desired_min_width, 'number' },
+ desired_min_height = { config.window.desired_min_height, 'number' },
+ border = { config.window.border, { 'string', 'table' } },
+ winblend = { config.window.winblend, 'number' },
+ winhighlight = { config.window.winhighlight, 'string' },
+ scrollbar = { config.window.scrollbar, 'boolean' },
+ direction_priority = { config.window.direction_priority, 'table' },
+ }, config.window)
+
+ validate('completion.documentation.window.direction_priority', {
+ menu_north = {
+ config.window.direction_priority.menu_north,
+ function(directions)
+ if type(directions) ~= 'table' or #directions == 0 then return false end
+ for _, direction in ipairs(directions) do
+ if not vim.tbl_contains({ 'n', 's', 'e', 'w' }, direction) then return false end
+ end
+ return true
+ end,
+ 'one of: "n", "s", "e", "w"',
+ },
+ menu_south = {
+ config.window.direction_priority.menu_south,
+ function(directions)
+ if type(directions) ~= 'table' or #directions == 0 then return false end
+ for _, direction in ipairs(directions) do
+ if not vim.tbl_contains({ 'n', 's', 'e', 'w' }, direction) then return false end
+ end
+ return true
+ end,
+ 'one of: "n", "s", "e", "w"',
+ },
+ }, config.window.direction_priority)
+end
+
+return documentation
diff --git a/lua/blink/cmp/config/completion/ghost_text.lua b/lua/blink/cmp/config/completion/ghost_text.lua
new file mode 100644
index 0000000..47fb2cd
--- /dev/null
+++ b/lua/blink/cmp/config/completion/ghost_text.lua
@@ -0,0 +1,19 @@
+--- Displays a preview of the selected item on the current line
+--- @class (exact) blink.cmp.CompletionGhostTextConfig
+--- @field enabled boolean
+
+local validate = require('blink.cmp.config.utils').validate
+local ghost_text = {
+ --- @type blink.cmp.CompletionGhostTextConfig
+ default = {
+ enabled = false,
+ },
+}
+
+function ghost_text.validate(config)
+ validate('completion.ghost_text', {
+ enabled = { config.enabled, 'boolean' },
+ }, config)
+end
+
+return ghost_text
diff --git a/lua/blink/cmp/config/completion/init.lua b/lua/blink/cmp/config/completion/init.lua
new file mode 100644
index 0000000..24407ff
--- /dev/null
+++ b/lua/blink/cmp/config/completion/init.lua
@@ -0,0 +1,42 @@
+--- @class (exact) blink.cmp.CompletionConfig
+--- @field keyword blink.cmp.CompletionKeywordConfig
+--- @field trigger blink.cmp.CompletionTriggerConfig
+--- @field list blink.cmp.CompletionListConfig
+--- @field accept blink.cmp.CompletionAcceptConfig
+--- @field menu blink.cmp.CompletionMenuConfig
+--- @field documentation blink.cmp.CompletionDocumentationConfig
+--- @field ghost_text blink.cmp.CompletionGhostTextConfig
+
+local validate = require('blink.cmp.config.utils').validate
+local completion = {
+ default = {
+ keyword = require('blink.cmp.config.completion.keyword').default,
+ trigger = require('blink.cmp.config.completion.trigger').default,
+ list = require('blink.cmp.config.completion.list').default,
+ accept = require('blink.cmp.config.completion.accept').default,
+ menu = require('blink.cmp.config.completion.menu').default,
+ documentation = require('blink.cmp.config.completion.documentation').default,
+ ghost_text = require('blink.cmp.config.completion.ghost_text').default,
+ },
+}
+
+function completion.validate(config)
+ validate('completion', {
+ keyword = { config.keyword, 'table' },
+ trigger = { config.trigger, 'table' },
+ list = { config.list, 'table' },
+ accept = { config.accept, 'table' },
+ menu = { config.menu, 'table' },
+ documentation = { config.documentation, 'table' },
+ ghost_text = { config.ghost_text, 'table' },
+ }, config)
+ require('blink.cmp.config.completion.keyword').validate(config.keyword)
+ require('blink.cmp.config.completion.trigger').validate(config.trigger)
+ require('blink.cmp.config.completion.list').validate(config.list)
+ require('blink.cmp.config.completion.accept').validate(config.accept)
+ require('blink.cmp.config.completion.menu').validate(config.menu)
+ require('blink.cmp.config.completion.documentation').validate(config.documentation)
+ require('blink.cmp.config.completion.ghost_text').validate(config.ghost_text)
+end
+
+return completion
diff --git a/lua/blink/cmp/config/completion/keyword.lua b/lua/blink/cmp/config/completion/keyword.lua
new file mode 100644
index 0000000..a922ac4
--- /dev/null
+++ b/lua/blink/cmp/config/completion/keyword.lua
@@ -0,0 +1,27 @@
+--- @class (exact) blink.cmp.CompletionKeywordConfig
+--- 'prefix' will fuzzy match on the text before the cursor
+--- 'full' will fuzzy match on the text before *and* after the cursor
+--- example: 'foo_|_bar' will match 'foo_' for 'prefix' and 'foo__bar' for 'full'
+--- @field range blink.cmp.CompletionKeywordRange
+---
+--- @alias blink.cmp.CompletionKeywordRange
+--- | 'prefix' Fuzzy match on the text before the cursor (example: 'foo_|bar' will match 'foo_')
+--- | 'full' Fuzzy match on the text before *and* after the cursor (example: 'foo_|_bar' will match 'foo__bar')
+
+local validate = require('blink.cmp.config.utils').validate
+local keyword = {
+ --- @type blink.cmp.CompletionKeywordConfig
+ default = { range = 'prefix' },
+}
+
+function keyword.validate(config)
+ validate('completion.keyword', {
+ range = {
+ config.range,
+ function(range) return vim.tbl_contains({ 'prefix', 'full' }, range) end,
+ 'one of: prefix, full',
+ },
+ }, config)
+end
+
+return keyword
diff --git a/lua/blink/cmp/config/completion/list.lua b/lua/blink/cmp/config/completion/list.lua
new file mode 100644
index 0000000..c0c5690
--- /dev/null
+++ b/lua/blink/cmp/config/completion/list.lua
@@ -0,0 +1,54 @@
+--- @class (exact) blink.cmp.CompletionListConfig
+--- @field max_items number Maximum number of items to display
+--- @field selection blink.cmp.CompletionListSelectionConfig
+--- @field cycle blink.cmp.CompletionListCycleConfig
+
+--- @class (exact) blink.cmp.CompletionListSelectionConfig
+--- @field preselect boolean | fun(ctx: blink.cmp.Context): boolean When `true`, will automatically select the first item in the completion list
+--- @field auto_insert boolean | fun(ctx: blink.cmp.Context): boolean When `true`, inserts the completion item automatically when selecting it. You may want to bind a key to the `cancel` command (default <C-e>) when using this option, which will both undo the selection and hide the completion menu
+
+--- @class (exact) blink.cmp.CompletionListCycleConfig
+--- @field from_bottom boolean When `true`, calling `select_next` at the *bottom* of the completion list will select the *first* completion item.
+--- @field from_top boolean When `true`, calling `select_prev` at the *top* of the completion list will select the *last* completion item.
+
+local validate = require('blink.cmp.config.utils').validate
+local list = {
+ --- @type blink.cmp.CompletionListConfig
+ default = {
+ max_items = 200,
+ selection = {
+ preselect = true,
+ auto_insert = true,
+ },
+ cycle = {
+ from_bottom = true,
+ from_top = true,
+ },
+ },
+}
+
+function list.validate(config)
+ if type(config.selection) == 'function' then
+ error(
+ '`completion.list.selection` has been replaced with `completion.list.selection.preselect` and `completion.list.selection.auto_insert`. See the docs for more info: https://cmp.saghen.dev/configuration/completion.html#list'
+ )
+ end
+
+ validate('completion.list', {
+ max_items = { config.max_items, 'number' },
+ selection = { config.selection, 'table' },
+ cycle = { config.cycle, 'table' },
+ }, config)
+
+ validate('completion.list.selection', {
+ preselect = { config.selection.preselect, { 'boolean', 'function' } },
+ auto_insert = { config.selection.auto_insert, { 'boolean', 'function' } },
+ }, config.selection)
+
+ validate('completion.list.cycle', {
+ from_bottom = { config.cycle.from_bottom, 'boolean' },
+ from_top = { config.cycle.from_top, 'boolean' },
+ }, config.cycle)
+end
+
+return list
diff --git a/lua/blink/cmp/config/completion/menu.lua b/lua/blink/cmp/config/completion/menu.lua
new file mode 100644
index 0000000..c807566
--- /dev/null
+++ b/lua/blink/cmp/config/completion/menu.lua
@@ -0,0 +1,223 @@
+local validate = require('blink.cmp.config.utils').validate
+
+--- @class (exact) blink.cmp.CompletionMenuConfig
+--- @field enabled boolean
+--- @field min_width number
+--- @field max_height number
+--- @field border blink.cmp.WindowBorder
+--- @field winblend number
+--- @field winhighlight string
+--- @field scrolloff number Keep the cursor X lines away from the top/bottom of the window
+--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none'
+--- @field direction_priority ("n" | "s")[] Which directions to show the window, falling back to the next direction when there's not enough space
+--- @field order blink.cmp.CompletionMenuOrderConfig TODO: implement
+--- @field auto_show boolean | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean Whether to automatically show the window when new completion items are available
+--- @field cmdline_position fun(): number[] Screen coordinates (0-indexed) of the command line
+--- @field draw blink.cmp.Draw Controls how the completion items are rendered on the popup window
+
+--- @class (exact) blink.cmp.CompletionMenuOrderConfig
+--- @field n 'top_down' | 'bottom_up'
+--- @field s 'top_down' | 'bottom_up'
+
+local window = {
+ --- @type blink.cmp.CompletionMenuConfig
+ default = {
+ enabled = true,
+ min_width = 15,
+ max_height = 10,
+ border = 'none',
+ winblend = 0,
+ winhighlight = 'Normal:BlinkCmpMenu,FloatBorder:BlinkCmpMenuBorder,CursorLine:BlinkCmpMenuSelection,Search:None',
+ -- keep the cursor X lines away from the top/bottom of the window
+ scrolloff = 2,
+ -- note that the gutter will be disabled when border ~= 'none'
+ scrollbar = true,
+ -- which directions to show the window,
+ -- falling back to the next direction when there's not enough space
+ direction_priority = { 's', 'n' },
+ -- which direction previous/next items show up
+ -- TODO: implement
+ order = { n = 'bottom_up', s = 'top_down' },
+
+ -- Whether to automatically show the window when new completion items are available
+ auto_show = true,
+
+ -- Screen coordinates of the command line
+ cmdline_position = function()
+ if vim.g.ui_cmdline_pos ~= nil then
+ local pos = vim.g.ui_cmdline_pos -- (1, 0)-indexed
+ return { pos[1] - 1, pos[2] }
+ end
+ local height = (vim.o.cmdheight == 0) and 1 or vim.o.cmdheight
+ return { vim.o.lines - height, 0 }
+ end,
+
+ -- Controls how the completion items are rendered on the popup window
+ draw = {
+ -- Aligns the keyword you've typed to a component in the menu
+ align_to = 'label', -- or 'none' to disable
+ -- Left and right padding, optionally { left, right } for different padding on each side
+ padding = 1,
+ -- Gap between columns
+ gap = 1,
+ treesitter = {}, -- Use treesitter to highlight the label text of completions from these sources
+ -- Components to render, grouped by column
+ columns = { { 'kind_icon' }, { 'label', 'label_description', gap = 1 } },
+ -- Definitions for possible components to render. Each component defines:
+ -- ellipsis: whether to add an ellipsis when truncating the text
+ -- width: control the min, max and fill behavior of the component
+ -- text function: will be called for each item
+ -- highlight function: will be called only when the line appears on screen
+ components = {
+ kind_icon = {
+ ellipsis = false,
+ text = function(ctx) return ctx.kind_icon .. ctx.icon_gap end,
+ highlight = function(ctx)
+ return require('blink.cmp.completion.windows.render.tailwind').get_hl(ctx) or ('BlinkCmpKind' .. ctx.kind)
+ end,
+ },
+
+ kind = {
+ ellipsis = false,
+ width = { fill = true },
+ text = function(ctx) return ctx.kind end,
+ highlight = function(ctx)
+ return require('blink.cmp.completion.windows.render.tailwind').get_hl(ctx) or ('BlinkCmpKind' .. ctx.kind)
+ end,
+ },
+
+ label = {
+ width = { fill = true, max = 60 },
+ text = function(ctx) return ctx.label .. ctx.label_detail end,
+ highlight = function(ctx)
+ -- label and label details
+ local label = ctx.label
+ local highlights = {
+ { 0, #label, group = ctx.deprecated and 'BlinkCmpLabelDeprecated' or 'BlinkCmpLabel' },
+ }
+ if ctx.label_detail then
+ table.insert(highlights, { #label, #label + #ctx.label_detail, group = 'BlinkCmpLabelDetail' })
+ end
+
+ if vim.list_contains(ctx.self.treesitter, ctx.source_id) then
+ -- add treesitter highlights
+ vim.list_extend(highlights, require('blink.cmp.completion.windows.render.treesitter').highlight(ctx))
+ end
+
+ -- characters matched on the label by the fuzzy matcher
+ for _, idx in ipairs(ctx.label_matched_indices) do
+ table.insert(highlights, { idx, idx + 1, group = 'BlinkCmpLabelMatch' })
+ end
+
+ return highlights
+ end,
+ },
+
+ label_description = {
+ width = { max = 30 },
+ text = function(ctx) return ctx.label_description end,
+ highlight = 'BlinkCmpLabelDescription',
+ },
+
+ source_name = {
+ width = { max = 30 },
+ -- source_name or source_id are supported
+ text = function(ctx) return ctx.source_name end,
+ highlight = 'BlinkCmpSource',
+ },
+ },
+ },
+ },
+}
+
+function window.validate(config)
+ validate('completion.menu', {
+ enabled = { config.enabled, 'boolean' },
+ min_width = { config.min_width, 'number' },
+ max_height = { config.max_height, 'number' },
+ border = { config.border, { 'string', 'table' } },
+ winblend = { config.winblend, 'number' },
+ winhighlight = { config.winhighlight, 'string' },
+ scrolloff = { config.scrolloff, 'number' },
+ scrollbar = { config.scrollbar, 'boolean' },
+ direction_priority = {
+ config.direction_priority,
+ function(direction_priority)
+ for _, direction in ipairs(direction_priority) do
+ if not vim.tbl_contains({ 'n', 's' }, direction) then return false end
+ end
+ return true
+ end,
+ 'one of: "n", "s"',
+ },
+ order = { config.order, 'table' },
+ auto_show = { config.auto_show, { 'boolean', 'function' } },
+ cmdline_position = { config.cmdline_position, 'function' },
+ draw = { config.draw, 'table' },
+ }, config)
+ validate('completion.menu.order', {
+ n = { config.order.n, { 'string', 'nil' } },
+ s = { config.order.s, { 'string', 'nil' } },
+ }, config.order)
+
+ validate('completion.menu.draw', {
+ align_to = {
+ config.draw.align_to,
+ function(align)
+ if align == 'none' or align == 'cursor' then return true end
+ for _, column in ipairs(config.draw.columns) do
+ for _, component in ipairs(column) do
+ if component == align then return true end
+ end
+ end
+ return false
+ end,
+ '"none" or one of the components defined in the "columns"',
+ },
+
+ padding = {
+ config.draw.padding,
+ function(padding)
+ if type(padding) == 'number' then return true end
+ if type(padding) ~= 'table' or #padding ~= 2 then return false end
+ if type(padding[1]) == 'number' and type(padding[2]) == 'number' then return true end
+ return false
+ end,
+ 'a number or a tuple of 2 numbers (i.e. [1, 2])',
+ },
+ gap = { config.draw.gap, 'number' },
+
+ treesitter = { config.draw.treesitter, 'table' },
+
+ columns = {
+ config.draw.columns,
+ function(columns)
+ local available_components = vim.tbl_keys(config.draw.components)
+
+ if type(columns) ~= 'table' or #columns == 0 then return false end
+ for _, column in ipairs(columns) do
+ if #column == 0 then return false end
+ for _, component in ipairs(column) do
+ if not vim.tbl_contains(available_components, component) then return false end
+ end
+ if column.gap ~= nil and type(column.gap) ~= 'number' then return false end
+ end
+ return true
+ end,
+ 'a table of tables, where each table contains a list of components and an optional gap. List of available components: '
+ .. table.concat(vim.tbl_keys(config.draw.components), ', '),
+ },
+ components = { config.draw.components, 'table' },
+ }, config.draw)
+
+ for component, definition in pairs(config.draw.components) do
+ validate('completion.menu.draw.components.' .. component, {
+ ellipsis = { definition.ellipsis, 'boolean', true },
+ width = { definition.width, 'table', true },
+ text = { definition.text, 'function' },
+ highlight = { definition.highlight, { 'string', 'function' }, true },
+ }, config.draw.components[component])
+ end
+end
+
+return window
diff --git a/lua/blink/cmp/config/completion/trigger.lua b/lua/blink/cmp/config/completion/trigger.lua
new file mode 100644
index 0000000..94d0af8
--- /dev/null
+++ b/lua/blink/cmp/config/completion/trigger.lua
@@ -0,0 +1,42 @@
+--- @class (exact) blink.cmp.CompletionTriggerConfig
+--- @field prefetch_on_insert boolean When true, will prefetch the completion items when entering insert mode. WARN: buggy, not recommended unless you'd like to help develop prefetching
+--- @field show_in_snippet boolean When false, will not show the completion window when in a snippet
+--- @field show_on_keyword boolean When true, will show the completion window after typing any of alphanumerics, `-` or `_`
+--- @field show_on_trigger_character boolean When true, will show the completion window after typing a trigger character
+--- @field show_on_blocked_trigger_characters string[] | (fun(): string[]) LSPs can indicate when to show the completion window via trigger characters. However, some LSPs (i.e. tsserver) return characters that would essentially always show the window. We block these by default.
+--- @field show_on_accept_on_trigger_character boolean When both this and show_on_trigger_character are true, will show the completion window when the cursor comes after a trigger character after accepting an item
+--- @field show_on_insert_on_trigger_character boolean When both this and show_on_trigger_character are true, will show the completion window when the cursor comes after a trigger character when entering insert mode
+--- @field show_on_x_blocked_trigger_characters string[] | (fun(): string[]) List of trigger characters (on top of `show_on_blocked_trigger_characters`) that won't trigger the completion window when the cursor comes after a trigger character when entering insert mode/accepting an item
+
+local validate = require('blink.cmp.config.utils').validate
+local trigger = {
+ --- @type blink.cmp.CompletionTriggerConfig
+ default = {
+ prefetch_on_insert = false,
+ show_in_snippet = true,
+ show_on_keyword = true,
+ show_on_trigger_character = true,
+ show_on_blocked_trigger_characters = function()
+ if vim.api.nvim_get_mode().mode == 'c' then return {} end
+ return { ' ', '\n', '\t' }
+ end,
+ show_on_accept_on_trigger_character = true,
+ show_on_insert_on_trigger_character = true,
+ show_on_x_blocked_trigger_characters = { "'", '"', '(', '{', '[' },
+ },
+}
+
+function trigger.validate(config)
+ validate('completion.trigger', {
+ prefetch_on_insert = { config.prefetch_on_insert, 'boolean' },
+ show_in_snippet = { config.show_in_snippet, 'boolean' },
+ show_on_keyword = { config.show_on_keyword, 'boolean' },
+ show_on_trigger_character = { config.show_on_trigger_character, 'boolean' },
+ show_on_blocked_trigger_characters = { config.show_on_blocked_trigger_characters, { 'function', 'table' } },
+ show_on_accept_on_trigger_character = { config.show_on_accept_on_trigger_character, 'boolean' },
+ show_on_insert_on_trigger_character = { config.show_on_insert_on_trigger_character, 'boolean' },
+ show_on_x_blocked_trigger_characters = { config.show_on_x_blocked_trigger_characters, { 'function', 'table' } },
+ }, config)
+end
+
+return trigger
diff --git a/lua/blink/cmp/config/fuzzy.lua b/lua/blink/cmp/config/fuzzy.lua
new file mode 100644
index 0000000..aa50e0b
--- /dev/null
+++ b/lua/blink/cmp/config/fuzzy.lua
@@ -0,0 +1,66 @@
+--- @class (exact) blink.cmp.FuzzyConfig
+--- @field use_typo_resistance boolean When enabled, allows for a number of typos relative to the length of the query. Disabling this matches the behavior of fzf
+--- @field use_frecency boolean Tracks the most recently/frequently used items and boosts the score of the item
+--- @field use_proximity boolean Boosts the score of items matching nearby words
+--- @field use_unsafe_no_lock boolean UNSAFE!! When enabled, disables the lock and fsync when writing to the frecency database. This should only be used on unsupported platforms (i.e. alpine termux)
+--- @field sorts ("label" | "sort_text" | "kind" | "score" | blink.cmp.SortFunction)[] Controls which sorts to use and in which order, these three are currently the only allowed options
+--- @field prebuilt_binaries blink.cmp.PrebuiltBinariesConfig
+
+--- @class (exact) blink.cmp.PrebuiltBinariesConfig
+--- @field download boolean Whenther or not to automatically download a prebuilt binary from github. If this is set to `false` you will need to manually build the fuzzy binary dependencies by running `cargo build --release`
+--- @field ignore_version_mismatch boolean Ignores mismatched version between the built binary and the current git sha, when building locally
+--- @field force_version? string When downloading a prebuilt binary, force the downloader to resolve this version. If this is unset then the downloader will attempt to infer the version from the checked out git tag (if any). WARN: Beware that `main` may be incompatible with the version you select
+--- @field force_system_triple? string When downloading a prebuilt binary, force the downloader to use this system triple. If this is unset then the downloader will attempt to infer the system triple from `jit.os` and `jit.arch`. Check the latest release for all available system triples. WARN: Beware that `main` may be incompatible with the version you select
+--- @field extra_curl_args string[] Extra arguments that will be passed to curl like { 'curl', ..extra_curl_args, ..built_in_args }
+
+--- @alias blink.cmp.SortFunction fun(a: blink.cmp.CompletionItem, b: blink.cmp.CompletionItem): boolean | nil
+
+local validate = require('blink.cmp.config.utils').validate
+local fuzzy = {
+ --- @type blink.cmp.FuzzyConfig
+ default = {
+ use_typo_resistance = true,
+ use_frecency = true,
+ use_proximity = true,
+ use_unsafe_no_lock = false,
+ sorts = { 'score', 'sort_text' },
+ prebuilt_binaries = {
+ download = true,
+ ignore_version_mismatch = false,
+ force_version = nil,
+ force_system_triple = nil,
+ extra_curl_args = {},
+ },
+ },
+}
+
+function fuzzy.validate(config)
+ validate('fuzzy', {
+ use_typo_resistance = { config.use_typo_resistance, 'boolean' },
+ use_frecency = { config.use_frecency, 'boolean' },
+ use_proximity = { config.use_proximity, 'boolean' },
+ use_unsafe_no_lock = { config.use_unsafe_no_lock, 'boolean' },
+ sorts = {
+ config.sorts,
+ function(sorts)
+ for _, sort in ipairs(sorts) do
+ if not vim.tbl_contains({ 'label', 'sort_text', 'kind', 'score' }, sort) and type(sort) ~= 'function' then
+ return false
+ end
+ end
+ return true
+ end,
+ 'one of: "label", "sort_text", "kind", "score" or a function',
+ },
+ prebuilt_binaries = { config.prebuilt_binaries, 'table' },
+ }, config)
+ validate('fuzzy.prebuilt_binaries', {
+ download = { config.prebuilt_binaries.download, 'boolean' },
+ ignore_version_mismatch = { config.prebuilt_binaries.ignore_version_mismatch, 'boolean' },
+ force_version = { config.prebuilt_binaries.force_version, { 'string', 'nil' } },
+ force_system_triple = { config.prebuilt_binaries.force_system_triple, { 'string', 'nil' } },
+ extra_curl_args = { config.prebuilt_binaries.extra_curl_args, { 'table' } },
+ }, config.prebuilt_binaries)
+end
+
+return fuzzy
diff --git a/lua/blink/cmp/config/init.lua b/lua/blink/cmp/config/init.lua
new file mode 100644
index 0000000..ac5678c
--- /dev/null
+++ b/lua/blink/cmp/config/init.lua
@@ -0,0 +1,57 @@
+--- @class (exact) blink.cmp.ConfigStrict
+--- @field enabled fun(): boolean
+--- @field keymap blink.cmp.KeymapConfig
+--- @field completion blink.cmp.CompletionConfig
+--- @field fuzzy blink.cmp.FuzzyConfig
+--- @field sources blink.cmp.SourceConfig
+--- @field signature blink.cmp.SignatureConfig
+--- @field snippets blink.cmp.SnippetsConfig
+--- @field appearance blink.cmp.AppearanceConfig
+
+local validate = require('blink.cmp.config.utils').validate
+--- @type blink.cmp.ConfigStrict
+local config = {
+ enabled = function() return vim.bo.buftype ~= 'prompt' and vim.b.completion ~= false end,
+ keymap = require('blink.cmp.config.keymap').default,
+ completion = require('blink.cmp.config.completion').default,
+ fuzzy = require('blink.cmp.config.fuzzy').default,
+ sources = require('blink.cmp.config.sources').default,
+ signature = require('blink.cmp.config.signature').default,
+ snippets = require('blink.cmp.config.snippets').default,
+ appearance = require('blink.cmp.config.appearance').default,
+}
+
+--- @type blink.cmp.ConfigStrict
+--- @diagnostic disable-next-line: missing-fields
+local M = {}
+
+--- @param cfg blink.cmp.ConfigStrict
+function M.validate(cfg)
+ validate('config', {
+ enabled = { cfg.enabled, 'function' },
+ keymap = { cfg.keymap, 'table' },
+ completion = { cfg.completion, 'table' },
+ fuzzy = { cfg.fuzzy, 'table' },
+ sources = { cfg.sources, 'table' },
+ signature = { cfg.signature, 'table' },
+ snippets = { cfg.snippets, 'table' },
+ appearance = { cfg.appearance, 'table' },
+ }, cfg)
+ require('blink.cmp.config.keymap').validate(cfg.keymap)
+ require('blink.cmp.config.completion').validate(cfg.completion)
+ require('blink.cmp.config.fuzzy').validate(cfg.fuzzy)
+ require('blink.cmp.config.sources').validate(cfg.sources)
+ require('blink.cmp.config.signature').validate(cfg.signature)
+ require('blink.cmp.config.snippets').validate(cfg.snippets)
+ require('blink.cmp.config.appearance').validate(cfg.appearance)
+end
+
+--- @param user_config blink.cmp.Config
+function M.merge_with(user_config)
+ config = vim.tbl_deep_extend('force', config, user_config)
+ M.validate(config)
+end
+
+return setmetatable(M, {
+ __index = function(_, k) return config[k] end,
+})
diff --git a/lua/blink/cmp/config/keymap.lua b/lua/blink/cmp/config/keymap.lua
new file mode 100644
index 0000000..86d74eb
--- /dev/null
+++ b/lua/blink/cmp/config/keymap.lua
@@ -0,0 +1,174 @@
+--- @alias blink.cmp.KeymapCommand
+--- | 'fallback' Fallback to the built-in behavior
+--- | 'show' Show the completion window
+--- | 'hide' Hide the completion window
+--- | 'cancel' Cancel the current completion, undoing the preview from auto_insert
+--- | 'accept' Accept the current completion item
+--- | 'select_and_accept' Select the first completion item, if there's no selection, and accept
+--- | 'select_prev' Select the previous completion item
+--- | 'select_next' Select the next completion item
+--- | 'show_documentation' Show the documentation window
+--- | 'hide_documentation' Hide the documentation window
+--- | 'scroll_documentation_up' Scroll the documentation window up
+--- | 'scroll_documentation_down' Scroll the documentation window down
+--- | 'snippet_forward' Move the cursor forward to the next snippet placeholder
+--- | 'snippet_backward' Move the cursor backward to the previous snippet placeholder
+--- | (fun(cmp: blink.cmp.API): boolean?) Custom function where returning true will prevent the next command from running
+
+--- @alias blink.cmp.KeymapPreset
+--- | 'none' No keymaps
+--- Mappings similar to the built-in completion:
+--- ```lua
+--- {
+--- ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
+--- ['<C-e>'] = { 'cancel', 'fallback' },
+--- ['<C-y>'] = { 'select_and_accept' },
+---
+--- ['<C-p>'] = { 'select_prev', 'fallback' },
+--- ['<C-n>'] = { 'select_next', 'fallback' },
+---
+--- ['<C-b>'] = { 'scroll_documentation_up', 'fallback' },
+--- ['<C-f>'] = { 'scroll_documentation_down', 'fallback' },
+---
+--- ['<Tab>'] = { 'snippet_forward', 'fallback' },
+--- ['<S-Tab>'] = { 'snippet_backward', 'fallback' },
+--- }
+--- ```
+--- | 'default'
+--- Mappings similar to VSCode.
+--- You may want to set `completion.trigger.show_in_snippet = false` or use `completion.list.selection.preselect = function(ctx) return not require('blink.cmp').snippet_active({ direction = 1 }) end` when using this mapping:
+--- ```lua
+--- {
+--- ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
+--- ['<C-e>'] = { 'cancel', 'fallback' },
+---
+--- ['<Tab>'] = {
+--- function(cmp)
+--- if cmp.snippet_active() then return cmp.accept()
+--- else return cmp.select_and_accept() end
+--- end,
+--- 'snippet_forward',
+--- 'fallback'
+--- },
+--- ['<S-Tab>'] = { 'snippet_backward', 'fallback' },
+---
+--- ['<Up>'] = { 'select_prev', 'fallback' },
+--- ['<Down>'] = { 'select_next', 'fallback' },
+--- ['<C-p>'] = { 'select_prev', 'fallback' },
+--- ['<C-n>'] = { 'select_next', 'fallback' },
+---
+--- ['<C-b>'] = { 'scroll_documentation_up', 'fallback' },
+--- ['<C-f>'] = { 'scroll_documentation_down', 'fallback' },
+--- }
+--- ```
+--- | 'super-tab'
+--- Similar to 'super-tab' but with `enter` to accept
+--- You may want to set `completion.list.selection.preselect = false` when using this keymap:
+--- ```lua
+--- {
+--- ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
+--- ['<C-e>'] = { 'cancel', 'fallback' },
+--- ['<CR>'] = { 'accept', 'fallback' },
+---
+--- ['<Tab>'] = { 'snippet_forward', 'fallback' },
+--- ['<S-Tab>'] = { 'snippet_backward', 'fallback' },
+---
+--- ['<Up>'] = { 'select_prev', 'fallback' },
+--- ['<Down>'] = { 'select_next', 'fallback' },
+--- ['<C-p>'] = { 'select_prev', 'fallback' },
+--- ['<C-n>'] = { 'select_next', 'fallback' },
+---
+--- ['<C-b>'] = { 'scroll_documentation_up', 'fallback' },
+--- ['<C-f>'] = { 'scroll_documentation_down', 'fallback' },
+--- }
+--- ```
+--- | 'enter'
+
+--- When specifying 'preset' in the keymap table, the custom key mappings are merged with the preset, and any conflicting keys will overwrite the preset mappings.
+--- The "fallback" command will run the next non blink keymap.
+---
+--- Example:
+---
+--- ```lua
+--- keymap = {
+--- preset = 'default',
+--- ['<Up>'] = { 'select_prev', 'fallback' },
+--- ['<Down>'] = { 'select_next', 'fallback' },
+---
+--- -- disable a keymap from the preset
+--- ['<C-e>'] = {},
+---
+--- -- optionally, define different keymaps for cmdline
+--- cmdline = {
+--- preset = 'cmdline',
+--- }
+--- }
+--- ```
+---
+--- When defining your own keymaps without a preset, no keybinds will be assigned automatically.
+--- @class (exact) blink.cmp.BaseKeymapConfig
+--- @field preset? blink.cmp.KeymapPreset
+--- @field [string] blink.cmp.KeymapCommand[] Table of keys => commands[]
+
+--- @class (exact) blink.cmp.KeymapConfig : blink.cmp.BaseKeymapConfig
+--- @field cmdline? blink.cmp.BaseKeymapConfig Optionally, define a separate keymap for cmdline
+
+local keymap = {
+ --- @type blink.cmp.KeymapConfig
+ default = {
+ preset = 'default',
+ },
+}
+
+--- @param config blink.cmp.KeymapConfig
+function keymap.validate(config)
+ local commands = {
+ 'fallback',
+ 'show',
+ 'hide',
+ 'cancel',
+ 'accept',
+ 'select_and_accept',
+ 'select_prev',
+ 'select_next',
+ 'show_documentation',
+ 'hide_documentation',
+ 'scroll_documentation_up',
+ 'scroll_documentation_down',
+ 'snippet_forward',
+ 'snippet_backward',
+ }
+ local presets = { 'default', 'super-tab', 'enter', 'none' }
+
+ local validation_schema = {}
+ for key, value in pairs(config) do
+ -- nested cmdline keymap
+ if key == 'cmdline' then
+ keymap.validate(value)
+
+ -- preset
+ elseif key == 'preset' then
+ validation_schema[key] = {
+ value,
+ function(preset) return vim.tbl_contains(presets, preset) end,
+ 'one of: ' .. table.concat(presets, ', '),
+ }
+
+ -- key
+ else
+ validation_schema[key] = {
+ value,
+ function(key_commands)
+ for _, command in ipairs(key_commands) do
+ if type(command) ~= 'function' and not vim.tbl_contains(commands, command) then return false end
+ end
+ return true
+ end,
+ 'commands must be one of: ' .. table.concat(commands, ', '),
+ }
+ end
+ end
+ require('blink.cmp.config.utils')._validate(validation_schema)
+end
+
+return keymap
diff --git a/lua/blink/cmp/config/shared.lua b/lua/blink/cmp/config/shared.lua
new file mode 100644
index 0000000..1ab7a60
--- /dev/null
+++ b/lua/blink/cmp/config/shared.lua
@@ -0,0 +1,2 @@
+--- @alias blink.cmp.WindowBorderChar string | table
+--- @alias blink.cmp.WindowBorder 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | 'padded' | 'none' | blink.cmp.WindowBorderChar[]
diff --git a/lua/blink/cmp/config/signature.lua b/lua/blink/cmp/config/signature.lua
new file mode 100644
index 0000000..987c7ca
--- /dev/null
+++ b/lua/blink/cmp/config/signature.lua
@@ -0,0 +1,72 @@
+--- @class (exact) blink.cmp.SignatureConfig
+--- @field enabled boolean
+--- @field trigger blink.cmp.SignatureTriggerConfig
+--- @field window blink.cmp.SignatureWindowConfig
+
+--- @class (exact) blink.cmp.SignatureTriggerConfig
+--- @field blocked_trigger_characters string[]
+--- @field blocked_retrigger_characters string[]
+--- @field show_on_insert_on_trigger_character boolean When true, will show the signature help window when the cursor comes after a trigger character when entering insert mode
+
+--- @class (exact) blink.cmp.SignatureWindowConfig
+--- @field min_width number
+--- @field max_width number
+--- @field max_height number
+--- @field border blink.cmp.WindowBorder
+--- @field winblend number
+--- @field winhighlight string
+--- @field scrollbar boolean Note that the gutter will be disabled when border ~= 'none'
+--- @field direction_priority ("n" | "s")[] Which directions to show the window, falling back to the next direction when there's not enough space, or another window is in the way.
+--- @field treesitter_highlighting boolean Disable if you run into performance issues
+
+local validate = require('blink.cmp.config.utils').validate
+local signature = {
+ --- @type blink.cmp.SignatureConfig
+ default = {
+ enabled = false,
+ trigger = {
+ enabled = true,
+ blocked_trigger_characters = {},
+ blocked_retrigger_characters = {},
+ show_on_insert_on_trigger_character = true,
+ },
+ window = {
+ min_width = 1,
+ max_width = 100,
+ max_height = 10,
+ border = 'padded',
+ winblend = 0,
+ winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder',
+ scrollbar = false,
+ direction_priority = { 'n', 's' },
+ treesitter_highlighting = true,
+ },
+ },
+}
+
+function signature.validate(config)
+ validate('signature', {
+ enabled = { config.enabled, 'boolean' },
+ trigger = { config.trigger, 'table' },
+ window = { config.window, 'table' },
+ }, config)
+ validate('signature.trigger', {
+ enabled = { config.trigger.enabled, 'boolean' },
+ blocked_trigger_characters = { config.trigger.blocked_trigger_characters, 'table' },
+ blocked_retrigger_characters = { config.trigger.blocked_retrigger_characters, 'table' },
+ show_on_insert_on_trigger_character = { config.trigger.show_on_insert_on_trigger_character, 'boolean' },
+ }, config.trigger)
+ validate('signature.window', {
+ min_width = { config.window.min_width, 'number' },
+ max_width = { config.window.max_width, 'number' },
+ max_height = { config.window.max_height, 'number' },
+ border = { config.window.border, { 'string', 'table' } },
+ winblend = { config.window.winblend, 'number' },
+ winhighlight = { config.window.winhighlight, 'string' },
+ scrollbar = { config.window.scrollbar, 'boolean' },
+ direction_priority = { config.window.direction_priority, 'table' },
+ treesitter_highlighting = { config.window.treesitter_highlighting, 'boolean' },
+ }, config.window)
+end
+
+return signature
diff --git a/lua/blink/cmp/config/snippets.lua b/lua/blink/cmp/config/snippets.lua
new file mode 100644
index 0000000..cd804e9
--- /dev/null
+++ b/lua/blink/cmp/config/snippets.lua
@@ -0,0 +1,65 @@
+--- @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 = {
+ 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' },
+ }, config)
+end
+
+return snippets
diff --git a/lua/blink/cmp/config/sources.lua b/lua/blink/cmp/config/sources.lua
new file mode 100644
index 0000000..ab06b5f
--- /dev/null
+++ b/lua/blink/cmp/config/sources.lua
@@ -0,0 +1,149 @@
+--- @class blink.cmp.SourceConfig
+--- Static list of providers to enable, or a function to dynamically enable/disable providers based on the context
+---
+--- Example dynamically picking providers based on the filetype and treesitter node:
+--- ```lua
+--- function(ctx)
+--- local node = vim.treesitter.get_node()
+--- if vim.bo.filetype == 'lua' then
+--- return { 'lsp', 'path' }
+--- elseif node and vim.tbl_contains({ 'comment', 'line_comment', 'block_comment' }), node:type())
+--- return { 'buffer' }
+--- else
+--- return { 'lsp', 'path', 'snippets', 'buffer' }
+--- end
+--- end
+--- ```
+--- @field default string[] | fun(): string[]
+--- @field per_filetype table<string, string[] | fun(): string[]>
+--- @field cmdline string[] | fun(): string[]
+---
+--- @field transform_items fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] Function to transform the items before they're returned
+--- @field min_keyword_length number | fun(ctx: blink.cmp.Context): number Minimum number of characters in the keyword to trigger
+---
+--- @field providers table<string, blink.cmp.SourceProviderConfig>
+
+--- @class blink.cmp.SourceProviderConfig
+--- @field name string
+--- @field module string
+--- @field enabled? boolean | fun(): boolean Whether or not to enable the provider
+--- @field opts? table
+--- @field async? boolean | fun(ctx: blink.cmp.Context): boolean Whether blink should wait for the source to return before showing the completions
+--- @field timeout_ms? number | fun(ctx: blink.cmp.Context): number How long to wait for the provider to return before showing completions and treating it as asynchronous
+--- @field transform_items? fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): blink.cmp.CompletionItem[] Function to transform the items before they're returned
+--- @field should_show_items? boolean | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): boolean Whether or not to show the items
+--- @field max_items? number | fun(ctx: blink.cmp.Context, items: blink.cmp.CompletionItem[]): number Maximum number of items to display in the menu
+--- @field min_keyword_length? number | fun(ctx: blink.cmp.Context): number Minimum number of characters in the keyword to trigger the provider
+--- @field fallbacks? string[] | fun(ctx: blink.cmp.Context, enabled_sources: string[]): string[] If this provider returns 0 items, it will fallback to these providers
+--- @field score_offset? number | fun(ctx: blink.cmp.Context, enabled_sources: string[]): number Boost/penalize the score of the items
+--- @field deduplicate? blink.cmp.DeduplicateConfig TODO: implement
+--- @field override? blink.cmp.SourceOverride Override the source's functions
+
+local validate = require('blink.cmp.config.utils').validate
+local sources = {
+ --- @type blink.cmp.SourceConfig
+ default = {
+ default = { 'lsp', 'path', 'snippets', 'buffer' },
+ per_filetype = {},
+ cmdline = function()
+ local type = vim.fn.getcmdtype()
+ -- Search forward and backward
+ if type == '/' or type == '?' then return { 'buffer' } end
+ -- Commands
+ if type == ':' or type == '@' then return { 'cmdline' } end
+ return {}
+ end,
+
+ transform_items = function(_, items) return items end,
+ min_keyword_length = 0,
+
+ providers = {
+ lsp = {
+ name = 'LSP',
+ module = 'blink.cmp.sources.lsp',
+ fallbacks = { 'buffer' },
+ transform_items = function(_, items)
+ -- demote snippets
+ for _, item in ipairs(items) do
+ if item.kind == require('blink.cmp.types').CompletionItemKind.Snippet then
+ item.score_offset = item.score_offset - 3
+ end
+ end
+
+ -- filter out text items, since we have the buffer source
+ return vim.tbl_filter(
+ function(item) return item.kind ~= require('blink.cmp.types').CompletionItemKind.Text end,
+ items
+ )
+ end,
+ },
+ path = {
+ name = 'Path',
+ module = 'blink.cmp.sources.path',
+ score_offset = 3,
+ fallbacks = { 'buffer' },
+ },
+ snippets = {
+ name = 'Snippets',
+ module = 'blink.cmp.sources.snippets',
+ score_offset = -3,
+ },
+ buffer = {
+ name = 'Buffer',
+ module = 'blink.cmp.sources.buffer',
+ score_offset = -3,
+ },
+ cmdline = {
+ name = 'cmdline',
+ module = 'blink.cmp.sources.cmdline',
+ },
+ },
+ },
+}
+
+function sources.validate(config)
+ assert(
+ config.completion == nil,
+ '`sources.completion.enabled_providers` has been replaced with `sources.default`. !!Note!! Be sure to update `opts_extend` as well if you have it set'
+ )
+
+ validate('sources', {
+ default = { config.default, { 'function', 'table' } },
+ per_filetype = { config.per_filetype, 'table' },
+ cmdline = { config.cmdline, { 'function', 'table' } },
+
+ transform_items = { config.transform_items, 'function' },
+ min_keyword_length = { config.min_keyword_length, { 'number', 'function' } },
+
+ providers = { config.providers, 'table' },
+ }, config)
+ for id, provider in pairs(config.providers) do
+ sources.validate_provider(id, provider)
+ end
+end
+
+function sources.validate_provider(id, provider)
+ assert(
+ provider.fallback_for == nil,
+ '`fallback_for` has been replaced with `fallbacks` which work in the opposite direction. For example, fallback_for = { "lsp" } on "buffer" would now be "fallbacks" = { "buffer" } on "lsp"'
+ )
+
+ validate('sources.providers.' .. id, {
+ name = { provider.name, 'string' },
+ module = { provider.module, 'string' },
+ enabled = { provider.enabled, { 'boolean', 'function' }, true },
+ opts = { provider.opts, 'table', true },
+ async = { provider.async, { 'boolean', 'function' }, true },
+ timeout_ms = { provider.timeout_ms, { 'number', 'function' }, true },
+ transform_items = { provider.transform_items, 'function', true },
+ should_show_items = { provider.should_show_items, { 'boolean', 'function' }, true },
+ max_items = { provider.max_items, { 'number', 'function' }, true },
+ min_keyword_length = { provider.min_keyword_length, { 'number', 'function' }, true },
+ fallbacks = { provider.fallback_for, { 'table', 'function' }, true },
+ score_offset = { provider.score_offset, { 'number', 'function' }, true },
+ deduplicate = { provider.deduplicate, 'table', true },
+ override = { provider.override, 'table', true },
+ }, provider)
+end
+
+return sources
diff --git a/lua/blink/cmp/config/types_partial.lua b/lua/blink/cmp/config/types_partial.lua
new file mode 100644
index 0000000..1f525d1
--- /dev/null
+++ b/lua/blink/cmp/config/types_partial.lua
@@ -0,0 +1,78 @@
+--- @class (exact) blink.cmp.Config : blink.cmp.ConfigStrict
+--- @field enabled? fun(): boolean
+--- @field keymap? blink.cmp.KeymapConfig
+--- @field completion? blink.cmp.CompletionConfigPartial
+--- @field fuzzy? blink.cmp.FuzzyConfigPartial
+--- @field sources? blink.cmp.SourceConfigPartial
+--- @field signature? blink.cmp.SignatureConfigPartial
+--- @field snippets? blink.cmp.SnippetsConfigPartial
+--- @field appearance? blink.cmp.AppearanceConfigPartial
+
+--- @class (exact) blink.cmp.CompletionConfigPartial : blink.cmp.CompletionConfig
+--- @field keyword? blink.cmp.CompletionKeywordConfigPartial
+--- @field trigger? blink.cmp.CompletionTriggerConfigPartial
+--- @field list? blink.cmp.CompletionListConfigPartial
+--- @field accept? blink.cmp.CompletionAcceptConfigPartial
+--- @field menu? blink.cmp.CompletionMenuConfigPartial
+--- @field documentation? blink.cmp.CompletionDocumentationConfigPartial
+--- @field ghost_text? blink.cmp.CompletionGhostTextConfigPartial
+
+--- @class (exact) blink.cmp.CompletionKeywordConfigPartial : blink.cmp.CompletionKeywordConfig, {}
+
+--- @class (exact) blink.cmp.CompletionTriggerConfigPartial : blink.cmp.CompletionTriggerConfig, {}
+
+--- @class (exact) blink.cmp.CompletionListConfigPartial : blink.cmp.CompletionListConfig, {}
+--- @field selection? blink.cmp.CompletionListSelectionConfigPartial
+--- @field cycle? blink.cmp.CompletionListCycleConfigPartial
+
+--- @class (exact) blink.cmp.CompletionListSelectionConfigPartial : blink.cmp.CompletionListSelectionConfig, {}
+
+--- @class (exact) blink.cmp.CompletionListCycleConfigPartial : blink.cmp.CompletionListCycleConfig, {}
+
+--- @class (exact) blink.cmp.CompletionAcceptConfigPartial : blink.cmp.CompletionAcceptConfig, {}
+--- @field auto_brackets? blink.cmp.AutoBracketsConfigPartial
+
+--- @class (exact) blink.cmp.AutoBracketsConfigPartial : blink.cmp.AutoBracketsConfig, {}
+--- @field kind_resolution? blink.cmp.AutoBracketResolutionConfigPartial Synchronously use the kind of the item to determine if brackets should be added
+--- @field semantic_token_resolution? blink.cmp.AutoBracketSemanticTokenResolutionConfigPartial Asynchronously use semantic token to determine if brackets should be added
+
+--- @class (exact) blink.cmp.AutoBracketResolutionConfigPartial : blink.cmp.AutoBracketResolutionConfig, {}
+
+--- @class (exact) blink.cmp.AutoBracketSemanticTokenResolutionConfigPartial : blink.cmp.AutoBracketSemanticTokenResolutionConfig, {}
+
+--- @class (exact) blink.cmp.CompletionMenuConfigPartial : blink.cmp.CompletionMenuConfig, {}
+--- @field order? blink.cmp.CompletionMenuOrderConfigPartial TODO: implement
+
+--- @class (exact) blink.cmp.CompletionMenuOrderConfigPartial : blink.cmp.CompletionMenuOrderConfig, {}
+
+--- @class (exact) blink.cmp.CompletionDocumentationConfigPartial : blink.cmp.CompletionDocumentationConfig, {}
+--- @field window? blink.cmp.CompletionDocumentationWindowConfigPartial
+
+--- @class (exact) blink.cmp.CompletionDocumentationWindowConfigPartial : blink.cmp.CompletionDocumentationWindowConfig, {}
+--- @field direction_priority? blink.cmp.CompletionDocumentationDirectionPriorityConfigPartial Which directions to show the window, for each of the possible menu window directions, falling back to the next direction when there's not enough space
+
+--- @class (exact) blink.cmp.CompletionDocumentationDirectionPriorityConfigPartial : blink.cmp.CompletionDocumentationDirectionPriorityConfig, {}
+
+--- @class (exact) blink.cmp.CompletionGhostTextConfigPartial : blink.cmp.CompletionGhostTextConfig, {}
+
+--- @class (exact) blink.cmp.FuzzyConfigPartial : blink.cmp.FuzzyConfig, {}
+--- @field prebuilt_binaries? blink.cmp.PrebuiltBinariesConfigPartial
+
+--- @class (exact) blink.cmp.PrebuiltBinariesConfigPartial : blink.cmp.PrebuiltBinariesConfig, {}
+
+--- @class blink.cmp.SourceConfigPartial : blink.cmp.SourceConfig, {}
+--- @field providers? table<string, blink.cmp.SourceProviderConfigPartial>
+
+--- @class blink.cmp.SourceProviderConfigPartial : blink.cmp.SourceProviderConfig, {}
+
+--- @class (exact) blink.cmp.SignatureConfigPartial : blink.cmp.SignatureConfig, {}
+--- @field trigger? blink.cmp.SignatureTriggerConfigPartial
+--- @field window? blink.cmp.SignatureWindowConfigPartial
+
+--- @class (exact) blink.cmp.SignatureTriggerConfigPartial : blink.cmp.SignatureTriggerConfig, {}
+
+--- @class (exact) blink.cmp.SignatureWindowConfigPartial : blink.cmp.SignatureWindowConfig, {}
+
+--- @class (exact) blink.cmp.SnippetsConfigPartial : blink.cmp.SnippetsConfig, {}
+
+--- @class (exact) blink.cmp.AppearanceConfigPartial : blink.cmp.AppearanceConfig, {}
diff --git a/lua/blink/cmp/config/utils.lua b/lua/blink/cmp/config/utils.lua
new file mode 100644
index 0000000..4cbc052
--- /dev/null
+++ b/lua/blink/cmp/config/utils.lua
@@ -0,0 +1,36 @@
+local utils = {}
+
+-- Code taken from @MariaSolOs in a indent-blankline.nvim PR:
+-- https://github.com/lukas-reineke/indent-blankline.nvim/pull/934/files#diff-09ebcaa8c75cd1e92d25640e377ab261cfecaf8351c9689173fd36c2d0c23d94R16
+-- Saves a whopping 20% compared to vim.validate (0.8ms -> 0.64ms)
+
+--- Use the faster validate version if available
+--- @param spec table<string, {[1]:any, [2]:function|string, [3]:string|true|nil}>
+--- NOTE: We disable some Lua diagnostics here since lua_ls isn't smart enough to
+--- realize that we're using an overloaded function.
+function utils._validate(spec)
+ if vim.fn.has('nvim-0.11') == 0 then return vim.validate(spec) end
+ for key, key_spec in pairs(spec) do
+ local message = type(key_spec[3]) == 'string' and key_spec[3] or nil --[[@as string?]]
+ local optional = type(key_spec[3]) == 'boolean' and key_spec[3] or nil --[[@as boolean?]]
+ ---@diagnostic disable-next-line:param-type-mismatch, redundant-parameter
+ vim.validate(key, key_spec[1], key_spec[2], optional, message)
+ end
+end
+
+--- @param path string The path to the field being validated
+--- @param tbl table The table to validate
+--- @param source table The original table that we're validating against
+--- @see vim.validate
+function utils.validate(path, tbl, source)
+ -- validate
+ local _, err = pcall(utils._validate, tbl)
+ if err then error(path .. '.' .. err) end
+
+ -- check for erroneous fields
+ for k, _ in pairs(source) do
+ if tbl[k] == nil then error(path .. '.' .. k .. ': unexpected field found in configuration') end
+ end
+end
+
+return utils