mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-03 13:21:56 +02:00
support custom commands
This commit is contained in:
parent
878a15aff4
commit
55538a3695
@ -296,3 +296,8 @@ func (c *OSCommand) GetLazygitPath() string {
|
|||||||
}
|
}
|
||||||
return filepath.ToSlash(ex)
|
return filepath.ToSlash(ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunCustomCommand returns the pointer to a custom command
|
||||||
|
func (c *OSCommand) RunCustomCommand(command string) *exec.Cmd {
|
||||||
|
return c.PrepareSubProcess(c.Platform.shell, c.Platform.shellArg, command)
|
||||||
|
}
|
||||||
|
@ -599,3 +599,15 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
// gui.subProcessChan <- gui.OSCommand.RunCustomCommand(`read -p "Name: " name; echo $name; read -p "Okay: " okay; echo $okay`)
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
|
||||||
|
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
command := gui.trimmedContent(v)
|
||||||
|
gui.SubProcess = gui.OSCommand.RunCustomCommand(command)
|
||||||
|
return gui.Errors.ErrSubProcess
|
||||||
|
})
|
||||||
|
}
|
||||||
|
146
pkg/gui/gui.go
146
pkg/gui/gui.go
@ -1,6 +1,8 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -62,19 +64,20 @@ type Teml i18n.Teml
|
|||||||
|
|
||||||
// Gui wraps the gocui Gui object which handles rendering and events
|
// Gui wraps the gocui Gui object which handles rendering and events
|
||||||
type Gui struct {
|
type Gui struct {
|
||||||
g *gocui.Gui
|
g *gocui.Gui
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
GitCommand *commands.GitCommand
|
GitCommand *commands.GitCommand
|
||||||
OSCommand *commands.OSCommand
|
OSCommand *commands.OSCommand
|
||||||
SubProcess *exec.Cmd
|
SubProcess *exec.Cmd
|
||||||
State guiState
|
subProcessChan chan (*exec.Cmd)
|
||||||
Config config.AppConfigurer
|
State guiState
|
||||||
Tr *i18n.Localizer
|
Config config.AppConfigurer
|
||||||
Errors SentinelErrors
|
Tr *i18n.Localizer
|
||||||
Updater *updates.Updater
|
Errors SentinelErrors
|
||||||
statusManager *statusManager
|
Updater *updates.Updater
|
||||||
credentials credentials
|
statusManager *statusManager
|
||||||
waitForIntro sync.WaitGroup
|
credentials credentials
|
||||||
|
waitForIntro sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now the staging panel state, unlike the other panel states, is going to be
|
// for now the staging panel state, unlike the other panel states, is going to be
|
||||||
@ -145,6 +148,7 @@ type guiState struct {
|
|||||||
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
||||||
Contexts map[string]string
|
Contexts map[string]string
|
||||||
CherryPickedCommits []*commands.Commit
|
CherryPickedCommits []*commands.Commit
|
||||||
|
SubProcessOutput string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGui builds a new gui handler
|
// NewGui builds a new gui handler
|
||||||
@ -457,30 +461,29 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// these are only called once (it's a place to put all the things you want
|
// doing this here because it'll only happen once
|
||||||
// to happen on startup after the screen is first rendered)
|
if err := gui.loadNewRepo(); err != nil {
|
||||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
|
||||||
if err := gui.updateRecentRepoList(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gui.waitForIntro.Done()
|
}
|
||||||
|
|
||||||
if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
|
if gui.g.CurrentView() == nil {
|
||||||
|
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.refreshSidePanels(gui.g); err != nil {
|
if err := gui.switchFocus(gui.g, nil, gui.getFilesView()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := gui.switchFocus(g, nil, filesView); err != nil {
|
if gui.State.SubProcessOutput != "" {
|
||||||
return err
|
output := gui.State.SubProcessOutput
|
||||||
}
|
gui.State.SubProcessOutput = ""
|
||||||
|
x, y := gui.g.Size()
|
||||||
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
|
// if we just came back from vim, we don't want vim's output to show up in our popup
|
||||||
if err := gui.promptAnonymousReporting(); err != nil {
|
if float64(len(output))*1.5 < float64(x*y) {
|
||||||
return err
|
return gui.createMessagePanel(gui.g, nil, "Output", output)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,6 +517,27 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return gui.resizeCurrentPopupPanel(g)
|
return gui.resizeCurrentPopupPanel(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) loadNewRepo() error {
|
||||||
|
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||||
|
if err := gui.updateRecentRepoList(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gui.waitForIntro.Done()
|
||||||
|
|
||||||
|
if err := gui.refreshSidePanels(gui.g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
|
||||||
|
if err := gui.promptAnonymousReporting(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go gui.listenForSubprocesses()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) promptAnonymousReporting() error {
|
func (gui *Gui) promptAnonymousReporting() error {
|
||||||
return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.waitForIntro.Done()
|
gui.waitForIntro.Done()
|
||||||
@ -622,6 +646,24 @@ func (gui *Gui) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) listenForSubprocesses() {
|
||||||
|
// every time there is a subprocess, we're going to halt the execution of the UI via an update block, and wait for the command to finish
|
||||||
|
gui.subProcessChan = make(chan *exec.Cmd, 0)
|
||||||
|
for {
|
||||||
|
subProcess := <-gui.subProcessChan
|
||||||
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
|
subProcess.Stdin = os.Stdin
|
||||||
|
output, _ := runCommand(subProcess)
|
||||||
|
gui.State.SubProcessOutput = output
|
||||||
|
|
||||||
|
subProcess.Stdout = ioutil.Discard
|
||||||
|
subProcess.Stderr = ioutil.Discard
|
||||||
|
subProcess.Stdin = nil
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration
|
// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration
|
||||||
// if the error returned from a run is a ErrSubProcess, it runs the subprocess
|
// if the error returned from a run is a ErrSubProcess, it runs the subprocess
|
||||||
// otherwise it handles the error, possibly by quitting the application
|
// otherwise it handles the error, possibly by quitting the application
|
||||||
@ -634,9 +676,11 @@ func (gui *Gui) RunWithSubprocesses() error {
|
|||||||
continue
|
continue
|
||||||
} else if err == gui.Errors.ErrSubProcess {
|
} else if err == gui.Errors.ErrSubProcess {
|
||||||
gui.SubProcess.Stdin = os.Stdin
|
gui.SubProcess.Stdin = os.Stdin
|
||||||
gui.SubProcess.Stdout = os.Stdout
|
output, err := gui.runCommand(gui.SubProcess)
|
||||||
gui.SubProcess.Stderr = os.Stderr
|
if err != nil {
|
||||||
gui.SubProcess.Run()
|
return err
|
||||||
|
}
|
||||||
|
gui.State.SubProcessOutput = output
|
||||||
gui.SubProcess.Stdout = ioutil.Discard
|
gui.SubProcess.Stdout = ioutil.Discard
|
||||||
gui.SubProcess.Stderr = ioutil.Discard
|
gui.SubProcess.Stderr = ioutil.Discard
|
||||||
gui.SubProcess.Stdin = nil
|
gui.SubProcess.Stdin = nil
|
||||||
@ -649,6 +693,46 @@ func (gui *Gui) RunWithSubprocesses() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adapted from https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html
|
||||||
|
func (gui *Gui) runCommand(cmd *exec.Cmd) (string, error) {
|
||||||
|
var stdoutBuf bytes.Buffer
|
||||||
|
stdoutIn, _ := cmd.StdoutPipe()
|
||||||
|
stderrIn, _ := cmd.StderrPipe()
|
||||||
|
|
||||||
|
stdout := io.MultiWriter(os.Stdout, &stdoutBuf)
|
||||||
|
stderr := io.MultiWriter(os.Stderr, &stdoutBuf)
|
||||||
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if _, err := io.Copy(stdout, stdoutIn); err != nil {
|
||||||
|
gui.Log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(stderr, stderrIn); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
// not handling the error explicitly because usually we're going to see it
|
||||||
|
// in the output anyway
|
||||||
|
gui.Log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outStr := stdoutBuf.String()
|
||||||
|
return outStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
|
||||||
if gui.State.Updating {
|
if gui.State.Updating {
|
||||||
return gui.createUpdateQuitConfirmation(g, v)
|
return gui.createUpdateQuitConfirmation(g, v)
|
||||||
|
@ -238,6 +238,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleGitFetch,
|
Handler: gui.handleGitFetch,
|
||||||
Description: gui.Tr.SLocalize("fetch"),
|
Description: gui.Tr.SLocalize("fetch"),
|
||||||
|
}, {
|
||||||
|
ViewName: "files",
|
||||||
|
Key: 'X',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCustomCommand,
|
||||||
|
Description: gui.Tr.SLocalize("executeCustomCommand"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
|
@ -718,6 +718,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "SureCreateFixupCommit",
|
ID: "SureCreateFixupCommit",
|
||||||
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "executeCustomCommand",
|
||||||
|
Other: "execute custom command",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CustomCommand",
|
||||||
|
Other: "Custom Command:",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -741,6 +741,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "SureCreateFixupCommit",
|
ID: "SureCreateFixupCommit",
|
||||||
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "executeCustomCommand",
|
||||||
|
Other: "execute custom command",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CustomCommand",
|
||||||
|
Other: "Custom Command:",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -701,6 +701,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "SureCreateFixupCommit",
|
ID: "SureCreateFixupCommit",
|
||||||
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "executeCustomCommand",
|
||||||
|
Other: "execute custom command",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CustomCommand",
|
||||||
|
Other: "Custom Command:",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user