diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/commands/os.go | 5 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 12 | ||||
-rw-r--r-- | pkg/gui/gui.go | 146 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 6 | ||||
-rw-r--r-- | pkg/i18n/dutch.go | 6 | ||||
-rw-r--r-- | pkg/i18n/english.go | 6 | ||||
-rw-r--r-- | pkg/i18n/polish.go | 6 |
7 files changed, 156 insertions, 31 deletions
diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 1fb8e8da6..02d79a339 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -296,3 +296,8 @@ func (c *OSCommand) GetLazygitPath() string { } 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) +} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index eb41ab033..5e89632eb 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -599,3 +599,15 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error { 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 + }) +} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 99250c9ac..14e748857 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -1,6 +1,8 @@ package gui import ( + "bytes" + "io" "math" "sync" @@ -62,19 +64,20 @@ type Teml i18n.Teml // Gui wraps the gocui Gui object which handles rendering and events type Gui struct { - g *gocui.Gui - Log *logrus.Entry - GitCommand *commands.GitCommand - OSCommand *commands.OSCommand - SubProcess *exec.Cmd - State guiState - Config config.AppConfigurer - Tr *i18n.Localizer - Errors SentinelErrors - Updater *updates.Updater - statusManager *statusManager - credentials credentials - waitForIntro sync.WaitGroup + g *gocui.Gui + Log *logrus.Entry + GitCommand *commands.GitCommand + OSCommand *commands.OSCommand + SubProcess *exec.Cmd + subProcessChan chan (*exec.Cmd) + State guiState + Config config.AppConfigurer + Tr *i18n.Localizer + Errors SentinelErrors + Updater *updates.Updater + statusManager *statusManager + credentials credentials + waitForIntro sync.WaitGroup } // 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" Contexts map[string]string CherryPickedCommits []*commands.Commit + SubProcessOutput string } // NewGui builds a new gui handler @@ -457,30 +461,29 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } - // these are only called once (it's a place to put all the things you want - // to happen on startup after the screen is first rendered) - gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false) - if err := gui.updateRecentRepoList(); err != nil { + // doing this here because it'll only happen once + if err := gui.loadNewRepo(); err != nil { 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 } - if err := gui.refreshSidePanels(gui.g); err != nil { + if err := gui.switchFocus(gui.g, nil, gui.getFilesView()); err != nil { return err } + } - if err := gui.switchFocus(g, nil, filesView); err != nil { - return err - } - - if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" { - if err := gui.promptAnonymousReporting(); err != nil { - return err - } + if gui.State.SubProcessOutput != "" { + output := gui.State.SubProcessOutput + gui.State.SubProcessOutput = "" + x, y := gui.g.Size() + // if we just came back from vim, we don't want vim's output to show up in our popup + if float64(len(output))*1.5 < float64(x*y) { + return gui.createMessagePanel(gui.g, nil, "Output", output) } } @@ -514,6 +517,27 @@ func (gui *Gui) layout(g *gocui.Gui) error { 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 { 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() @@ -622,6 +646,24 @@ func (gui *Gui) Run() error { 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 // if the error returned from a run is a ErrSubProcess, it runs the subprocess // otherwise it handles the error, possibly by quitting the application @@ -634,9 +676,11 @@ func (gui *Gui) RunWithSubprocesses() error { continue } else if err == gui.Errors.ErrSubProcess { gui.SubProcess.Stdin = os.Stdin - gui.SubProcess.Stdout = os.Stdout - gui.SubProcess.Stderr = os.Stderr - gui.SubProcess.Run() + output, err := gui.runCommand(gui.SubProcess) + if err != nil { + return err + } + gui.State.SubProcessOutput = output gui.SubProcess.Stdout = ioutil.Discard gui.SubProcess.Stderr = ioutil.Discard gui.SubProcess.Stdin = nil @@ -649,6 +693,46 @@ func (gui *Gui) RunWithSubprocesses() error { 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 { if gui.State.Updating { return gui.createUpdateQuitConfirmation(g, v) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 7397aa8d2..0387b6028 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -239,6 +239,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleGitFetch, Description: gui.Tr.SLocalize("fetch"), }, { + ViewName: "files", + Key: 'X', + Modifier: gocui.ModNone, + Handler: gui.handleCustomCommand, + Description: gui.Tr.SLocalize("executeCustomCommand"), + }, { ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 2a5cd5551..a7c6efd23 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -718,6 +718,12 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "SureCreateFixupCommit", 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:", }, ) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 7cebb04d0..b64cc582f 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -741,6 +741,12 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "SureCreateFixupCommit", 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:", }, ) } diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 1eb2486dc..fc17323b9 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -701,6 +701,12 @@ func addPolish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "SureCreateFixupCommit", 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:", }, ) } |