1package gui 2 3import ( 4 "io" 5 "os/exec" 6 "strings" 7 8 "github.com/jesseduffield/gocui" 9 "github.com/jesseduffield/lazygit/pkg/tasks" 10) 11 12func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error { 13 cmdStr := strings.Join(cmd.Args, " ") 14 gui.Log.WithField( 15 "command", 16 cmdStr, 17 ).Debug("RunCommand") 18 19 _, height := view.Size() 20 _, oy := view.Origin() 21 22 manager := gui.getManager(view) 23 24 start := func() (*exec.Cmd, io.Reader) { 25 r, err := cmd.StdoutPipe() 26 if err != nil { 27 gui.Log.Warn(err) 28 } 29 cmd.Stderr = cmd.Stdout 30 31 if err := cmd.Start(); err != nil { 32 gui.Log.Warn(err) 33 } 34 35 return cmd, r 36 } 37 38 if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil { 39 gui.Log.Warn(err) 40 } 41 42 return nil 43} 44 45func (gui *Gui) newStringTask(view *gocui.View, str string) error { 46 // using str so that if rendering the exact same thing we don't reset the origin 47 return gui.newStringTaskWithKey(view, str, str) 48} 49 50func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error { 51 manager := gui.getManager(view) 52 53 f := func(stop chan struct{}) error { 54 gui.setViewContent(view, str) 55 return nil 56 } 57 58 // Using empty key so that on subsequent calls we won't reset the view's origin. 59 // Note this means that we will be scrolling back to the top if we're switching from a different key 60 if err := manager.NewTask(f, ""); err != nil { 61 return err 62 } 63 64 return nil 65} 66 67func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error { 68 manager := gui.getManager(view) 69 70 f := func(stop chan struct{}) error { 71 gui.renderString(view, str) 72 return nil 73 } 74 75 if err := manager.NewTask(f, key); err != nil { 76 return err 77 } 78 79 return nil 80} 81 82func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { 83 manager, ok := gui.viewBufferManagerMap[view.Name()] 84 if !ok { 85 manager = tasks.NewViewBufferManager( 86 gui.Log, 87 view, 88 func() { 89 // we could clear here, but that actually has the effect of causing a flicker 90 // where the view may contain no content momentarily as the gui refreshes. 91 // Instead, we're rewinding the write pointer so that we will just start 92 // overwriting the existing content from the top down. Once we've reached 93 // the end of the content do display, we call view.FlushStaleCells() to 94 // clear out the remaining content from the previous render. 95 view.Reset() 96 }, 97 func() { 98 gui.render() 99 }, 100 func() { 101 // Need to check if the content of the view is well past the origin. 102 // It would be better to use .ViewLinesHeight here (given it considers 103 // wrapping) but when this function is called they haven't been written to yet. 104 linesHeight := view.LinesHeight() 105 _, height := view.Size() 106 _, originY := view.Origin() 107 if linesHeight < originY { 108 newOriginY := linesHeight - height 109 if newOriginY < 0 { 110 newOriginY = 0 111 } 112 err := view.SetOrigin(0, newOriginY) 113 if err != nil { 114 panic(err) 115 } 116 117 } 118 119 view.FlushStaleCells() 120 }, 121 func() { 122 _ = view.SetOrigin(0, 0) 123 }, 124 ) 125 gui.viewBufferManagerMap[view.Name()] = manager 126 } 127 128 return manager 129} 130