Repository: jesseduffield/lazygit (language: go)

Issue:
Informational prompt?
**Is your feature request related to a problem? Please describe.**
In our project we use git describe all the time for issue tracking. I implemented a custom command that sort of does what I want but I'm interested if you have ever thought about informational only prompts. I.e. not 'input' or 'menu', just outputting the result of something like git describe. There may be other use cases for informational prompts but lg is pretty comprehensive and I'm struggling to think of anything else where you haven't covered my bases already.

**Describe the solution you'd like**
I'd like to have a bound customCommand that pops up the prompt and exits perhaps on Esc or Enter (doesn't matter, whichever makes sense to you).

**Describe alternatives you've considered**
Here's what I implemented, which works fine but it feels a bit like a context switch:
```
customCommands:
  - key: 'D'
    command: 'git describe'
    context: 'commits'
    subprocess: true
```
I can also use the ':' option and type in git describe as an on-the-fly custom command from the localBranches or commits tabs. Also a workable solution but also feels context switchy. And I'd prefer a single command to typing the full command each time.

**Additional context**
Thanks so much for lg. A few of my colleagues and I love it.
Informational prompt?
**Is your feature request related to a problem? Please describe.**
In our project we use git describe all the time for issue tracking. I implemented a custom command that sort of does what I want but I'm interested if you have ever thought about informational only prompts. I.e. not 'input' or 'menu', just outputting the result of something like git describe. There may be other use cases for informational prompts but lg is pretty comprehensive and I'm struggling to think of anything else where you haven't covered my bases already.

**Describe the solution you'd like**
I'd like to have a bound customCommand that pops up the prompt and exits perhaps on Esc or Enter (doesn't matter, whichever makes sense to you).

**Describe alternatives you've considered**
Here's what I implemented, which works fine but it feels a bit like a context switch:
```
customCommands:
  - key: 'D'
    command: 'git describe'
    context: 'commits'
    subprocess: true
```
I can also use the ':' option and type in git describe as an on-the-fly custom command from the localBranches or commits tabs. Also a workable solution but also feels context switchy. And I'd prefer a single command to typing the full command each time.

**Additional context**
Thanks so much for lg. A few of my colleagues and I love it.


Discussion / hints:
First off, thanks for your kind words :)

Sounds like we want a `showOutput` boolean key there so that the output of the command appears in a popup
I'm chucking the good-first-issue label on this because I reckon this will be pretty straightforward to implement. Relevant places that need updating:
`handleCustomCommandKeybinding` function in `pkg/gui/custom_commands.go`
`CustomCommand` struct in `pkg/config/user_config.go`
I would like to fix this. I can understand that we can add the showOutput in the CustomCommand to check whether we need to show output in the handleCustomCommandKeybinding. But I am quite newbie so I don't know show to popup the output of the command. Could you please give me the reference of the popup @jesseduffield 
I am willing to contribute to the community.

Thanks
@dutroctu you would want to go into `handleCustomCommandKeybinding` in `pkg/gui/custom_commands.go` and do something like the following:
```go
		f := func() error {
			cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
			if err != nil {
				return gui.surfaceError(err)
			}

			if customCommand.Subprocess {
				return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr))
			}

			loadingText := customCommand.LoadingText
			if loadingText == "" {
				loadingText = gui.Tr.LcRunningCustomCommandStatus
			}
			return gui.WithWaitingStatus(loadingText, func() error {
				output, err := gui.OSCommand.
					WithSpan(gui.Tr.Spans.CustomCommand).
					RunShellCommandWithOutput(cmdStr)
				if err != nil {
					return gui.surfaceError(err)
				}

				if err := gui.refreshSidePanels(refreshOptions{}); err != nil {
					return err
				}

				if customCommand.ShowOutput {
					return gui.ask(askOpts{
						title:  customCommand.Command,
						prompt: output,
					})
				}

				return nil
			})
		}
```

The `RunShellCommandWithOutput` function does not yet exist but you can go to `RunShellCommand` and see that it actually drops the output of the command that's run. So you can create a `RunShellCommandWithOutput` which does _not_ drop that output, and then have `RunShellCommand` call `RunShellCommandWithOutput` and just return the error.

Lemme know if you need any help
First off, thanks for your kind words :)

Sounds like we want a `showOutput` boolean key there so that the output of the command appears in a popup
I'm chucking the good-first-issue label on this because I reckon this will be pretty straightforward to implement. Relevant places that need updating:
`handleCustomCommandKeybinding` function in `pkg/gui/custom_commands.go`
`CustomCommand` struct in `pkg/config/user_config.go`
I would like to fix this. I can understand that we can add the showOutput in the CustomCommand to check whether we need to show output in the handleCustomCommandKeybinding. But I am quite newbie so I don't know show to popup the output of the command. Could you please give me the reference of the popup @jesseduffield 
I am willing to contribute to the community.

