Files
nixos-config/modules/_neovim/plugins/opencode.nix
2026-03-12 19:01:53 +00:00

297 lines
6.8 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{pkgs, ...}: let
opencode-nvim =
pkgs.vimUtils.buildVimPlugin {
pname = "opencode-nvim";
version = "unstable-2026-03-07";
src =
pkgs.fetchFromGitHub {
owner = "sudo-tee";
repo = "opencode.nvim";
rev = "dffa3f39a8251c7ba53b1544d8536b5f51b4e90d";
hash = "sha256-KxIuToMpyo/Yi4xKirMV8Fznlma6EL1k4YQm5MQdGw4=";
};
doCheck = false;
};
in {
programs.nixvim = {
extraPlugins = with pkgs.vimPlugins; [
opencode-nvim
plenary-nvim
render-markdown-nvim
];
extraConfigLua = ''
local api = vim.api
local opencode_output_filetype = 'opencode_output'
local opencode_window_filetypes = {
opencode = true,
opencode_output = true,
}
local palette = {
base = '#eff1f5',
mantle = '#e6e9ef',
surface0 = '#ccd0da',
surface1 = '#bcc0cc',
text = '#4c4f69',
subtext0 = '#6c6f85',
overlay0 = '#9ca0b0',
blue = '#1e66f5',
lavender = '#7287fd',
sapphire = '#209fb5',
teal = '#179299',
green = '#40a02b',
mauve = '#8839ef',
peach = '#fe640b',
}
local function set_highlights(highlights)
for group, spec in pairs(highlights) do
api.nvim_set_hl(0, group, spec)
end
end
local opencode_markdown_conceal_query = vim.treesitter.query.parse('markdown_inline', [[
[
(emphasis_delimiter)
(code_span_delimiter)
(latex_span_delimiter)
] @conceal
(inline_link
[
"["
"]"
"("
(link_destination)
")"
] @conceal)
(full_reference_link
[
"["
"]"
(link_label)
] @conceal)
(collapsed_reference_link
[
"["
"]"
] @conceal)
(shortcut_link
[
"["
"]"
] @conceal)
(image
[
"!"
"["
"]"
"("
(link_destination)
")"
] @conceal)
]])
local function collect_conceal_marks(ctx)
local marks = {}
for _, node in opencode_markdown_conceal_query:iter_captures(ctx.root, ctx.buf) do
local start_row, start_col, end_row, end_col = node:range()
marks[#marks + 1] = {
conceal = true,
start_row = start_row,
start_col = start_col,
opts = {
end_row = end_row,
end_col = end_col,
conceal = "",
},
}
end
return marks
end
local function set_opencode_output_conceal()
if vim.bo.filetype ~= opencode_output_filetype then
return
end
vim.wo.conceallevel = 3
vim.wo.concealcursor = 'nvic'
end
local function hide_opencode_statusline()
if not opencode_window_filetypes[vim.bo.filetype] then
return
end
vim.wo.statusline = ' '
end
vim.treesitter.language.register('markdown', opencode_output_filetype)
vim.treesitter.language.register('markdown_inline', opencode_output_filetype)
api.nvim_create_autocmd({ 'FileType', 'BufWinEnter', 'WinEnter' }, {
callback = set_opencode_output_conceal,
})
api.nvim_create_autocmd({ 'FileType', 'BufWinEnter', 'WinEnter', 'BufEnter' }, {
pattern = '*',
callback = hide_opencode_statusline,
})
set_highlights({
RenderMarkdownCode = { bg = palette.mantle },
RenderMarkdownCodeBorder = { fg = palette.surface0, bg = palette.mantle },
RenderMarkdownCodeInline = { bg = palette.mantle },
RenderMarkdownH1 = { fg = palette.blue, bold = true },
RenderMarkdownH2 = { fg = palette.mauve, bold = true },
RenderMarkdownH3 = { fg = palette.teal, bold = true },
RenderMarkdownH4 = { fg = palette.peach, bold = true },
OpencodeInputLegend = { fg = palette.subtext0, bold = true },
OpencodeAgentBuild = { bg = palette.overlay0, fg = palette.base, bold = true },
})
local render_markdown_config = {
anti_conceal = { enabled = false },
heading = {
icons = { ' ', ' ', ' ', '· ', '· ', '· ' },
backgrounds = {},
position = 'inline',
width = 'block',
left_pad = 0,
right_pad = 2,
border = false,
sign = false,
},
code = {
sign = false,
width = 'full',
left_pad = 2,
right_pad = 0,
border = 'thin',
language_icon = false,
language_name = true,
},
bullet = {
icons = { '·', '', '·', '' },
},
custom_handlers = {
markdown_inline = {
extends = true,
parse = collect_conceal_marks,
},
},
file_types = { opencode_output_filetype },
win_options = {
conceallevel = { rendered = 3 },
concealcursor = { rendered = 'nvic' },
},
}
require('render-markdown').setup(render_markdown_config)
local opencode_icon_overrides = {
header_user = '',
header_assistant = '',
run = '',
task = '',
read = '',
edit = '',
write = '',
plan = '',
search = '',
web = '',
list = '',
tool = '',
snapshot = '',
restore_point = '',
file = '·',
folder = '·',
attached_file = '·',
agent = '·',
reference = '·',
reasoning = '',
question = '?',
border = '',
}
require('opencode').setup({
server = {
url = 'http://127.0.0.1',
port = 18822,
auto_kill = false,
},
input = {
text = {
wrap = true,
},
},
debug = {
show_ids = false,
},
ui = {
icons = {
preset = 'nerdfonts',
overrides = opencode_icon_overrides,
},
},
})
do
local config = require('opencode.config')
local formatter = require('opencode.ui.formatter')
local format_utils = require('opencode.ui.formatter.utils')
local icons = require('opencode.ui.icons')
local util = require('opencode.util')
local function format_reasoning_title(part)
local title = 'Reasoning'
local time = part.time
if time and type(time) == 'table' and time.start then
local duration_text = util.format_duration_seconds(time.start, time['end'])
if duration_text then
title = string.format('%s %s', title, duration_text)
end
end
return title
end
local function highlight_reasoning_block(output, start_line)
local end_line = output:get_line_count()
if end_line - start_line > 1 then
formatter.add_vertical_border(output, start_line, end_line, 'OpencodeToolBorder', -1, 'OpencodeReasoningText')
return
end
output:add_extmark(start_line - 1, {
line_hl_group = 'OpencodeReasoningText',
})
end
formatter._format_reasoning = function(output, part)
local text = vim.trim(part.text or "")
local start_line = output:get_line_count() + 1
format_utils.format_action(output, icons.get('reasoning'), format_reasoning_title(part), "")
if config.ui.output.tools.show_reasoning_output and text ~= "" then
output:add_empty_line()
output:add_lines(vim.split(text, '\n'), ' ')
output:add_empty_line()
end
highlight_reasoning_block(output, start_line)
end
end
'';
};
}