From 4f8411786e304b370835117e37b81620a79dbf14 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 17:06:02 -0600 Subject: [PATCH 1/6] Remove duplicated `PopupData` definition This is a subset of the `PopupData` defined in `popup/init.lua` and is throwing LSP warnings. --- lua/neogit/lib/popup/builder.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 5ffa0da1c..f5d28ef5d 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -5,9 +5,6 @@ local notification = require("neogit.lib.notification") local M = {} ----@class PopupData ----@field state PopupState - ---@class PopupState ---@field name string ---@field args PopupOption[]|PopupSwitch[]|PopupHeading[] From 5781297eed487b1666bc4eceaa6a77591101c48a Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 17:08:47 -0600 Subject: [PATCH 2/6] Fix config entry type --- lua/neogit/lib/popup/builder.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index f5d28ef5d..3fb00f4a5 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -55,7 +55,7 @@ local M = {} ---@field id string ---@field key string ---@field name string ----@field entry string +---@field entry ConfigEntry ---@field value string ---@field type string From ab40701e437dac7e8e3324cb60081a79d6733ae6 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 17:47:18 -0600 Subject: [PATCH 3/6] Clean up some of the builder annotations --- lua/neogit/lib/popup/builder.lua | 75 +++++++++++++++++++++----------- lua/neogit/lib/popup/init.lua | 1 + 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 3fb00f4a5..150064820 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -76,7 +76,7 @@ local M = {} ---@field user_input boolean If true, allows user to customise the value of the cli flag ---@field dependant string[] other switches/options with a state dependency on this one ----@class PopupOptionsOpts +---@class PopupOptionOpts ---@field key_prefix string Allows overwriting the default '=' to set option ---@field cli_prefix string Allows overwriting the default '--' cli prefix ---@field choices table Table of predefined choices that a user can select for option @@ -87,7 +87,7 @@ local M = {} ---@class PopupConfigOpts ---@field options { display: string, value: string, config: function? } ---@field passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. +--- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. function M.new(builder_fn) local instance = { @@ -107,17 +107,23 @@ function M.new(builder_fn) return instance end -function M:name(x) - self.state.name = x +-- Set the popup's name. This must be set for all popups. +---@param name string The name +---@return self +function M:name(name) + self.state.name = name return self end -function M:env(x) - self.state.env = x or {} +-- Set initial context for the popup +---@param env table The initial context +---@return self +function M:env(env) + self.state.env = env or {} return self end ----Adds new column to actions section of popup +-- adds a new column to the actions section of the popup ---@param heading string? ---@return self function M:new_action_group(heading) @@ -125,7 +131,7 @@ function M:new_action_group(heading) return self end ----Conditionally adds new column to actions section of popup +-- Conditionally adds a new column to the actions section of the popup ---@param cond boolean ---@param heading string? ---@return self @@ -137,7 +143,7 @@ function M:new_action_group_if(cond, heading) return self end ----Adds new heading to current column within actions section of popup +-- adds a new heading to current column within the actions section of the popup ---@param heading string ---@return self function M:group_heading(heading) @@ -145,7 +151,7 @@ function M:group_heading(heading) return self end ----Conditionally adds new heading to current column within actions section of popup +-- Conditionally adds a new heading to current column within the actions section of the popup ---@param cond boolean ---@param heading string ---@return self @@ -157,10 +163,11 @@ function M:group_heading_if(cond, heading) return self end +-- Adds a switch to the popup ---@param key string Which key triggers switch ---@param cli string Git cli flag to use ---@param description string Description text to show user ----@param opts PopupSwitchOpts? +---@param opts PopupSwitchOpts? Additional options ---@return self function M:switch(key, cli, description, opts) opts = opts or {} @@ -232,13 +239,13 @@ function M:switch(key, cli, description, opts) return self end --- Conditionally adds a switch. +-- Conditionally adds a switch to the popup ---@see M:switch ----@param cond boolean +---@param cond boolean The condition under which to add the config ---@param key string Which key triggers switch ---@param cli string Git cli flag to use ---@param description string Description text to show user ----@param opts PopupSwitchOpts? +---@param opts PopupSwitchOpts? Additional options ---@return self function M:switch_if(cond, key, cli, description, opts) if cond then @@ -248,10 +255,12 @@ function M:switch_if(cond, key, cli, description, opts) return self end +-- Adds an option to the popup ---@param key string Key for the user to engage option ---@param cli string CLI value used ---@param value string Current value of option ---@param description string Description of option, presented to user +---@param opts PopupOptionOpts? Additional options function M:option(key, cli, value, description, opts) opts = opts or {} @@ -300,7 +309,7 @@ function M:option(key, cli, value, description, opts) return self end --- Adds heading text within Arguments (options/switches) section of popup +-- adds a heading text within Arguments (options/switches) section of the popup ---@param heading string Heading to show ---@return self function M:arg_heading(heading) @@ -308,8 +317,13 @@ function M:arg_heading(heading) return self end +-- Conditionally adds an option to the popup ---@see M:option ----@param cond boolean +---@param cond boolean The condition under which to add the config +---@param key string Which key triggers switch +---@param cli string Git cli flag to use +---@param description string Description text to show user +---@param opts PopupOptionOpts? Additional options ---@return self function M:option_if(cond, key, cli, value, description, opts) if cond then @@ -319,16 +333,18 @@ function M:option_if(cond, key, cli, value, description, opts) return self end ----@param heading string Heading to render within config section of popup +-- adds a heading text with the config section of the popup +---@param heading string Heading to render ---@return self function M:config_heading(heading) table.insert(self.state.config, { heading = heading }) return self end +-- Adds config to the popup ---@param key string Key for user to use that engages config ---@param name string Name of config ----@param options PopupConfigOpts? +---@param options PopupConfigOpts? Additional options ---@return self function M:config(key, name, options) local entry = git.config.get(name) @@ -352,9 +368,12 @@ function M:config(key, name, options) return self end --- Conditionally adds config to popup +-- Conditionally adds config to the popup ---@see M:config ----@param cond boolean +---@param cond boolean The condition under which to add the config +---@param key string Key for user to use that engages config +---@param name string Name of config +---@param options PopupConfigOpts? Additional options ---@return self function M:config_if(cond, key, name, options) if cond then @@ -364,9 +383,10 @@ function M:config_if(cond, key, name, options) return self end +-- Adds an action to the popup ---@param keys string|string[] Key or list of keys for the user to press that runs the action ---@param description string Description of action in UI ----@param callback function Function that gets run in async context +---@param callback fun(popup: PopupData) Function that gets run in async context ---@return self function M:action(keys, description, callback) if type(keys) == "string" then @@ -391,18 +411,23 @@ function M:action(keys, description, callback) return self end --- Conditionally adds action to popup ----@param cond boolean +-- Conditionally adds an action to the popup ---@see M:action +---@param cond boolean The condition under which to add the action +---@param keys string|string[] Key or list of keys for the user to press that runs the action +---@param description string Description of action in UI +---@param callback fun(popup: PopupData) Function that gets run in async context ---@return self -function M:action_if(cond, key, description, callback) +function M:action_if(cond, keys, description, callback) if cond then - return self:action(key, description, callback) + return self:action(keys, description, callback) end return self end +-- Builds the popup +---@return PopupData # The popup function M:build() if self.state.name == nil then error("A popup needs to have a name!") diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 9ffb3fc71..700734a1e 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -26,6 +26,7 @@ local ui = require("neogit.lib.popup.ui") ---@field buffer Buffer local M = {} +-- Create a new popup builder function M.builder() return PopupBuilder.new(M.new) end From 563b4d6b83da3582adc5422cfe026b754b9ad2b2 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Tue, 5 Nov 2024 18:40:55 -0600 Subject: [PATCH 4/6] Flesh out some missing builder annotations --- lua/neogit/lib/popup/builder.lua | 20 ++++++++++++++++---- lua/neogit/lib/popup/init.lua | 17 ++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lua/neogit/lib/popup/builder.lua b/lua/neogit/lib/popup/builder.lua index 150064820..77066b185 100644 --- a/lua/neogit/lib/popup/builder.lua +++ b/lua/neogit/lib/popup/builder.lua @@ -3,6 +3,7 @@ local state = require("neogit.lib.state") local util = require("neogit.lib.util") local notification = require("neogit.lib.notification") +---@class PopupBuilder local M = {} ---@class PopupState @@ -31,7 +32,7 @@ local M = {} ---@field key_prefix string ---@field separator string ---@field type string ----@field value string +---@field value string? ---@class PopupSwitch ---@field cli string @@ -56,8 +57,17 @@ local M = {} ---@field key string ---@field name string ---@field entry ConfigEntry ----@field value string +---@field value string? ---@field type string +---@field passive boolean? +---@field options PopupConfigOption[]? +---@field callback fun(popup: PopupData, config: self)? Called after the config is set +---@field fn fun(popup: PopupData, config: self)? If set, overrides the actual config setting behavior + +---@class PopupConfigOption An option that can be selected as a value for a config +---@field display string The display name for the option +---@field value string The value to set in git config +---@field condition fun()? An option predicate to determine if the option should appear ---@class PopupAction ---@field keys table @@ -85,8 +95,10 @@ local M = {} ---@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with ---@class PopupConfigOpts ----@field options { display: string, value: string, config: function? } ----@field passive boolean Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI +---@field options PopupConfigOption[] +---@field fn fun(popup: PopupData, config: self) If set, overrides the actual config setting behavior +---@field callback fun(popup: PopupData, config: PopupConfig)? A callback that will be invoked after the config is set +---@field passive boolean? Controls if this config setting can be manipulated directly, or if it is managed by git, and should just be shown in UI --- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean. function M.new(builder_fn) diff --git a/lua/neogit/lib/popup/init.lua b/lua/neogit/lib/popup/init.lua index 700734a1e..ae5c994ce 100644 --- a/lua/neogit/lib/popup/init.lua +++ b/lua/neogit/lib/popup/init.lua @@ -27,6 +27,7 @@ local ui = require("neogit.lib.popup.ui") local M = {} -- Create a new popup builder +---@return PopupBuilder function M.builder() return PopupBuilder.new(M.new) end @@ -92,7 +93,7 @@ function M:close() end -- Toggle a switch on/off ----@param switch table +---@param switch PopupSwitch ---@return nil function M:toggle_switch(switch) if switch.options then @@ -134,8 +135,10 @@ function M:toggle_switch(switch) for _, var in ipairs(self.state.args) do if switch.incompatible[var.cli] then if var.type == "switch" then + ---@cast var PopupSwitch self:disable_switch(var) elseif var.type == "option" then + ---@cast var PopupOption self:disable_option(var) end end @@ -147,8 +150,10 @@ function M:toggle_switch(switch) for _, var in ipairs(self.state.args) do if switch.dependant[var.cli] then if var.type == "switch" then + ---@cast var PopupSwitch self:disable_switch(var) elseif var.type == "option" then + ---@cast var PopupOption self:disable_option(var) end end @@ -157,7 +162,7 @@ function M:toggle_switch(switch) end -- Toggle an option on/off and set it's value ----@param option table +---@param option PopupOption ---@param value? string ---@return nil function M:set_option(option, value) @@ -225,7 +230,7 @@ function M:set_option(option, value) end ---Disables a switch. ----@param switch table +---@param switch PopupSwitch function M:disable_switch(switch) if switch.enabled then self:toggle_switch(switch) @@ -235,7 +240,7 @@ end ---Disables an option, setting its value to "". Doesn't use the default, which ---is important to ensure that we don't use incompatible switches/options ---together. ----@param option table +---@param option PopupOption function M:disable_option(option) if option.value and option.value ~= "" then self:set_option(option, "") @@ -243,7 +248,7 @@ function M:disable_option(option) end -- Set a config value ----@param config table +---@param config PopupConfig ---@return nil function M:set_config(config) if config.options then @@ -315,8 +320,10 @@ function M:mappings() arg_prefixes[arg.key_prefix] = true mappings.n[arg.id] = a.void(function() if arg.type == "switch" then + ---@cast arg PopupSwitch self:toggle_switch(arg) elseif arg.type == "option" then + ---@cast arg PopupOption self:set_option(arg) end From bee092aa9e802181771faf8108d2c11dc9bcacc3 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Wed, 6 Nov 2024 12:52:23 -0600 Subject: [PATCH 5/6] Clean up trailing whitespaces in docs --- doc/neogit.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/neogit.txt b/doc/neogit.txt index 4aada53d3..3bec9a8a8 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1249,7 +1249,7 @@ Arguments: *neogit_pull_popup_args* upstream branch and the upstream branch was rebased since last fetched, the rebase uses that information to avoid rebasing non-local changes. - See pull.rebase, branch..rebase and branch.autoSetupRebase if you + See pull.rebase, branch..rebase and branch.autoSetupRebase if you want to make git pull always use --rebase instead of merging. Note: @@ -1850,4 +1850,3 @@ The following keys, in normal mode, will act on the commit under the cursor: ------------------------------------------------------------------------------ vim:tw=78:ts=8:ft=help:norl: - From b917302de3f02aeb62cf769d26ad593fa3d6dfd7 Mon Sep 17 00:00:00 2001 From: Brian Lyles Date: Wed, 6 Nov 2024 01:51:30 -0600 Subject: [PATCH 6/6] Mention the possibility of custom popups in the docs --- doc/neogit.txt | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/doc/neogit.txt b/doc/neogit.txt index 3bec9a8a8..ea763b368 100644 --- a/doc/neogit.txt +++ b/doc/neogit.txt @@ -1848,5 +1848,55 @@ The following keys, in normal mode, will act on the commit under the cursor: • `b` Insert breakpoint • `` Open current commit in Commit Buffer +============================================================================== +Custom Popups *neogit_custom_popups* + +You can leverage Neogit's infrastructure to create your own popups and +actions. For example: >lua + local function my_action(popup) + -- You can use Neogit's git abstraction for many common operations + -- local git = require("neogit.lib.git") + local input = require("neogit.lib.input") + local user_input = input.get_user_input("User-specified free text for the action") + local cli_args = popup:get_arguments() + vim.notify( + "Hello from my custom action!\n" + .. "CLI args: `" .. table.concat(cli_args, " ") .. "`\n" + .. "User input: `" .. user_input .. "`") + end + + function create_custom_popup() + local popup = require("neogit.lib.popup") + local p = popup + .builder() + :name("NeogitMyCustomPopup") + :switch("s", "my-switch", "My switch") + :option("o", "my-option", "default_value", "My option", { key_prefix = "-" }) + :new_action_group("My actions") + :action("a", "Some action", my_action) + :build() + + p:show() + + return p + end + + require("neogit") +< + +Look at the builder APIs in `lua/neogit/lib/popup/builder.lua`, the built-in +popups/actions in `lua/neogit/popups/*`, and the git APIs in +`lua/neogit/lib/git` for more information (and inspiration!). + +To access your custom popup via a keymapping, you can include a mapping when +calling the setup function: >lua + require("neogit").setup({ + mappings = { + status = { + ["A"] = create_custom_popup, + }, + }, + }) +< ------------------------------------------------------------------------------ vim:tw=78:ts=8:ft=help:norl: