Skip to content

Commit

Permalink
Merge pull request #1546 from zivarah/custom-popups
Browse files Browse the repository at this point in the history
  • Loading branch information
CKolkey authored Nov 7, 2024
2 parents 1944086 + b917302 commit a9c6864
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 40 deletions.
53 changes: 51 additions & 2 deletions doc/neogit.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.<name>.rebase and branch.autoSetupRebase if you
See pull.rebase, branch.<name>.rebase and branch.autoSetupRebase if you
want to make git pull always use --rebase instead of merging.

Note:
Expand Down Expand Up @@ -1848,6 +1848,55 @@ The following keys, in normal mode, will act on the commit under the cursor:
`b` Insert breakpoint
`<cr>` 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 = {
["<leader>A"] = create_custom_popup,
},
},
})
<
------------------------------------------------------------------------------
vim:tw=78:ts=8:ft=help:norl:

100 changes: 67 additions & 33 deletions lua/neogit/lib/popup/builder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ local state = require("neogit.lib.state")
local util = require("neogit.lib.util")
local notification = require("neogit.lib.notification")

---@class PopupBuilder
local M = {}

---@class PopupData
---@field state PopupState

---@class PopupState
---@field name string
---@field args PopupOption[]|PopupSwitch[]|PopupHeading[]
Expand All @@ -34,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
Expand All @@ -58,9 +56,18 @@ local M = {}
---@field id string
---@field key string
---@field name string
---@field entry string
---@field value string
---@field entry ConfigEntry
---@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
Expand All @@ -79,7 +86,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
Expand All @@ -88,9 +95,11 @@ 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
-- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean.
---@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)
local instance = {
Expand All @@ -110,25 +119,31 @@ 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)
table.insert(self.state.actions, { { heading = heading or "" } })
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
Expand All @@ -140,15 +155,15 @@ 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)
table.insert(self.state.actions[#self.state.actions], { 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
Expand All @@ -160,10 +175,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 {}
Expand Down Expand Up @@ -235,13 +251,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
Expand All @@ -251,10 +267,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 {}

Expand Down Expand Up @@ -303,16 +321,21 @@ 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)
table.insert(self.state.args, { type = "heading", 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
Expand All @@ -322,16 +345,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)
Expand All @@ -355,9 +380,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
Expand All @@ -367,9 +395,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
Expand All @@ -394,18 +423,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!")
Expand Down
Loading

0 comments on commit a9c6864

Please sign in to comment.