@@ -61,7 +61,6 @@ function M._format_revert_message(session_data, start_idx)
6161 local message_text = stats .messages == 1 and ' message' or ' messages'
6262 local tool_text = stats .tool_calls == 1 and ' tool call' or ' tool calls'
6363
64- output :add_lines (M .separator )
6564 output :add_line (
6665 string.format (' > %d %s reverted, %d %s reverted' , stats .messages , message_text , stats .tool_calls , tool_text )
6766 )
@@ -95,12 +94,14 @@ function M._format_revert_message(session_data, start_idx)
9594 end
9695 end
9796 end
97+
98+ output :add_empty_line ()
9899 return output
99100end
100101
101102local function add_action (output , text , action_type , args , key , line )
102103 -- actions use api-indexing (e.g. 0 indexed)
103- line = (line or output :get_line_count ()) - 1
104+ line = (line or output :get_line_count ()) - 2
104105 output :add_action ({
105106 text = text ,
106107 type = action_type ,
154155function M .format_message_header (message )
155156 local output = Output .new ()
156157
158+ if message .info and message .info .id == ' __opencode_revert_message__' then
159+ output :add_lines (M .separator )
160+ return output
161+ end
162+
157163 output :add_lines (M .separator )
158164 local role = message .info .role or ' unknown'
159165 local icon = message .info .role == ' user' and icons .get (' header_user' ) or icons .get (' header_assistant' )
@@ -167,18 +173,13 @@ function M.format_message_header(message)
167173 local display_name
168174 if role == ' assistant' then
169175 local mode = message .info .mode
170- if mode and mode ~= ' ' then
171- display_name = mode :upper ()
172- else
173- -- For the most recent assistant message, show current_mode if mode is missing
174- -- This handles new messages that haven't been stamped yet
175- local is_last_message = # state .messages == 0 or message .info .id == state .messages [# state .messages ].info .id
176- if is_last_message and state .current_mode and state .current_mode ~= ' ' then
176+ if mode and mode ~= ' ' then
177+ display_name = mode :upper ()
178+ elseif state .current_mode and state .current_mode ~= ' ' then
177179 display_name = state .current_mode :upper ()
178180 else
179181 display_name = ' ASSISTANT'
180182 end
181- end
182183 else
183184 display_name = role :upper ()
184185 end
@@ -291,11 +292,32 @@ end
291292--- @param output Output Output object to write to
292293--- @param part OpencodeMessagePart
293294function M ._format_selection_context (output , part )
295+ local part_message = part ._message_context
294296 local json = context_module .decode_json_context (part .text or ' ' , ' selection' )
295297 if not json then
296298 return
297299 end
298- local start_line = output :get_line_count ()
300+ local start_line = output :get_line_count () + 1
301+
302+ if part_message and part_message .parts then
303+ for i , message_part in ipairs (part_message .parts ) do
304+ if message_part .id == part .id then
305+ local previous_part = part_message .parts [i - 1 ]
306+ if previous_part and previous_part .type == ' text' and previous_part .synthetic then
307+ local has_selection = context_module .decode_json_context (previous_part .text or ' ' , ' selection' ) ~= nil
308+ local has_cursor = context_module .decode_json_context (previous_part .text or ' ' , ' cursor-data' ) ~= nil
309+ local diagnostics = context_module .decode_json_context (previous_part .text or ' ' , ' diagnostics' )
310+ local has_diagnostics = diagnostics and diagnostics .content and type (diagnostics .content ) == ' table' and # diagnostics .content > 0
311+
312+ if has_selection or has_cursor or has_diagnostics then
313+ start_line = output :get_line_count ()
314+ end
315+ end
316+ break
317+ end
318+ end
319+ end
320+
299321 output :add_lines (vim .split (json .content or ' ' , ' \n ' ))
300322 output :add_empty_line ()
301323
@@ -359,6 +381,75 @@ function M._format_diagnostics_context(output, part)
359381 M .add_vertical_border (output , start_line , end_line , ' OpencodeMessageRoleUser' , - 3 )
360382end
361383
384+ local function get_visible_user_part_kind (part )
385+ if not part then
386+ return nil
387+ end
388+
389+ if part .type == ' file' and part .filename and part .filename ~= ' ' then
390+ return ' file'
391+ end
392+
393+ if part .type ~= ' text' or not part .text or part .text == ' ' then
394+ return nil
395+ end
396+
397+ if not part .synthetic then
398+ return ' text'
399+ end
400+
401+ if context_module .decode_json_context (part .text , ' selection' ) then
402+ return ' selection'
403+ end
404+
405+ if context_module .decode_json_context (part .text , ' cursor-data' ) then
406+ return ' cursor-data'
407+ end
408+
409+ local diagnostics = context_module .decode_json_context (part .text , ' diagnostics' )
410+ if diagnostics and diagnostics .content and type (diagnostics .content ) == ' table' and # diagnostics .content > 0 then
411+ return ' diagnostics'
412+ end
413+
414+ return nil
415+ end
416+
417+ local function get_user_part_neighbors (message , part )
418+ if not message or not message .parts or not part or not part .id then
419+ return nil , nil
420+ end
421+
422+ local current_index = nil
423+ for i , message_part in ipairs (message .parts ) do
424+ if message_part .id == part .id then
425+ current_index = i
426+ break
427+ end
428+ end
429+
430+ if not current_index then
431+ return nil , nil
432+ end
433+
434+ local previous_kind = nil
435+ for i = current_index - 1 , 1 , - 1 do
436+ previous_kind = get_visible_user_part_kind (message .parts [i ])
437+ if previous_kind then
438+ break
439+ end
440+ end
441+
442+ local next_kind = nil
443+ for i = current_index + 1 , # message .parts do
444+ next_kind = get_visible_user_part_kind (message .parts [i ])
445+ if next_kind then
446+ break
447+ end
448+ end
449+
450+ return previous_kind , next_kind
451+ end
452+
362453--- Format and display the file path in the context
363454--- @param output Output Output object to write to
364455--- @param path string | nil File path
@@ -450,19 +541,14 @@ end
450541--- @param win_col number
451542--- @param text_hl_group ? string Optional highlight group for the background /foreground of text lines
452543function M .add_vertical_border (output , start_line , end_line , hl_group , win_col , text_hl_group )
544+ local extmark_opts = {
545+ virt_text = { { require (' opencode.ui.icons' ).get (' border' ), hl_group } },
546+ virt_text_pos = ' overlay' ,
547+ virt_text_win_col = win_col ,
548+ virt_text_repeat_linebreak = true ,
549+ line_hl_group = text_hl_group or nil ,
550+ }
453551 for line = start_line , end_line do
454- local extmark_opts = {
455- virt_text = { { require (' opencode.ui.icons' ).get (' border' ), hl_group } },
456- virt_text_pos = ' overlay' ,
457- virt_text_win_col = win_col ,
458- virt_text_repeat_linebreak = true ,
459- }
460-
461- -- Add line highlight if text_hl_group is provided
462- if text_hl_group then
463- extmark_opts .line_hl_group = text_hl_group
464- end
465-
466552 output :add_extmark (line - 1 , extmark_opts --[[ @as OutputExtmark]] )
467553 end
468554end
@@ -486,17 +572,30 @@ function M.format_part(part, message, is_last_part, get_child_parts)
486572 if role == ' user' then
487573 if part .type == ' text' and part .text then
488574 if part .synthetic == true then
575+ part ._message_context = message
489576 M ._format_selection_context (output , part )
490577 M ._format_cursor_data_context (output , part )
491578 M ._format_diagnostics_context (output , part )
579+ part ._message_context = nil
492580 else
493581 M ._format_user_prompt (output , vim .trim (part .text ), message )
494582 content_added = true
495583 end
496584 elseif part .type == ' file' then
497585 local file_line = M ._format_context_file (output , part .filename )
498586 if file_line then
499- M .add_vertical_border (output , file_line - 1 , file_line , ' OpencodeMessageRoleUser' , - 3 )
587+ local previous_kind , next_kind = get_user_part_neighbors (message , part )
588+ local previous_is_context = previous_kind == ' selection'
589+ or previous_kind == ' cursor-data'
590+ or previous_kind == ' diagnostics'
591+
592+ if next_kind == ' text' or (previous_is_context and not next_kind ) then
593+ M .add_vertical_border (output , file_line - 1 , file_line , ' OpencodeMessageRoleUser' , - 3 )
594+ elseif next_kind == ' file' then
595+ M .add_vertical_border (output , file_line , file_line + 1 , ' OpencodeMessageRoleUser' , - 3 )
596+ else
597+ M .add_vertical_border (output , file_line , file_line , ' OpencodeMessageRoleUser' , - 3 )
598+ end
500599 content_added = true
501600 end
502601 end
0 commit comments