Skip to content

Commit ea31d6d

Browse files
feat: Add blink.cmp completion source
1 parent dc41cab commit ea31d6d

File tree

4 files changed

+208
-6
lines changed

4 files changed

+208
-6
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,31 @@ cmp.setup({
153153
})
154154
```
155155

156+
### blink.cmp
157+
158+
Configuration example for [blink.cmp](https://github.com/Saghen/blink.cmp):
159+
160+
```lua
161+
{
162+
'saghen/blink.cmp',
163+
dependencies = {
164+
{
165+
'Exafunction/codeium.nvim',
166+
},
167+
},
168+
opts = {
169+
sources = {
170+
completion = {
171+
enabled_providers = { 'lsp', 'path', 'snippets', 'buffer', 'codeium' },
172+
},
173+
providers = {
174+
codeium = { name = 'Codeium', module = 'codeium.blink' },
175+
},
176+
},
177+
},
178+
}
179+
```
180+
156181
### Virtual Text
157182

158183
The plugin supports showing completions in virtual text. Set `virtual_text.enabled` in the options to `true` to enable it.

lua/codeium/api.lua

+5-5
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ end
4545

4646
---
4747
--- Codeium Server API
48-
--- @class Server
48+
--- @class codeium.Server
4949
--- @field port? number
5050
--- @field job? plenary.job
5151
--- @field current_cookie? number
@@ -177,7 +177,7 @@ function Server.authenticate()
177177
prompt()
178178
end
179179

180-
---@return Server
180+
---@return codeium.Server
181181
function Server.new()
182182
local m = setmetatable({}, Server)
183183
m.__index = m
@@ -353,7 +353,7 @@ function Server:do_heartbeat()
353353
end
354354

355355
function Server:request_completion(document, editor_options, other_documents, callback)
356-
if not self.enabled then
356+
if not self.enabled or not self.port then
357357
return
358358
end
359359
self.pending_request[2](true)
@@ -415,14 +415,14 @@ function Server:request_completion(document, editor_options, other_documents, ca
415415
end
416416
end
417417

418-
notify.error("completion request failed", err)
418+
notify.error("completion request failed: " .. err.response.body)
419419
complete(false, nil)
420420
return
421421
end
422422

423423
local ok, json = pcall(vim.fn.json_decode, body)
424424
if not ok then
425-
notify.error("completion request failed", "invalid JSON:", json)
425+
notify.error("completion request failed: " .. "invalid JSON:" .. json)
426426
return
427427
end
428428

lua/codeium/blink.lua

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
local enums = require("codeium.enums")
2+
local util = require("codeium.util")
3+
4+
local function utf8len(str)
5+
if not str then
6+
return 0
7+
end
8+
return str:len()
9+
end
10+
11+
local function codeium_to_item(comp, offset, right)
12+
local documentation = comp.completion.text
13+
14+
local label = documentation:sub(offset)
15+
if label:sub(-#right) == right then
16+
label = label:sub(1, -#right - 1)
17+
end
18+
19+
-- We get the completion part that has the largest offset
20+
local max_offset = offset
21+
if comp.completionParts then
22+
for _, v in pairs(comp.completionParts) do
23+
local part_offset = tonumber(v.offset)
24+
if part_offset > max_offset then
25+
max_offset = part_offset
26+
end
27+
end
28+
end
29+
30+
-- We get where the suffix difference between the completion and the range of code
31+
local suffix_diff = comp.range.endOffset - max_offset
32+
33+
local range = {
34+
start = {
35+
-- Codeium returns an empty row for the first line
36+
line = (tonumber(comp.range.startPosition.row) or 0),
37+
character = offset - 1,
38+
},
39+
["end"] = {
40+
-- Codeium returns an empty row for the first line
41+
line = (tonumber(comp.range.endPosition.row) or 0),
42+
-- We only want to replace up to where the completion ends
43+
character = (comp.range.endPosition.col or suffix_diff) - suffix_diff,
44+
},
45+
}
46+
47+
local display_label = string.match(label, "([^\n]*)")
48+
if display_label ~= label then
49+
display_label = display_label .. ""
50+
end
51+
52+
return {
53+
type = 1,
54+
documentation = {
55+
kind = "markdown",
56+
value = table.concat({
57+
"```" .. vim.api.nvim_get_option_value("filetype", {}),
58+
label,
59+
"```",
60+
}, "\n"),
61+
},
62+
label = display_label,
63+
insertText = label,
64+
kind = 1, -- Text
65+
insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText,
66+
textEdit = {
67+
newText = label,
68+
insert = range,
69+
replace = range,
70+
},
71+
cmp = {
72+
kind_text = "Codeium",
73+
kind_hl_group = "BlinkCmpKindCodeium",
74+
},
75+
codeium_completion_id = comp.completion.completionId,
76+
}
77+
end
78+
79+
--- @class blink.cmp.Source
80+
--- @field server codeium.Server
81+
local M = {}
82+
83+
function M.new()
84+
local o = {}
85+
o.server = require("codeium").s
86+
return setmetatable(o, { __index = M })
87+
end
88+
89+
function M:get_trigger_characters()
90+
return { '"', "`", "[", "]", ".", " ", "\n" }
91+
end
92+
--
93+
function M:enabled()
94+
return self.server.enabled
95+
end
96+
97+
function M:get_completions(ctx, callback)
98+
local context = ctx
99+
local offset = ctx.bounds.start_col -- ctx.offset -- param.offset
100+
local cursor = context.cursor
101+
local bufnr = context.bufnr
102+
local filetype = vim.api.nvim_get_option_value("filetype", { buf = ctx.bufnr })
103+
filetype = enums.filetype_aliases[filetype] or filetype or "text"
104+
local language = enums.languages[filetype] or enums.languages.unspecified
105+
local after_line = string.sub(ctx.line, cursor[2])
106+
local before_line = string.sub(ctx.line, 1, cursor[2] - 1)
107+
local line_ending = util.get_newline(bufnr)
108+
local line_ending_len = utf8len(line_ending)
109+
local editor_options = util.get_editor_options(bufnr)
110+
111+
-- We need to calculate the number of bytes prior to the current character,
112+
-- that starts with all the prior lines
113+
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
114+
115+
-- For the current line, we want to exclude any extra characters that were
116+
-- entered after the popup displayed
117+
lines[cursor[1]] = context.line
118+
119+
-- We exclude the current line from the loop below, so add it's length here
120+
local cursor_offset = utf8len(before_line)
121+
for i = 1, (cursor[1] - 1) do
122+
local line = lines[i]
123+
cursor_offset = cursor_offset + utf8len(line) + line_ending_len
124+
end
125+
126+
-- Ensure that there is always a newline at the end of the file
127+
table.insert(lines, "")
128+
local text = table.concat(lines, line_ending)
129+
130+
local function handle_completions(completion_items)
131+
local duplicates = {}
132+
local completions = {}
133+
for _, comp in ipairs(completion_items) do
134+
if not duplicates[comp.completion.text] then
135+
duplicates[comp.completion.text] = true
136+
table.insert(completions, codeium_to_item(comp, offset, after_line))
137+
end
138+
end
139+
callback({
140+
is_incomplete_forward = false,
141+
is_incomplete_backward = false,
142+
items = completions,
143+
context = ctx,
144+
})
145+
end
146+
147+
local other_documents = util.get_other_documents(bufnr)
148+
149+
self.server:request_completion(
150+
{
151+
text = text,
152+
editor_language = filetype,
153+
language = language,
154+
cursor_position = { row = cursor[1] - 1, col = cursor[2] },
155+
absolute_uri = util.get_uri(vim.api.nvim_buf_get_name(bufnr)),
156+
workspace_uri = util.get_uri(util.get_project_root()),
157+
line_ending = line_ending,
158+
cursor_offset = cursor_offset,
159+
},
160+
editor_options,
161+
other_documents,
162+
function(success, json)
163+
if not success then
164+
return nil
165+
end
166+
167+
if json and json.state and json.state.state == "CODEIUM_STATE_SUCCESS" and json.completionItems then
168+
handle_completions(json.completionItems)
169+
else
170+
return nil
171+
end
172+
end
173+
)
174+
return function() end
175+
end
176+
177+
return M

lua/codeium/health.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function M.check()
3131
instance:checkhealth(health_logger)
3232
end
3333

34-
---@param server Server
34+
---@param server codeium.Server
3535
function M.register(server)
3636
instance = server
3737
end

0 commit comments

Comments
 (0)