diff --git a/README.md b/README.md index 400e9d2f..7f892069 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,10 @@ require("diffview").setup({ { "n", "", 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", "", actions.close, { desc = "Close help menu" } }, + }, }, }) ``` diff --git a/doc/diffview_defaults.txt b/doc/diffview_defaults.txt index f2cfaac6..4f917107 100644 --- a/doc/diffview_defaults.txt +++ b/doc/diffview_defaults.txt @@ -172,6 +172,10 @@ require("diffview").setup({ { "n", "", 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", "", actions.close, { desc = "Close help menu" } }, + }, }, }) diff --git a/lua/diffview/actions.lua b/lua/diffview/actions.lua index 04c49a33..564d022a 100644 --- a/lua/diffview/actions.lua +++ b/lua/diffview/actions.lua @@ -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" @@ -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", diff --git a/lua/diffview/config.lua b/lua/diffview/config.lua index bbc1076e..4ff547dc 100644 --- a/lua/diffview/config.lua +++ b/lua/diffview/config.lua @@ -94,6 +94,11 @@ M.defaults = { win_opts = {} }, }, + help_panel = { + win_config = { + win_opts = {} + }, + }, default_args = { DiffviewOpen = {}, DiffviewFileHistory = {}, @@ -120,6 +125,7 @@ M.defaults = { { "n", "cb", actions.conflict_choose("base"), { desc = "Choose the BASE version of a conflict" } }, { "n", "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 ]] }, @@ -162,35 +168,42 @@ M.defaults = { { "n", "g", 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", "", 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", "", 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", "", actions.prev_entry, { desc = "Bring the cursor to the previous file entry." } }, - { "n", "", 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", "", actions.scroll_view(-0.25), { desc = "Scroll the view up" } }, - { "n", "", actions.scroll_view(0.25), { desc = "Scroll the view down" } }, - { "n", "", actions.select_next_entry, { desc = "Open the diff for the next file" } }, - { "n", "", 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", "", actions.goto_file_split, { desc = "Open the file in a new split" } }, - { "n", "gf", actions.goto_file_tab, { desc = "Open the file in a new tabpage" } }, - { "n", "e", actions.focus_files, { desc = "Bring focus to the file panel" } }, - { "n", "b", actions.toggle_files, { desc = "Toggle the file panel" } }, - { "n", "g", actions.cycle_layout, { desc = "Cycle available layouts" } }, + { "n", "g!", actions.options, { desc = "Open the option panel" } }, + { "n", "", 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", "", 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", "", actions.prev_entry, { desc = "Bring the cursor to the previous file entry." } }, + { "n", "", 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", "", actions.scroll_view(-0.25), { desc = "Scroll the view up" } }, + { "n", "", actions.scroll_view(0.25), { desc = "Scroll the view down" } }, + { "n", "", actions.select_next_entry, { desc = "Open the diff for the next file" } }, + { "n", "", 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", "", actions.goto_file_split, { desc = "Open the file in a new split" } }, + { "n", "gf", actions.goto_file_tab, { desc = "Open the file in a new tabpage" } }, + { "n", "e", actions.focus_files, { desc = "Bring focus to the file panel" } }, + { "n", "b", actions.toggle_files, { desc = "Toggle the file panel" } }, + { "n", "g", actions.cycle_layout, { desc = "Cycle available layouts" } }, + { "n", "g?", actions.help("file_history_panel"), { desc = "Open the help panel" } }, }, option_panel = { - { "n", "", actions.select_entry, { desc = "Change the current option" } }, - { "n", "q", actions.close, { desc = "Close the panel" } }, + { "n", "", 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", "", actions.close, { desc = "Close help menu" } }, }, }, } diff --git a/lua/diffview/ui/panels/help_panel.lua b/lua/diffview/ui/panels/help_panel.lua new file mode 100644 index 00000000..55822fc4 --- /dev/null +++ b/lua/diffview/ui/panels/help_panel.lua @@ -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", "", 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 = (""):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("^ %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' — 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