/
githubmirror
/
lazygit
Обзор
Документация
Войти
/
githubmirror
/
lazygit
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
pkg/gui/gui_driver.go
255 строк
7 KB
Stefan Haller
Keep the destination visible while commits move
31 июл 2026, 09:37
31 июл 2026, 09:37
104fdf3
Код
Авторство
О чём код?
package gui import ( "fmt" "os" "strings" "time" "github.com/gdamore/tcell/v3" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" ) // this gives our integration test a way of interacting with the gui for sending keypresses // and reading state. type GuiDriver struct { gui *Gui toastChan chan string headless bool } var _ integrationTypes.GuiDriver = &GuiDriver{} func (self *GuiDriver) PressKey(keyStr string) { self.PressKeysRapidly(keyStr) } // PressKeysRapidly presses the given keys in immediate succession, waiting for // lazygit to become idle only after the last one. Keys pressed this way can // arrive while the previous key's processing is still in flight, like a user // typing faster than lazygit handles the input. func (self *GuiDriver) PressKeysRapidly(keyStrs ...string) { self.CheckAllToastsAcknowledged() for _, keyStr := range keyStrs { key, ok := config.KeyFromLabel(keyStr) if !ok { self.Fail("Unrecognized key: " + keyStr) } self.gui.g.ReplayKeyEvent(gocui.NewTcellKeyEventWrapper( tcell.NewEventKey(tcell.Key(key.KeyName()), key.Str(), tcell.ModMask(key.Mod())), 0, )) } self.waitTillIdle() } func (self *GuiDriver) Click(x, y int) { self.CheckAllToastsAcknowledged() self.replayMouseEvent(x, y, tcell.ButtonPrimary) self.replayMouseEvent(x, y, tcell.ButtonNone) } func (self *GuiDriver) ClickAndHold(x, y int) { self.CheckAllToastsAcknowledged() self.replayMouseEvent(x, y, tcell.ButtonPrimary) } // MouseMove reports the mouse at a new position with the left button still // held down, i.e. a drag movement. (No test needs pointer motion without a // button held, so that variant doesn't exist.) func (self *GuiDriver) MouseMove(x, y int) { self.replayMouseEvent(x, y, tcell.ButtonPrimary) } func (self *GuiDriver) MouseRelease(x, y int) { self.replayMouseEvent(x, y, tcell.ButtonNone) } func (self *GuiDriver) MouseReleaseWithoutWaiting(x, y int) { self.replayMouseEventWithoutWaiting(x, y, tcell.ButtonNone) } func (self *GuiDriver) WaitUntilIdle() { self.waitTillIdle() } func (self *GuiDriver) OnUIThreadAndWait(f func()) { _ = self.gui.g.OnUIThreadAndWait(func() error { f(); return nil }) } func (self *GuiDriver) replayMouseEvent(x, y int, buttons tcell.ButtonMask) { self.replayMouseEventWithoutWaiting(x, y, buttons) self.waitTillIdle() } func (self *GuiDriver) replayMouseEventWithoutWaiting(x, y int, buttons tcell.ButtonMask) { self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper( tcell.NewEventMouse(x, y, buttons, 0), 0, )) } // FocusIn simulates the terminal window regaining focus, which is how lazygit // learns to reload changed config files. Tests use it to exercise the live // config-reload path. func (self *GuiDriver) FocusIn() { self.gui.g.ReplayFocusEvent(gocui.NewTcellFocusEventWrapper( tcell.NewEventFocus(true), 0, )) self.waitTillIdle() } func (self *GuiDriver) FocusInAndClick(x, y int) { self.CheckAllToastsAcknowledged() self.gui.g.ReplayFocusEvent(gocui.NewTcellFocusEventWrapper( tcell.NewEventFocus(true), 0, )) self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper( tcell.NewEventMouse(x, y, tcell.ButtonPrimary, 0), 0, )) self.waitTillIdle() self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper( tcell.NewEventMouse(x, y, tcell.ButtonNone, 0), 0, )) self.waitTillIdle() } func (self *GuiDriver) PretendMergeOrRebaseStartedInLazygit() { self.gui.onUIThread(func() error { self.gui.State.SetMergeOrRebaseStartedInLazygit(true) return nil }) self.waitTillIdle() } // wait until lazygit is idle (i.e. all processing is done) before continuing func (self *GuiDriver) waitTillIdle() { self.gui.g.WaitUntilIdle() } func (self *GuiDriver) CheckAllToastsAcknowledged() { if t := self.NextToast(); t != nil { self.Fail("Toast not acknowledged: " + *t) } } func (self *GuiDriver) Keys() config.KeybindingConfig { return self.gui.Config.GetUserConfig().Keybinding } func (self *GuiDriver) CurrentContext() types.Context { // Read the context manager directly rather than through c.Context(): the // driver runs on the test goroutine, not the UI thread, so it must bypass // the UI-thread assertion that accessor carries. return self.gui.State.ContextMgr.Current() } func (self *GuiDriver) ContextForView(viewName string) types.Context { context, ok := self.gui.helpers.View.ContextForView(viewName) if !ok { return nil } return context } func (self *GuiDriver) Fail(message string) { currentView := self.gui.g.CurrentView() // Check for unacknowledged toast: it may give us a hint as to why the test failed toastMessage := "" if t := self.NextToast(); t != nil { toastMessage = fmt.Sprintf("Unacknowledged toast message: %s\n", *t) } fullMessage := fmt.Sprintf( "%s\nFinal Lazygit state:\n%s\nUpon failure, focused view was '%s'.\n%sLog:\n%s", message, self.gui.g.Snapshot(), currentView.Name(), toastMessage, strings.Join(self.gui.GuiLog, "\n"), ) self.gui.g.Close() // need to give the gui time to close time.Sleep(time.Millisecond * 100) _, err := fmt.Fprintln(os.Stderr, fullMessage) if err != nil { panic("Test failed. Failed writing to stderr") } panic("Test failed") } // logs to the normal place that you log to i.e. viewable with `lazygit --logs` func (self *GuiDriver) Log(message string) { self.gui.c.Log.Warn(message) } // logs in the actual UI (in the commands panel) func (self *GuiDriver) LogUI(message string) { self.gui.c.LogAction(message) } func (self *GuiDriver) CheckedOutRef() *models.Branch { return self.gui.helpers.Refs.GetCheckedOutRef() } func (self *GuiDriver) MainView() *gocui.View { return self.gui.mainView() } func (self *GuiDriver) SecondaryView() *gocui.View { return self.gui.secondaryView() } func (self *GuiDriver) View(viewName string) *gocui.View { view, err := self.gui.g.View(viewName) if err != nil { panic(err) } return view } // TopViewInWindow returns the frontmost visible view in the given window, i.e. // the tab that is currently shown when a window holds several tabbed views. func (self *GuiDriver) TopViewInWindow(windowName string) *gocui.View { return self.gui.helpers.Window.TopViewInWindow(windowName, false) } func (self *GuiDriver) SetCaption(caption string) { self.gui.setCaption(caption) self.waitTillIdle() } func (self *GuiDriver) SetCaptionPrefix(prefix string) { self.gui.setCaptionPrefix(prefix) self.waitTillIdle() } func (self *GuiDriver) NextToast() *string { select { case t := <-self.toastChan: return &t default: return nil } } func (self *GuiDriver) Headless() bool { return self.headless }