diff --git a/docs/configuration/completion.md b/docs/configuration/completion.md
index 8e05a754..9f90e70e 100644
--- a/docs/configuration/completion.md
+++ b/docs/configuration/completion.md
@@ -57,33 +57,58 @@ TODO: Find a case where this actually fires : )
## List
-Manages the completion list and its behavior when selecting items. The most commonly changed option is `completion.list.selection`, which controls whether the list will automatically select the first item in the list, and whether selection shows a preview:
-
-To control the selection behavior per mode, pass a function to `completion.list.selection` that returns the selection mode:
+Manages the completion list and its behavior when selecting items. The most commonly changed option is `selection.preselect/auto_insert`, which controls whether the list will automatically select the first item in the list, and whether a "preview" will be inserted on selection.
+:::tabs
+== Preselect, Auto Insert (default)
```lua
-completion.list.selection = 'preselect'
--- or
-completion.list.selection = function(ctx)
- return ctx.mode == 'cmdline' and 'auto_insert' or 'preselect'
-end
+completion.list.selection = { preselect = true, auto_insert = true }
```
+Selects the first item automatically, and inserts a preview of the item on selection. The `cancel` keymap (default ``) will close the menu and undo the preview.
+
+
-:::tabs
== Preselect
+```lua
+completion.list.selection = { preselect = true, auto_insert = false }
+```
Selects the first item automatically
== Manual
+```lua
+completion.list.selection = { preselect = false, auto_insert = false }
+```
+
No item will be selected by default. You may use the `select_and_accept` keymap command to select the first item and accept it when there's no selection. The `accept` keymap command, on the other hand, will only trigger if an item is selected.
-== Auto Insert
-No item will be selected by default, and selecting an item will insert a "preview" of the item automatically. You may use the `select_and_accept` keymap command to select the first item and accept it when there's no selection. The `accept` keymap command, on the other hand, will only trigger if an item is selected.
+== Manual, Auto Insert
+```lua
+completion.list.selection = { preselect = false, auto_insert = true }
+```
+
+Selecting an item will insert a "preview" of the item automatically. You may use the `select_and_accept` keymap command to select the first item and accept it when there's no selection. The `accept` keymap command will only trigger if an item is selected. The `cancel` keymap (default ``) will close the menu and undo the preview.
:::
+To control the selection behavior per mode, pass a function to `selection.preselect/auto_insert`:
+
+```lua
+completion.list.selection = {
+ preselect = true,
+ auto_insert = true,
+
+ -- or a function
+ preselect = function(ctx)
+ return ctx.mode ~= 'cmdline' and not require('blink.cmp').snippet_active({ direction = 1 })
+ end,
+ -- auto_insert = function(ctx) return ctx.mode ~= 'cmdline' end,
+}
+```
+
+
## Accept
Manages the behavior when accepting an item in the completion menu.
diff --git a/docs/configuration/general.md b/docs/configuration/general.md
index 50ece7af..300dcf24 100644
--- a/docs/configuration/general.md
+++ b/docs/configuration/general.md
@@ -25,10 +25,10 @@ For more common configurations, see the [recipes](../recipes.md).
-- NOTE: some LSPs may add auto brackets themselves anyway
accept = { auto_brackets = { enabled = false }, },
- -- Insert completion item on selection, don't select by default
- list = { selection = 'auto_insert' },
- -- or set per mode
- list = { selection = function(ctx) return ctx.mode == 'cmdline' and 'auto_insert' or 'preselect' end },
+ -- Don't select by default, auto insert on selection
+ list = { selection = { preselect = false, auto_insert = true } },
+ -- or set either per mode via a function
+ list = { selection = { preselect = function(ctx) return ctx.mode ~= 'cmdline' end } },
menu = {
-- Don't automatically show the completion menu
diff --git a/docs/configuration/keymap.md b/docs/configuration/keymap.md
index 8f4ee55d..d82c5b86 100644
--- a/docs/configuration/keymap.md
+++ b/docs/configuration/keymap.md
@@ -41,13 +41,15 @@ keymap = {
- `show`: Shows the completion menu
- Optionally use `function(cmp) cmp.show({ providers = { 'snippets' } }) end` to show with a specific list of providers
- `hide`: Hides the completion menu
-- `cancel`: Reverts `completion.list.selection = 'auto_insert'` and hides the completion menu
+- `cancel`: Reverts `completion.list.selection.auto_insert` and hides the completion menu
- `accept`: Accepts the currently selected item
- Optionally pass an index to select a specific item in the list: `function(cmp) cmp.accept({ index = 1 }) end`
- Optionally pass a `callback` to run after the item is accepted: `function(cmp) cmp.accept({ callback = function() vim.api.nvim_feedkeys('\n', 'n', true) end }) end`
- `select_and_accept`: Accepts the currently selected item, or the first item if none are selected
- `select_prev`: Selects the previous item, cycling to the bottom of the list if at the top, if `completion.list.cycle.from_top == true`
+ - Optionally control the `auto_insert` property of `completion.list.selection`: `function(cmp) cmp.select_prev({ auto_insert = false }) end`
- `select_next`: Selects the next item, cycling to the top of the list if at the bottom, if `completion.list.cycle.from_bottom == true`
+ - Optionally control the `auto_insert` property of `completion.list.selection`: `function(cmp) cmp.select_next({ auto_insert = false }) end`
- `show_documentation`: Shows the documentation for the currently selected item
- `hide_documentation`: Hides the documentation
- `scroll_documentation_up`: Scrolls the documentation up by 4 lines
@@ -96,7 +98,7 @@ Set the preset to `none` to disable the presets
### `super-tab`
-You may want to use `completion.list.selection = "manual" | "auto_insert"`. You can also set it per mode: https://cmp.saghen.dev/configuration/completion.html#list
+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`. See more info in: https://cmp.saghen.dev/configuration/completion.html#list
```lua
[''] = { 'show', 'show_documentation', 'hide_documentation' },
@@ -123,7 +125,7 @@ You may want to use `completion.list.selection = "manual" | "auto_insert"`. You
### `enter`
-You may want to set `completion.list.selection = "manual" | "auto_insert"`. You can also set it per mode: https://cmp.saghen.dev/configuration/completion.html#list
+You may want to set `completion.list.selection.preselect = false`. See more info in: https://cmp.saghen.dev/configuration/completion.html#list
```lua
[''] = { 'show', 'show_documentation', 'hide_documentation' },
diff --git a/docs/configuration/reference.md b/docs/configuration/reference.md
index 1dd5dba8..b1be1a8f 100644
--- a/docs/configuration/reference.md
+++ b/docs/configuration/reference.md
@@ -93,18 +93,18 @@ completion.list = {
-- Maximum number of items to display
max_items = 200,
- -- Controls if completion items will be selected automatically,
- -- and whether selection automatically inserts
- selection = 'preselect',
- -- selection = function(ctx) return ctx.mode == 'cmdline' and 'auto_insert' or 'preselect' end,
-
- -- Controls how the completion items are selected
- -- 'preselect' will automatically select the first item in the completion list
- -- 'manual' will not select any item by default
- -- 'auto_insert' will not select any item by default, and insert the completion items automatically when selecting them
- --
- -- You may want to bind a key to the `cancel` command, which will undo the selection
- -- when using 'auto_insert'
+ selection = {
+ -- When `true`, will automatically select the first item in the completion list
+ preselect = true,
+ -- preselect = function(ctx) return ctx.mode ~= 'cmdline' end,
+
+ -- When `true`, inserts the completion item automatically when selecting it
+ -- You may want to bind a key to the `cancel` command (default ) when using this option,
+ -- which will both undo the selection and hide the completion menu
+ auto_insert = true,
+ -- auto_insert = function(ctx) return ctx.mode ~= 'cmdline' end
+ },
+
cycle = {
-- When `true`, calling `select_next` at the *bottom* of the completion list
-- will select the *first* completion item.
diff --git a/docs/recipes.md b/docs/recipes.md
index 55b34ffd..9b5cf3da 100644
--- a/docs/recipes.md
+++ b/docs/recipes.md
@@ -29,9 +29,10 @@ signature = { window = { border = 'single' } },
```lua
completion = {
list = {
- selection = function(ctx)
- return ctx.mode == 'cmdline' and 'auto_insert' or 'preselect'
- end
+ selection = {
+ preselect = function(ctx) return ctx.mode ~= 'cmdline' end,
+ auto_insert = function(ctx) return ctx.mode ~= 'cmdline' end
+ }
}
}
```
diff --git a/lua/blink/cmp/completion/init.lua b/lua/blink/cmp/completion/init.lua
index a9f75f13..49c6fc2c 100644
--- a/lua/blink/cmp/completion/init.lua
+++ b/lua/blink/cmp/completion/init.lua
@@ -78,7 +78,7 @@ function completion.setup()
-- run 'resolve' on the item ahead of time to avoid delays
-- when accepting the item or showing documentation
list.select_emitter:on(function(event)
- -- when selection == 'manual' | 'auto_insert', we still want to prefetch the first item
+ -- when selection.preselect == false, we still want to prefetch the first item
local item = event.item or list.items[1]
if item == nil then return end
require('blink.cmp.completion.prefetch')(event.context, event.item)
diff --git a/lua/blink/cmp/completion/list.lua b/lua/blink/cmp/completion/list.lua
index 50f04eaa..61fa2c32 100644
--- a/lua/blink/cmp/completion/list.lua
+++ b/lua/blink/cmp/completion/list.lua
@@ -1,30 +1,34 @@
--- Manages most of the state for the completion list such that downstream consumers can be mostly stateless
--- @class (exact) blink.cmp.CompletionList
--- @field config blink.cmp.CompletionListConfig
---- @field context? blink.cmp.Context
---- @field items blink.cmp.CompletionItem[]
---- @field selected_item_idx? number
---- @field preview_undo? { text_edit: lsp.TextEdit, cursor: integer[]?}
--- @field show_emitter blink.cmp.EventEmitter
--- @field hide_emitter blink.cmp.EventEmitter
--- @field select_emitter blink.cmp.EventEmitter
--- @field accept_emitter blink.cmp.EventEmitter
---
+--- @field context? blink.cmp.Context
+--- @field items blink.cmp.CompletionItem[]
+--- @field selected_item_idx? number
+--- @field preview_undo? { text_edit: lsp.TextEdit, cursor: integer[]?}
+---
--- @field show fun(context: blink.cmp.Context, items: table)
--- @field fuzzy fun(context: blink.cmp.Context, items: table): blink.cmp.CompletionItem[]
--- @field hide fun()
---
--- @field get_selected_item fun(): blink.cmp.CompletionItem?
---- @field get_selection_mode fun(context: blink.cmp.Context): blink.cmp.CompletionListSelection
---- @field select fun(idx?: number, opts?: { undo_preview?: boolean, is_explicit_selection?: boolean })
---- @field select_next fun()
---- @field select_prev fun()
---- @field get_item_idx_in_list fun(item?: blink.cmp.CompletionItem): number
+--- @field get_selection_mode fun(context: blink.cmp.Context): { preselect: boolean, auto_insert: boolean }
+--- @field get_item_idx_in_list fun(item?: blink.cmp.CompletionItem): number?
+--- @field select fun(idx?: number, opts?: { auto_insert?: boolean, undo_preview?: boolean, is_explicit_selection?: boolean })
+--- @field select_next fun(opts?: blink.cmp.CompletionListSelectOpts)
+--- @field select_prev fun(opts?: blink.cmp.CompletionListSelectOpts)
---
--- @field undo_preview fun()
--- @field apply_preview fun(item: blink.cmp.CompletionItem)
--- @field accept fun(opts?: blink.cmp.CompletionListAcceptOpts): boolean Applies the currently selected item, returning true if it succeeded
+--- @class blink.cmp.CompletionListSelectOpts
+--- @field auto_insert? boolean When `true`, inserts the completion item automatically when selecting it
+
--- @class blink.cmp.CompletionListSelectAndAcceptOpts
--- @field callback? fun() Called after the item is accepted
@@ -58,7 +62,6 @@ local list = {
config = require('blink.cmp.config').completion.list,
context = nil,
items = {},
- selection_mode = nil,
is_explicitly_selected = false,
preview_undo = nil,
}
@@ -84,7 +87,6 @@ function list.show(context, items_by_source)
-- update the context/list and emit
list.context = context
list.items = list.fuzzy(context, items_by_source)
- list.selection_mode = list.get_selection_mode(list.context)
if #list.items == 0 then
list.hide_emitter:emit({ context = context })
@@ -95,13 +97,13 @@ function list.show(context, items_by_source)
-- maintain the selection if the user selected an item
local previous_item_idx = list.get_item_idx_in_list(previous_selected_item)
if list.is_explicitly_selected and previous_item_idx ~= nil and previous_item_idx <= 10 then
- list.select(previous_item_idx, { undo_preview = false })
+ list.select(previous_item_idx, { auto_insert = false, undo_preview = false })
-- otherwise, use the default selection
else
list.select(
- list.selection_mode == 'preselect' and 1 or nil,
- { undo_preview = false, is_explicit_selection = false }
+ list.get_selection_mode(list.context).preselect and 1 or nil,
+ { auto_insert = false, undo_preview = false, is_explicit_selection = false }
)
end
end
@@ -130,19 +132,33 @@ function list.get_selected_item() return list.items[list.selected_item_idx] end
function list.get_selection_mode(context)
assert(context ~= nil, 'Context must be set before getting selection mode')
- if type(list.config.selection) == 'function' then return list.config.selection(context) end
- --- @diagnostic disable-next-line: return-type-mismatch
- return list.config.selection
+
+ local preselect = list.config.selection.preselect
+ if type(preselect) == 'function' then preselect = preselect(context) end
+ --- @cast preselect boolean
+
+ local auto_insert = list.config.selection.auto_insert
+ if type(auto_insert) == 'function' then auto_insert = auto_insert(context) end
+ --- @cast auto_insert boolean
+
+ return { preselect = preselect, auto_insert = auto_insert }
+end
+
+function list.get_item_idx_in_list(item)
+ if item == nil then return end
+ return require('blink.cmp.lib.utils').find_idx(list.items, function(i) return i.label == item.label end)
end
function list.select(idx, opts)
opts = opts or {}
local item = list.items[idx]
+ local auto_insert = opts.auto_insert
+ if auto_insert == nil then auto_insert = list.get_selection_mode(list.context).auto_insert end
+
require('blink.cmp.completion.trigger').suppress_events_for_callback(function()
- -- default to undoing the preview
if opts.undo_preview ~= false then list.undo_preview() end
- if list.selection_mode == 'auto_insert' and item then list.apply_preview(item) end
+ if auto_insert and item ~= nil then list.apply_preview(item) end
end)
--- @diagnostic disable-next-line: assign-type-mismatch
@@ -151,11 +167,11 @@ function list.select(idx, opts)
list.select_emitter:emit({ idx = idx, item = item, items = list.items, context = list.context })
end
-function list.select_next()
- if #list.items == 0 then return end
+function list.select_next(opts)
+ if #list.items == 0 or list.context == nil then return end
-- haven't selected anything yet, select the first item
- if list.selected_item_idx == nil then return list.select(1) end
+ if list.selected_item_idx == nil then return list.select(1, opts) end
-- end of the list
if list.selected_item_idx == #list.items then
@@ -163,21 +179,21 @@ function list.select_next()
if not list.config.cycle.from_bottom then return end
-- preselect is not enabled, we go back to no selection
- if list.selection_mode ~= 'preselect' then return list.select(nil) end
+ if not list.get_selection_mode(list.context).preselect then return list.select(nil, opts) end
-- otherwise, we cycle around
- return list.select(1)
+ return list.select(1, opts)
end
-- typical case, select the next item
- list.select(list.selected_item_idx + 1)
+ list.select(list.selected_item_idx + 1, opts)
end
-function list.select_prev()
- if #list.items == 0 then return end
+function list.select_prev(opts)
+ if #list.items == 0 or list.context == nil then return end
-- haven't selected anything yet, select the last item
- if list.selected_item_idx == nil then return list.select(#list.items) end
+ if list.selected_item_idx == nil then return list.select(#list.items, opts) end
-- start of the list
if list.selected_item_idx == 1 then
@@ -185,19 +201,14 @@ function list.select_prev()
if not list.config.cycle.from_top then return end
-- auto_insert is enabled, we go back to no selection
- if list.selection_mode == 'auto_insert' then return list.select(nil) end
+ if list.get_selection_mode(list.context).auto_insert then return list.select(nil, opts) end
-- otherwise, we cycle around
- return list.select(#list.items)
+ return list.select(#list.items, opts)
end
-- typical case, select the previous item
- list.select(list.selected_item_idx - 1)
-end
-
-function list.get_item_idx_in_list(item)
- if item == nil then return end
- return require('blink.cmp.lib.utils').find_idx(list.items, function(i) return i.label == item.label end)
+ list.select(list.selected_item_idx - 1, opts)
end
---------- Preview ----------
diff --git a/lua/blink/cmp/config/completion/list.lua b/lua/blink/cmp/config/completion/list.lua
index c49c831f..c0c5690c 100644
--- a/lua/blink/cmp/config/completion/list.lua
+++ b/lua/blink/cmp/config/completion/list.lua
@@ -1,12 +1,11 @@
--- @class (exact) blink.cmp.CompletionListConfig
--- @field max_items number Maximum number of items to display
---- @field selection blink.cmp.CompletionListSelection | fun(ctx: blink.cmp.Context): blink.cmp.CompletionListSelection Controls if completion items will be selected automatically, and whether selection automatically inserts
+--- @field selection blink.cmp.CompletionListSelectionConfig
--- @field cycle blink.cmp.CompletionListCycleConfig
---- @alias blink.cmp.CompletionListSelection
---- | 'preselect' Select the first item in the completion list
---- | 'manual' Don't select any item by default
---- | 'auto_insert' Don't select any item by default, and insert the completion items automatically when selecting them. You may want to bind a key to the `cancel` command when using this option, which will undo the selection and hide the completion menu
+--- @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 ) 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.
@@ -17,7 +16,10 @@ local list = {
--- @type blink.cmp.CompletionListConfig
default = {
max_items = 200,
- selection = 'preselect',
+ selection = {
+ preselect = true,
+ auto_insert = true,
+ },
cycle = {
from_bottom = true,
from_top = true,
@@ -26,19 +28,23 @@ local list = {
}
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,
- function()
- return vim.tbl_contains({ 'preselect', 'manual', 'auto_insert' }, config.selection)
- or type(config.selection) == 'function'
- end,
- 'one of: preselect, manual, auto_insert',
- },
+ 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' },
diff --git a/lua/blink/cmp/config/keymap.lua b/lua/blink/cmp/config/keymap.lua
index 5cbc1e3f..86d74ebb 100644
--- a/lua/blink/cmp/config/keymap.lua
+++ b/lua/blink/cmp/config/keymap.lua
@@ -36,7 +36,7 @@
--- ```
--- | 'default'
--- Mappings similar to VSCode.
---- You may want to set `completion.trigger.show_in_snippet = false` or use `completion.list.selection = "manual" | "auto_insert"` when using this mapping:
+--- 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
--- {
--- [''] = { 'show', 'show_documentation', 'hide_documentation' },
@@ -63,7 +63,7 @@
--- ```
--- | 'super-tab'
--- Similar to 'super-tab' but with `enter` to accept
---- You may want to set `completion.list.selection = "manual" | "auto_insert"` when using this keymap:
+--- You may want to set `completion.list.selection.preselect = false` when using this keymap:
--- ```lua
--- {
--- [''] = { 'show', 'show_documentation', 'hide_documentation' },
diff --git a/lua/blink/cmp/config/types_partial.lua b/lua/blink/cmp/config/types_partial.lua
index d597183d..1f525d14 100644
--- a/lua/blink/cmp/config/types_partial.lua
+++ b/lua/blink/cmp/config/types_partial.lua
@@ -22,8 +22,11 @@
--- @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, {}
diff --git a/lua/blink/cmp/init.lua b/lua/blink/cmp/init.lua
index 68d44e9e..2df5892f 100644
--- a/lua/blink/cmp/init.lua
+++ b/lua/blink/cmp/init.lua
@@ -131,16 +131,18 @@ function cmp.select_and_accept(opts)
end
--- Select the previous completion item
-function cmp.select_prev()
+--- @param opts? blink.cmp.CompletionListSelectOpts
+function cmp.select_prev(opts)
if not cmp.is_visible() then return end
- vim.schedule(function() require('blink.cmp.completion.list').select_prev() end)
+ vim.schedule(function() require('blink.cmp.completion.list').select_prev(opts) end)
return true
end
--- Select the next completion item
-function cmp.select_next()
+--- @param opts? blink.cmp.CompletionListSelectOpts
+function cmp.select_next(opts)
if not cmp.is_visible() then return end
- vim.schedule(function() require('blink.cmp.completion.list').select_next() end)
+ vim.schedule(function() require('blink.cmp.completion.list').select_next(opts) end)
return true
end
diff --git a/lua/blink/cmp/lib/utils.lua b/lua/blink/cmp/lib/utils.lua
index b3ac2bf6..84bcc3db 100644
--- a/lua/blink/cmp/lib/utils.lua
+++ b/lua/blink/cmp/lib/utils.lua
@@ -73,7 +73,7 @@ end
--- @generic T
--- @param arr T[]
--- @param val T
---- @return number | nil
+--- @return number?
function utils.index_of(arr, val)
for idx, v in ipairs(arr) do
if v == val then return idx end
@@ -85,7 +85,7 @@ end
--- @generic T
--- @param arr T[]
--- @param predicate fun(item: T): boolean
---- @return T | nil
+--- @return number?
function utils.find_idx(arr, predicate)
for idx, v in ipairs(arr) do
if predicate(v) then return idx end