1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-14 11:23:09 +02:00
lazygit/pkg/integration/clients/tui.go

390 lines
9.0 KiB
Go
Raw Normal View History

2022-08-14 06:33:44 +02:00
package clients
import (
"fmt"
"log"
"os"
"path/filepath"
2022-08-14 06:33:44 +02:00
"strings"
2022-08-14 06:33:44 +02:00
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/gui/style"
2022-08-12 01:24:39 +02:00
"github.com/jesseduffield/lazygit/pkg/integration/components"
2022-08-14 03:24:07 +02:00
"github.com/jesseduffield/lazygit/pkg/integration/tests"
"github.com/jesseduffield/lazygit/pkg/secureexec"
)
// This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info.
2021-04-06 01:02:01 +02:00
2022-08-14 12:47:09 +02:00
var SLOW_KEY_PRESS_DELAY = 300
2022-08-14 06:33:44 +02:00
func RunTUI() {
rootDir := utils.GetLazyRootDirectory()
testDir := filepath.Join(rootDir, "test", "integration")
2022-08-14 06:33:44 +02:00
app := newApp(testDir)
app.loadTests()
g, err := gocui.NewGui(gocui.OutputTrue, false, gocui.NORMAL, false, gui.RuneReplacements)
2021-04-05 02:20:02 +02:00
if err != nil {
log.Panicln(err)
}
2021-04-05 02:20:02 +02:00
g.Cursor = false
2021-04-05 02:20:02 +02:00
app.g = g
2021-04-05 02:20:02 +02:00
g.SetManagerFunc(app.layout)
if err := g.SetKeybinding("list", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
2021-04-05 02:20:02 +02:00
if app.itemIdx > 0 {
app.itemIdx--
}
listView, err := g.View("list")
if err != nil {
return err
}
listView.FocusPoint(0, app.itemIdx)
2021-04-05 02:20:02 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
2022-08-14 06:33:44 +02:00
if err := g.SetKeybinding("list", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
if app.itemIdx < len(app.filteredTests)-1 {
app.itemIdx++
}
listView, err := g.View("list")
if err != nil {
return err
}
listView.FocusPoint(0, app.itemIdx)
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
2021-04-05 02:20:02 +02:00
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'q', gocui.ModNone, quit); err != nil {
2021-04-05 02:20:02 +02:00
log.Panicln(err)
}
if err := g.SetKeybinding("list", 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
}
suspendAndRunTest(currentTest, true, 0)
2021-04-05 02:20:02 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
2021-04-05 02:20:02 +02:00
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
}
suspendAndRunTest(currentTest, false, 0)
2021-04-05 12:49:32 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 't', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
}
suspendAndRunTest(currentTest, false, SLOW_KEY_PRESS_DELAY)
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
2021-04-05 02:20:02 +02:00
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
}
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r pkg/integration/tests/%s", currentTest.Name()))
2021-04-05 02:20:02 +02:00
if err := cmd.Run(); err != nil {
return err
}
2021-04-05 02:20:02 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", 'O', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
2021-04-05 02:20:02 +02:00
currentTest := app.getCurrentTest()
if currentTest == nil {
return nil
}
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code test/integration_new/%s", currentTest.Name()))
2021-04-05 02:20:02 +02:00
if err := cmd.Run(); err != nil {
return err
}
2021-04-05 02:20:02 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("list", '/', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
app.filtering = true
2021-04-05 02:20:02 +02:00
if _, err := g.SetCurrentView("editor"); err != nil {
return err
}
editorView, err := g.View("editor")
if err != nil {
return err
}
editorView.Clear()
2021-04-05 02:20:02 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
// not using the editor yet, but will use it to help filter the list
if err := g.SetKeybinding("editor", gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
app.filtering = false
2021-04-05 02:20:02 +02:00
if _, err := g.SetCurrentView("list"); err != nil {
return err
}
app.filteredTests = app.allTests
2022-08-14 06:33:44 +02:00
app.renderTests()
app.editorView.TextArea.Clear()
app.editorView.Clear()
app.editorView.Reset()
return nil
}); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("editor", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
app.filtering = false
if _, err := g.SetCurrentView("list"); err != nil {
return err
}
app.renderTests()
2021-04-05 02:20:02 +02:00
return nil
}); err != nil {
log.Panicln(err)
}
2021-04-05 02:20:02 +02:00
err = g.MainLoop()
g.Close()
2022-01-15 06:18:16 +02:00
switch err {
case gocui.ErrQuit:
return
default:
log.Panicln(err)
2021-04-05 02:20:02 +02:00
}
}
2022-08-14 06:33:44 +02:00
type app struct {
allTests []*components.IntegrationTest
2022-08-14 06:33:44 +02:00
filteredTests []*components.IntegrationTest
itemIdx int
testDir string
filtering bool
g *gocui.Gui
listView *gocui.View
editorView *gocui.View
}
func newApp(testDir string) *app {
return &app{testDir: testDir, allTests: tests.GetTests()}
2022-08-14 06:33:44 +02:00
}
func (self *app) getCurrentTest() *components.IntegrationTest {
self.adjustCursor()
if len(self.filteredTests) > 0 {
return self.filteredTests[self.itemIdx]
}
return nil
}
func (self *app) loadTests() {
self.filteredTests = self.allTests
2022-08-14 06:33:44 +02:00
self.adjustCursor()
}
func (self *app) adjustCursor() {
self.itemIdx = utils.Clamp(self.itemIdx, 0, len(self.filteredTests)-1)
}
func (self *app) filterWithString(needle string) {
if needle == "" {
self.filteredTests = self.allTests
2022-08-14 06:33:44 +02:00
} else {
self.filteredTests = slices.Filter(self.allTests, func(test *components.IntegrationTest) bool {
2022-08-14 06:33:44 +02:00
return strings.Contains(test.Name(), needle)
})
}
self.renderTests()
self.g.Update(func(g *gocui.Gui) error { return nil })
}
func (self *app) renderTests() {
self.listView.Clear()
for _, test := range self.filteredTests {
fmt.Fprintln(self.listView, test.Name())
}
}
func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
matched := f(v, key, ch, mod)
if matched {
self.filterWithString(v.TextArea.GetContent())
}
return matched
}
}
func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, keyPressDelay int) {
2021-04-05 04:19:45 +02:00
if err := gocui.Screen.Suspend(); err != nil {
panic(err)
}
2021-04-05 02:20:02 +02:00
runTuiTest(test, sandbox, keyPressDelay)
2021-04-05 02:20:02 +02:00
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return"))
2021-04-05 02:20:02 +02:00
fmt.Scanln() // wait for enter press
2021-04-05 04:19:45 +02:00
if err := gocui.Screen.Resume(); err != nil {
panic(err)
}
}
2022-08-14 06:33:44 +02:00
func (self *app) layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
descriptionViewHeight := 7
keybindingsViewHeight := 3
editorViewHeight := 3
2022-08-14 06:33:44 +02:00
if !self.filtering {
editorViewHeight = 0
} else {
descriptionViewHeight = 0
keybindingsViewHeight = 0
}
2022-08-14 06:33:44 +02:00
g.Cursor = self.filtering
g.FgColor = gocui.ColorGreen
listView, err := g.SetView("list", 0, 0, maxX-1, maxY-descriptionViewHeight-keybindingsViewHeight-editorViewHeight-1, 0)
if err != nil {
if err.Error() != "unknown view" {
return err
}
2022-08-14 06:33:44 +02:00
if self.listView == nil {
self.listView = listView
}
2022-08-14 06:33:44 +02:00
listView.Highlight = true
self.renderTests()
listView.Title = "Tests"
listView.FgColor = gocui.ColorDefault
if _, err := g.SetCurrentView("list"); err != nil {
return err
}
}
descriptionView, err := g.SetViewBeneath("description", "list", descriptionViewHeight)
if err != nil {
if err.Error() != "unknown view" {
return err
}
descriptionView.Title = "Test description"
descriptionView.Wrap = true
descriptionView.FgColor = gocui.ColorDefault
}
keybindingsView, err := g.SetViewBeneath("keybindings", "description", keybindingsViewHeight)
if err != nil {
if err.Error() != "unknown view" {
return err
}
keybindingsView.Title = "Keybindings"
keybindingsView.Wrap = true
keybindingsView.FgColor = gocui.ColorDefault
fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, t: run test slow, s: sandbox, o: open test file, shift+o: open test snapshot directory, forward-slash: filter")
}
editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight)
if err != nil {
if err.Error() != "unknown view" {
return err
}
2022-08-14 06:33:44 +02:00
if self.editorView == nil {
self.editorView = editorView
}
editorView.Title = "Filter"
editorView.FgColor = gocui.ColorDefault
editorView.Editable = true
2022-08-14 06:33:44 +02:00
editorView.Editor = gocui.EditorFunc(self.wrapEditor(gocui.SimpleEditor))
}
2022-08-14 06:33:44 +02:00
currentTest := self.getCurrentTest()
if currentTest == nil {
return nil
}
descriptionView.Clear()
fmt.Fprint(descriptionView, currentTest.Description())
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
2022-08-14 06:33:44 +02:00
func runTuiTest(test *components.IntegrationTest, sandbox bool, keyPressDelay int) {
2022-08-14 06:33:44 +02:00
err := components.RunTests(
[]*components.IntegrationTest{test},
log.Printf,
runCmdInTerminal,
runAndPrintError,
sandbox,
2022-08-14 06:33:44 +02:00
keyPressDelay,
2022-09-17 07:31:46 +02:00
1,
2022-08-14 06:33:44 +02:00
)
if err != nil {
log.Println(err.Error())
}
}
func runAndPrintError(test *components.IntegrationTest, f func() error) {
if err := f(); err != nil {
log.Println(err.Error())
}
}