Skip to content

Commit fbcf4ee

Browse files
committed
fix(formatter): ensure trailing slash for read dir
1 parent fca0534 commit fbcf4ee

2 files changed

Lines changed: 76 additions & 5 deletions

File tree

lua/opencode/ui/formatter.lua

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,11 @@ end
513513
---@param tool_type string Tool type (e.g., 'read', 'edit', 'write')
514514
---@param input FileToolInput data for the tool
515515
---@param metadata FileToolMetadata Metadata for the tool use
516+
---@param tool_output? string Tool output payload for detecting directory reads
516517
---@param duration_text? string
517-
function M._format_file_tool(output, tool_type, input, metadata, duration_text)
518-
local file_name = M._resolve_file_name(input and input.filePath or '')
518+
function M._format_file_tool(output, tool_type, input, metadata, tool_output, duration_text)
519+
local file_name = tool_type == 'read' and M._resolve_display_file_name(input and input.filePath or '', tool_output)
520+
or M._resolve_file_name(input and input.filePath or '')
519521

520522
local file_type = input and util.get_markdown_filetype(input.filePath) or ''
521523

@@ -679,6 +681,34 @@ function M._resolve_file_name(file_path)
679681
return absolute
680682
end
681683

684+
---@param file_path string
685+
---@param tool_output? string
686+
---@return boolean
687+
function M._is_directory_path(file_path, tool_output)
688+
if not file_path or file_path == '' then
689+
return false
690+
end
691+
692+
if vim.endswith(file_path, '/') then
693+
return true
694+
end
695+
696+
return type(tool_output) == 'string' and tool_output:match('<type>directory</type>') ~= nil
697+
end
698+
699+
---@param file_path string
700+
---@param tool_output? string
701+
---@return string
702+
function M._resolve_display_file_name(file_path, tool_output)
703+
local resolved = M._resolve_file_name(file_path)
704+
705+
if resolved ~= '' and M._is_directory_path(file_path, tool_output) and not vim.endswith(resolved, '/') then
706+
resolved = resolved .. '/'
707+
end
708+
709+
return resolved
710+
end
711+
682712
function M._resolve_grep_string(input)
683713
if not input then
684714
return ''
@@ -714,7 +744,14 @@ function M._format_tool(output, part, get_child_parts)
714744
if tool == 'bash' then
715745
M._format_bash_tool(output, input --[[@as BashToolInput]], metadata --[[@as BashToolMetadata]], duration_text)
716746
elseif tool == 'read' or tool == 'edit' or tool == 'write' then
717-
M._format_file_tool(output, tool, input --[[@as FileToolInput]], metadata --[[@as FileToolMetadata]], duration_text)
747+
M._format_file_tool(
748+
output,
749+
tool,
750+
input --[[@as FileToolInput]],
751+
metadata --[[@as FileToolMetadata]],
752+
tool_output,
753+
duration_text
754+
)
718755
elseif tool == 'todowrite' then
719756
M._format_todo_tool(output, part.state.title, input --[[@as TodoToolInput]], duration_text)
720757
elseif tool == 'glob' then
@@ -775,8 +812,9 @@ local tool_summary_handlers = {
775812
bash = function(_, input)
776813
return 'run', 'run', input.description or ''
777814
end,
778-
read = function(_, input)
779-
return 'read', 'read', M._resolve_file_name(input.filePath)
815+
read = function(part, input)
816+
local tool_output = part.state and part.state.output or nil
817+
return 'read', 'read', M._resolve_display_file_name(input.filePath, tool_output)
780818
end,
781819
edit = function(_, input)
782820
return 'edit', 'edit', M._resolve_file_name(input.filePath)

tests/unit/formatter_spec.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,37 @@ describe('formatter', function()
5858
assert.are.equal('**A1:** First line', output.lines[4])
5959
assert.are.equal('Second line', output.lines[5])
6060
end)
61+
62+
it('renders directory reads with trailing slash', function()
63+
local message = {
64+
info = {
65+
id = 'msg_1',
66+
role = 'assistant',
67+
sessionID = 'ses_1',
68+
},
69+
parts = {},
70+
}
71+
72+
local part = {
73+
id = 'prt_1',
74+
type = 'tool',
75+
tool = 'read',
76+
messageID = 'msg_1',
77+
sessionID = 'ses_1',
78+
state = {
79+
status = 'completed',
80+
input = {
81+
filePath = '/tmp/project',
82+
},
83+
output = '<path>/tmp/project</path>\n<type>directory</type>\n<entries>\nfoo\n</entries>',
84+
time = {
85+
start = 1,
86+
['end'] = 2,
87+
},
88+
},
89+
}
90+
91+
local output = formatter.format_part(part, message, true)
92+
assert.are.equal('** read** `/tmp/project/` 1s', output.lines[1])
93+
end)
6194
end)

0 commit comments

Comments
 (0)