Skip to content

Commit

Permalink
feat: Add blink.cmp completion source
Browse files Browse the repository at this point in the history
  • Loading branch information
aliaksandr-trush committed Dec 23, 2024
1 parent dc41cab commit e72afbc
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 6 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,29 @@ cmp.setup({
})
```

### blink.cmp

Configuration example for [blink.cmp](https://github.com/Saghen/blink.cmp):

```lua
{
'saghen/blink.cmp',
dependencies = {
{
'Exafunction/codeium.nvim',
},
},
opts = {
sources = {
default = { 'lsp', 'path', 'snippets', 'buffer', 'codeium' },
providers = {
codeium = { name = 'Codeium', module = 'codeium.blink', async = true },
},
},
},
}
```

### Virtual Text

The plugin supports showing completions in virtual text. Set `virtual_text.enabled` in the options to `true` to enable it.
Expand Down
10 changes: 5 additions & 5 deletions lua/codeium/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ end

---
--- Codeium Server API
--- @class Server
--- @class codeium.Server
--- @field port? number
--- @field job? plenary.job
--- @field current_cookie? number
Expand Down Expand Up @@ -177,7 +177,7 @@ function Server.authenticate()
prompt()
end

---@return Server
---@return codeium.Server
function Server.new()
local m = setmetatable({}, Server)
m.__index = m
Expand Down Expand Up @@ -353,7 +353,7 @@ function Server:do_heartbeat()
end

function Server:request_completion(document, editor_options, other_documents, callback)
if not self.enabled then
if not self.enabled or not self.port then
return
end
self.pending_request[2](true)
Expand Down Expand Up @@ -415,14 +415,14 @@ function Server:request_completion(document, editor_options, other_documents, ca
end
end

notify.error("completion request failed", err)
notify.error("completion request failed: " .. err.response.body)
complete(false, nil)
return
end

local ok, json = pcall(vim.fn.json_decode, body)
if not ok then
notify.error("completion request failed", "invalid JSON:", json)
notify.error("completion request failed: " .. "invalid JSON:" .. json)
return
end

Expand Down
177 changes: 177 additions & 0 deletions lua/codeium/blink.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
--- Module for the codeium completion source
local enums = require("codeium.enums")
local util = require("codeium.util")

local function utf8len(str)
if not str then
return 0
end
return str:len()
end

local function codeium_to_item(comp, offset, right)
local documentation = comp.completion.text

local label = documentation:sub(offset)
if label:sub(-#right) == right then
label = label:sub(1, -#right - 1)
end

-- We get the completion part that has the largest offset
local max_offset = offset
if comp.completionParts then
for _, v in pairs(comp.completionParts) do
local part_offset = tonumber(v.offset)
if part_offset > max_offset then
max_offset = part_offset
end
end
end

-- We get where the suffix difference between the completion and the range of code
local suffix_diff = comp.range.endOffset - max_offset

local range = {
start = {
-- Codeium returns an empty row for the first line
line = (tonumber(comp.range.startPosition.row) or 0),
character = offset - 1,
},
["end"] = {
-- Codeium returns an empty row for the first line
line = (tonumber(comp.range.endPosition.row) or 0),
-- We only want to replace up to where the completion ends
character = (comp.range.endPosition.col or suffix_diff) - suffix_diff,
},
}

local display_label = string.match(label, "([^\n]*)")
if display_label ~= label then
display_label = display_label .. ""
end

return {
type = 1,
documentation = {
kind = "markdown",
value = table.concat({
"```" .. vim.api.nvim_get_option_value("filetype", {}),
label,
"```",
}, "\n"),
},
label = display_label,
insertText = label,
kind = require('blink.cmp.types').CompletionItemKind.Text,
insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText,
textEdit = {
newText = label,
insert = range,
replace = range,
},
cmp = {
kind_text = "Codeium",
kind_hl_group = "BlinkCmpKindCodeium",
},
codeium_completion_id = comp.completion.completionId,
}
end

--- @class blink.cmp.Source
--- @field server codeium.Server
local M = {}

function M.new()
local o = {}
o.server = require("codeium").s
return setmetatable(o, { __index = M })
end

function M:get_trigger_characters()
return { '"', "`", "[", "]", ".", " ", "\n" }
end
--
function M:enabled()
return self.server.enabled
end

function M:get_completions(ctx, callback)
local offset = ctx.bounds.start_col
local cursor = ctx.cursor
local bufnr = ctx.bufnr
local filetype = vim.api.nvim_get_option_value("filetype", { buf = ctx.bufnr })
filetype = enums.filetype_aliases[filetype] or filetype or "text"
local language = enums.languages[filetype] or enums.languages.unspecified
local after_line = string.sub(ctx.line, cursor[2])
local before_line = string.sub(ctx.line, 1, cursor[2] - 1)
local line_ending = util.get_newline(bufnr)
local line_ending_len = utf8len(line_ending)
local editor_options = util.get_editor_options(bufnr)

-- We need to calculate the number of bytes prior to the current character,
-- that starts with all the prior lines
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)

-- For the current line, we want to exclude any extra characters that were
-- entered after the popup displayed
lines[cursor[1]] = ctx.line

-- We exclude the current line from the loop below, so add it's length here
local cursor_offset = utf8len(before_line)
for i = 1, (cursor[1] - 1) do
local line = lines[i]
cursor_offset = cursor_offset + utf8len(line) + line_ending_len
end

-- Ensure that there is always a newline at the end of the file
table.insert(lines, "")
local text = table.concat(lines, line_ending)

local function handle_completions(completion_items)
local duplicates = {}
local completions = {}
for _, comp in ipairs(completion_items) do
if not duplicates[comp.completion.text] then
duplicates[comp.completion.text] = true
table.insert(completions, codeium_to_item(comp, offset, after_line))
end
end
callback({
is_incomplete_forward = false,
is_incomplete_backward = false,
items = completions,
context = ctx,
})
end

local other_documents = util.get_other_documents(bufnr)

self.server:request_completion(
{
text = text,
editor_language = filetype,
language = language,
cursor_position = { row = cursor[1] - 1, col = cursor[2] },
absolute_uri = util.get_uri(vim.api.nvim_buf_get_name(bufnr)),
workspace_uri = util.get_uri(util.get_project_root()),
line_ending = line_ending,
cursor_offset = cursor_offset,
},
editor_options,
other_documents,
function(success, json)
if not success then
return nil
end

if json and json.state and json.state.state == "CODEIUM_STATE_SUCCESS" and json.completionItems then
handle_completions(json.completionItems)
else
return nil
end
end
)
return function() end
end

return M
2 changes: 1 addition & 1 deletion lua/codeium/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function M.check()
instance:checkhealth(health_logger)
end

---@param server Server
---@param server codeium.Server
function M.register(server)
instance = server
end
Expand Down

0 comments on commit e72afbc

Please sign in to comment.