mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-06 05:57:38 -04:00
A patch built from what is on screen has to show what is in it, over the diff those lines were taken from — which may be a diff renderer's rendering of it, whose bytes are none of ours to touch. So the marks are drawn in a gutter over the content, from the lines the patch holds rather than from where they were drawn last. They are worked out again whenever a pane's content settles and whenever the patch itself changes. Working them out as the content settles keeps them right across a renderer switch, a change of context size, or a walk through the commits. They are shown whenever the main view shows the diff the patch is built from, whether or not it has the focus. The pane beside the diff previews the patch all the while, and marks that came and went with the focus would look like a bug while browsing. A diff of any other commit gets none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
107 lines
3.1 KiB
Go
107 lines
3.1 KiB
Go
package gui
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/tasks"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
)
|
|
|
|
func (gui *Gui) desiredPtySize(view *gocui.View) (cols, rows uint16) {
|
|
return uint16(gui.renderWidth(view)), uint16(view.InnerHeight())
|
|
}
|
|
|
|
func (gui *Gui) onResize() error {
|
|
gui.Mutexes.PtyMutex.Lock()
|
|
defer gui.Mutexes.PtyMutex.Unlock()
|
|
|
|
for viewName, p := range gui.viewPtmxMap {
|
|
// TODO: handle resizing properly: we need to actually clear the main view
|
|
// and re-read the output from our pty. Or we could just re-run the original
|
|
// command from scratch
|
|
view, _ := gui.g.View(viewName)
|
|
cols, rows := gui.desiredPtySize(view)
|
|
if err := p.Resize(cols, rows); err != nil {
|
|
return utils.WrapError(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ptyCmd adapts an oscommands.StartedPty result into the tasks.Cmd shape.
|
|
// On Windows the original *exec.Cmd was never Start()ed, so we go through
|
|
// the explicit Process handle rather than cmd.Process.
|
|
type ptyCmd struct {
|
|
cmd *exec.Cmd
|
|
process *os.Process
|
|
wait func() error
|
|
}
|
|
|
|
func (p ptyCmd) Wait() error { return p.wait() }
|
|
func (p ptyCmd) String() string { return p.cmd.String() }
|
|
func (p ptyCmd) Terminate() error { return oscommands.TerminateProcessGracefully(p.process) }
|
|
|
|
// ptyRender runs the command in a pseudo-terminal. git invokes the stdin filter
|
|
// named by GIT_PAGER only when it talks to a terminal, and a renderer reads the
|
|
// width it lays out to off it.
|
|
//
|
|
// Must be called on the UI thread: it reads the view's dimensions, which the
|
|
// layout writes.
|
|
func (gui *Gui) ptyRender(spec renderSpec) (startRender, onCloseRender) {
|
|
view := spec.view
|
|
cmd := spec.cmd
|
|
|
|
// git runs the stdin filter itself, as the pager it is told about here.
|
|
// Named even when there is none, so that git doesn't reach for the user's
|
|
// core.pager instead.
|
|
cmd.Env = append(cmd.Env, "GIT_PAGER="+spec.stdinFilter)
|
|
|
|
cols, rows := gui.desiredPtySize(view)
|
|
|
|
var p oscommands.Pty
|
|
var fallbackPipe io.ReadCloser
|
|
start := func() (tasks.Cmd, io.Reader) {
|
|
// The pty (and diff renderer) wrap to this width; apply it here, on the
|
|
// task's goroutine once the previous task has stopped, so it doesn't
|
|
// race that task's writes (see View.SetContentWidth).
|
|
view.SetContentWidth(spec.width)
|
|
|
|
sp, err := oscommands.StartPty(cmd, cols, rows)
|
|
if err != nil {
|
|
gui.c.Log.Error(err)
|
|
// Fall back to running the command without a pty: the diff renderer is
|
|
// lost, but the command's output still renders.
|
|
execCmd, pipe := startCmdWithPipe(cmd, gui.c.Log)
|
|
fallbackPipe = pipe
|
|
return execCmd, pipe
|
|
}
|
|
p = sp.Pty
|
|
|
|
gui.Mutexes.PtyMutex.Lock()
|
|
gui.viewPtmxMap[view.Name()] = p
|
|
gui.Mutexes.PtyMutex.Unlock()
|
|
|
|
return ptyCmd{cmd: cmd, process: sp.Process, wait: sp.Wait}, p
|
|
}
|
|
|
|
onClose := func() {
|
|
gui.Mutexes.PtyMutex.Lock()
|
|
if p != nil {
|
|
p.Close()
|
|
}
|
|
if fallbackPipe != nil {
|
|
fallbackPipe.Close()
|
|
fallbackPipe = nil
|
|
}
|
|
delete(gui.viewPtmxMap, view.Name())
|
|
gui.Mutexes.PtyMutex.Unlock()
|
|
}
|
|
|
|
return start, onClose
|
|
}
|