Skip to content

Commit

Permalink
feat: terminal mode completions
Browse files Browse the repository at this point in the history
Remove debug line

Use chansend() to set prompt text

Add configuration options for terminal sources and keymaps

Make a note about terminal completions in README (and fix a broken link)
  • Loading branch information
wurli authored and Saghen committed Dec 19, 2024
1 parent 4ac2c27 commit 06e6b4f
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 30 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
- Auto-bracket support based on semantic tokens
- Signature help (experimental, opt-in)
- Command line completion
- [WIP Terminal shell completion](/~https://github.com/Saghen/blink.cmp/pull/665)
- Terminal completion (although no source for shell completions exists yet, contributions welcome!)
- [Comparison with nvim-cmp](https://cmp.saghen.dev/#compared-to-nvim-cmp)

## Installation
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
- Auto-bracket support based on semantic tokens
- Signature help (experimental, opt-in)
- Command line completion
- [WIP Terminal shell completion](/~https://github.com/Saghen/blink.cmp/pull/665)
- Terminal completion (although no source for shell completions exists yet, contributions welcome!)
- [Comparison with nvim-cmp](https://cmp.saghen.dev/#compared-to-nvim-cmp)

## Special Thanks
Expand Down
2 changes: 1 addition & 1 deletion lua/blink/cmp/completion/accept/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ local function accept(ctx, item, callback)
table.insert(all_text_edits, item.textEdit)
text_edits_lib.apply(all_text_edits)

ctx.set_cursor(new_cursor)
if ctx.get_mode() ~= 'term' then ctx.set_cursor(new_cursor) end
end

-- Let the source execute the item itself
Expand Down
6 changes: 5 additions & 1 deletion lua/blink/cmp/completion/trigger/context.lua
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ function context:within_query_bounds(cursor)
return row == bounds.line_number and col >= bounds.start_col and col < (bounds.start_col + bounds.length)
end

function context.get_mode() return vim.api.nvim_get_mode().mode == 'c' and 'cmdline' or 'default' end
function context.get_mode()
local mode = vim.api.nvim_get_mode().mode
return (mode == 'c' and 'cmdline') or (mode == 't' and 'term') or 'default'
end

function context.get_cursor()
return context.get_mode() == 'cmdline' and { 1, vim.fn.getcmdpos() - 1 } or vim.api.nvim_win_get_cursor(0)
Expand All @@ -106,6 +109,7 @@ function context.get_line(num)
return vim.fn.getcmdline()
end

-- This method works for normal buffers and the terminal prompt
if num == nil then num = context.get_cursor()[1] - 1 end
return vim.api.nvim_buf_get_lines(0, num, num + 1, false)[1]
end
Expand Down
40 changes: 28 additions & 12 deletions lua/blink/cmp/completion/trigger/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
--- @class blink.cmp.CompletionTrigger
--- @field buffer_events blink.cmp.BufferEvents
--- @field cmdline_events blink.cmp.CmdlineEvents
--- @field term_events blink.cmp.TermEvents
--- @field current_context_id number
--- @field context? blink.cmp.Context
--- @field show_emitter blink.cmp.EventEmitter<{ context: blink.cmp.Context }>
Expand Down Expand Up @@ -48,22 +49,24 @@ local function on_char_added(char, is_ignored)
if is_ignored then
if trigger.context ~= nil then trigger.show({ send_upstream = false, trigger_kind = 'keyword' }) end

-- character forces a trigger according to the sources, create a fresh context
-- character forces a trigger according to the sources, create a fresh context
elseif trigger.is_trigger_character(char) and (config.show_on_trigger_character or trigger.context ~= nil) then
trigger.context = nil
trigger.show({ trigger_kind = 'trigger_character', trigger_character = char })

-- character is part of a keyword
-- character is part of a keyword
elseif fuzzy.is_keyword_character(char) and (config.show_on_keyword or trigger.context ~= nil) then
trigger.show({ trigger_kind = 'keyword' })

-- nothing matches so hide
-- nothing matches so hide
else
trigger.hide()
end
end

local function on_cursor_moved(event, is_ignored)
local is_enter_event = event == 'InsertEnter' or event == 'TermEnter'

local cursor = context.get_cursor()
local cursor_col = cursor[2]

Expand Down Expand Up @@ -91,32 +94,32 @@ local function on_cursor_moved(event, is_ignored)
-- Reproducible with `example.|a` and pressing `a`, should not show the menu
local insert_enter_on_trigger_character = config.show_on_trigger_character
and config.show_on_insert_on_trigger_character
and event == 'InsertEnter'
and is_enter_event
and trigger.is_trigger_character(char_under_cursor, true)

-- check if we're still within the bounds of the query used for the context
if trigger.context ~= nil and trigger.context:within_query_bounds(cursor) then
trigger.show({ trigger_kind = 'keyword' })

-- check if we've entered insert mode on a trigger character
-- or if we've moved onto a trigger character while open
-- check if we've entered insert mode on a trigger character
-- or if we've moved onto a trigger character while open
elseif
insert_enter_on_trigger_character
or (is_on_trigger_for_show and trigger.context ~= nil and trigger.context.trigger.kind ~= 'prefetch')
then
trigger.context = nil
trigger.show({ trigger_kind = 'trigger_character', trigger_character = char_under_cursor })

-- show if we currently have a context, and we've moved outside of it's bounds by 1 char
-- show if we currently have a context, and we've moved outside of it's bounds by 1 char
elseif is_keyword and trigger.context ~= nil and cursor_col == trigger.context.bounds.start_col - 1 then
trigger.context = nil
trigger.show({ trigger_kind = 'keyword' })

-- prefetch completions without opening window on InsertEnter
elseif event == 'InsertEnter' and config.prefetch_on_insert then
-- prefetch completions without opening window on InsertEnter
elseif is_enter_event and config.prefetch_on_insert then
trigger.show({ trigger_kind = 'prefetch' })

-- otherwise hide
-- otherwise hide
else
trigger.hide()
end
Expand All @@ -128,6 +131,7 @@ function trigger.activate()
has_context = function() return trigger.context ~= nil end,
show_in_snippet = config.show_in_snippet,
})

trigger.buffer_events:listen({
on_char_added = on_char_added,
on_cursor_moved = on_cursor_moved,
Expand All @@ -140,6 +144,14 @@ function trigger.activate()
on_cursor_moved = on_cursor_moved,
on_leave = function() trigger.hide() end,
})

trigger.term_events = require('blink.cmp.lib.term_events').new({
has_context = function() return trigger.context ~= nil end,
})
trigger.term_events:listen({
on_char_added = on_char_added,
on_term_leave = function() trigger.hide() end,
})
end

function trigger.resubscribe()
Expand Down Expand Up @@ -168,9 +180,13 @@ end

--- Suppresses on_hide and on_show events for the duration of the callback
function trigger.suppress_events_for_callback(cb)
local mode = vim.api.nvim_get_mode().mode == 'c' and 'cmdline' or 'default'
local mode = vim.api.nvim_get_mode().mode
mode = (vim.api.nvim_get_mode().mode == 'c' and 'cmdline') or (mode == 't' and 'term') or 'default'

local events = (mode == 'default' and trigger.buffer_events)
or (mode == 'term' and trigger.term_events)
or trigger.cmdline_events

local events = mode == 'default' and trigger.buffer_events or trigger.cmdline_events
if not events then return cb() end

events:suppress_events_for_callback(cb)
Expand Down
11 changes: 9 additions & 2 deletions lua/blink/cmp/config/keymap.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
--- @alias blink.cmp.KeymapCommand

--- | 'fallback' Fallback to the built-in behavior
--- | 'show' Show the completion window
--- | 'show_and_insert' Show the completion window and select the first item
Expand Down Expand Up @@ -109,6 +110,11 @@
--- cmdline = {
--- preset = 'cmdline',
--- }
---
--- -- optionally, define different keymaps for Neovim's built-in terminal
--- term = {
--- preset = 'term',
--- }
--- }
--- ```
---
Expand All @@ -119,6 +125,7 @@

--- @class (exact) blink.cmp.KeymapConfig : blink.cmp.BaseKeymapConfig
--- @field cmdline? blink.cmp.BaseKeymapConfig Optionally, define a separate keymap for cmdline
--- @field term? blink.cmp.BaseKeymapConfig Optionally, define a separate keymap for cmdline

local keymap = {
--- @type blink.cmp.KeymapConfig
Expand Down Expand Up @@ -152,8 +159,8 @@ function keymap.validate(config)

local validation_schema = {}
for key, value in pairs(config) do
-- nested cmdline keymap
if key == 'cmdline' then
-- nested cmdline/term keymap
if key == 'cmdline' or key == 'term' then
keymap.validate(value)

-- preset
Expand Down
10 changes: 10 additions & 0 deletions lua/blink/cmp/config/sources.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
--- @field default string[] | fun(): string[]
--- @field per_filetype table<string, string[] | fun(): string[]>
--- @field cmdline string[] | fun(): string[]
--- @field term 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
Expand Down Expand Up @@ -53,6 +54,7 @@ local sources = {
if type == ':' or type == '@' then return { 'cmdline' } end
return {}
end,
term = { 'buffer', 'path' },

transform_items = function(_, items) return items end,
min_keyword_length = 0,
Expand Down Expand Up @@ -101,6 +103,13 @@ local sources = {
name = 'Omni',
module = 'blink.cmp.sources.omni',
},
-- NOTE: in future we may want a built-in terminal source. For now
-- the infrastructure exists, e.g. so community terminal sources can be
-- added, but this functionality is not baked into blink.cmp.
-- term = {
-- name = 'term',
-- module = 'blink.cmp.sources.term',
-- },
},
},
}
Expand All @@ -115,6 +124,7 @@ function sources.validate(config)
default = { config.default, { 'function', 'table' } },
per_filetype = { config.per_filetype, 'table' },
cmdline = { config.cmdline, { 'function', 'table' } },
term = { config.term, { 'function', 'table' } },

transform_items = { config.transform_items, 'function' },
min_keyword_length = { config.min_keyword_length, { 'number', 'function' } },
Expand Down
38 changes: 35 additions & 3 deletions lua/blink/cmp/keymap/apply.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ function apply.keymap_to_current_buffer(keys_to_commands)
if command == 'fallback' then
return fallback()

-- run user defined functions
-- run user defined functions
elseif type(command) == 'function' then
if command(require('blink.cmp')) then return end

-- otherwise, run the built-in command
-- otherwise, run the built-in command
elseif require('blink.cmp')[command]() then
return
end
Expand Down Expand Up @@ -70,6 +70,38 @@ function apply.keymap_to_current_buffer(keys_to_commands)
end
end

function apply.term_keymaps(keys_to_commands)
-- skip if we've already applied the keymaps
for _, mapping in ipairs(vim.api.nvim_buf_get_keymap(0, 't')) do
if mapping.desc == 'blink.cmp' then return end
end

-- terminal mode: uses insert commands only
for key, commands in pairs(keys_to_commands) do
if #commands == 0 then goto continue end

local fallback = require('blink.cmp.keymap.fallback').wrap('i', key)
apply.set('t', key, function()
for _, command in ipairs(commands) do
-- special case for fallback
if command == 'fallback' then
return fallback()

-- run user defined functions
elseif type(command) == 'function' then
if command(require('blink.cmp')) then return end

-- otherwise, run the built-in command
elseif require('blink.cmp')[command]() then
return
end
end
end)

::continue::
end
end

function apply.cmdline_keymaps(keys_to_commands)
-- cmdline mode: uses only insert commands
for key, commands in pairs(keys_to_commands) do
Expand Down Expand Up @@ -106,7 +138,7 @@ end
--- @param key string
--- @param callback fun(): string | nil
function apply.set(mode, key, callback)
if mode == 'c' then
if mode == 'c' or mode == 't' then
vim.api.nvim_set_keymap(mode, key, '', {
callback = callback,
expr = true,
Expand Down
8 changes: 8 additions & 0 deletions lua/blink/cmp/keymap/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ function keymap.setup()
local cmdline_mappings = keymap.get_mappings(config.keymap.cmdline or config.keymap)
require('blink.cmp.keymap.apply').cmdline_keymaps(cmdline_mappings)
end


-- Apply term keymaps
local term_sources = require('blink.cmp.config').sources.term
if type(term_sources) ~= 'table' or #term_sources > 0 then
local term_mappings = keymap.get_mappings(config.keymap.term or config.keymap)
require('blink.cmp.keymap.apply').term_keymaps(term_mappings)
end
end

return keymap
Loading

0 comments on commit 06e6b4f

Please sign in to comment.