Skip to content

Commit 22e56af

Browse files
committed
feat: use custom kinds
1 parent b478fb2 commit 22e56af

12 files changed

Lines changed: 115 additions & 83 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ require('opencode').setup({
236236
snacks_layout = nil -- `layout` opts to pass to Snacks.picker.pick({ layout = ... })
237237
},
238238
completion = {
239+
supports_kind_icons = false, -- set to true if your completion engine supports kind icons (blink does) it will show icons with proper highlight instead of a generic kind icon
240+
use_native_completion = false, -- Set to true if you use native nvim completion otherwise it might not auto trigger in the input window.
239241
file_sources = {
240242
enabled = true,
241243
preferred_cli_tool = 'server', -- 'fd','fdfind','rg','git','server' if nil, it will use the best available tool, 'server' uses opencode cli to get file list (works cross platform) and supports folders
@@ -622,6 +624,7 @@ The plugin provides the following actions that can be triggered via keymaps, com
622624
- `open_input` (boolean, default: `true`): Whether to open the input window after adding the selection. Set to `false` to add selection silently without changing focus.
623625

624626
Example keymap for silent add:
627+
625628
```lua
626629
['<leader>oY'] = { 'add_visual_selection', { open_input = false }, mode = {'v'} }
627630
```

ftplugin/opencode.lua

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ local bufnr = vim.api.nvim_get_current_buf()
1212
local opencode_completion_ls = require('opencode.lsp.opencode_completion_ls')
1313
local client_id = opencode_completion_ls.start(bufnr)
1414
local completion = require('opencode.ui.completion')
15+
local config = require('opencode.config')
16+
local use_native_completion = config.ui.completion.use_native_completion
1517

1618
if client_id then
1719
-- track insert start state
1820
vim.api.nvim_create_autocmd('InsertEnter', {
1921
buffer = bufnr,
2022
callback = function()
21-
if not completion.has_completion_engine() then
23+
if use_native_completion then
2224
vim.lsp.completion.enable(true, client_id, bufnr, { autotrigger = true })
2325
end
2426
completion.on_insert_enter()
@@ -32,3 +34,54 @@ if client_id then
3234
end,
3335
})
3436
end
37+
-- blink.cmp capabilities
38+
-- completion = {
39+
-- completionItem = {
40+
-- commitCharactersSupport = false,
41+
-- deprecatedSupport = true,
42+
-- documentationFormat = { "markdown", "plaintext" },
43+
-- insertReplaceSupport = true,
44+
-- preselectSupport = false,
45+
-- resolveSupport = {
46+
-- properties = { "additionalTextEdits", "command" }
47+
-- },
48+
-- snippetSupport = true,
49+
-- tagSupport = {
50+
-- valueSet = { 1 }
51+
-- }
52+
-- },
53+
-- completionItemKind = {
54+
-- valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }
55+
-- },
56+
-- completionList = {
57+
-- itemDefaults = { "editRange", "insertTextFormat", "insertTextMode", "data" }
58+
-- },
59+
-- contextSupport = true,
60+
-- dynamicRegistration = false
61+
-- },
62+
63+
-- cmp capabilities
64+
-- completion = {
65+
-- completionItem = {
66+
-- commitCharactersSupport = false,
67+
-- deprecatedSupport = true,
68+
-- documentationFormat = { "markdown", "plaintext" },
69+
-- insertReplaceSupport = true,
70+
-- preselectSupport = false,
71+
-- resolveSupport = {
72+
-- properties = { "additionalTextEdits", "command" }
73+
-- },
74+
-- snippetSupport = true,
75+
-- tagSupport = {
76+
-- valueSet = { 1 }
77+
-- }
78+
-- },
79+
-- completionItemKind = {
80+
-- valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }
81+
-- },
82+
-- completionList = {
83+
-- itemDefaults = { "editRange", "insertTextFormat", "insertTextMode", "data" }
84+
-- },
85+
-- contextSupport = true,
86+
-- dynamicRegistration = false
87+
-- },

lua/opencode/config.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ M.defaults = {
149149
snacks_layout = nil,
150150
},
151151
completion = {
152+
supports_kind_icons = false,
153+
use_native_completion = false,
152154
file_sources = {
153155
enabled = true,
154156
preferred_cli_tool = 'server',

lua/opencode/lsp/opencode_completion_ls.lua

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local config = require('opencode.config')
12
---In-process LSP server for opencode completion
23
---Provides completion for files, subagents, commands, and context items
34
---Works with any LSP-compatible completion plugin (blink.cmp, nvim-cmp, etc.)
@@ -60,10 +61,8 @@ local function get_completion_context(params)
6061
return word, trigger_char, line
6162
end
6263

63-
local function supports_kind_icons()
64-
-- only blink.cmp supports kind icons currently, so we check for its presence
65-
local has_blink_cmp = pcall(require, 'blink.cmp')
66-
return has_blink_cmp
64+
function M.supports_kind_icons()
65+
return config.ui.completion.supports_kind_icons
6766
end
6867

6968
---Convert opencode CompletionItem to LSP CompletionItem
@@ -72,12 +71,11 @@ end
7271
---@return lsp.CompletionItem
7372
local function to_lsp_item(item, index)
7473
local source = require('opencode.ui.completion').get_source_by_name(item.source_name)
75-
7674
local lsp_item = {
77-
label = (supports_kind_icons() and '' or (item.kind_icon .. ' ')) .. item.label,
78-
kind = vim.lsp.protocol.CompletionItemKind.Text,
79-
kind_icon = supports_kind_icons() and item.kind_icon or nil, -- Only include kind_icon if supported
75+
label = (M.supports_kind_icons() and '' or item.kind_icon .. ' ') .. item.label,
76+
kind = source.custom_kind or vim.lsp.protocol.CompletionItemKind.Function,
8077
kind_hl = item.kind_hl,
78+
kind_icon = M.supports_kind_icons() and item.kind_icon or '',
8179
detail = item.detail,
8280
documentation = item.documentation and {
8381
kind = 'plaintext',

lua/opencode/types.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@
107107

108108
---@class OpencodeCompletionConfig
109109
---@field file_sources OpencodeCompletionFileSourcesConfig
110+
---@field supports_kind_icons boolean
111+
---@field use_native_completion boolean
110112

111113
---@class OpencodeLoadingAnimationConfig
112114
---@field frames string[]
@@ -196,7 +198,6 @@
196198

197199
---@class OpencodeConfig
198200
---@field preferred_picker 'telescope' | 'fzf' | 'mini.pick' | 'snacks' | 'select' | nil
199-
---@field preferred_completion 'blink' | 'nvim-cmp' | 'vim_complete' | nil -- Preferred completion strategy for mentons and commands
200201
---@field default_global_keymaps boolean
201202
---@field default_mode 'build' | 'plan' | string -- Default mode
202203
---@field default_system_prompt string | nil
@@ -413,6 +414,7 @@
413414
---@field on_complete fun(item: CompletionItem): nil Optional callback when item is selected
414415
---@field is_incomplete? boolean Whether the completion results are incomplete (for sources that support pagination)
415416
---@field get_trigger_character? fun(): string|nil Optional function returning the trigger character for this source
417+
---@field custom_kind? integer Custom LSP CompletionItemKind registered for this source
416418

417419
---@class OpencodeContext
418420
---@field current_file OpencodeContextFile|nil

lua/opencode/ui/completion.lua

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -114,27 +114,4 @@ function M.is_visible()
114114
return M._pending and next(M._pending) ~= nil
115115
end
116116

117-
function M.has_completion_engine()
118-
local config = require('opencode.config')
119-
local preferred = config.preferred_completion or config.preferred_completion_engine
120-
if preferred and preferred ~= 'vim_complete' then
121-
return true
122-
end
123-
124-
local known_engines = {
125-
'cmp',
126-
'blink.cmp',
127-
'completion',
128-
'mini.completion',
129-
'minuet',
130-
}
131-
132-
for _, engine in ipairs(known_engines) do
133-
if package.loaded[engine] then
134-
return true
135-
end
136-
end
137-
return false
138-
end
139-
140117
return M

lua/opencode/ui/completion/commands.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ local get_available_commands = Promise.async(function()
2020
return results
2121
end)
2222

23+
local custom_kind = require('opencode.ui.completion.kind')
24+
2325
---@type CompletionSource
2426
local command_source = {
2527
name = 'commands',
2628
priority = 1,
29+
custom_kind = custom_kind.register('commands', require('opencode.ui.icons').get('command')),
2730
complete = Promise.async(function(context)
2831
local icons = require('opencode.ui.icons')
2932
if not context.line:match('^' .. vim.pesc(context.trigger_char) .. '[^%s/]*$') then
@@ -47,7 +50,7 @@ local command_source = {
4750
if context.input == '' or name_lower:find(input_lower, 1, true) or desc_lower:find(input_lower, 1, true) then
4851
local item = {
4952
label = command.name .. (command.args and ' *' or ''),
50-
kind = 'command',
53+
kind = 'commands',
5154
kind_icon = icons.get('command'),
5255
detail = command.description,
5356
documentation = command.documentation .. (command.args and '\n\n* This command takes arguments.' or ''),

lua/opencode/ui/completion/context.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,13 @@ local function remove_inserted_text(item)
226226
vim.fn.feedkeys(vim.api.nvim_replace_termcodes('a', true, false, true), 'n')
227227
end
228228

229+
local custom_kind = require('opencode.ui.completion.kind')
230+
229231
---@type CompletionSource
230232
local context_source = {
231233
name = 'context',
232234
priority = 1,
235+
custom_kind = custom_kind.register('context', icons.get('status_on')),
233236
complete = Promise.async(function(completion_context)
234237
local input = completion_context.input or ''
235238

lua/opencode/ui/completion/files.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,14 @@ local function create_file_item(file, suffix, priority)
107107
}
108108
end
109109

110+
local custom_kind = require('opencode.ui.completion.kind')
111+
110112
---@type CompletionSource
111113
local file_source = {
112114
name = 'files',
113115
priority = 5,
114116
is_incomplete = true,
117+
custom_kind = custom_kind.register('files', icons.get('file')),
115118
complete = Promise.async(function(context)
116119
local sort_util = require('opencode.ui.completion.sort')
117120
local file_config = config.ui.completion.file_sources
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
local M = {}
2+
3+
local KIND_OFFSET = 100
4+
5+
---Register a custom LSP CompletionItemKind for a completion source.
6+
---@param source_name string
7+
---@param kind_icon? string
8+
---@return integer custom_kind_id
9+
function M.register(source_name, kind_icon)
10+
local kind_name = 'Opencode' .. (source_name:gsub('^%l', string.upper))
11+
12+
if not vim.lsp.protocol.CompletionItemKind[kind_name] then
13+
local next_id = KIND_OFFSET
14+
while vim.lsp.protocol.CompletionItemKind[next_id] do
15+
next_id = next_id + 1
16+
end
17+
18+
vim.lsp.protocol.CompletionItemKind[kind_name] = next_id
19+
vim.lsp.protocol.CompletionItemKind[next_id] = (kind_icon and kind_icon .. ' ' or '') .. kind_name
20+
end
21+
22+
return vim.lsp.protocol.CompletionItemKind[kind_name]
23+
end
24+
25+
return M

0 commit comments

Comments
 (0)