Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up annotations on APIs that can be made public and document the ability to create custom popups #1546

Merged
merged 6 commits into from
Nov 7, 2024
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
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")
<

CKolkey marked this conversation as resolved.
Show resolved Hide resolved
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
Loading