Skip to content

Commit

Permalink
feat(diff): add option to show full diff with diff mode
Browse files Browse the repository at this point in the history
Add new `full_diff` option to show_diff mapping which allows showing a full
diff view instead of unified diff view. When enabled, it will show the
modified file content side by side with original file using Vim's built-in
diff mode.

The full diff mode provides better visualization of changes and allows using
Vim's diff navigation commands to review changes.

Closes #705
  • Loading branch information
deathbeam committed Feb 4, 2025
1 parent 36ae22b commit bc5434e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 37 deletions.
12 changes: 8 additions & 4 deletions lua/CopilotChat/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ local utils = require('CopilotChat.utils')
---@field insert string?
---@field detail string?

---@class CopilotChat.config.mapping.register : CopilotChat.config.mapping
---@class CopilotChat.config.mapping.yank_diff : CopilotChat.config.mapping
---@field register string?

---@class CopilotChat.config.mapping.show_diff : CopilotChat.config.mapping
---@field full_diff boolean?

---@class CopilotChat.config.mappings
---@field complete CopilotChat.config.mapping?
---@field close CopilotChat.config.mapping?
Expand All @@ -42,8 +45,8 @@ local utils = require('CopilotChat.utils')
---@field accept_diff CopilotChat.config.mapping?
---@field jump_to_diff CopilotChat.config.mapping?
---@field quickfix_diffs CopilotChat.config.mapping?
---@field yank_diff CopilotChat.config.mapping.register?
---@field show_diff CopilotChat.config.mapping?
---@field yank_diff CopilotChat.config.mapping.yank_diff?
---@field show_diff CopilotChat.config.mapping.show_diff?
---@field show_info CopilotChat.config.mapping?
---@field show_context CopilotChat.config.mapping?
---@field show_help CopilotChat.config.mapping?
Expand Down Expand Up @@ -391,10 +394,11 @@ return {
},
yank_diff = {
normal = 'gy',
register = '"',
register = '"', -- Default register to use for yanking
},
show_diff = {
normal = 'gd',
full_diff = false, -- Show full diff instead of unified diff when showing diff window
},
show_info = {
normal = 'gi',
Expand Down
31 changes: 15 additions & 16 deletions lua/CopilotChat/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ local context = require('CopilotChat.context')
local prompts = require('CopilotChat.prompts')
local utils = require('CopilotChat.utils')

local Chat = require('CopilotChat.ui.chat')
local Diff = require('CopilotChat.ui.diff')
local Overlay = require('CopilotChat.ui.overlay')
local Debug = require('CopilotChat.ui.debug')

local M = {}
local PLUGIN_NAME = 'CopilotChat'
local WORD = '([^%s]+)'
Expand Down Expand Up @@ -959,34 +954,38 @@ function M.setup(config)
if state.overlay then
state.overlay:delete()
end
state.overlay = Overlay('copilot-overlay', overlay_help, function(bufnr)
state.overlay = require('CopilotChat.ui.overlay')('copilot-overlay', overlay_help, function(bufnr)
map_key('close', bufnr, function()
state.overlay:restore(state.chat.winnr, state.chat.bufnr)
end)
end)

if not state.debug then
state.debug = Debug()
state.debug = require('CopilotChat.ui.debug')()
end

if state.diff then
state.diff:delete()
end
state.diff = Diff(diff_help, function(bufnr)
map_key('close', bufnr, function()
state.diff:restore(state.chat.winnr, state.chat.bufnr)
end)
state.diff = require('CopilotChat.ui.diff')(
M.config.mappings.show_diff.full_diff,
diff_help,
function(bufnr)
map_key('close', bufnr, function()
state.diff:restore(state.chat.winnr, state.chat.bufnr)
end)

map_key('accept_diff', bufnr, function()
apply_diff(state.diff:get_diff(), state.chat.config)
end)
end)
map_key('accept_diff', bufnr, function()
apply_diff(state.diff:get_diff(), state.chat.config)
end)
end
)

if state.chat then
state.chat:close(state.source and state.source.bufnr or nil)
state.chat:delete()
end
state.chat = Chat(
state.chat = require('CopilotChat.ui.chat')(
M.config.question_header,
M.config.answer_header,
M.config.separator,
Expand Down
85 changes: 68 additions & 17 deletions lua/CopilotChat/ui/diff.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ local class = utils.class
---@class CopilotChat.ui.Diff : CopilotChat.ui.Overlay
---@field hl_ns number
---@field diff CopilotChat.ui.Diff.Diff?
local Diff = class(function(self, help, on_buf_create)
---@field augroup number
---@field full_diff boolean
local Diff = class(function(self, full_diff, help, on_buf_create)
Overlay.init(self, 'copilot-diff', help, on_buf_create)
self.hl_ns = vim.api.nvim_create_namespace('copilot-chat-highlights')
vim.api.nvim_set_hl(self.hl_ns, '@diff.plus', { bg = utils.blend_color('DiffAdd', 20) })
vim.api.nvim_set_hl(self.hl_ns, '@diff.minus', { bg = utils.blend_color('DiffDelete', 20) })
vim.api.nvim_set_hl(self.hl_ns, '@diff.delta', { bg = utils.blend_color('DiffChange', 20) })

self.augroup = vim.api.nvim_create_augroup('CopilotChatDiff', { clear = true })
self.full_diff = full_diff
self.diff = nil
end, Overlay)

Expand All @@ -31,27 +35,74 @@ function Diff:show(diff, winnr)
self:validate()
vim.api.nvim_win_set_hl_ns(winnr, self.hl_ns)

Overlay.show(
self,
tostring(vim.diff(diff.reference, diff.change, {
result_type = 'unified',
ignore_blank_lines = true,
ignore_whitespace = true,
ignore_whitespace_change = true,
ignore_whitespace_change_at_eol = true,
ignore_cr_at_eol = true,
algorithm = 'myers',
ctxlen = #diff.reference,
})),
winnr,
diff.filetype,
'diff'
)
if not self.full_diff then
-- Create unified diff view
Overlay.show(
self,
tostring(vim.diff(diff.reference, diff.change, {
result_type = 'unified',
ignore_blank_lines = true,
ignore_whitespace = true,
ignore_whitespace_change = true,
ignore_whitespace_change_at_eol = true,
ignore_cr_at_eol = true,
algorithm = 'myers',
ctxlen = #diff.reference,
})),
winnr,
diff.filetype,
'diff'
)

return
end

-- Create modified version by applying the change
local modified = {}
if diff.bufnr and utils.buf_valid(diff.bufnr) then
modified = vim.api.nvim_buf_get_lines(diff.bufnr, 0, -1, false)
end
local change_lines = vim.split(diff.change, '\n')

-- Replace the lines in the modified content
if #modified > 0 then
local start_idx = diff.start_line - 1
local end_idx = diff.end_line - 1
for _ = start_idx, end_idx do
table.remove(modified, start_idx)
end
for i, line in ipairs(change_lines) do
table.insert(modified, start_idx + i - 1, line)
end
else
modified = change_lines
end

Overlay.show(self, table.concat(modified, '\n'), winnr, diff.filetype)

if diff.bufnr and vim.api.nvim_buf_is_valid(diff.bufnr) then
vim.cmd('diffthis')
vim.api.nvim_set_current_win(vim.fn.bufwinid(diff.bufnr))
vim.api.nvim_win_set_cursor(0, { diff.start_line, 0 })
vim.cmd('diffthis')
vim.api.nvim_set_current_win(winnr)
vim.api.nvim_win_set_cursor(winnr, { diff.start_line, 0 })

-- Link diff buffers lifecycle
vim.api.nvim_create_autocmd('BufWipeout', {
group = self.augroup,
buffer = self.bufnr,
callback = function()
vim.cmd('diffoff')
end,
})
end
end

---@param winnr number
---@param bufnr number
function Diff:restore(winnr, bufnr)
vim.cmd('diffoff')
Overlay.restore(self, winnr, bufnr)
vim.api.nvim_win_set_hl_ns(winnr, 0)
end
Expand Down

0 comments on commit bc5434e

Please sign in to comment.