Skip to content

Commit a9c6864

Browse files
authored
Merge pull request #1546 from zivarah/custom-popups
2 parents 1944086 + b917302 commit a9c6864

File tree

3 files changed

+131
-40
lines changed

3 files changed

+131
-40
lines changed

doc/neogit.txt

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,7 +1249,7 @@ Arguments: *neogit_pull_popup_args*
12491249
upstream branch and the upstream branch was rebased since last fetched,
12501250
the rebase uses that information to avoid rebasing non-local changes.
12511251

1252-
See pull.rebase, branch.<name>.rebase and branch.autoSetupRebase if you
1252+
See pull.rebase, branch.<name>.rebase and branch.autoSetupRebase if you
12531253
want to make git pull always use --rebase instead of merging.
12541254

12551255
Note:
@@ -1848,6 +1848,55 @@ The following keys, in normal mode, will act on the commit under the cursor:
18481848
`b` Insert breakpoint
18491849
`<cr>` Open current commit in Commit Buffer
18501850

1851+
==============================================================================
1852+
Custom Popups *neogit_custom_popups*
1853+
1854+
You can leverage Neogit's infrastructure to create your own popups and
1855+
actions. For example: >lua
1856+
local function my_action(popup)
1857+
-- You can use Neogit's git abstraction for many common operations
1858+
-- local git = require("neogit.lib.git")
1859+
local input = require("neogit.lib.input")
1860+
local user_input = input.get_user_input("User-specified free text for the action")
1861+
local cli_args = popup:get_arguments()
1862+
vim.notify(
1863+
"Hello from my custom action!\n"
1864+
.. "CLI args: `" .. table.concat(cli_args, " ") .. "`\n"
1865+
.. "User input: `" .. user_input .. "`")
1866+
end
1867+
1868+
function create_custom_popup()
1869+
local popup = require("neogit.lib.popup")
1870+
local p = popup
1871+
.builder()
1872+
:name("NeogitMyCustomPopup")
1873+
:switch("s", "my-switch", "My switch")
1874+
:option("o", "my-option", "default_value", "My option", { key_prefix = "-" })
1875+
:new_action_group("My actions")
1876+
:action("a", "Some action", my_action)
1877+
:build()
1878+
1879+
p:show()
1880+
1881+
return p
1882+
end
1883+
1884+
require("neogit")
1885+
<
1886+
1887+
Look at the builder APIs in `lua/neogit/lib/popup/builder.lua`, the built-in
1888+
popups/actions in `lua/neogit/popups/*`, and the git APIs in
1889+
`lua/neogit/lib/git` for more information (and inspiration!).
1890+
1891+
To access your custom popup via a keymapping, you can include a mapping when
1892+
calling the setup function: >lua
1893+
require("neogit").setup({
1894+
mappings = {
1895+
status = {
1896+
["<leader>A"] = create_custom_popup,
1897+
},
1898+
},
1899+
})
1900+
<
18511901
------------------------------------------------------------------------------
18521902
vim:tw=78:ts=8:ft=help:norl:
1853-

lua/neogit/lib/popup/builder.lua

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ local state = require("neogit.lib.state")
33
local util = require("neogit.lib.util")
44
local notification = require("neogit.lib.notification")
55

6+
---@class PopupBuilder
67
local M = {}
78

8-
---@class PopupData
9-
---@field state PopupState
10-
119
---@class PopupState
1210
---@field name string
1311
---@field args PopupOption[]|PopupSwitch[]|PopupHeading[]
@@ -34,7 +32,7 @@ local M = {}
3432
---@field key_prefix string
3533
---@field separator string
3634
---@field type string
37-
---@field value string
35+
---@field value string?
3836

3937
---@class PopupSwitch
4038
---@field cli string
@@ -58,9 +56,18 @@ local M = {}
5856
---@field id string
5957
---@field key string
6058
---@field name string
61-
---@field entry string
62-
---@field value string
59+
---@field entry ConfigEntry
60+
---@field value string?
6361
---@field type string
62+
---@field passive boolean?
63+
---@field options PopupConfigOption[]?
64+
---@field callback fun(popup: PopupData, config: self)? Called after the config is set
65+
---@field fn fun(popup: PopupData, config: self)? If set, overrides the actual config setting behavior
66+
67+
---@class PopupConfigOption An option that can be selected as a value for a config
68+
---@field display string The display name for the option
69+
---@field value string The value to set in git config
70+
---@field condition fun()? An option predicate to determine if the option should appear
6471

