Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding mini.diff module as inline diff mechanism #210

Merged
merged 7 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lua/codecompanion/config.lua
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should give users the choice of a provider i.e. default or mini_diff. I think we should hardcode the revert_delay in too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a setting called config.display.inline.diff.diff_method that can be set to either default or mini_diff. I'm not sure if this is what you asking for.

I also removed the revert_delay setting and replaced it with a hardcoded value.

Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,8 @@ Use Markdown formatting and include the programming language name at the start o
layout = "vertical", -- vertical|horizontal|buffer
diff = {
enabled = true,
use_mini_diff = true,
revert_delay = 5 * 60 * 1000, -- Default: 5 minutes
close_chat_at = 240, -- Close an open chat buffer if the total columns of your display are less than...
layout = "vertical", -- vertical|horizontal
opts = { "internal", "filler", "closeoff", "algorithm:patience", "followwrap", "linematch:120" },
Expand Down
103 changes: 62 additions & 41 deletions lua/codecompanion/strategies/inline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -556,67 +556,88 @@ function Inline:append_to_buf(content)
self.classification.pos.col = col
end

if config.display.inline.diff.use_mini_diff then
local miniDiff = require("codecompanion.strategies.inline.miniDiff")
miniDiff.setup()
end

---Apply diff coloring to any replaced text
---@return nil
function Inline:start_diff()
if config.display.inline.diff.enabled == false then
return
end
if config.display.inline.diff.use_mini_diff then
local miniDiff = require("codecompanion.strategies.inline.miniDiff")
miniDiff.start_diff(self.context.bufnr)
else
-- Taken from the awesome:
-- https://github.com/S1M0N38/dante.nvim

-- Taken from the awesome:
-- https://github.com/S1M0N38/dante.nvim

-- Get current window properties
local wrap = vim.wo.wrap
local linebreak = vim.wo.linebreak
local breakindent = vim.wo.breakindent
vim.cmd("set diffopt=" .. table.concat(config.display.inline.diff.opts, ","))
-- Get current window properties
local wrap = vim.wo.wrap
local linebreak = vim.wo.linebreak
local breakindent = vim.wo.breakindent
vim.cmd("set diffopt=" .. table.concat(config.display.inline.diff.opts, ","))

-- Close the chat buffer
local last_chat = require("codecompanion").last_chat()
if last_chat and last_chat:is_visible() and config.display.inline.diff.close_chat_at > vim.o.columns then
last_chat:hide()
end
-- Close the chat buffer
local last_chat = require("codecompanion").last_chat()
if last_chat and last_chat:is_visible() and config.display.inline.diff.close_chat_at > vim.o.columns then
last_chat:hide()
end

-- Create the diff buffer
if config.display.inline.diff.layout == "vertical" then
vim.cmd("vsplit")
else
vim.cmd("split")
-- Create the diff buffer
if config.display.inline.diff.layout == "vertical" then
vim.cmd("vsplit")
else
vim.cmd("split")
end
self.diff.winnr = api.nvim_get_current_win()
self.diff.bufnr = api.nvim_create_buf(false, true)
api.nvim_win_set_buf(self.diff.winnr, self.diff.bufnr)
api.nvim_set_option_value("filetype", self.context.filetype, { buf = self.diff.bufnr })
api.nvim_set_option_value("wrap", wrap, { win = self.diff.winnr })
api.nvim_set_option_value("linebreak", linebreak, { win = self.diff.winnr })
api.nvim_set_option_value("breakindent", breakindent, { win = self.diff.winnr })

-- Set the diff buffer to the contents, prior to any modifications
api.nvim_buf_set_lines(self.diff.bufnr, 0, 0, true, self.diff.lines)
api.nvim_win_set_cursor(self.diff.winnr, { self.context.cursor_pos[1], self.context.cursor_pos[2] })

-- Begin diffing
api.nvim_set_current_win(self.diff.winnr)
vim.cmd("diffthis")
api.nvim_set_current_win(self.context.winnr)
vim.cmd("diffthis")
end
self.diff.winnr = api.nvim_get_current_win()
self.diff.bufnr = api.nvim_create_buf(false, true)
api.nvim_win_set_buf(self.diff.winnr, self.diff.bufnr)
api.nvim_set_option_value("filetype", self.context.filetype, { buf = self.diff.bufnr })
api.nvim_set_option_value("wrap", wrap, { win = self.diff.winnr })
api.nvim_set_option_value("linebreak", linebreak, { win = self.diff.winnr })
api.nvim_set_option_value("breakindent", breakindent, { win = self.diff.winnr })

-- Set the diff buffer to the contents, prior to any modifications
api.nvim_buf_set_lines(self.diff.bufnr, 0, 0, true, self.diff.lines)
api.nvim_win_set_cursor(self.diff.winnr, { self.context.cursor_pos[1], self.context.cursor_pos[2] })

-- Begin diffing
api.nvim_set_current_win(self.diff.winnr)
vim.cmd("diffthis")
api.nvim_set_current_win(self.context.winnr)
vim.cmd("diffthis")
end

---Accept the changes in the diff
---@return nil
function Inline:accept()
api.nvim_win_close(self.diff.winnr, false)
self.diff = {}
if config.display.inline.diff.use_mini_diff then
local miniDiff = require("codecompanion.strategies.inline.miniDiff")
miniDiff.accept(self.context.bufnr)
else
-- Original accept mechanism
api.nvim_win_close(self.diff.winnr, false)
self.diff = {}
end
end

---Reject the changes in the diff
---@return nil
function Inline:reject()
vim.cmd("diffoff")
api.nvim_win_close(self.diff.winnr, false)
api.nvim_buf_set_lines(self.context.bufnr, 0, -1, true, self.diff.lines)
self.diff = {}
if config.display.inline.diff.use_mini_diff then
local miniDiff = require("codecompanion.strategies.inline.miniDiff")
miniDiff.reject(self.context.bufnr)
else
-- Original reject mechanism
vim.cmd("diffoff")
api.nvim_win_close(self.diff.winnr, false)
api.nvim_buf_set_lines(self.context.bufnr, 0, -1, true, self.diff.lines)
self.diff = {}
end
end

return Inline
162 changes: 162 additions & 0 deletions lua/codecompanion/strategies/inline/miniDiff.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
local M = {}

local api = vim.api
local config = require("codecompanion").config
local log = require("codecompanion.utils.log")

local original_buffer_content = {}
local codecompanion_buffers = {}
local revert_timers = {}

local function is_valid_buffer(buf_id)
return buf_id and api.nvim_buf_is_valid(buf_id)
end

local function safe_get_lines(buf_id)
if not is_valid_buffer(buf_id) then
return {}
end
return api.nvim_buf_get_lines(buf_id, 0, -1, false)
end

local function set_diff_source(buf_id, source)
if is_valid_buffer(buf_id) then
vim.b[buf_id].diffCompGit = source
end
end

local codecompanion_source = {
name = "codecompanion",
attach = function(buf_id)
if not is_valid_buffer(buf_id) then
return false
end
original_buffer_content[buf_id] = safe_get_lines(buf_id)
set_diff_source(buf_id, "llm")
return true
end,
detach = function(buf_id)
original_buffer_content[buf_id] = nil
set_diff_source(buf_id, "git")
end,
}

local MiniDiff = require("mini.diff")
local git_source = MiniDiff.gen_source.git()

local function switch_to_codecompanion(buf_id)
if not codecompanion_buffers[buf_id] then
codecompanion_buffers[buf_id] = true
MiniDiff.disable(buf_id)
MiniDiff.enable(buf_id, { source = codecompanion_source })
M.update_diff(buf_id)
set_diff_source(buf_id, "llm")
end
end

local function switch_to_git(buf_id)
if codecompanion_buffers[buf_id] then
codecompanion_buffers[buf_id] = nil
MiniDiff.disable(buf_id)
MiniDiff.enable(buf_id, { source = git_source })
set_diff_source(buf_id, "git")
end
end

local function schedule_revert_to_git(buf_id, delay)
if revert_timers[buf_id] then
revert_timers[buf_id]:stop()
end
revert_timers[buf_id] = vim.defer_fn(function()
switch_to_git(buf_id)
revert_timers[buf_id] = nil
end, delay)
end

function M.start_diff(buf_id)
switch_to_codecompanion(buf_id)
M.update_diff(buf_id)
end

function M.accept(buf_id)
M.update_diff(buf_id)
local revert_delay = config.display.inline.diff.revert_delay or 5 * 60 * 1000
schedule_revert_to_git(buf_id, revert_delay)
end

function M.reject(buf_id)
if original_buffer_content[buf_id] then
api.nvim_buf_set_lines(buf_id, 0, -1, false, original_buffer_content[buf_id])
end
switch_to_git(buf_id)
end

function M.update_diff(buf_id)
if not is_valid_buffer(buf_id) then
return
end

local current_content = safe_get_lines(buf_id)
pcall(MiniDiff.set_ref_text, buf_id, original_buffer_content[buf_id] or {})
original_buffer_content[buf_id] = current_content
end

function M.force_git(buf_id)
buf_id = buf_id or api.nvim_get_current_buf()
switch_to_git(buf_id)
end

function M.force_codecompanion(buf_id)
buf_id = buf_id or api.nvim_get_current_buf()
if not is_valid_buffer(buf_id) then
log:error("Invalid buffer ID")
return
end

if not original_buffer_content[buf_id] then
original_buffer_content[buf_id] = safe_get_lines(buf_id)
end

switch_to_codecompanion(buf_id)
M.update_diff(buf_id)
end

function M.get_current_source(buf_id)
buf_id = buf_id or api.nvim_get_current_buf()
return vim.b[buf_id].diffCompGit or "git"
end

function M.setup()
local revert_delay = config.display.inline.diff.revert_delay or 5 * 60 * 1000

api.nvim_create_autocmd("User", {
pattern = "CodeCompanionInline*",
callback = function(args)
local buf_id = args.buf
if not is_valid_buffer(buf_id) then
return
end

if args.match == "CodeCompanionInlineStarted" then
switch_to_codecompanion(buf_id)
elseif args.match == "CodeCompanionInlineFinished" then
local current_content = safe_get_lines(buf_id)
pcall(MiniDiff.set_ref_text, buf_id, original_buffer_content[buf_id] or {})
original_buffer_content[buf_id] = current_content
schedule_revert_to_git(buf_id, revert_delay)
MiniDiff.toggle_overlay()
end
end,
})

api.nvim_create_autocmd("BufReadPost", {
callback = function(args)
local buf_id = args.buf
if is_valid_buffer(buf_id) and vim.b[buf_id].diffCompGit == nil then
set_diff_source(buf_id, "git")
end
end,
})
end

return M
Loading