Skip to content

Commit 059f818

Browse files
committed
Merge upstream/main into feat/picker-ai-code-referenes
Resolves conflicts in: - lua/opencode/ui/highlight.lua: Added both OpencodeReference and OpencodeReasoningText highlights - lua/opencode/ui/icons.lua: Added both reference and reasoning icons to nerdfonts preset, added reference to text preset
2 parents 4848e5a + db91a85 commit 059f818

File tree

9 files changed

+264
-30
lines changed

9 files changed

+264
-30
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ require('opencode').setup({
127127
['<leader>opa'] = { 'permission_accept' }, -- Accept permission request once
128128
['<leader>opA'] = { 'permission_accept_all' }, -- Accept all (for current tool)
129129
['<leader>opd'] = { 'permission_deny' }, -- Deny permission request once
130+
['<leader>ott'] = { 'toggle_tool_output' }, -- Toggle tools output (diffs, cmd output, etc.)
131+
['<leader>otr'] = { 'toggle_reasoning_output' }, -- Toggle reasoning output (thinking steps)
130132
},
131133
input_window = {
132134
['<cr>'] = { 'submit_input_prompt', mode = { 'n', 'i' } }, -- Submit prompt (normal mode and insert mode)
@@ -191,6 +193,7 @@ require('opencode').setup({
191193
output = {
192194
tools = {
193195
show_output = true, -- Show tools output [diffs, cmd output, etc.] (default: true)
196+
show_reasoning_output = true, -- Show reasoning/thinking steps output (default: true)
194197
},
195198
rendering = {
196199
markdown_debounce_ms = 250, -- Debounce time for markdown rendering on new data (default: 250ms)
@@ -390,7 +393,8 @@ The plugin provides the following actions that can be triggered via keymaps, com
390393
| Navigate to next prompt in history | `<down>` | - | `require('opencode.api').next_history()` |
391394
| Toggle input/output panes | `<tab>` | - | - |
392395
| Swap Opencode pane left/right | `<leader>ox` | `:Opencode swap position` | `require('opencode.api').swap_position()` |
393-
| Toggle tools output (diffs, cmd output, etc.) | - | `:Opencode toggle_tools_output` | `require('opencode.api').toggle_tools_output()` |
396+
| Toggle tools output (diffs, cmd output, etc.) | `<leader>ott` | `:Opencode toggle_tool_output` | `require('opencode.api').toggle_tool_output()` |
397+
| Toggle reasoning output (thinking steps) | `<leader>otr` | `:Opencode toggle_reasoning_output` | `require('opencode.api').toggle_reasoning_output()` |
394398

395399
---
396400

lua/opencode/api.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,10 +908,19 @@ function M.permission_deny()
908908
end
909909

910910
function M.toggle_tool_output()
911+
local action_text = config.ui.output.tools.show_output and 'Hiding' or 'Showing'
912+
vim.notify(action_text .. ' tool output display', vim.log.levels.INFO)
911913
config.values.ui.output.tools.show_output = not config.ui.output.tools.show_output
912914
ui.render_output()
913915
end
914916

917+
function M.toggle_reasoning_output()
918+
local action_text = config.ui.output.tools.show_reasoning_output and 'Hiding' or 'Showing'
919+
vim.notify(action_text .. ' reasoning output display', vim.log.levels.INFO)
920+
config.values.ui.output.tools.show_reasoning_output = not config.ui.output.tools.show_reasoning_output
921+
ui.render_output()
922+
end
923+
915924
---@type table<string, OpencodeUICommand>
916925
M.commands = {
917926
open = {
@@ -1214,6 +1223,11 @@ M.commands = {
12141223
desc = 'Toggle tool output visibility in the output window',
12151224
fn = M.toggle_tool_output,
12161225
},
1226+
1227+
toggle_reasoning_output = {
1228+
desc = 'Toggle reasoning output visibility in the output window',
1229+
fn = M.toggle_reasoning_output,
1230+
},
12171231
paste_image = {
12181232
desc = 'Paste image from clipboard and add to context',
12191233
fn = M.paste_image,
@@ -1243,6 +1257,8 @@ M.slash_commands_map = {
12431257
['/undo'] = { fn = M.undo, desc = 'Undo last action' },
12441258
['/unshare'] = { fn = M.unshare, desc = 'Unshare current session' },
12451259
['/rename'] = { fn = M.rename_session, desc = 'Rename current session' },
1260+
['/thinking'] = { fn = M.toggle_reasoning_output, desc = 'Toggle reasoning output' },
1261+
['/reasoning'] = { fn = M.toggle_reasoning_output, desc = 'Toggle reasoning output' },
12461262
}
12471263

12481264
M.legacy_command_map = {

lua/opencode/config.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ M.defaults = {
4141
['<leader>oPa'] = { 'permission_accept', desc = 'Accept permission' },
4242
['<leader>oPA'] = { 'permission_accept_all', desc = 'Accept all permissions' },
4343
['<leader>oPd'] = { 'permission_deny', desc = 'Deny permission' },
44+
['<leader>otr'] = { 'toggle_reasoning_output', desc = 'Toggle reasoning output' },
45+
['<leader>ott'] = { 'toggle_tool_output', desc = 'Toggle tool output' },
4446
},
4547
output_window = {
4648
['<esc>'] = { 'close' },
@@ -120,6 +122,7 @@ M.defaults = {
120122
},
121123
tools = {
122124
show_output = true,
125+
show_reasoning_output = true,
123126
},
124127
always_scroll_to_bottom = false,
125128
},

lua/opencode/types.lua

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@
129129
---@field output OpencodeUIOutputConfig
130130
---@field input { text: { wrap: boolean } }
131131
---@field completion OpencodeCompletionConfig
132+
---@field highlights? OpencodeHighlightConfig
133+
134+
---@class OpencodeHighlightConfig
135+
---@field vertical_borders? { tool?: { fg?: string, bg?: string }, user?: { fg?: string, bg?: string }, assistant?: { fg?: string, bg?: string } }
132136

133137
---@class OpencodeUIOutputRenderingConfig
134138
---@field markdown_debounce_ms number
@@ -137,7 +141,7 @@
137141
---@field event_collapsing boolean
138142

139143
---@class OpencodeUIOutputConfig
140-
---@field tools { show_output: boolean }
144+
---@field tools { show_output: boolean, show_reasoning_output: boolean }
141145
---@field rendering OpencodeUIOutputRenderingConfig
142146
---@field always_scroll_to_bottom boolean
143147

@@ -383,7 +387,7 @@
383387
---@field value string|nil
384388

385389
---@class OpencodeMessagePart
386-
---@field type 'text'|'file'|'agent'|'tool'|'step-start'|'patch'|string
390+
---@field type 'text'|'file'|'agent'|'tool'|'step-start'|'patch'|'reasoning'|string
387391
---@field id string|nil Unique identifier for tool use parts
388392
---@field text string|nil
389393
---@field tool string|nil Name of the tool being used
@@ -400,6 +404,7 @@
400404
---@field callID string|nil Call identifier (used for tools)
401405
---@field hash string|nil Hash identifier for patch parts
402406
---@field files string[]|nil List of file paths for patch parts
407+
---@field time { start: number, end?: number }|nil Timestamps for the part
403408

404409
---@class OpencodeModelModalities
405410
---@field input ('text'|'image'|'audio'|'video')[] Supported input modalities

lua/opencode/ui/formatter.lua

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,46 @@ M.separator = {
1414
'',
1515
}
1616

17+
---@param output Output
18+
---@param part OpencodeMessagePart
19+
function M._format_reasoning(output, part)
20+
local text = vim.trim(part.text or '')
21+
if text == '' then
22+
return
23+
end
24+
25+
local start_line = output:get_line_count() + 1
26+
27+
local title = 'Reasoning'
28+
local time = part.time
29+
if time and type(time) == 'table' and time.start then
30+
local start_text = util.format_time(time.start) or ''
31+
local end_text = (time['end'] and util.format_time(time['end'])) or nil
32+
if end_text and end_text ~= '' then
33+
title = string.format('%s (%s - %s)', title, start_text, end_text)
34+
elseif start_text ~= '' then
35+
title = string.format('%s (%s)', title, start_text)
36+
end
37+
end
38+
39+
M._format_action(output, icons.get('reasoning') .. ' ' .. title, '')
40+
41+
if config.ui.output.tools.show_reasoning_output then
42+
output:add_empty_line()
43+
output:add_lines(vim.split(text, '\n'))
44+
output:add_empty_line()
45+
end
46+
47+
local end_line = output:get_line_count()
48+
if end_line - start_line > 1 then
49+
M._add_vertical_border(output, start_line, end_line, 'OpencodeToolBorder', -1, 'OpencodeReasoningText')
50+
else
51+
output:add_extmark(start_line - 1, {
52+
line_hl_group = 'OpencodeReasoningText',
53+
} --[[@as OutputExtmark]])
54+
end
55+
end
56+
1757
function M._handle_permission_request(output, part)
1858
if part.state and part.state.status == 'error' and part.state.error then
1959
if part.state.error:match('rejected permission') then
@@ -462,14 +502,15 @@ function M._format_assistant_message(output, text)
462502
end
463503

464504
---@param output Output Output object to write to
465-
---@param type string Tool type (e.g., 'run', 'read', 'edit', etc.)
505+
---@param tool_type string Tool type (e.g., 'run', 'read', 'edit', etc.)
466506
---@param value string Value associated with the action (e.g., filename, command)
467-
function M._format_action(output, type, value)
468-
if not type or not value then
507+
function M._format_action(output, tool_type, value)
508+
if not tool_type or not value then
469509
return
470510
end
511+
local line = string.format('**%s** %s', tool_type, value and #value > 0 and ('`' .. value .. '`') or '')
471512

472-
output:add_line('**' .. type .. '** `' .. value .. '`')
513+
output:add_line(line)
473514
end
474515

475516
---@param output Output Output object to write to
@@ -744,16 +785,24 @@ end
744785
---@param output Output Output object to write to
745786
---@param start_line number
746787
---@param end_line number
747-
---@param hl_group string
788+
---@param hl_group string Highlight group for the border character
748789
---@param win_col number
749-
function M._add_vertical_border(output, start_line, end_line, hl_group, win_col)
790+
---@param text_hl_group? string Optional highlight group for the background/foreground of text lines
791+
function M._add_vertical_border(output, start_line, end_line, hl_group, win_col, text_hl_group)
750792
for line = start_line, end_line do
751-
output:add_extmark(line - 1, {
793+
local extmark_opts = {
752794
virt_text = { { require('opencode.ui.icons').get('border'), hl_group } },
753795
virt_text_pos = 'overlay',
754796
virt_text_win_col = win_col,
755797
virt_text_repeat_linebreak = true,
756-
} --[[@as OutputExtmark]])
798+
}
799+
800+
-- Add line highlight if text_hl_group is provided
801+
if text_hl_group then
802+
extmark_opts.line_hl_group = text_hl_group
803+
end
804+
805+
output:add_extmark(line - 1, extmark_opts --[[@as OutputExtmark]])
757806
end
758807
end
759808

@@ -793,6 +842,9 @@ function M.format_part(part, message, is_last_part)
793842
if part.type == 'text' and part.text then
794843
M._format_assistant_message(output, vim.trim(part.text))
795844
content_added = true
845+
elseif part.type == 'reasoning' and part.text then
846+
M._format_reasoning(output, part)
847+
content_added = true
796848
elseif part.type == 'tool' then
797849
M._format_tool(output, part)
798850
content_added = true

lua/opencode/ui/highlight.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function M.setup()
3535
vim.api.nvim_set_hl(0, 'OpencodePickerTime', { link = 'Comment', default = true })
3636
vim.api.nvim_set_hl(0, 'OpencodeDebugText', { link = 'Comment', default = true })
3737
vim.api.nvim_set_hl(0, 'OpencodeReference', { fg = '#1976D2', default = true })
38+
vim.api.nvim_set_hl(0, 'OpencodeReasoningText', { link = 'Comment', default = true })
3839
else
3940
vim.api.nvim_set_hl(0, 'OpencodeBorder', { fg = '#616161', default = true })
4041
vim.api.nvim_set_hl(0, 'OpencodeBackground', { link = 'Normal', default = true })
@@ -66,6 +67,7 @@ function M.setup()
6667
vim.api.nvim_set_hl(0, 'OpencodePickerTime', { link = 'Comment', default = true })
6768
vim.api.nvim_set_hl(0, 'OpencodeDebugText', { link = 'Comment', default = true })
6869
vim.api.nvim_set_hl(0, 'OpencodeReference', { fg = '#7AA2F7', default = true })
70+
vim.api.nvim_set_hl(0, 'OpencodeReasoningText', { link = 'Comment', default = true })
6971
end
7072
end
7173

lua/opencode/ui/icons.lua

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,39 @@ local presets = {
77
nerdfonts = {
88
-- headers
99
header_user = '▌󰭻 ',
10-
header_assistant = ' ',
10+
header_assistant = ' ',
1111
-- actions/tools
12-
run = ' ',
13-
task = ' ',
14-
read = ' ',
15-
edit = ' ',
16-
write = ' ',
12+
run = ' ',
13+
task = ' ',
14+
read = ' ',
15+
edit = ' ',
16+
write = ' ',
1717
plan = '󰝖 ',
18-
search = ' ',
18+
search = ' ',
1919
web = '󰖟 ',
20-
list = ' ',
21-
tool = ' ',
20+
list = ' ',
21+
tool = ' ',
2222
snapshot = '󰻛 ',
2323
restore_point = '󱗚 ',
24-
file = ' ',
25-
folder = ' ',
24+
file = ' ',
25+
folder = ' ',
2626
attached_file = '󰌷 ',
2727
agent = '󰚩 ',
28-
reference = '',
28+
reference = ' ',
29+
reasoning = '󰧑 ',
2930
-- statuses
30-
status_on = ' ',
31-
status_off = ' ',
32-
guard_on = ' ',
31+
status_on = ' ',
32+
status_off = ' ',
33+
guard_on = ' ',
3334
-- borders and misc
3435
border = '',
3536
-- context bar
3637
cursor_data = '󰗧 ',
37-
error = ' ',
38-
warning = ' ',
39-
info = ' ',
38+
error = ' ',
39+
warning = ' ',
40+
info = ' ',
4041
selection = '󰫙 ',
41-
command = ' ',
42+
command = ' ',
4243
},
4344
text = {
4445
-- headers

tests/data/reasoning.expected.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"lines":["----","","","Explain your plan, then provide the final title.","","----","","","**󰧑 Reasoning (2025-12-17 19:33:20 - 2025-12-17 19:33:21)** ","","The user is asking me to explain my plan and then provide a final title. I need to think through what they're asking for. It seems like they want me to demonstrate reasoning capabilities - showing my thought process before giving an answer. Let me break this down into steps.","","1. First, I should understand what kind of title they want - is this for a project, document, or something else?","2. Since they didn't specify the context, I should provide a general approach","3. I'll demonstrate my reasoning process transparently","4. Then I'll give them a concrete example title","","Given that this appears to be related to the OpenCode project (based on the file structure I can see), I should provide a title that reflects the nature of this AI coding assistant tool.","","Based on my reasoning above, here's my plan:","","1. Acknowledge the request for demonstrating reasoning","2. Show transparency in my thought process","3. Provide context-appropriate suggestions","","For the title, considering this is the OpenCode Neovim plugin - an AI-powered coding assistant - a good title would be:","","**\"OpenCode.nvim: AI-Powered Coding Assistant for Neovim\"**","","This title clearly identifies what the tool is (OpenCode), the platform it works with (Neovim), and its primary function (AI-powered coding assistance).","",""],"actions":[],"extmarks":[[1,1,0,{"virt_text":[["▌󰭻 ","OpencodeMessageRoleUser"],[" "],["USER","OpencodeMessageRoleUser"],["","OpencodeHint"],[" (2025-12-17 19:33:20)","OpencodeHint"],[" [msg_reason_user1]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":10,"virt_text_repeat_linebreak":false,"right_gravity":true,"virt_text_win_col":-3,"ns_id":3}],[2,2,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"right_gravity":true,"virt_text_win_col":-3,"ns_id":3}],[3,3,0,{"virt_text":[["▌","OpencodeMessageRoleUser"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"right_gravity":true,"virt_text_win_col":-3,"ns_id":3}],[4,6,0,{"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["BUILD","OpencodeMessageRoleAssistant"],[" claude-sonnet-4","OpencodeHint"],[" (2025-12-17 19:33:20)","OpencodeHint"],[" [msg_reason_asst1]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":10,"virt_text_repeat_linebreak":false,"right_gravity":true,"virt_text_win_col":-3,"ns_id":3}],[5,8,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[6,9,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[7,10,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[8,11,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[9,12,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[10,13,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[11,14,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[12,15,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[13,16,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[14,17,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}],[15,18,0,{"line_hl_group":"OpencodeReasoningText","right_gravity":true,"ns_id":3,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_hide":false,"priority":4096,"virt_text_repeat_linebreak":true,"virt_text_win_col":-1}]],"timestamp":1766413340}

0 commit comments

Comments
 (0)