Skip to content

Commit

Permalink
feat: source should_show, windowing config/fixes, misc
Browse files Browse the repository at this point in the history
  • Loading branch information
Saghen committed Aug 2, 2024
1 parent a7ee523 commit 3d1c168
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 86 deletions.
File renamed without changes.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- Simple hackable codebase
- Updates on every keystroke (0.5-4ms non-blocking, single core)
- Typo resistant fuzzy with frecncy and proximity bonus
- Extensive LSP support ([tracker](./lsp-support-tracker.md))
- Extensive LSP support ([tracker](./LSP_TRACKER.md))
- Snippet support (including `friendly-snippets`)
- TODO: Cmdline support
- External sources support (including `nvim-cmp` compatibility layer)
Expand All @@ -19,8 +19,8 @@
```lua
{
'saghen/blink.cmp',
lazy = false, -- handled internally
version = "^1"
-- todo: should handle lazy loading internally
event = 'InsertEnter',
keys = {
map_cmp('')
},
Expand Down
7 changes: 1 addition & 6 deletions lua/blink/cmp/accept.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
local utils = {}

local function accept(item)
-- create an undo point
-- fixme: doesnt work
-- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-G>u', true, false, true), 'n', false)
-- vim.cmd('normal! i<C-G>u')
-- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-G>u', true, false, true), 'im', true)

local sources = require('blink.cmp.sources')
local fuzzy = require('blink.cmp.fuzzy')

Expand Down Expand Up @@ -47,6 +41,7 @@ local function accept(item)
-- LSPs can either include these in the initial response or require a resolve
-- These are used for things like auto-imports
-- todo: check capabilities to know ahead of time
-- todo: if the text edit above was before this text edit, we need to compensate
if item.additionalTextEdits ~= nil then
utils.apply_text_edits(item.client_id, item.additionalTextEdits)
else
Expand Down
2 changes: 1 addition & 1 deletion lua/blink/cmp/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ local config = {
autocomplete_north = { 'e', 'w', 'n', 's' },
autocomplete_south = { 'e', 'w', 's', 'n' },
},
auto_show = true,
auto_show = false,
delay_ms = 0,
debounce_ms = 100,
},
Expand Down
10 changes: 9 additions & 1 deletion lua/blink/cmp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,32 @@ cmp.setup = function(opts)
cmp.fuzzy = require('blink.cmp.fuzzy')
cmp.fuzzy.init_db(vim.fn.stdpath('data') .. '/blink/cmp/fuzzy.db')

cmp.trigger.listen_on_show(function(context) cmp.sources.completions(context) end)
local start_time = vim.loop.hrtime()
cmp.trigger.listen_on_show(function(context)
start_time = vim.loop.hrtime()
cmp.sources.completions(context)
end)
cmp.trigger.listen_on_hide(function()
cmp.sources.cancel_completions()
cmp.windows.autocomplete.close()
end)
cmp.sources.listen_on_completions(function(context, items)
local duration = vim.loop.hrtime() - start_time
print('cmp.sources.listen_on_completions duration: ' .. duration / 1000000 .. 'ms')
-- avoid adding 1-4ms to insertion latency by scheduling for later
vim.schedule(function()
local filtered_items = cmp.fuzzy.filter_items(require('blink.cmp.util').get_query(), items)
if #filtered_items > 0 then
cmp.windows.autocomplete.open_with_items(context, filtered_items)
print('cmp.windows.autocomplete.open_with_items duration: ' .. duration / 1000000 .. 'ms')
else
cmp.windows.autocomplete.close()
end
end)
end)
end

-- todo: dont default to cmp, use new hl groups
cmp.add_default_highlights = function()
--- @class Opts
--- @field name string
Expand Down
70 changes: 35 additions & 35 deletions lua/blink/cmp/sources/buffer.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- todo: nvim-cmp only updates the lines that got changed which is better
-- but this is *speeeeeed* and super simple. should add the better way
-- but this is *speeeeeed* and simple. should add the better way
-- but ensure it doesn't add too much complexity

local uv = vim.uv
Expand Down Expand Up @@ -62,6 +62,7 @@ end

--- Public API

--- @class Source
local buffer = {}

function buffer.completions(_, callback)
Expand All @@ -76,44 +77,43 @@ function buffer.completions(_, callback)
run_async(buf_text, transformed_callback)
-- too big so ignore
else
callback({})
transformed_callback({})
end
end

function buffer.should_show_completions(context, sources_responses)
local context_length = context.bounds.end_col - context.bounds.start_col
if context_length <= 3 then return false end
if sources_responses.lsp ~= nil and #sources_responses.lsp.items > 0 then return false end
return true
end

function buffer.filter_completions(context, sources_responses)
-- if sources_responses.buffer == nil then return sources_responses end
--
-- -- copy to avoid mutating the original
-- local copied_sources_responses = {}
-- for name, response in pairs(sources_responses) do
-- copied_sources_responses[name] = response
-- end
-- sources_responses = copied_sources_responses
--
-- -- don't show if a trigger character triggered this
-- -- todo: the idea here is that situations like `text.|` shouldn't show
-- -- the buffer completions since it's likely not helpful
-- if context.trigger_character ~= nil then
-- sources_responses.buffer.items = {}
-- return sources_responses
-- end
--
-- -- get all of the unique labels
-- local unique_words = {}
-- for name, response in pairs(sources_responses) do
-- if name ~= 'buffer' then
-- for _, item in ipairs(response.items) do
-- unique_words[item.label] = true
-- end
-- end
-- end
--
-- -- filter any buffer words that already have a source
-- local filtered_buffer_items = {}
-- for _, item in ipairs(sources_responses.buffer.items) do
-- if not unique_words[item.label] then table.insert(filtered_buffer_items, item) end
-- end
-- sources_responses.buffer.items = filtered_buffer_items
if sources_responses.buffer == nil then return sources_responses end

-- copy to avoid mutating the original
local copied_sources_responses = {}
for name, response in pairs(sources_responses) do
copied_sources_responses[name] = response
end
sources_responses = copied_sources_responses

-- get all of the unique labels
local unique_words = {}
for name, response in pairs(sources_responses) do
if name ~= 'buffer' then
for _, item in ipairs(response.items) do
unique_words[item.label] = true
end
end
end

-- filter any buffer words that already have a source
local filtered_buffer_items = {}
for _, item in ipairs(sources_responses.buffer.items) do
if not unique_words[item.label] then table.insert(filtered_buffer_items, item) end
end
sources_responses.buffer.items = filtered_buffer_items

return sources_responses
end
Expand Down
61 changes: 38 additions & 23 deletions lua/blink/cmp/sources/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ local sources = {
snippets = -1,
},

sources_items = {},
sources_responses = {},
current_context_id = -1,
on_completions_callback = function(_) end,
}

--- @return string[]
function sources.get_trigger_characters()
local blocked_trigger_characters = {}
for _, char in ipairs(config.trigger.blocked_trigger_characters) do
Expand All @@ -39,23 +40,24 @@ end

function sources.listen_on_completions(callback) sources.on_completions_callback = callback end

--- @param context ShowContext
function sources.completions(context)
-- a new context means we should refetch everything
local is_new_context = context.id ~= sources.current_context_id
sources.current_context_id = context.id

if is_new_context then sources.sources_items = {} end
if is_new_context then sources.sources_responses = {} end

for source_name, source in pairs(sources.registered) do
-- the source indicates we should refetch when this character is typed
local trigger_characters = source.get_trigger_characters ~= nil and source.get_trigger_characters() or {}
local trigger_character = context.trigger_character
and vim.tbl_contains(trigger_characters, context.trigger_character)
-- the source indicates the previous results were incomplete and should be refetched on the next trigger
local previous_incomplete = sources.sources_items[source_name] ~= nil
and sources.sources_items[source_name].isIncomplete
local previous_incomplete = sources.sources_responses[source_name] ~= nil
and sources.sources_responses[source_name].isIncomplete
-- check if we have no data and no calls are in flight
local no_data = sources.sources_items[source_name] == nil and sources.in_flight_id[source_name] == -1
local no_data = sources.sources_responses[source_name] == nil and sources.in_flight_id[source_name] == -1

-- if none of these are true, we can use the existing cached results
if is_new_context or trigger_character or previous_incomplete or no_data then
Expand All @@ -76,15 +78,13 @@ function sources.completions(context)
-- fixme: what if we refetch due to incomplete items or a trigger_character? the context trigger id wouldnt change
-- change so stale data would be returned if the source doesn't support cancellation
local cursor_column = vim.api.nvim_win_get_cursor(0)[2]
vim.schedule(function()
source.completions({ trigger = trigger_context }, function(items)
-- a new call was made or this one was cancelled
if sources.in_flight_id[source_name] ~= in_flight_id then return end
sources.in_flight_id[source_name] = -1

sources.add_source_completions(source_name, items, cursor_column)
if not sources.some_in_flight() then sources.send_completions(context) end
end)
source.completions({ trigger = trigger_context }, function(items)
-- a new call was made or this one was cancelled
if sources.in_flight_id[source_name] ~= in_flight_id then return end
sources.in_flight_id[source_name] = -1

sources.add_source_completions(source_name, items, cursor_column)
if not sources.some_in_flight() then sources.send_completions(context) end
end)
end
end
Expand All @@ -94,34 +94,43 @@ function sources.completions(context)
if not sources.some_in_flight() then sources.send_completions(context) end
end

function sources.add_source_completions(source_name, source_items, cursor_column)
for _, item in ipairs(source_items.items) do
--- @param source_name string
--- @param source_response CompletionResponse
--- @param cursor_column number
function sources.add_source_completions(source_name, source_response, cursor_column)
for _, item in ipairs(source_response.items) do
item.source = source_name
item.cursor_column = cursor_column
end

sources.sources_items[source_name] = source_items
sources.sources_responses[source_name] = source_response
end

--- @return boolean
function sources.some_in_flight()
for _, in_flight in pairs(sources.in_flight_id) do
if in_flight ~= -1 then return true end
end
return false
end

--- @param context ShowContext
function sources.send_completions(context)
local sources_items = sources.sources_items
local sources_responses = sources.sources_responses
-- apply source filters
for _, source in pairs(sources.registered) do
if source.filter_completions ~= nil then sources_items = source.filter_completions(context, sources_items) end
if source.filter_completions ~= nil then
sources_responses = source.filter_completions(context, sources_responses)
end
end

-- flatten the items
local flattened_items = {}
for source_name, response in pairs(sources.sources_items) do
for source_name, response in pairs(sources.sources_responses) do
local source = sources.registered[source_name]
if source.should_show == nil or source.should_show() then vim.list_extend(flattened_items, response.items) end
if source.should_show_completions == nil or source.should_show_completions(context, sources_responses) then
vim.list_extend(flattened_items, response.items)
end
end

sources.on_completions_callback(context, flattened_items)
Expand All @@ -134,10 +143,16 @@ function sources.cancel_completions()
end
end

--- @param item CompletionItem
--- @param callback fun(resolved_item: CompletionItem | nil)
--- @return fun(): nil Cancelation function
function sources.resolve(item, callback)
local item_source = sources.registered[item.source]
if item_source == nil or item_source.resolve == nil then return callback(item) end
item_source.resolve(item, callback)
if item_source == nil or item_source.resolve == nil then
callback(nil)
return function() end
end
return item_source.resolve(item, callback) or function() end
end

return sources
13 changes: 10 additions & 3 deletions lua/blink/cmp/sources/lsp.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--- @class Source
local lsp = {}

---@param capability string|table|nil Server capability (possibly nested
Expand Down Expand Up @@ -39,6 +40,7 @@ function lsp.get_clients_with_capability(capability, filter)
end

function lsp.completions(context, callback)
local start_time = vim.loop.hrtime()
-- no providers with completion support
if not lsp.has_capability('completionProvider') then return callback({ isIncomplete = false, items = {} }) end

Expand All @@ -54,6 +56,8 @@ function lsp.completions(context, callback)
-- request from each of the clients
-- todo: refactor
lsp.cancel_completions_func = vim.lsp.buf_request_all(0, 'textDocument/completion', params, function(result)
local duration = vim.loop.hrtime() - start_time
print('lsp.completions duration: ' .. duration / 1000000 .. 'ms')
local responses = {}
for client_id, response in pairs(result) do
-- todo: pass error upstream
Expand Down Expand Up @@ -103,15 +107,18 @@ function lsp.cancel_completions()
end
end

-- @return function Cancel function
function lsp.resolve(item, callback)
local client = vim.lsp.get_client_by_id(item.client_id)
if client == nil then return callback(item) end
if client == nil then
callback(item)
return
end

client.request('completionItem/resolve', item, function(error, result)
local _, request_id = client.request('completionItem/resolve', item, function(error, result)
if error or result == nil then callback(item) end
callback(result)
end)
if request_id ~= nil then return function() client.cancel_request(request_id) end end
end

return lsp
1 change: 1 addition & 0 deletions lua/blink/cmp/sources/snippets.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--- @class Source
local snippets = {}

function snippets.completions(_, callback)
Expand Down
20 changes: 15 additions & 5 deletions lua/blink/cmp/sources/types.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
--- @class TriggerContext
--- @field kind number
--- @field character string
---
--- @class CompletionContext : ShowContext
--- @field trigger TriggerContext | nil
---
--- @class CompletionResponse
--- @field isIncomplete boolean
--- @field items CompletionItem[]
---
--- @class Source
--- @field get_trigger_characters fun(): string[]
--- @field completions fun(context: ShowContext, callback: fun(items: lsp.CompletionItem[]))
--- @field filter_completions fun(context: ShowContext, items: CompletionItem[]): CompletionItem[]
--- @field completions fun(context: CompletionContext, callback: fun(response: CompletionResponse))
--- @field filter_completions fun(context: CompletionContext, source_responses: table<string, CompletionItem[]>): CompletionItem[]
--- @field cancel_completions fun()
--- @field should_show_completions fun(): boolean
---
--- @field resolve fun(item: lsp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil))
--- @field should_show_completions fun(context: CompletionContext, source_responses: table<string, CompletionResponse>): boolean
--- @field resolve fun(item: CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): ((fun(): nil) | nil)
1 change: 1 addition & 0 deletions lua/blink/cmp/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
--- @field score_offset number | nil
--- @field source string
--- @field cursor_column number
--- @field client_id number
Loading

0 comments on commit 3d1c168

Please sign in to comment.