6572
---@class PopupAction
6673
---@field keys table
@@ -79,7 +86,7 @@ local M = {}
7986
---@field user_input boolean If true, allows user to customise the value of the cli flag
8087
---@field dependant string[] other switches/options with a state dependency on this one
8188

82-
---@class PopupOptionsOpts
89+
---@class PopupOptionOpts
8390
---@field key_prefix string Allows overwriting the default '=' to set option
8491
---@field cli_prefix string Allows overwriting the default '--' cli prefix
8592
---@field choices table Table of predefined choices that a user can select for option
@@ -88,9 +95,11 @@ local M = {}
8895
---@field incompatible table A table of strings that represent other cli switches/options that this one cannot be used with
8996

9097
---@class PopupConfigOpts
91-
---@field options { display: string, value: string, config: function? }
92-
---@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
93-
-- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean.
98+
---@field options PopupConfigOption[]
99+
---@field fn fun(popup: PopupData, config: self) If set, overrides the actual config setting behavior
100+
---@field callback fun(popup: PopupData, config: PopupConfig)? A callback that will be invoked after the config is set
101+
---@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
102+
--- A 'condition' key with function value can also be present in the option, which controls if the option gets shown by returning boolean.
94103

95104
function M.new(builder_fn)
96105
local instance = {
@@ -110,25 +119,31 @@ function M.new(builder_fn)
110119
return instance
111120
end
112121

113-
function M:name(x)
114-
self.state.name = x
122+
-- Set the popup's name. This must be set for all popups.
123+
---@param name string The name
124+
---@return self
125+
function M:name(name)
126+
self.state.name = name
115127
return self
116128
end
117129

118-
function M:env(x)
119-
self.state.env = x or {}
130+
-- Set initial context for the popup
131+
---@param env table The initial context
132+
---@return self
133+
function M:env(env)
134+
self.state.env = env or {}
120135
return self
121136
end
122137

123-
---Adds new column to actions section of popup
138+
-- adds a new column to the actions section of the popup
124139
---@param heading string?
125140
---@return self
126141
function M:new_action_group(heading)
127142
table.insert(self.state.actions, { { heading = heading or "" } })
128143
return self
129144
end
130145

131-
---Conditionally adds new column to actions section of popup
146+
-- Conditionally adds a new column to the actions section of the popup
132147
---@param cond boolean
133148
---@param heading string?
134149
---@return self
@@ -140,15 +155,15 @@ function M:new_action_group_if(cond, heading)
140155
return self
141156
end
142157

143-
---Adds new heading to current column within actions section of popup
158+
-- adds a new heading to current column within the actions section of the popup
144159
---@param heading string
145160
---@return self
146161
function M:group_heading(heading)
147162
table.insert(self.state.actions[#self.state.actions], { heading = heading })
148163
return self
149164
end
150165

151-
---Conditionally adds new heading to current column within actions section of popup
166+
-- Conditionally adds a new heading to current column within the actions section of the popup
152167
---@param cond boolean
153168
---@param heading string
154169
---@return self
@@ -160,10 +175,11 @@ function M:group_heading_if(cond, heading)
160175
return self
161176
end
162177

178+
-- Adds a switch to the popup
163179
---@param key string Which key triggers switch
164180
---@param cli string Git cli flag to use
165181
---@param description string Description text to show user
166-
---@param opts PopupSwitchOpts?
182+
---@param opts PopupSwitchOpts? Additional options
167183
---@return self
168184
function M:switch(key, cli, description, opts)
169185
opts = opts or {}
@@ -235,13 +251,13 @@ function M:switch(key, cli, description, opts)
235251
return self
236252
end
237253

238-
-- Conditionally adds a switch.
254+
-- Conditionally adds a switch to the popup
239255
---@see M:switch
240-
---@param cond boolean
256+
---@param cond boolean The condition under which to add the config
241257
---@param key string Which key triggers switch
242258
---@param cli string Git cli flag to use
243259
---@param description string Description text to show user
244-
---@param opts PopupSwitchOpts?
260+
---@param opts PopupSwitchOpts? Additional options
245261
---@return self
246262
function M:switch_if(cond, key, cli, description, opts)
247263
if cond then
@@ -251,10 +267,12 @@ function M:switch_if(cond, key, cli, description, opts)
251267
return self
252268
end
253269

270+
-- Adds an option to the popup
254271
---@param key string Key for the user to engage option
255272
---@param cli string CLI value used
256273
---@param value string Current value of option
257274
---@param description string Description of option, presented to user
275+
---@param opts PopupOptionOpts? Additional options
258276
function M:option(key, cli, value, description, opts)
259277
opts = opts or {}
260278

@@ -303,16 +321,21 @@ function M:option(key, cli, value, description, opts)
303321
return self
304322
end
305323

306-
-- Adds heading text within Arguments (options/switches) section of popup
324+
-- adds a heading text within Arguments (options/switches) section of the popup
307325
---@param heading string Heading to show
308326
---@return self
309327
function M:arg_heading(heading)
310328
table.insert(self.state.args, { type = "heading", heading = heading })
311329
return self
312330
end
313331

332+
-- Conditionally adds an option to the popup
314333
---@see M:option
315-
---@param cond boolean
334+
---@param cond boolean The condition under which to add the config
335+
---@param key string Which key triggers switch
336+
---@param cli string Git cli flag to use
337+
---@param description string Description text to show user
338+
---@param opts PopupOptionOpts? Additional options
316339
---@return self
317340
function M:option_if(cond, key, cli, value, description, opts)
318341
if cond then
@@ -322,16 +345,18 @@ function M:option_if(cond, key, cli, value, description, opts)
322345
return self
323346
end
324347

325-
---@param heading string Heading to render within config section of popup
348+
-- adds a heading text with the config section of the popup
349+
---@param heading string Heading to render
326350
---@return self
327351
function M:config_heading(heading)
328352
table.insert(self.state.config, { heading = heading })
329353
return self
330354
end
331355

356+
-- Adds config to the popup
332357
---@param key string Key for user to use that engages config
333358
---@param name string Name of config
334-
---@param options PopupConfigOpts?
359+
---@param options PopupConfigOpts? Additional options
335360
---@return self
336361
function M:config(key, name, options)
337362
local entry = git.config.get(name)
@@ -355,9 +380,12 @@ function M:config(key, name, options)
355380
return self
356381
end
357382

358-
-- Conditionally adds config to popup
383+
-- Conditionally adds config to the popup
359384
---@see M:config
360-
---@param cond boolean
385+
---@param cond boolean The condition under which to add the config
386+
---@param key string Key for user to use that engages config
387+
---@param name string Name of config
388+
---@param options PopupConfigOpts? Additional options
361389
---@return self
362390
function M:config_if(cond, key, name, options)
363391
if cond then
@@ -367,9 +395,10 @@ function M:config_if(cond, key, name, options)
367395
return self
368396
end
369397

398+
-- Adds an action to the popup
370399
---@param keys string|string[] Key or list of keys for the user to press that runs the action
371400
---@param description string Description of action in UI
372-
---@param callback function Function that gets run in async context
401+
---@param callback fun(popup: PopupData) Function that gets run in async context
373402
---@return self
374403
function M:action(keys, description, callback)
375404
if type(keys) == "string" then
@@ -394,18 +423,23 @@ function M:action(keys, description, callback)
394423
return self
395424
end
396425

397-
-- Conditionally adds action to popup
398-
---@param cond boolean
426+
-- Conditionally adds an action to the popup
399427
---@see M:action
428+
---@param cond boolean The condition under which to add the action
429+
---@param keys string|string[] Key or list of keys for the user to press that runs the action
430+
---@param description string Description of action in UI
431+
---@param callback fun(popup: PopupData) Function that gets run in async context
400432
---@return self
401-
function M:action_if(cond, key, description, callback)
433+
function M:action_if(cond, keys, description, callback)
402434
if cond then
403-
return self:action(key, description, callback)
435+
return self:action(keys, description, callback)
404436
end
405437

406438
return self
407439
end
408440

441+
-- Builds the popup
442+
---@return PopupData # The popup
409443
function M:build()
410444
if self.state.name == nil then
411445
error("A popup needs to have a name!")

0 commit comments

Comments
 (0)