Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ require("diffview").setup({
{ "n", "<tab>", actions.select_entry, { desc = "Change the current option" } },
{ "n", "q", actions.close, { desc = "Close the panel" } },
},
help_panel = {
{ "n", "q", actions.close, { desc = "Close help menu" } },
{ "n", "<esc>", actions.close, { desc = "Close help menu" } },
},
},
})
```
Expand Down
4 changes: 4 additions & 0 deletions doc/diffview_defaults.txt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ require("diffview").setup({
{ "n", "<tab>", actions.select_entry, { desc = "Change the current option" } },
{ "n", "q", actions.close, { desc = "Close the panel" } },
},
help_panel = {
{ "n", "q", actions.close, { desc = "Close help menu" } },
{ "n", "<esc>", actions.close, { desc = "Close help menu" } },
},
},
})

Expand Down
12 changes: 12 additions & 0 deletions lua/diffview/actions.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local lazy = require("diffview.lazy")

local DiffView = lazy.access("diffview.scene.views.diff.diff_view", "DiffView") ---@type DiffView|LazyModule
local HelpPanel = lazy.access("diffview.ui.panels.help_panel", "HelpPanel") ---@type HelpPanel|LazyModule
local FileHistoryView = lazy.access("diffview.scene.views.file_history.file_history_view", "FileHistoryView") ---@type FileHistoryView|LazyModule
local StandardView = lazy.access("diffview.scene.views.standard.standard_view", "StandardView") ---@type StandardView|LazyModule
local vcs = lazy.require("diffview.vcs.utils") ---@module "diffview.vcs.utils"
Expand Down Expand Up @@ -465,6 +466,17 @@ function M.cycle_layout()
end
end

function M.help(conf_name)
return function()
local view = lib.get_current_view()

if view then
local help_panel = HelpPanel(view, conf_name) --[[@as HelpPanel ]]
help_panel:focus()
end
end
end

local action_names = {
"close",
"close_all_folds",
Expand Down
63 changes: 38 additions & 25 deletions lua/diffview/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ M.defaults = {
win_opts = {}
},
},
help_panel = {
win_config = {
win_opts = {}
},
},
default_args = {
DiffviewOpen = {},
DiffviewFileHistory = {},
Expand All @@ -120,6 +125,7 @@ M.defaults = {
{ "n", "<leader>cb", actions.conflict_choose("base"), { desc = "Choose the BASE version of a conflict" } },
{ "n", "<leader>ca", actions.conflict_choose("all"), { desc = "Choose all the versions of a conflict" } },
{ "n", "dx", actions.conflict_choose("none"), { desc = "Delete the conflict region" } },
{ "n", "g?", actions.help("view"), { desc = "Open the help panel" } },
},
diff1 = { --[[ Mappings in single window diff layouts ]] },
diff2 = { --[[ Mappings in 2-way diff layouts ]] },
Expand Down Expand Up @@ -162,35 +168,42 @@ M.defaults = {
{ "n", "g<C-x>", actions.cycle_layout, { desc = "Cycle available layouts" } },
{ "n", "[x", actions.prev_conflict, { desc = "Go to the previous conflict" } },
{ "n", "]x", actions.next_conflict, { desc = "Go to the next conflict" } },
{ "n", "g?", actions.help("file_panel"), { desc = "Open the help panel" } },
},
file_history_panel = {
{ "n", "g!", actions.options, { desc = "Open the option panel" } },
{ "n", "<C-A-d>", actions.open_in_diffview, { desc = "Open the entry under the cursor in a diffview" } },
{ "n", "y", actions.copy_hash, { desc = "Copy the commit hash of the entry under the cursor" } },
{ "n", "L", actions.open_commit_log, { desc = "Show commit details" } },
{ "n", "zR", actions.open_all_folds, { desc = "Expand all folds" } },
{ "n", "zM", actions.close_all_folds, { desc = "Collapse all folds" } },
{ "n", "j", actions.next_entry, { desc = "Bring the cursor to the next file entry" } },
{ "n", "<down>", actions.next_entry, { desc = "Bring the cursor to the next file entry" } },
{ "n", "k", actions.prev_entry, { desc = "Bring the cursor to the previous file entry." } },
{ "n", "<up>", actions.prev_entry, { desc = "Bring the cursor to the previous file entry." } },
{ "n", "<cr>", actions.select_entry, { desc = "Open the diff for the selected entry." } },
{ "n", "o", actions.select_entry, { desc = "Open the diff for the selected entry." } },
{ "n", "<2-LeftMouse>", actions.select_entry, { desc = "Open the diff for the selected entry." } },
{ "n", "<c-b>", actions.scroll_view(-0.25), { desc = "Scroll the view up" } },
{ "n", "<c-f>", actions.scroll_view(0.25), { desc = "Scroll the view down" } },
{ "n", "<tab>", actions.select_next_entry, { desc = "Open the diff for the next file" } },
{ "n", "<s-tab>", actions.select_prev_entry, { desc = "Open the diff for the previous file" } },
{ "n", "gf", actions.goto_file, { desc = "Open the file in a new split in the previous tabpage" } },
{ "n", "<C-w><C-f>", actions.goto_file_split, { desc = "Open the file in a new split" } },
{ "n", "<C-w>gf", actions.goto_file_tab, { desc = "Open the file in a new tabpage" } },
{ "n", "<leader>e", actions.focus_files, { desc = "Bring focus to the file panel" } },
{ "n", "<leader>b", actions.toggle_files, { desc = "Toggle the file panel" } },
{ "n", "g<C-x>", actions.cycle_layout, { desc = "Cycle available layouts" } },
{ "n", "g!", actions.options, { desc = "Open the option panel" } },
{ "n", "<C-A-d>", actions.open_in_diffview, { desc = "Open the entry under the cursor in a diffview" } },
{ "n", "y", actions.copy_hash, { desc = "Copy the commit hash of the entry under the cursor" } },
{ "n", "L", actions.open_commit_log, { desc = "Show commit details" } },
{ "n", "zR", actions.open_all_folds, { desc = "Expand all folds" } },
{ "n", "zM", actions.close_all_folds, { desc = "Collapse all folds" } },
{ "n", "j", actions.next_entry, { desc = "Bring the cursor to the next file entry" } },
{ "n", "<down>", actions.next_entry, { desc = "Bring the cursor to the next file entry" } },
{ "n", "k", actions.prev_entry, { desc = "Bring the cursor to the previous file entry." } },
{ "n", "<up>", actions.prev_entry, { desc = "Bring the cursor to the previous file entry." } },
{ "n", "<cr>", actions.select_entry, { desc = "Open the diff for the selected entry." } },
{ "n", "o", actions.select_entry, { desc = "Open the diff for the selected entry." } },
{ "n", "<2-LeftMouse>", actions.select_entry, { desc = "Open the diff for the selected entry." } },
{ "n", "<c-b>", actions.scroll_view(-0.25), { desc = "Scroll the view up" } },
{ "n", "<c-f>", actions.scroll_view(0.25), { desc = "Scroll the view down" } },
{ "n", "<tab>", actions.select_next_entry, { desc = "Open the diff for the next file" } },
{ "n", "<s-tab>", actions.select_prev_entry, { desc = "Open the diff for the previous file" } },
{ "n", "gf", actions.goto_file, { desc = "Open the file in a new split in the previous tabpage" } },
{ "n", "<C-w><C-f>", actions.goto_file_split, { desc = "Open the file in a new split" } },
{ "n", "<C-w>gf", actions.goto_file_tab, { desc = "Open the file in a new tabpage" } },
{ "n", "<leader>e", actions.focus_files, { desc = "Bring focus to the file panel" } },
{ "n", "<leader>b", actions.toggle_files, { desc = "Toggle the file panel" } },
{ "n", "g<C-x>", actions.cycle_layout, { desc = "Cycle available layouts" } },
{ "n", "g?", actions.help("file_history_panel"), { desc = "Open the help panel" } },
},
option_panel = {
{ "n", "<tab>", actions.select_entry, { desc = "Change the current option" } },
{ "n", "q", actions.close, { desc = "Close the panel" } },
{ "n", "<tab>", actions.select_entry, { desc = "Change the current option" } },
{ "n", "q", actions.close, { desc = "Close the panel" } },
{ "n", "g?", actions.help("option_panel"), { desc = "Open the help panel" } },
},
help_panel = {
{ "n", "q", actions.close, { desc = "Close help menu" } },
{ "n", "<esc>", actions.close, { desc = "Close help menu" } },
},
},
}
Expand Down
165 changes: 165 additions & 0 deletions lua/diffview/ui/panels/help_panel.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
local Panel = require("diffview.ui.panel").Panel
local get_user_config = require("diffview.config").get_config
local oop = require("diffview.oop")
local utils = require("diffview.utils")

local api = vim.api

local M = {}

---@class HelpPanel : Panel
---@field keymap_name string
---@field lines string[]
---@field maps table[]
local HelpPanel = oop.create_class("HelpPanel", Panel)

HelpPanel.winopts = vim.tbl_extend("force", Panel.winopts, {
wrap = false,
breakindent = true,
signcolumn = "no",
})

HelpPanel.bufopts = vim.tbl_extend("force", Panel.bufopts, {
buftype = "nofile",
})

HelpPanel.default_type = "float"

---@class HelpPanelSpec
---@field parent StandardView
---@field config PanelConfig
---@field name string

---@param parent StandardView
---@param keymap_name string
---@param opt HelpPanelSpec
function HelpPanel:init(parent, keymap_name, opt)
opt = opt or {}
HelpPanel:super().init(self, {
bufname = opt.name,
config = opt.config or get_user_config().help_panel.win_config,
})

self.parent = parent
self.keymap_name = keymap_name
self.lines = {}

self:on_autocmd("BufWinEnter", {
callback = function()
vim.bo[self.bufid].bufhidden = "wipe"
end,
})

self:on_autocmd("WinLeave", {
callback = function()
pcall(self.close, self)
end,
})

parent.emitter:on("close", function(e)
if self:is_focused() then
pcall(self.close, self)
e:stop_propagation()
end
end)
end

function HelpPanel:apply_cmd()
local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
local mapping = self.maps[row-2]
local last_winid = vim.fn.win_getid(vim.fn.winnr("#"))

if mapping then
api.nvim_win_call(last_winid, function()
api.nvim_feedkeys(utils.t(mapping[2]), "m", false)
end)

pcall(self.close, self)
end
end

function HelpPanel:init_buffer()
HelpPanel:super().init_buffer(self)
local conf = get_user_config().keymaps
local default_opt = { silent = true, nowait = true, buffer = self.bufid }

for _, mapping in ipairs(conf.help_panel) do
local map_opt = vim.tbl_extend("force", default_opt, mapping[4] or {}, { buffer = self.bufid })
vim.keymap.set(mapping[1], mapping[2], mapping[3], map_opt)
end

vim.keymap.set("n", "<cr>", function()
self:apply_cmd()
end, default_opt)
end

function HelpPanel:update_components()
local keymaps = get_user_config().keymaps
local maps = keymaps[self.keymap_name]

if not maps then
utils.err(("Unknown keymap group '%s'!"):format(self.keymap_name))
else
maps = vim.tbl_map(function(v)
local desc = v[4] and v[4].desc

if not desc then
if type(v[3]) == "string" then
desc = v[3]
elseif type(v[3]) == "function" then
local info = debug.getinfo(v[3], "S")
desc = ("<Lua @ %s:%d>"):format(info.short_src, info.linedefined)
end
end

return utils.vec_join(v, desc)
end, maps)
-- Sort mappings by description
table.sort(maps, function(a, b)
a, b = a[5], b[5]
-- Ensure lua functions are sorted last
if a:match("^<Lua") then a = "~" .. a end
if b:match("^<Lua") then b = "~" .. b end
return a < b
end)

local lines = { "" }
local max_width = 0

for _, mapping in ipairs(maps) do
local txt = string.format("%14s -> %s", mapping[2], mapping[5])

max_width = math.max(max_width, #txt)
table.insert(lines, txt)
end

local height = #lines + 1
local width = max_width + 2
local title_line = ("Keymaps for '%s' — <cr> to use"):format(self.keymap_name)
title_line = string.rep(" ", math.floor(width * 0.5 - #title_line * 0.5) - 1) .. title_line
table.insert(lines, 1, title_line)

self.maps = maps
self.lines = lines

self.config_producer = function()
local c = vim.deepcopy(Panel.default_config_float)
local viewport_width = vim.o.columns
local viewport_height = vim.o.lines
c.col = math.floor(viewport_width * 0.5 - width * 0.5)
c.row = math.floor(viewport_height * 0.5 - height * 0.5)
c.width = width
c.height = height

return c
end
end
end

function HelpPanel:render()
self.render_data:clear()
self.render_data.lines = utils.vec_slice(self.lines or {})
end

M.HelpPanel = HelpPanel
return M