diff --git a/lua/diffview/actions.lua b/lua/diffview/actions.lua index a84b9426..5dda8ebf 100644 --- a/lua/diffview/actions.lua +++ b/lua/diffview/actions.lua @@ -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 @@ -418,6 +419,7 @@ function M.cycle_layout() standard = { Diff2Hor.__get(), Diff2Ver.__get(), + Diff1Inline.__get(), }, merge_tool = { Diff3Hor.__get(), diff --git a/lua/diffview/config.lua b/lua/diffview/config.lua index 844351bb..4399db0b 100644 --- a/lua/diffview/config.lua +++ b/lua/diffview/config.lua @@ -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 @@ -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" @@ -321,6 +323,7 @@ end local layout_map = { diff1_plain = Diff1, + diff1_inline = Diff1Inline, diff2_horizontal = Diff2Hor, diff2_vertical = Diff2Ver, diff3_horizontal = Diff3Hor, @@ -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", diff --git a/lua/diffview/init.lua b/lua/diffview/init.lua index 22194a89..ee393fbc 100644 --- a/lua/diffview/init.lua +++ b/lua/diffview/init.lua @@ -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" @@ -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(_) diff --git a/lua/diffview/scene/file_entry.lua b/lua/diffview/scene/file_entry.lua index 7529c764..c8d2896c 100644 --- a/lua/diffview/scene/file_entry.lua +++ b/lua/diffview/scene/file_entry.lua @@ -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"), @@ -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, @@ -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 diff --git a/lua/diffview/scene/layout.lua b/lua/diffview/scene/layout.lua index 46555578..5fc5e1e4 100644 --- a/lua/diffview/scene/layout.lua +++ b/lua/diffview/scene/layout.lua @@ -8,6 +8,7 @@ local api = vim.api local M = {} ---@class Layout : diffview.Object +---@field parent FileEntry ---@field windows Window[] ---@field emitter EventEmitter ---@field pivot_producer fun(): integer? @@ -15,6 +16,7 @@ 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() @@ -321,5 +323,7 @@ function Layout:sync_scroll() end end +function Layout:gs_update_folds() end + M.Layout = Layout return M diff --git a/lua/diffview/scene/layouts/diff_1.lua b/lua/diffview/scene/layouts/diff_1.lua index d7561f17..63b160d6 100644 --- a/lua/diffview/scene/layouts/diff_1.lua +++ b/lua/diffview/scene/layouts/diff_1.lua @@ -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 diff --git a/lua/diffview/scene/layouts/diff_1_inline.lua b/lua/diffview/scene/layouts/diff_1_inline.lua new file mode 100644 index 00000000..81f3e2e7 --- /dev/null +++ b/lua/diffview/scene/layouts/diff_1_inline.lua @@ -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 diff --git a/lua/diffview/scene/window.lua b/lua/diffview/scene/window.lua index f361d382..b3aaac78 100644 --- a/lua/diffview/scene/window.lua +++ b/lua/diffview/scene/window.lua @@ -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" @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/lua/diffview/vcs/file.lua b/lua/diffview/vcs/file.lua index 5fa88077..bb0cc74f 100644 --- a/lua/diffview/vcs/file.lua +++ b/lua/diffview/vcs/file.lua @@ -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 = {} @@ -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]) @@ -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) @@ -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 @@ -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