mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-11-04 12:31:43 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			396 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path/filepath"
 | 
						|
 | 
						|
	"github.com/fatih/color"
 | 
						|
	"github.com/jesseduffield/gocui"
 | 
						|
	"github.com/jesseduffield/lazygit/pkg/integration"
 | 
						|
	"github.com/jesseduffield/lazygit/pkg/secureexec"
 | 
						|
)
 | 
						|
 | 
						|
// this program lets you manage integration tests in a TUI.
 | 
						|
 | 
						|
type App struct {
 | 
						|
	tests   []*integration.Test
 | 
						|
	itemIdx int
 | 
						|
	testDir string
 | 
						|
	editing bool
 | 
						|
	g       *gocui.Gui
 | 
						|
}
 | 
						|
 | 
						|
func (app *App) getCurrentTest() *integration.Test {
 | 
						|
	if len(app.tests) > 0 {
 | 
						|
		return app.tests[app.itemIdx]
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (app *App) refreshTests() {
 | 
						|
	app.loadTests()
 | 
						|
	app.g.Update(func(*gocui.Gui) error {
 | 
						|
		listView, err := app.g.View("list")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		listView.Clear()
 | 
						|
		for _, test := range app.tests {
 | 
						|
			fmt.Fprintln(listView, test.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func (app *App) loadTests() {
 | 
						|
	tests, err := integration.LoadTests(app.testDir)
 | 
						|
	if err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	app.tests = tests
 | 
						|
	if app.itemIdx > len(app.tests)-1 {
 | 
						|
		app.itemIdx = len(app.tests) - 1
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func main() {
 | 
						|
	rootDir := integration.GetRootDirectory()
 | 
						|
	testDir := filepath.Join(rootDir, "test", "integration")
 | 
						|
 | 
						|
	app := &App{testDir: testDir}
 | 
						|
	app.loadTests()
 | 
						|
 | 
						|
	g, err := gocui.NewGui(gocui.OutputTrue, false, gocui.NORMAL, false)
 | 
						|
	if err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	g.Cursor = false
 | 
						|
 | 
						|
	app.g = g
 | 
						|
 | 
						|
	g.SetManagerFunc(app.layout)
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		if app.itemIdx > 0 {
 | 
						|
			app.itemIdx--
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 'q', gocui.ModNone, quit); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 'r', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true RECORD_EVENTS=true go run test/runner/main.go %s", currentTest.Name))
 | 
						|
		app.runSubprocess(cmd)
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true go run test/runner/main.go %s", currentTest.Name))
 | 
						|
		app.runSubprocess(cmd)
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true UPDATE_SNAPSHOTS=true go run test/runner/main.go %s", currentTest.Name))
 | 
						|
		app.runSubprocess(cmd)
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r %s/%s/test.json", app.testDir, currentTest.Name))
 | 
						|
		if err := cmd.Run(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 'n', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// need to duplicate that folder and then re-fetch our tests.
 | 
						|
		dir := app.testDir + "/" + app.getCurrentTest().Name
 | 
						|
		newDir := dir + "_Copy"
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("cp -r %s %s", dir, newDir))
 | 
						|
		if err := cmd.Run(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		app.loadTests()
 | 
						|
 | 
						|
		app.refreshTests()
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 'm', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		app.editing = true
 | 
						|
		if _, err := g.SetCurrentView("editor"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		editorView, err := g.View("editor")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		editorView.Clear()
 | 
						|
		fmt.Fprint(editorView, currentTest.Name)
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		dir := app.testDir + "/" + app.getCurrentTest().Name
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", dir))
 | 
						|
		if err := cmd.Run(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		app.refreshTests()
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("editor", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		currentTest := app.getCurrentTest()
 | 
						|
		if currentTest == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		app.editing = false
 | 
						|
		if _, err := g.SetCurrentView("list"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		editorView, err := g.View("editor")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		dir := app.testDir + "/" + app.getCurrentTest().Name
 | 
						|
		newDir := app.testDir + "/" + editorView.Buffer()
 | 
						|
 | 
						|
		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("mv %s %s", dir, newDir))
 | 
						|
		if err := cmd.Run(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		editorView.Clear()
 | 
						|
 | 
						|
		app.refreshTests()
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := g.SetKeybinding("editor", nil, gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		app.editing = false
 | 
						|
		if _, err := g.SetCurrentView("list"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = g.MainLoop()
 | 
						|
	g.Close()
 | 
						|
	if err != nil {
 | 
						|
		switch err {
 | 
						|
		case gocui.ErrQuit:
 | 
						|
			return
 | 
						|
		default:
 | 
						|
			log.Panicln(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (app *App) runSubprocess(cmd *exec.Cmd) {
 | 
						|
	if err := gocui.Screen.Suspend(); err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	cmd.Stdin = os.Stdin
 | 
						|
	cmd.Stderr = os.Stderr
 | 
						|
	cmd.Stdout = os.Stdout
 | 
						|
	if err := cmd.Run(); err != nil {
 | 
						|
		log.Println(err.Error())
 | 
						|
	}
 | 
						|
	cmd.Stdin = nil
 | 
						|
	cmd.Stderr = nil
 | 
						|
	cmd.Stdout = nil
 | 
						|
 | 
						|
	fmt.Fprintf(os.Stdout, "\n%s", coloredString("press enter to return", color.FgGreen))
 | 
						|
	fmt.Scanln() // wait for enter press
 | 
						|
 | 
						|
	if err := gocui.Screen.Resume(); err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (app *App) layout(g *gocui.Gui) error {
 | 
						|
	maxX, maxY := g.Size()
 | 
						|
	descriptionViewHeight := 7
 | 
						|
	keybindingsViewHeight := 3
 | 
						|
	editorViewHeight := 3
 | 
						|
	if !app.editing {
 | 
						|
		editorViewHeight = 0
 | 
						|
	} else {
 | 
						|
		descriptionViewHeight = 0
 | 
						|
		keybindingsViewHeight = 0
 | 
						|
	}
 | 
						|
	g.Cursor = app.editing
 | 
						|
	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
 | 
						|
		}
 | 
						|
		listView.Highlight = true
 | 
						|
		listView.Clear()
 | 
						|
		for _, test := range app.tests {
 | 
						|
			fmt.Fprintln(listView, test.Name)
 | 
						|
		}
 | 
						|
		listView.Title = "Tests"
 | 
						|
		listView.FgColor = gocui.ColorDefault
 | 
						|
		if _, err := g.SetCurrentView("list"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := listView.SetCursor(0, app.itemIdx); 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, s: run test and update snapshots, r: record test, o: open test config, n: duplicate test, m: rename test, d: delete test")
 | 
						|
	}
 | 
						|
 | 
						|
	editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight)
 | 
						|
	if err != nil {
 | 
						|
		if err.Error() != "unknown view" {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		editorView.Title = "Enter Name"
 | 
						|
		editorView.FgColor = gocui.ColorDefault
 | 
						|
		editorView.Editable = true
 | 
						|
	}
 | 
						|
 | 
						|
	currentTest := app.getCurrentTest()
 | 
						|
	if currentTest == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	descriptionView.Clear()
 | 
						|
	fmt.Fprintf(descriptionView, "Speed: %f. %s", currentTest.Speed, currentTest.Description)
 | 
						|
 | 
						|
	if err := g.SetKeybinding("list", nil, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
 | 
						|
		if app.itemIdx < len(app.tests)-1 {
 | 
						|
			app.itemIdx++
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}); err != nil {
 | 
						|
		log.Panicln(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func quit(g *gocui.Gui, v *gocui.View) error {
 | 
						|
	return gocui.ErrQuit
 | 
						|
}
 | 
						|
 | 
						|
func coloredString(str string, colorAttributes ...color.Attribute) string {
 | 
						|
	colour := color.New(colorAttributes...)
 | 
						|
	return coloredStringDirect(str, colour)
 | 
						|
}
 | 
						|
 | 
						|
// coloredStringDirect used for aggregating a few color attributes rather than
 | 
						|
// just sending a single one
 | 
						|
func coloredStringDirect(str string, colour *color.Color) string {
 | 
						|
	return colour.SprintFunc()(fmt.Sprint(str))
 | 
						|
}
 |