@@ -939,40 +939,98 @@ function M._format_code(output, lines, language)
939939 output :add_line (' `````' )
940940end
941941
942- --- @param output Output Output object to write to
943- --- @param code string
944- --- @param file_type string
942+ --- @param lines string[]
943+ local function parse_diff_line_numbers (lines )
944+ local numbered_lines = {}
945+ local old_line
946+ local new_line
947+ local max_line_number = 0
948+
949+ for idx , line in ipairs (lines ) do
950+ local old_start , new_start = line :match (' ^@@ %-(%d+),?%d* %+(%d+),?%d* @@' )
951+
952+ if old_start and new_start then
953+ old_line = tonumber (old_start )
954+ new_line = tonumber (new_start )
955+ elseif old_line and new_line then
956+ local first_char = line :sub (1 , 1 )
957+
958+ if first_char == ' ' then
959+ numbered_lines [idx ] = { old = old_line , new = new_line }
960+ max_line_number = math.max (max_line_number , old_line , new_line )
961+ old_line = old_line + 1
962+ new_line = new_line + 1
963+ elseif first_char == ' +' and not line :match (' ^%+%+%+' ) then
964+ numbered_lines [idx ] = { old = nil , new = new_line }
965+ max_line_number = math.max (max_line_number , new_line )
966+ new_line = new_line + 1
967+ elseif first_char == ' -' and not line :match (' ^%-%-%-' ) then
968+ numbered_lines [idx ] = { old = old_line , new = nil }
969+ max_line_number = math.max (max_line_number , old_line )
970+ old_line = old_line + 1
971+ end
972+ end
973+ end
974+
975+ return numbered_lines , math.max (# tostring (max_line_number ), 4 )
976+ end
977+
978+ local function build_diff_gutter (line_numbers , width )
979+ local line_number = line_numbers .new or line_numbers .old
980+ return string.format (' %-' .. width .. ' s' , line_number and tostring (line_number ) or ' ' )
981+ end
982+
983+ local function add_diff_line (output , line , line_numbers , width )
984+ local first_char = line :sub (1 , 1 )
985+ local line_hl = first_char == ' +' and ' OpencodeDiffAdd' or first_char == ' -' and ' OpencodeDiffDelete' or nil
986+ local gutter_hl = first_char == ' +' and ' OpencodeDiffAddGutter'
987+ or first_char == ' -' and ' OpencodeDiffDeleteGutter'
988+ or ' OpencodeDiffGutter'
989+ local sign_hl = gutter_hl
990+ local gutter = build_diff_gutter (line_numbers , width )
991+ local gutter_width = # gutter + 2
992+
993+ output :add_line (string.rep (' ' , gutter_width ) .. line :sub (2 ))
994+
995+ local line_idx = output :get_line_count ()
996+ local extmark = {
997+ end_col = 0 ,
998+ end_row = line_idx ,
999+ virt_text = {
1000+ { gutter , gutter_hl },
1001+ { first_char , sign_hl },
1002+ { ' ' , gutter_hl },
1003+ },
1004+ priority = 5000 ,
1005+ right_gravity = true ,
1006+ end_right_gravity = false ,
1007+ virt_text_hide = false ,
1008+ virt_text_pos = ' overlay' ,
1009+ virt_text_repeat_linebreak = false ,
1010+ }
1011+
1012+ if line_hl then
1013+ extmark .hl_group = line_hl
1014+ extmark .hl_eol = true
1015+ end
1016+
1017+ output :add_extmark (line_idx - 1 , extmark --[[ @as OutputExtmark]] )
1018+ end
1019+
9451020function M .format_diff (output , code , file_type )
9461021 output :add_empty_line ()
9471022
9481023 --- NOTE: use longer code fence because code could contain ```
9491024 output :add_line (' `````' .. file_type )
950- local lines = vim .split (code , ' \n ' )
951- if # lines > 5 then
952- lines = vim .list_slice (lines , 6 )
953- end
954-
955- for _ , line in ipairs (lines ) do
956- local first_char = line :sub (1 , 1 )
957- if first_char == ' +' or first_char == ' -' then
958- local hl_group = first_char == ' +' and ' OpencodeDiffAdd' or ' OpencodeDiffDelete'
959- output :add_line (' ' .. line :sub (2 ))
960- local line_idx = output :get_line_count ()
961- output :add_extmark (line_idx - 1 , function ()
962- return {
963- end_col = 0 ,
964- end_row = line_idx ,
965- virt_text = { { first_char , hl_group } },
966- hl_group = hl_group ,
967- hl_eol = true ,
968- priority = 5000 ,
969- right_gravity = true ,
970- end_right_gravity = false ,
971- virt_text_hide = false ,
972- virt_text_pos = ' overlay' ,
973- virt_text_repeat_linebreak = false ,
974- }
975- end )
1025+ local full_lines = vim .split (code , ' \n ' )
1026+ local numbered_lines , line_number_width = parse_diff_line_numbers (full_lines )
1027+ local first_visible_line = # full_lines > 5 and 6 or 1
1028+ local lines = first_visible_line > 1 and vim .list_slice (full_lines , first_visible_line ) or full_lines
1029+
1030+ for idx , line in ipairs (lines ) do
1031+ local source_idx = first_visible_line + idx - 1
1032+ if numbered_lines [source_idx ] then
1033+ add_diff_line (output , line , numbered_lines [source_idx ], line_number_width )
9761034 else
9771035 output :add_line (line )
9781036 end
0 commit comments