Skip to content

Commit 3d1c168

Browse files
committed
feat: source should_show, windowing config/fixes, misc
1 parent a7ee523 commit 3d1c168

14 files changed

+148
-86
lines changed
File renamed without changes.

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- Simple hackable codebase
88
- Updates on every keystroke (0.5-4ms non-blocking, single core)
99
- Typo resistant fuzzy with frecncy and proximity bonus
10-
- Extensive LSP support ([tracker](./lsp-support-tracker.md))
10+
- Extensive LSP support ([tracker](./LSP_TRACKER.md))
1111
- Snippet support (including `friendly-snippets`)
1212
- TODO: Cmdline support
1313
- External sources support (including `nvim-cmp` compatibility layer)
@@ -19,8 +19,8 @@
1919
```lua
2020
{
2121
'saghen/blink.cmp',
22-
lazy = false, -- handled internally
23-
version = "^1"
22+
-- todo: should handle lazy loading internally
23+
event = 'InsertEnter',
2424
keys = {
2525
map_cmp('')
2626
},

lua/blink/cmp/accept.lua

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
local utils = {}
22

33
local function accept(item)
4-
-- create an undo point
5-
-- fixme: doesnt work
6-
-- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-G>u', true, false, true), 'n', false)
7-
-- vim.cmd('normal! i<C-G>u')
8-
-- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-G>u', true, false, true), 'im', true)
9-
104
local sources = require('blink.cmp.sources')
115
local fuzzy = require('blink.cmp.fuzzy')
126

@@ -47,6 +41,7 @@ local function accept(item)
4741
-- LSPs can either include these in the initial response or require a resolve
4842
-- These are used for things like auto-imports
4943
-- todo: check capabilities to know ahead of time
44+
-- todo: if the text edit above was before this text edit, we need to compensate
5045
if item.additionalTextEdits ~= nil then
5146
utils.apply_text_edits(item.client_id, item.additionalTextEdits)
5247
else

lua/blink/cmp/config.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ local config = {
9797
autocomplete_north = { 'e', 'w', 'n', 's' },
9898
autocomplete_south = { 'e', 'w', 's', 'n' },
9999
},
100-
auto_show = true,
100+
auto_show = false,
101101
delay_ms = 0,
102102
debounce_ms = 100,
103103
},

lua/blink/cmp/init.lua

+9-1
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,32 @@ cmp.setup = function(opts)
2828
cmp.fuzzy = require('blink.cmp.fuzzy')
2929
cmp.fuzzy.init_db(vim.fn.stdpath('data') .. '/blink/cmp/fuzzy.db')
3030

31-
cmp.trigger.listen_on_show(function(context) cmp.sources.completions(context) end)
31+
local start_time = vim.loop.hrtime()
32+
cmp.trigger.listen_on_show(function(context)
33+
start_time = vim.loop.hrtime()
34+
cmp.sources.completions(context)
35+
end)
3236
cmp.trigger.listen_on_hide(function()
3337
cmp.sources.cancel_completions()
3438
cmp.windows.autocomplete.close()
3539
end)
3640
cmp.sources.listen_on_completions(function(context, items)
41+
local duration = vim.loop.hrtime() - start_time
42+
print('cmp.sources.listen_on_completions duration: ' .. duration / 1000000 .. 'ms')
3743
-- avoid adding 1-4ms to insertion latency by scheduling for later
3844
vim.schedule(function()
3945
local filtered_items = cmp.fuzzy.filter_items(require('blink.cmp.util').get_query(), items)
4046
if #filtered_items > 0 then
4147
cmp.windows.autocomplete.open_with_items(context, filtered_items)
48+
print('cmp.windows.autocomplete.open_with_items duration: ' .. duration / 1000000 .. 'ms')
4249
else
4350
cmp.windows.autocomplete.close()
4451
end
4552
end)
4653
end)
4754
end
4855

56+
-- todo: dont default to cmp, use new hl groups
4957
cmp.add_default_highlights = function()
5058
--- @class Opts
5159
--- @field name string

lua/blink/cmp/sources/buffer.lua

+35-35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-- todo: nvim-cmp only updates the lines that got changed which is better
2-
-- but this is *speeeeeed* and super simple. should add the better way
2+
-- but this is *speeeeeed* and simple. should add the better way
33
-- but ensure it doesn't add too much complexity
44

55
local uv = vim.uv
@@ -62,6 +62,7 @@ end
6262

6363
--- Public API
6464

65+
--- @class Source
6566
local buffer = {}
6667

6768
function buffer.completions(_, callback)
@@ -76,44 +77,43 @@ function buffer.completions(_, callback)
7677
run_async(buf_text, transformed_callback)
7778
-- too big so ignore
7879
else
79-
callback({})
80+
transformed_callback({})
8081
end
8182
end
8283

84+
function buffer.should_show_completions(context, sources_responses)
85+
local context_length = context.bounds.end_col - context.bounds.start_col
86+
if context_length <= 3 then return false end
87+
if sources_responses.lsp ~= nil and #sources_responses.lsp.items > 0 then return false end
88+
return true
89+
end
90+
8391
function buffer.filter_completions(context, sources_responses)
84-
-- if sources_responses.buffer == nil then return sources_responses end
85-
--
86-
-- -- copy to avoid mutating the original
87-
-- local copied_sources_responses = {}
88-
-- for name, response in pairs(sources_responses) do
89-
-- copied_sources_responses[name] = response
90-
-- end
91-
-- sources_responses = copied_sources_responses
92-
--
93-
-- -- don't show if a trigger character triggered this
94-
-- -- todo: the idea here is that situations like `text.|` shouldn't show
95-
-- -- the buffer completions since it's likely not helpful
96-
-- if context.trigger_character ~= nil then
97-
-- sources_responses.buffer.items = {}
98-
-- return sources_responses
99-
-- end
100-
--
101-
-- -- get all of the unique labels
102-
-- local unique_words = {}
103-
-- for name, response in pairs(sources_responses) do
104-
-- if name ~= 'buffer' then
105-
-- for _, item in ipairs(response.items) do
106-
-- unique_words[item.label] = true
107-
-- end
108-
-- end
109-
-- end
110-
--
111-
-- -- filter any buffer words that already have a source
112-
-- local filtered_buffer_items = {}
113-
-- for _, item in ipairs(sources_responses.buffer.items) do
114-
-- if not unique_words[item.label] then table.insert(filtered_buffer_items, item) end
115-
-- end
116-
-- sources_responses.buffer.items = filtered_buffer_items
92+
if sources_responses.buffer == nil then return sources_responses end
93+
94+
-- copy to avoid mutating the original
95+
local copied_sources_responses = {}
96+
for name, response in pairs(sources_responses) do
97+
copied_sources_responses[name] = response
98+
end
99+
sources_responses = copied_sources_responses
100+
101+
-- get all of the unique labels
102+
local unique_words = {}
103+
for name, response in pairs(sources_responses) do
104+
if name ~= 'buffer' then
105+
for _, item in ipairs(response.items) do
106+
unique_words[item.label] = true
107+
end
108+
end
109+
end
110+
111+
-- filter any buffer words that already have a source
112+
local filtered_buffer_items = {}
113+
for _, item in ipairs(sources_responses.buffer.items) do
114+
if not unique_words[item.label] then table.insert(filtered_buffer_items, item) end
115+
end
116+
sources_responses.buffer.items = filtered_buffer_items
117117

118118
return sources_responses
119119
end

lua/blink/cmp/sources/init.lua

+38-23
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ local sources = {
1414
snippets = -1,
1515
},
1616

17-
sources_items = {},
17+
sources_responses = {},
1818
current_context_id = -1,
1919
on_completions_callback = function(_) end,
2020
}
2121

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

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

43+
--- @param context ShowContext
4244
function sources.completions(context)
4345
-- a new context means we should refetch everything
4446
local is_new_context = context.id ~= sources.current_context_id
4547
sources.current_context_id = context.id
4648

47-
if is_new_context then sources.sources_items = {} end
49+
if is_new_context then sources.sources_responses = {} end
4850

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

6062
-- if none of these are true, we can use the existing cached results
6163
if is_new_context or trigger_character or previous_incomplete or no_data then
@@ -76,15 +78,13 @@ function sources.completions(context)
7678
-- fixme: what if we refetch due to incomplete items or a trigger_character? the context trigger id wouldnt change
7779
-- change so stale data would be returned if the source doesn't support cancellation
7880
local cursor_column = vim.api.nvim_win_get_cursor(0)[2]
79-
vim.schedule(function()
80-
source.completions({ trigger = trigger_context }, function(items)
81-
-- a new call was made or this one was cancelled
82-
if sources.in_flight_id[source_name] ~= in_flight_id then return end
83-
sources.in_flight_id[source_name] = -1
84-
85-
sources.add_source_completions(source_name, items, cursor_column)
86-
if not sources.some_in_flight() then sources.send_completions(context) end
87-
end)
81+
source.completions({ trigger = trigger_context }, function(items)
82+
-- a new call was made or this one was cancelled
83+
if sources.in_flight_id[source_name] ~= in_flight_id then return end
84+
sources.in_flight_id[source_name] = -1
85+
86+
sources.add_source_completions(source_name, items, cursor_column)
87+
if not sources.some_in_flight() then sources.send_completions(context) end
8888
end)
8989
end
9090
end
@@ -94,34 +94,43 @@ function sources.completions(context)
9494
if not sources.some_in_flight() then sources.send_completions(context) end
9595
end
9696

97-
function sources.add_source_completions(source_name, source_items, cursor_column)
98-
for _, item in ipairs(source_items.items) do
97+
--- @param source_name string
98+
--- @param source_response CompletionResponse
99+
--- @param cursor_column number
100+
function sources.add_source_completions(source_name, source_response, cursor_column)
101+
for _, item in ipairs(source_response.items) do
99102
item.source = source_name
100103
item.cursor_column = cursor_column
101104
end
102105

103-
sources.sources_items[source_name] = source_items
106+
sources.sources_responses[source_name] = source_response
104107
end
105108

109+
--- @return boolean
106110
function sources.some_in_flight()
107111
for _, in_flight in pairs(sources.in_flight_id) do
108112
if in_flight ~= -1 then return true end
109113
end
110114
return false
111115
end
112116

117+
--- @param context ShowContext
113118
function sources.send_completions(context)
114-
local sources_items = sources.sources_items
119+
local sources_responses = sources.sources_responses
115120
-- apply source filters
116121
for _, source in pairs(sources.registered) do
117-
if source.filter_completions ~= nil then sources_items = source.filter_completions(context, sources_items) end
122+
if source.filter_completions ~= nil then
123+
sources_responses = source.filter_completions(context, sources_responses)
124+
end
118125
end
119126

120127
-- flatten the items
121128
local flattened_items = {}
122-
for source_name, response in pairs(sources.sources_items) do
129+
for source_name, response in pairs(sources.sources_responses) do
123130
local source = sources.registered[source_name]
124-
if source.should_show == nil or source.should_show() then vim.list_extend(flattened_items, response.items) end
131+
if source.should_show_completions == nil or source.should_show_completions(context, sources_responses) then
132+
vim.list_extend(flattened_items, response.items)
133+
end
125134
end
126135

127136
sources.on_completions_callback(context, flattened_items)
@@ -134,10 +143,16 @@ function sources.cancel_completions()
134143
end
135144
end
136145

146+
--- @param item CompletionItem
147+
--- @param callback fun(resolved_item: CompletionItem | nil)
148+
--- @return fun(): nil Cancelation function
137149
function sources.resolve(item, callback)
138150
local item_source = sources.registered[item.source]
139-
if item_source == nil or item_source.resolve == nil then return callback(item) end
140-
item_source.resolve(item, callback)
151+
if item_source == nil or item_source.resolve == nil then
152+
callback(nil)
153+
return function() end
154+
end
155+
return item_source.resolve(item, callback) or function() end
141156
end
142157

143158
return sources

lua/blink/cmp/sources/lsp.lua

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
--- @class Source
12
local lsp = {}
23

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

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

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

106-
-- @return function Cancel function
107110
function lsp.resolve(item, callback)
108111
local client = vim.lsp.get_client_by_id(item.client_id)
109-
if client == nil then return callback(item) end
112+
if client == nil then
113+
callback(item)
114+
return
115+
end
110116

111-
client.request('completionItem/resolve', item, function(error, result)
117+
local _, request_id = client.request('completionItem/resolve', item, function(error, result)
112118
if error or result == nil then callback(item) end
113119
callback(result)
114120
end)
121+
if request_id ~= nil then return function() client.cancel_request(request_id) end end
115122
end
116123

117124
return lsp

lua/blink/cmp/sources/snippets.lua

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
--- @class Source
12
local snippets = {}
23

34
function snippets.completions(_, callback)

lua/blink/cmp/sources/types.lua

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
--- @class TriggerContext
2+
--- @field kind number
3+
--- @field character string
4+
---
5+
--- @class CompletionContext : ShowContext
6+
--- @field trigger TriggerContext | nil
7+
---
8+
--- @class CompletionResponse
9+
--- @field isIncomplete boolean
10+
--- @field items CompletionItem[]
11+
---
112
--- @class Source
213
--- @field get_trigger_characters fun(): string[]
3-
--- @field completions fun(context: ShowContext, callback: fun(items: lsp.CompletionItem[]))
4-
--- @field filter_completions fun(context: ShowContext, items: CompletionItem[]): CompletionItem[]
14+
--- @field completions fun(context: CompletionContext, callback: fun(response: CompletionResponse))
15+
--- @field filter_completions fun(context: CompletionContext, source_responses: table<string, CompletionItem[]>): CompletionItem[]
516
--- @field cancel_completions fun()
6-
--- @field should_show_completions fun(): boolean
7-
---
8-
--- @field resolve fun(item: lsp.CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil))
17+
--- @field should_show_completions fun(context: CompletionContext, source_responses: table<string, CompletionResponse>): boolean
18+
--- @field resolve fun(item: CompletionItem, callback: fun(resolved_item: lsp.CompletionItem | nil)): ((fun(): nil) | nil)

lua/blink/cmp/types.lua

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
--- @field score_offset number | nil
33
--- @field source string
44
--- @field cursor_column number
5+
--- @field client_id number

0 commit comments

Comments
 (0)