Thanks
@dutroctu you would want to go into `handleCustomCommandKeybinding` in `pkg/gui/custom_commands.go` and do something like the following:
```go
		f := func() error {
			cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
			if err != nil {
				return gui.surfaceError(err)
			}

			if customCommand.Subprocess {
				return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr))
			}

			loadingText := customCommand.LoadingText
			if loadingText == "" {
				loadingText = gui.Tr.LcRunningCustomCommandStatus
			}
			return gui.WithWaitingStatus(loadingText, func() error {
				output, err := gui.OSCommand.
					WithSpan(gui.Tr.Spans.CustomCommand).
					RunShellCommandWithOutput(cmdStr)
				if err != nil {
					return gui.surfaceError(err)
				}

				if err := gui.refreshSidePanels(refreshOptions{}); err != nil {
					return err
				}

				if customCommand.ShowOutput {
					return gui.ask(askOpts{
						title:  customCommand.Command,
						prompt: output,
					})
				}

				return nil
			})
		}
```

The `RunShellCommandWithOutput` function does not yet exist but you can go to `RunShellCommand` and see that it actually drops the output of the command that's run. So you can create a `RunShellCommandWithOutput` which does _not_ drop that output, and then have `RunShellCommand` call `RunShellCommandWithOutput` and just return the error.

Lemme know if you need any help

Fix patch:
diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md
index 47b9d8f970b..ac61c128259 100644
--- a/docs/Custom_Command_Keybindings.md
+++ b/docs/Custom_Command_Keybindings.md
@@ -77,6 +77,7 @@ For a given custom command, here are the allowed fields:
 | loadingText | text to display while waiting for command to finish | no |
 | description | text to display in the keybindings menu that appears when you press 'x' | no |
 | stream | whether you want to stream the command's output to the Command Log panel | no |
+| showOutput | whether you want to show the command's output in a gui prompt | no |
 
 ### Contexts
 
@@ -159,7 +160,7 @@ If your custom keybinding collides with an inbuilt keybinding that is defined fo
 
 ### Debugging
 
-If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `subprocess: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved. Alternatively you can run lazygit in debug mode with `lazygit --debug` and in another terminal window run `lazygit --logs` to see which commands are actually run
+If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `showOutput: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved. Alternatively you can run lazygit in debug mode with `lazygit --debug` and in another terminal window run `lazygit --logs` to see which commands are actually run
 
 ### More Examples
 
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 59bfcc04294..804a5bee08d 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -310,6 +310,7 @@ type CustomCommand struct {
 	LoadingText string                `yaml:"loadingText"`
 	Description string                `yaml:"description"`
 	Stream      bool                  `yaml:"stream"`
+	ShowOutput  bool                  `yaml:"showOutput"`
 }
 
 type CustomCommandPrompt struct {
diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go
index 3dd9a0517c1..6ac9fb733a6 100644
--- a/pkg/gui/services/custom_commands/handler_creator.go
+++ b/pkg/gui/services/custom_commands/handler_creator.go
@@ -1,6 +1,8 @@
 package custom_commands
 
 import (
+	"strings"
+
 	"github.com/jesseduffield/generics/slices"
 	"github.com/jesseduffield/lazygit/pkg/commands"
 	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -187,10 +189,20 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
 		if customCommand.Stream {
 			cmdObj.StreamOutput()
 		}
-		err := cmdObj.Run()
+		output, err := cmdObj.RunWithOutput()
 		if err != nil {
 			return self.c.Error(err)
 		}
+
+		if customCommand.ShowOutput {
+			if strings.TrimSpace(output) == "" {
+				output = self.c.Tr.EmptyOutput
+			}
+			if err = self.c.Alert(cmdStr, output); err != nil {
+				return self.c.Error(err)
+			}
+			return self.c.Refresh(types.RefreshOptions{})
+		}
 		return self.c.Refresh(types.RefreshOptions{})
 	})
 }
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index af5a8a99be9..be771d813ad 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -501,6 +501,7 @@ type TranslationSet struct {
 	UpstreamGone                        string
 	NukeDescription                     string
 	DiscardStagedChangesDescription     string
+	EmptyOutput                         string
 	Actions                             Actions
 	Bisect                              Bisect
 }
@@ -1136,6 +1137,7 @@ func EnglishTranslationSet() TranslationSet {
 		UpstreamGone:                        "(upstream gone)",
 		NukeDescription:                     "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
 		DiscardStagedChangesDescription:     "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",
+		EmptyOutput:                         "<empty output>",
 		Actions: Actions{
 			// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
 			CheckoutCommit:                    "Checkout commit",


Test patch: