1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-11-23 22:24:51 +02:00
Files
lazygit/pkg/gui/pty.go
Stefan Haller 765c9eb85c Add PagerConfig
This is an object that is owned by Gui, is accessible through GuiCommon.State(),
and also passed down to GitCommand, where it is mostly needed. Right now it
simply wraps access to the Git.Paging config, which isn't very exciting, but
we'll extend it in the next commit to handle a slice of pagers (and maintain the
currently selected pager index), and doing this refactoring up front allows us
to make that change without having to touch clients.
2025-10-14 12:17:13 +02:00

121 lines
3.7 KiB
Go

//go:build !windows
package gui
import (
"io"
"os"
"os/exec"
"strings"
"github.com/creack/pty"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
func (gui *Gui) desiredPtySize(view *gocui.View) *pty.Winsize {
width, height := view.InnerSize()
return &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}
}
func (gui *Gui) onResize() error {
gui.Mutexes.PtyMutex.Lock()
defer gui.Mutexes.PtyMutex.Unlock()
for viewName, ptmx := 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)
if err := pty.Setsize(ptmx, gui.desiredPtySize(view)); err != nil {
return utils.WrapError(err)
}
}
return nil
}
// Some commands need to output for a terminal to active certain behaviour.
// For example, git won't invoke the GIT_PAGER env var unless it thinks it's
// talking to a terminal. We typically write cmd outputs straight to a view,
// which is just an io.Reader. the pty package lets us wrap a command in a
// pseudo-terminal meaning we'll get the behaviour we want from the underlying
// command.
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
width := view.InnerWidth()
pager := gui.stateAccessor.GetPagerConfig().GetPagerCommand(width)
externalDiffCommand := gui.stateAccessor.GetPagerConfig().GetExternalDiffCommand()
if pager == "" && externalDiffCommand == "" {
// if we're not using a custom pager we don't need to use a pty
return gui.newCmdTask(view, cmd, prefix)
}
// Run the pty after layout so that it gets the correct size
gui.afterLayout(func() error {
// Need to get the width and the pager again because the layout might have
// changed the size of the view
width = view.InnerWidth()
pager := gui.stateAccessor.GetPagerConfig().GetPagerCommand(width)
cmdStr := strings.Join(cmd.Args, " ")
// This communicates to pagers that we're in a very simple
// terminal that they should not expect to have much capabilities.
// Moving the cursor, clearing the screen, or querying for colors are among such "advanced" capabilities.
// Context: https://github.com/jesseduffield/lazygit/issues/3419
cmd.Env = removeExistingTermEnvVars(cmd.Env)
cmd.Env = append(cmd.Env, "TERM=dumb")
cmd.Env = append(cmd.Env, "GIT_PAGER="+pager)
manager := gui.getManager(view)
var ptmx *os.File
start := func() (*exec.Cmd, io.Reader) {
var err error
ptmx, err = pty.StartWithSize(cmd, gui.desiredPtySize(view))
if err != nil {
gui.c.Log.Error(err)
}
gui.Mutexes.PtyMutex.Lock()
gui.viewPtmxMap[view.Name()] = ptmx
gui.Mutexes.PtyMutex.Unlock()
return cmd, ptmx
}
onClose := func() {
gui.Mutexes.PtyMutex.Lock()
ptmx.Close()
delete(gui.viewPtmxMap, view.Name())
gui.Mutexes.PtyMutex.Unlock()
}
linesToRead := gui.linesToReadFromCmdTask(view)
return manager.NewTask(manager.NewCmdTask(start, prefix, linesToRead, onClose), cmdStr)
})
return nil
}
func removeExistingTermEnvVars(env []string) []string {
return lo.Filter(env, func(envVar string, _ int) bool {
return !isTermEnvVar(envVar)
})
}
// Terminals set a variety of different environment variables
// to identify themselves to processes. This list should catch the most common among them.
func isTermEnvVar(envVar string) bool {
return strings.HasPrefix(envVar, "TERM=") ||
strings.HasPrefix(envVar, "TERM_PROGRAM=") ||
strings.HasPrefix(envVar, "TERM_PROGRAM_VERSION=") ||
strings.HasPrefix(envVar, "TERMINAL_EMULATOR=") ||
strings.HasPrefix(envVar, "TERMINAL_NAME=") ||
strings.HasPrefix(envVar, "TERMINAL_VERSION_")
}