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: Use gitsigns.nvim to display inline diffs #267

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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/diffview/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local utils = lazy.require("diffview.utils") ---@module "diffview.utils"
local vcs_utils = lazy.require("diffview.vcs.utils") ---@module "diffview.vcs.utils"

local Diff1 = lazy.access("diffview.scene.layouts.diff_1", "Diff1") ---@type Diff1|LazyModule
local Diff1Inline = lazy.access("diffview.scene.layouts.diff_1_inline", "Diff1Inline") ---@type Diff1Inline|LazyModule
local Diff2Hor = lazy.access("diffview.scene.layouts.diff_2_hor", "Diff2Hor") ---@type Diff2Hor|LazyModule
local Diff2Ver = lazy.access("diffview.scene.layouts.diff_2_ver", "Diff2Ver") ---@type Diff2Ver|LazyModule
local Diff3 = lazy.access("diffview.scene.layouts.diff_3", "Diff3") ---@type Diff3|LazyModule
Expand Down Expand Up @@ -418,6 +419,7 @@ function M.cycle_layout()
standard = {
Diff2Hor.__get(),
Diff2Ver.__get(),
Diff1Inline.__get(),
},
merge_tool = {
Diff3Hor.__get(),
Expand Down
5 changes: 4 additions & 1 deletion lua/diffview/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local actions = require("diffview.actions")
local lazy = require("diffview.lazy")

local Diff1 = lazy.access("diffview.scene.layouts.diff_1", "Diff1") ---@type Diff1|LazyModule
local Diff1Inline = lazy.access("diffview.scene.layouts.diff_1_inline", "Diff1Inline") ---@type Diff1Inline|LazyModule
local Diff2 = lazy.access("diffview.scene.layouts.diff_2", "Diff2") ---@type Diff2|LazyModule
local Diff2Hor = lazy.access("diffview.scene.layouts.diff_2_hor", "Diff2Hor") ---@type Diff2Hor|LazyModule
local Diff2Ver = lazy.access("diffview.scene.layouts.diff_2_ver", "Diff2Ver") ---@type Diff2Ver|LazyModule
Expand Down Expand Up @@ -312,6 +313,7 @@ function M.get_log_options(single_file, t, vcs)
end

---@alias LayoutName "diff1_plain"
--- | "diff1_inline"
--- | "diff2_horizontal"
--- | "diff2_vertical"
--- | "diff3_horizontal"
Expand All @@ -321,6 +323,7 @@ end

local layout_map = {
diff1_plain = Diff1,
diff1_inline = Diff1Inline,
diff2_horizontal = Diff2Hor,
diff2_vertical = Diff2Ver,
diff3_horizontal = Diff3Hor,
Expand Down Expand Up @@ -510,7 +513,7 @@ function M.setup(user_config)
do
-- Validate layouts
local view = M._config.view
local standard_layouts = { "diff2_horizontal", "diff2_vertical", -1 }
local standard_layouts = { "diff2_horizontal", "diff2_vertical", "diff1_inline", -1 }
local merge_layuots = {
"diff1_plain",
"diff3_horizontal",
Expand Down
13 changes: 13 additions & 0 deletions lua/diffview/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ end
local hl = require("diffview.hl")
local lazy = require("diffview.lazy")

local StandardView = lazy.access("diffview.scene.views.standard.standard_view", "StandardView") ---@type StandardView|LazyModule
local arg_parser = lazy.require("diffview.arg_parser") ---@module "diffview.arg_parser"
local config = lazy.require("diffview.config") ---@module "diffview.config"
local lib = lazy.require("diffview.lib") ---@module "diffview.lib"
Expand Down Expand Up @@ -75,6 +76,18 @@ function M.init()
M.emit("refresh_files")
end,
})
au("User", {
group = M.augroup,
pattern = "GitSignsUpdate",
callback = function()
local view = lib.get_current_view()

if view and view:instanceof(StandardView.__get()) then
---@cast view StandardView
view.cur_layout:gs_update_folds()
end
end,
})

-- Set up user autocommand emitters
DiffviewGlobal.emitter:on("view_opened", function(_)
Expand Down
19 changes: 12 additions & 7 deletions lua/diffview/scene/file_entry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function FileEntry:convert_layout(target_layout)
end

self.layout = target_layout({
parent = self,
a = utils.tbl_access(self.layout, "a.file") or create_file(self.revs.a, "a"),
b = utils.tbl_access(self.layout, "b.file") or create_file(self.revs.b, "b"),
c = utils.tbl_access(self.layout, "c.file") or create_file(self.revs.c, "c"),
Expand Down Expand Up @@ -243,7 +244,7 @@ function FileEntry.with_layout(layout_class, opt)
}) --[[@as vcs.File ]]
end

return FileEntry({
local entry = FileEntry({
adapter = opt.adapter,
path = opt.path,
oldpath = opt.oldpath,
Expand All @@ -252,13 +253,17 @@ function FileEntry.with_layout(layout_class, opt)
kind = opt.kind,
commit = opt.commit,
revs = opt.revs,
layout = layout_class({
a = create_file(opt.revs.a, "a"),
b = create_file(opt.revs.b, "b"),
c = create_file(opt.revs.c, "c"),
d = create_file(opt.revs.d, "d"),
}),
})

entry.layout = layout_class({
parent = entry,
a = create_file(opt.revs.a, "a"),
b = create_file(opt.revs.b, "b"),
c = create_file(opt.revs.c, "c"),
d = create_file(opt.revs.d, "d"),
})

return entry
end

M.FileEntry = FileEntry
Expand Down
4 changes: 4 additions & 0 deletions lua/diffview/scene/layout.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ local api = vim.api
local M = {}

---@class Layout : diffview.Object
---@field parent FileEntry
---@field windows Window[]
---@field emitter EventEmitter
---@field pivot_producer fun(): integer?
local Layout = oop.create_class("Layout")

function Layout:init(opt)
opt = opt or {}
self.parent = opt.parent
self.windows = opt.windows or {}
self.emitter = opt.emitter or EventEmitter()

Expand Down Expand Up @@ -321,5 +323,7 @@ function Layout:sync_scroll()
end
end

function Layout:gs_update_folds() end

M.Layout = Layout
return M
2 changes: 1 addition & 1 deletion lua/diffview/scene/layouts/diff_1.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local lazy = require("diffview.lazy")
local Layout = require("diffview.scene.layout").Layout
local lazy = require("diffview.lazy")
local oop = require("diffview.oop")

local Diff3 = lazy.access("diffview.scene.layouts.diff_3", "Diff3") ---@type Diff3|LazyModule
Expand Down
27 changes: 27 additions & 0 deletions lua/diffview/scene/layouts/diff_1_inline.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local Diff1 = require("diffview.scene.layouts.diff_1").Diff1
local lazy = require("diffview.lazy")
local oop = require("diffview.oop")

local GitAdapter = lazy.access("diffview.vcs.adapters.git", "GitAdapter") ---@type GitAdapter|LazyModule

local M = {}

---@class Diff1Inline : Diff1
local Diff1Inline = oop.create_class("Diff1Inline", Diff1)

---@param opt Diff1.init.Opt
function Diff1Inline:init(opt)
Diff1Inline:super().init(self, opt)
end

function Diff1Inline:gs_update_folds()
if self.b:is_file_open()
and self.b.file.adapter:instanceof(GitAdapter.__get())
and self.b.file.kind ~= "conflicting"
then
self.b:gs_update_folds()
end
end

M.Diff1Inline = Diff1Inline
return M
76 changes: 73 additions & 3 deletions lua/diffview/scene/window.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
local lazy = require("diffview.lazy")
local oop = require("diffview.oop")

local Diff1 = lazy.access("diffview.scene.layouts.diff_1", "Diff1") ---@type Diff1|LazyModule
local File = lazy.access("diffview.vcs.file", "File") ---@type vcs.File|LazyModule
local FileHistoryView = lazy.access("diffview.scene.views.file_history.file_history_view", "FileHistoryView") ---@type FileHistoryView|LazyModule
local GitAdapter = lazy.access("diffview.vcs.adapters.git", "GitAdapter") ---@type GitAdapter|LazyModule
local RevType = lazy.access("diffview.vcs.rev", "RevType") ---@type RevType|LazyModule
local config = lazy.require("diffview.config") ---@module "diffview.config"
local gs_actions = lazy.require("gitsigns.actions") ---@module "gitsigns.actions"
local lib = lazy.require("diffview.lib") ---@module "diffview.lib"
local utils = lazy.require("diffview.utils") ---@module "diffview.utils"

Expand Down Expand Up @@ -69,6 +72,12 @@ function Window:pre_open()
end
end

function Window:is_file_open()
return self:is_valid()
and self.file:is_valid()
and api.nvim_win_get_buf(self.id) == self.file.bufnr
end

---@param callback fun(file: vcs.File)
function Window:load_file(callback)
assert(self.file)
Expand All @@ -95,11 +104,25 @@ function Window:open_file(callback)
self:_save_winopts()
end

self:apply_file_winopts()
local winopt_overrides
local base_rev = utils.tbl_access(self, "parent.parent.revs.a") --[[@as Rev? ]]
local use_inline_diff = self.file.kind ~= "conflicting"
and self.parent:instanceof(Diff1.__get())
and self.file.adapter:instanceof(GitAdapter.__get())

if use_inline_diff then
winopt_overrides = { foldmethod = "manual", diff = false }
end

self:apply_file_winopts(winopt_overrides)
self.file:attach_buffer(false, {
keymaps = config.get_layout_keymaps(self.parent),
disable_diagnostics = self.file.kind == "conflicting"
and conf.view.merge_tool.disable_diagnostics,
inline_diff = {
enabled = use_inline_diff,
base = base_rev and base_rev:object_name(),
}
})

if self:show_winbar_info() then
Expand Down Expand Up @@ -198,10 +221,15 @@ function Window:_restore_winopts()
end
end

function Window:apply_file_winopts()
---@param overrides WindowOptions?
function Window:apply_file_winopts(overrides)
assert(self.file)
if self.file.winopts then
utils.set_local(self.id, self.file.winopts)
if overrides then
utils.set_local(self.id, vim.tbl_extend("force", self.file.winopts, overrides))
else
utils.set_local(self.id, self.file.winopts)
end
end
end

Expand All @@ -219,5 +247,47 @@ function Window:set_file(file)
self.file = file
end

function Window:gs_update_folds()
if self:is_file_open() and vim.wo[self.id].foldenable then
api.nvim_win_call(self.id, function()
pcall(vim.cmd, "norm! zE") -- Delete all folds in window
local hunks = gs_actions.get_hunks(self.file.bufnr) or {}
local context

for _, v in ipairs(vim.opt.diffopt:get()) do
context = tonumber(v:match("^context:(%d+)"))
if context then break end
end

context = math.max(1, context or 6)

local prev_last = -context + 1
local lcount = api.nvim_buf_line_count(self.file.bufnr)

for i = 1, #hunks + 1 do
local hunk = hunks[i]
local first, last

if hunk then
first = hunk.added.start
last = first + hunk.added.count - 1
else
first = lcount + context
last = first
end

-- print(prev_last, first, last, hunk and "hunk" or "nil")

if first - prev_last > context * 2 + 1 then
-- print("FOLD:", prev_last + context, first - context)
vim.cmd(("%d,%dfold"):format(prev_last + context, first - context))
end

prev_last = last
end
end)
end
end

M.Window = Window
return M
49 changes: 43 additions & 6 deletions lua/diffview/vcs/file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ local GitRev = lazy.access("diffview.vcs.adapters.git.rev", "GitRev") ---@type G
local RevType = lazy.access("diffview.vcs.rev", "RevType") ---@type RevType|LazyModule
local async = lazy.require("plenary.async") ---@module "plenary.async"
local config = lazy.require("diffview.config") ---@module "diffview.config"
local debounce = lazy.require("diffview.debounce") ---@module "diffview.debounce"
local gs_actions = lazy.require("gitsigns.actions") ---@module "gitsigns.actions"
local lib = lazy.require("diffview.lib") ---@module "diffview.lib"
local utils = lazy.require("diffview.utils") ---@module "diffview.utils"

local pl = lazy.access(utils, "path") ---@type PathLib|LazyModule

local gs_refresh = debounce.debounce_trailing(20, false, vim.schedule_wrap(function(callback)
gs_actions.refresh()
if vim.is_callable(callback) then callback() end
end))

local api = vim.api
local M = {}

Expand Down Expand Up @@ -279,10 +286,18 @@ end
---@param t2 table
---@return vcs.File.AttachState
local function prepare_attach_opt(t1, t2)
local res = vim.tbl_extend("keep", t1, {
---@class vcs.File.AttachState
local default_opt = {
keymaps = {},
disable_diagnostics = false,
})
inline_diff = {
enabled = false,
base = nil --[[@as string? ]],
update = nil --[[@as function? ]],
}
}

local res = vim.tbl_extend("force", default_opt, t1)

for k, v in pairs(t2) do
local t = type(res[k])
Expand All @@ -299,10 +314,6 @@ local function prepare_attach_opt(t1, t2)
return res
end

---@class vcs.File.AttachState
---@field keymaps table
---@field disable_diagnostics boolean

---@param force? boolean
---@param opt? vcs.File.AttachState
function File:attach_buffer(force, opt)
Expand Down Expand Up @@ -332,6 +343,23 @@ function File:attach_buffer(force, opt)
vim.diagnostic.disable(self.bufnr)
end

-- Inline diff
if state.inline_diff.enabled then
local gitsigns = require("gitsigns")
local gs_config = require("gitsigns.config").config
gitsigns.attach(self.bufnr, {
file = self.path,
toplevel = self.adapter.ctx.toplevel,
gitdir = self.adapter.ctx.dir,
commit = self.rev.type ~= RevType.LOCAL and self.rev:object_name() or nil,
base = utils.sate(state.inline_diff.base, self.rev.type == RevType.STAGE and "HEAD"),
})
gs_config.linehl = true
gs_config.show_deleted = true
gs_config.word_diff = true
gs_refresh(state.inline_diff.update)
end

File.attached[self.bufnr] = state
end
end
Expand Down Expand Up @@ -359,6 +387,15 @@ function File:detach_buffer()
vim.diagnostic.enable(self.bufnr)
end

-- Inline diff
if state.inline_diff.enabled then
local gs_config = require("gitsigns.config").config
gs_config.linehl = false
gs_config.show_deleted = false
gs_config.word_diff = false
gs_refresh(state.inline_diff.update)
end

File.attached[self.bufnr] = nil
end
end
Expand Down