From 355f1615aba5b0b75485596fee0ae93d054081d4 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 1 Mar 2020 12:30:48 +1100 Subject: supporing custom pagers step 1 --- pkg/commands/git.go | 44 ++++++++++++---- pkg/commands/os.go | 8 +++ pkg/gui/commit_files_panel.go | 2 +- pkg/gui/commits_panel.go | 2 +- pkg/gui/files_panel.go | 4 +- pkg/gui/gui.go | 15 +++++- pkg/gui/pty.go | 21 ++++++++ pkg/gui/reflog_panel.go | 2 +- pkg/gui/stash_panel.go | 2 +- pkg/gui/tasks_adapter.go | 49 +++++++++++++++++- pkg/tasks/tasks.go | 114 +++++++++++++++++++++++++++++++++++++----- 11 files changed, 233 insertions(+), 30 deletions(-) create mode 100644 pkg/gui/pty.go (limited to 'pkg') diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 1e4f1e990..e41648ed3 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -177,7 +177,7 @@ func stashEntryFromLine(line string, index int) *StashEntry { // GetStashEntryDiff stash diff func (c *GitCommand) ShowStashEntryCmdStr(index int) string { - return fmt.Sprintf("git stash show -p --color stash@{%d}", index) + return fmt.Sprintf("git stash show -p --color=%s stash@{%d}", c.colorArg(), index) } // GetStatusFiles git status files @@ -558,11 +558,11 @@ func (c *GitCommand) Ignore(filename string) error { } func (c *GitCommand) ShowCmdStr(sha string) string { - return fmt.Sprintf("git show --color --no-renames --stat -p %s", sha) + return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s", c.colorArg(), sha) } func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string { - return fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium %s", branchName) + return fmt.Sprintf("git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium %s", branchName) } // GetRemoteURL returns current repo remote url @@ -591,7 +591,7 @@ func (c *GitCommand) Diff(file *File, plain bool, cached bool) string { func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string { cachedArg := "" trackedArg := "--" - colorArg := "--color" + colorArg := c.colorArg() split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename fileName := c.OSCommand.Quote(split[len(split)-1]) if cached { @@ -601,10 +601,10 @@ func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string { trackedArg = "--no-index /dev/null" } if plain { - colorArg = "" + colorArg = "never" } - return fmt.Sprintf("git diff --stat -p %s %s %s %s", colorArg, cachedArg, trackedArg, fileName) + return fmt.Sprintf("git diff --stat -p --color=%s %s %s %s", colorArg, cachedArg, trackedArg, fileName) } func (c *GitCommand) ApplyPatch(patch string, flags ...string) error { @@ -896,12 +896,12 @@ func (c *GitCommand) ShowCommitFile(commitSha, fileName string, plain bool) (str } func (c *GitCommand) ShowCommitFileCmdStr(commitSha, fileName string, plain bool) string { - colorArg := "--color" + colorArg := c.colorArg() if plain { - colorArg = "" + colorArg = "never" } - return fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName) + return fmt.Sprintf("git show --no-renames --color=%s %s -- %s", colorArg, commitSha, fileName) } // CheckoutFile checks out the file for the given commit @@ -967,7 +967,7 @@ func (c *GitCommand) ResetSoft(ref string) error { // DiffCommits show diff between commits func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) { - return c.OSCommand.RunCommandWithOutput("git diff --color --stat -p %s %s", sha1, sha2) + return c.OSCommand.RunCommandWithOutput("git diff --color=%s --stat -p %s %s", c.colorArg(), sha1, sha2) } // CreateFixupCommit creates a commit that fixes up a previous commit @@ -1128,3 +1128,27 @@ func (c *GitCommand) GetReflogCommits() ([]*Commit, error) { return commits, nil } + +func (c *GitCommand) GetPager(width int) (string, error) { + pager := c.Config.GetUserConfig().GetString("git.pager") + switch pager { + case "": + return "", nil + case "diff-so-fancy": + return "diff-so-fancy", nil + case "ydiff": + return fmt.Sprintf("ydiff -s --wrap --width=%d", width/2-6), nil + case "delta": + return "delta --dark", nil + default: + return "", errors.New("pager not supported. Pick one of diff-so-fancy, ydiff, delta, or nothing") + } +} + +func (c *GitCommand) colorArg() string { + pager := c.Config.GetUserConfig().GetString("git.pager") + if pager == "diff-so-fancy" || pager == "" { + return "always" + } + return "never" +} diff --git a/pkg/commands/os.go b/pkg/commands/os.go index e85b3e19c..9cf266f58 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -401,3 +401,11 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error { } return nil } + +func Kill(cmd *exec.Cmd) error { + if cmd.Process == nil { + // somebody got to it before we were able to, poor bastard + return nil + } + return cmd.Process.Kill() +} diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index 63458926b..5fa7a4683 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -50,7 +50,7 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false), ) - if err := gui.newCmdTask("main", cmd); err != nil { + if err := gui.newPtyTask("main", cmd); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index fe6b7acb9..0c901d085 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -67,7 +67,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCmdStr(commit.Sha), ) - if err := gui.newCmdTask("main", cmd); err != nil { + if err := gui.newPtyTask("main", cmd); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 4e00dc523..6bd2c7076 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -62,7 +62,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error { gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges") cmdStr := gui.GitCommand.DiffCmdStr(file, false, true) cmd := gui.OSCommand.ExecutableFromString(cmdStr) - if err := gui.newCmdTask("secondary", cmd); err != nil { + if err := gui.newPtyTask("secondary", cmd); err != nil { return err } } else { @@ -76,7 +76,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error { cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges) cmd := gui.OSCommand.ExecutableFromString(cmdStr) - if err := gui.newCmdTask("main", cmd); err != nil { + if err := gui.newPtyTask("main", cmd); err != nil { return err } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 70c3c3f51..9289c4e31 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -211,6 +211,9 @@ type guiState struct { Searching searchingState ScreenMode int SideView *gocui.View + Ptmx *os.File + PrevMainWidth int + PrevMainHeight int } // for now the split view will always be on @@ -247,6 +250,7 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma }, ScreenMode: SCREEN_NORMAL, SideView: nil, + Ptmx: nil, } gui := &Gui{ @@ -811,6 +815,15 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } + mainViewWidth, mainViewHeight := gui.getMainView().Size() + if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight { + gui.State.PrevMainWidth = mainViewWidth + gui.State.PrevMainHeight = mainViewHeight + if err := gui.onResize(); err != nil { + return err + } + } + // here is a good place log some stuff // if you download humanlog and do tail -f development.log | humanlog // this will let you see these branches as prettified json @@ -942,7 +955,7 @@ func (gui *Gui) startBackgroundFetch() { // Run setup the gui with keybindings and start the mainloop func (gui *Gui) Run() error { - g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges) + g, err := gocui.NewGui(gocui.Output256, OverlappingEdges) if err != nil { return err } diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go new file mode 100644 index 000000000..18fb98fda --- /dev/null +++ b/pkg/gui/pty.go @@ -0,0 +1,21 @@ +package gui + +import ( + "github.com/jesseduffield/pty" +) + +func (gui *Gui) onResize() error { + if gui.State.Ptmx == nil { + return nil + } + mainView := gui.getMainView() + width, height := mainView.Size() + + if err := pty.Setsize(gui.State.Ptmx, &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}); err != nil { + return err + } + + // TODO: handle resizing properly + + return nil +} diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index 37144d8c6..1837e2f7d 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -41,7 +41,7 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowCmdStr(commit.Sha), ) - if err := gui.newCmdTask("main", cmd); err != nil { + if err := gui.newPtyTask("main", cmd); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index db12767e8..92d84df9f 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -41,7 +41,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { cmd := gui.OSCommand.ExecutableFromString( gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index), ) - if err := gui.newCmdTask("main", cmd); err != nil { + if err := gui.newPtyTask("main", cmd); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index 22ebb9da9..38d7ce36b 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -5,6 +5,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/tasks" + "github.com/jesseduffield/pty" ) func (gui *Gui) newCmdTask(viewName string, cmd *exec.Cmd) error { @@ -25,6 +26,49 @@ func (gui *Gui) newCmdTask(viewName string, cmd *exec.Cmd) error { return nil } +func (gui *Gui) newPtyTask(viewName string, cmd *exec.Cmd) error { + width, _ := gui.getMainView().Size() + pager, err := gui.GitCommand.GetPager(width) + if err != nil { + return err + } + + if pager == "" { + // if we're not using a custom pager we don't need to use a pty + return gui.newCmdTask(viewName, cmd) + } + + cmd.Env = append(cmd.Env, "GIT_PAGER="+pager) + + view, err := gui.g.View(viewName) + if err != nil { + return nil // swallowing for now + } + + _, height := view.Size() + _, oy := view.Origin() + + manager := gui.getManager(view) + + ptmx, err := pty.Start(cmd) + if err != nil { + return err + } + + gui.State.Ptmx = ptmx + onClose := func() { gui.State.Ptmx = nil } + + if err := gui.onResize(); err != nil { + return err + } + + if err := manager.NewTask(manager.NewPtyTask(ptmx, cmd, height+oy+10, onClose)); err != nil { + return err + } + + return nil +} + func (gui *Gui) newTask(viewName string, f func(chan struct{}) error) error { view, err := gui.g.View(viewName) if err != nil { @@ -69,7 +113,10 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { view.Clear() }, func() { - gui.g.Update(func(*gocui.Gui) error { return nil }) + gui.g.Update(func(*gocui.Gui) error { + gui.Log.Warn("updating view") + return nil + }) }) gui.viewBufferManagerMap[view.Name()] = manager } diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 26846c439..716de488e 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -4,10 +4,12 @@ import ( "bufio" "fmt" "io" + "os" "os/exec" "sync" "time" + "github.com/jesseduffield/lazygit/pkg/commands" "github.com/sirupsen/logrus" ) @@ -60,7 +62,7 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan go func() { <-stop if cmd.ProcessState == nil { - if err := kill(cmd); err != nil { + if err := commands.Kill(cmd); err != nil { m.Log.Warn(err) } } @@ -103,6 +105,95 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan loaded = true } + select { + case <-stop: + m.refreshView() + break outer + default: + } + if !ok { + m.refreshView() + break outer + } + m.writer.Write(append(scanner.Bytes(), []byte("\n")...)) + } + m.refreshView() + case <-stop: + m.refreshView() + break outer + } + } + m.refreshView() + + if err := cmd.Wait(); err != nil { + m.Log.Warn(err) + } + + close(done) + }() + + m.readLines <- linesToRead + + <-done + + return nil + } +} + +func (m *ViewBufferManager) NewPtyTask(ptmx *os.File, cmd *exec.Cmd, linesToRead int, onClose func()) func(chan struct{}) error { + return func(stop chan struct{}) error { + r := ptmx + + defer ptmx.Close() + + done := make(chan struct{}) + go func() { + <-stop + commands.Kill(cmd) + ptmx.Close() + }() + + loadingMutex := sync.Mutex{} + + // not sure if it's the right move to redefine this or not + m.readLines = make(chan int, 1024) + + go func() { + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanLines) + + loaded := false + + go func() { + ticker := time.NewTicker(time.Millisecond * 100) + defer ticker.Stop() + select { + case <-ticker.C: + loadingMutex.Lock() + if !loaded { + m.beforeStart() + m.writer.Write([]byte("loading...")) + m.refreshView() + } + loadingMutex.Unlock() + case <-stop: + return + } + }() + + outer: + for { + select { + case linesToRead := <-m.readLines: + for i := 0; i < linesToRead; i++ { + ok := scanner.Scan() + loadingMutex.Lock() + if !loaded { + m.beforeStart() + loaded = true + } + loadingMutex.Unlock() + select { case <-stop: break outer @@ -124,12 +215,18 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan m.Log.Warn(err) } + m.refreshView() + + onClose() + close(done) }() m.readLines <- linesToRead + m.Log.Warn("waiting for done channel") <-done + m.Log.Warn("done channel returned") return nil } @@ -141,7 +238,7 @@ func (t *ViewBufferManager) Close() { return } - c := make(chan struct{}, 1) + c := make(chan struct{}) go func() { t.currentTask.Stop() @@ -170,7 +267,10 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error { m.waitingMutex.Lock() defer m.waitingMutex.Unlock() + + m.Log.Infof("done waiting") if taskID < m.newTaskId { + m.Log.Infof("returning cos the task is obsolete") return } @@ -216,13 +316,3 @@ func (t *Task) Stop() { t.stopped = true return } - -// kill kills a process -func kill(cmd *exec.Cmd) error { - if cmd.Process == nil { - // somebody got to it before we were able to, poor bastard - return nil - } - - return cmd.Process.Kill() -} -- cgit v1.2.3