Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39c900c7e7 | ||
|
|
6e247c1583 | ||
|
|
87bf1dbc7f | ||
|
|
1f920ae6ba | ||
|
|
932e01b41a | ||
|
|
373f24c80f | ||
|
|
a548b289ef | ||
|
|
b168fc8cdd | ||
|
|
94845dcf98 | ||
|
|
1af6dff64e | ||
|
|
72de4f436e | ||
|
|
effda8291b | ||
|
|
7985e31020 | ||
|
|
866e0a618b | ||
|
|
a5ee61c117 | ||
|
|
08aad924d7 | ||
|
|
be02786dad | ||
|
|
949022db8c | ||
|
|
b1a090d4c0 | ||
|
|
39f3f150ed |
26
.github/release.yml
vendored
Normal file
26
.github/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
categories:
|
||||
- title: Features ✨
|
||||
labels:
|
||||
- feature
|
||||
- title: Enhancements 🔥
|
||||
labels:
|
||||
- enhancement
|
||||
- title: Fixes 🔧
|
||||
labels:
|
||||
- bug
|
||||
- title: Maintenance ⚙️
|
||||
labels:
|
||||
- maintenance
|
||||
- title: Docs 📖
|
||||
labels:
|
||||
- docs
|
||||
- title: I18n 🌎
|
||||
labels:
|
||||
- i18n
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
4
.github/workflows/cd.yml
vendored
4
.github/workflows/cd.yml
vendored
@@ -19,6 +19,10 @@ jobs:
|
||||
go-version: 1.18.x
|
||||
- name: Run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v1.17.2
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
|
||||
homebrew:
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -204,3 +204,11 @@ jobs:
|
||||
- name: errors
|
||||
run: golangci-lint run
|
||||
if: ${{ failure() }}
|
||||
check-required-label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
mode: exactly
|
||||
count: 1
|
||||
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
|
||||
|
||||
@@ -12,7 +12,7 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- 386
|
||||
- '386'
|
||||
# Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`.
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease
|
||||
|
||||
2
go.mod
2
go.mod
@@ -18,7 +18,7 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
|
||||
4
go.sum
4
go.sum
@@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b h1:8FmmdaYHes1m3oNyNdS+VIgkgkFpNZAWuwTnvp0tG14=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f h1:w/pxI34XepTAx4HwxUu8ipimbVRgSTS+7ahmgFQwH80=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
|
||||
@@ -331,9 +331,13 @@ func (self *cmdObjRunner) processOutput(
|
||||
askFor, ok := checkForCredentialRequest(newBytes)
|
||||
if ok {
|
||||
responseChan := promptUserForCredential(askFor)
|
||||
task.Pause()
|
||||
if task != nil {
|
||||
task.Pause()
|
||||
}
|
||||
toInput := <-responseChan
|
||||
task.Continue()
|
||||
if task != nil {
|
||||
task.Continue()
|
||||
}
|
||||
// If the return data is empty we don't write anything to stdin
|
||||
if toInput != "" {
|
||||
_, _ = writer.Write([]byte(toInput))
|
||||
|
||||
@@ -14,7 +14,7 @@ type ListContextTrait struct {
|
||||
list types.IList
|
||||
getDisplayStrings func(startIdx int, length int) [][]string
|
||||
// Alignment for each column. If nil, the default is left alignment
|
||||
columnAlignments []utils.Alignment
|
||||
getColumnAlignments func() []utils.Alignment
|
||||
// Some contexts, like the commit context, will highlight the path from the selected commit
|
||||
// to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport
|
||||
// so that we show the highlighted path.
|
||||
@@ -31,7 +31,14 @@ func (self *ListContextTrait) GetList() types.IList {
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) FocusLine() {
|
||||
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
|
||||
// Doing this at the end of the layout function because we need the view to be
|
||||
// resized before we focus the line, otherwise if we're in accordion mode
|
||||
// the view could be squashed and won't how to adjust the cursor/origin
|
||||
self.c.AfterLayout(func() error {
|
||||
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
|
||||
return nil
|
||||
})
|
||||
|
||||
self.setFooter()
|
||||
|
||||
if self.refreshViewportOnChange {
|
||||
@@ -75,9 +82,13 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
|
||||
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
||||
func (self *ListContextTrait) HandleRender() error {
|
||||
self.list.RefreshSelectedIdx()
|
||||
var columnAlignments []utils.Alignment
|
||||
if self.getColumnAlignments != nil {
|
||||
columnAlignments = self.getColumnAlignments()
|
||||
}
|
||||
content := utils.RenderDisplayStrings(
|
||||
self.getDisplayStrings(0, self.list.Len()),
|
||||
self.columnAlignments,
|
||||
columnAlignments,
|
||||
)
|
||||
self.GetViewTrait().SetContent(content)
|
||||
self.c.Render()
|
||||
|
||||
@@ -35,10 +35,10 @@ func NewMenuContext(
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
})),
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
c: c,
|
||||
columnAlignments: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
c: c,
|
||||
getColumnAlignments: func() []utils.Alignment { return viewModel.columnAlignment },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,9 @@ func (self *MenuContext) GetSelectedItemId() string {
|
||||
}
|
||||
|
||||
type MenuViewModel struct {
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
columnAlignment []utils.Alignment
|
||||
*FilteredListViewModel[*types.MenuItem]
|
||||
}
|
||||
|
||||
@@ -73,8 +74,9 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem) {
|
||||
func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment []utils.Alignment) {
|
||||
self.menuItems = items
|
||||
self.columnAlignment = columnAlignment
|
||||
}
|
||||
|
||||
// TODO: move into presentation package
|
||||
@@ -135,6 +137,10 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if selectedItem == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := selectedItem.OnPress(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -302,7 +302,12 @@ func (self *ConfirmationHelper) resizeMenu() {
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
|
||||
|
||||
tooltipTop := menuBottom + 1
|
||||
tooltipHeight := getMessageHeight(true, self.c.Contexts().Menu.GetSelected().Tooltip, panelWidth) + 2 // plus 2 for the frame
|
||||
tooltip := ""
|
||||
selectedItem := self.c.Contexts().Menu.GetSelected()
|
||||
if selectedItem != nil {
|
||||
tooltip = selectedItem.Tooltip
|
||||
}
|
||||
tooltipHeight := getMessageHeight(true, tooltip, panelWidth) + 2 // plus 2 for the frame
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ func (self *MenuController) GetOnClick() func() error {
|
||||
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := self.context().GetSelected()
|
||||
self.c.Views().Tooltip.SetContent(selectedMenuItem.Tooltip)
|
||||
if selectedMenuItem != nil {
|
||||
self.c.Views().Tooltip.SetContent(selectedMenuItem.Tooltip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -37,9 +38,10 @@ func (self *OptionsMenuAction) Call() error {
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Keybindings,
|
||||
Items: menuItems,
|
||||
HideCancel: true,
|
||||
Title: self.c.Tr.Keybindings,
|
||||
Items: menuItems,
|
||||
HideCancel: true,
|
||||
ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,8 @@ type Gui struct {
|
||||
helpers *helpers.Helpers
|
||||
|
||||
integrationTest integrationTypes.IntegrationTest
|
||||
|
||||
afterLayoutFuncs chan func() error
|
||||
}
|
||||
|
||||
type StateAccessor struct {
|
||||
@@ -458,7 +460,8 @@ func NewGui(
|
||||
PopupMutex: &deadlock.Mutex{},
|
||||
PtyMutex: &deadlock.Mutex{},
|
||||
},
|
||||
InitialDir: initialDir,
|
||||
InitialDir: initialDir,
|
||||
afterLayoutFuncs: make(chan func() error, 1000),
|
||||
}
|
||||
|
||||
gui.WatchFilesForChanges()
|
||||
@@ -519,9 +522,29 @@ var RuneReplacements = map[rune]string{
|
||||
}
|
||||
|
||||
func (gui *Gui) initGocui(headless bool, test integrationTypes.IntegrationTest) (*gocui.Gui, error) {
|
||||
playRecording := test != nil && os.Getenv(components.SANDBOX_ENV_VAR) != "true"
|
||||
runInSandbox := os.Getenv(components.SANDBOX_ENV_VAR) == "true"
|
||||
playRecording := test != nil && !runInSandbox
|
||||
|
||||
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playRecording, headless, RuneReplacements)
|
||||
width, height := 0, 0
|
||||
if test != nil {
|
||||
if test.RequiresHeadless() {
|
||||
if runInSandbox {
|
||||
panic("Test requires headless, can't run in sandbox")
|
||||
}
|
||||
headless = true
|
||||
}
|
||||
width, height = test.HeadlessDimensions()
|
||||
}
|
||||
|
||||
g, err := gocui.NewGui(gocui.NewGuiOpts{
|
||||
OutputMode: gocui.OutputTrue,
|
||||
SupportOverlaps: OverlappingEdges,
|
||||
PlayRecording: playRecording,
|
||||
Headless: headless,
|
||||
RuneReplacements: RuneReplacements,
|
||||
Width: width,
|
||||
Height: height,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -168,3 +168,12 @@ func (self *guiCommon) IsAnyModeActive() bool {
|
||||
func (self *guiCommon) GetInitialKeybindingsWithCustomCommands() ([]*types.Binding, []*gocui.ViewMouseBinding) {
|
||||
return self.gui.GetInitialKeybindingsWithCustomCommands()
|
||||
}
|
||||
|
||||
func (self *guiCommon) AfterLayout(f func() error) {
|
||||
select {
|
||||
case self.gui.afterLayoutFuncs <- f:
|
||||
default:
|
||||
// hopefully this never happens
|
||||
self.gui.c.Log.Error("afterLayoutFuncs channel is full, skipping function")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,23 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
// if you run `lazygit --logs`
|
||||
// this will let you see these branches as prettified json
|
||||
// gui.c.Log.Info(utils.AsJson(gui.State.Model.Branches[0:4]))
|
||||
return gui.helpers.Confirmation.ResizeCurrentPopupPanel()
|
||||
if err := gui.helpers.Confirmation.ResizeCurrentPopupPanel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case f := <-gui.afterLayoutFuncs:
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.Contexts.Menu.SetMenuItems(opts.Items)
|
||||
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
|
||||
gui.State.Contexts.Menu.SetSelectedLineIdx(0)
|
||||
|
||||
gui.Views.Menu.Title = opts.Title
|
||||
|
||||
@@ -80,6 +80,10 @@ type IGuiCommon interface {
|
||||
// Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact
|
||||
// that lazygit is still busy. See docs/dev/Busy.md
|
||||
OnWorker(f func(gocui.Task))
|
||||
// Function to call at the end of our 'layout' function which renders views
|
||||
// For example, you may want a view's line to be focused only after that view is
|
||||
// resized, if in accordion mode.
|
||||
AfterLayout(f func() error)
|
||||
|
||||
// returns the gocui Gui struct. There is a good chance you don't actually want to use
|
||||
// this struct and instead want to use another method above
|
||||
@@ -129,9 +133,10 @@ type IPopupHandler interface {
|
||||
}
|
||||
|
||||
type CreateMenuOptions struct {
|
||||
Title string
|
||||
Items []*MenuItem
|
||||
HideCancel bool
|
||||
Title string
|
||||
Items []*MenuItem
|
||||
HideCancel bool
|
||||
ColumnAlignment []utils.Alignment
|
||||
}
|
||||
|
||||
type CreatePopupPanelOpts struct {
|
||||
|
||||
@@ -28,7 +28,10 @@ func RunTUI() {
|
||||
app := newApp(testDir)
|
||||
app.loadTests()
|
||||
|
||||
g, err := gocui.NewGui(gocui.OutputTrue, false, false, false, gui.RuneReplacements)
|
||||
g, err := gocui.NewGui(gocui.NewGuiOpts{
|
||||
OutputMode: gocui.OutputTrue,
|
||||
RuneReplacements: gui.RuneReplacements,
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ import (
|
||||
// to get the test's name via it's file's path.
|
||||
const unitTestDescription = "test test"
|
||||
|
||||
const (
|
||||
defaultWidth = 100
|
||||
defaultHeight = 100
|
||||
)
|
||||
|
||||
type IntegrationTest struct {
|
||||
name string
|
||||
description string
|
||||
@@ -32,6 +37,8 @@ type IntegrationTest struct {
|
||||
keys config.KeybindingConfig,
|
||||
)
|
||||
gitVersion GitVersionRestriction
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
var _ integrationTypes.IntegrationTest = &IntegrationTest{}
|
||||
@@ -52,6 +59,11 @@ type NewIntegrationTestArgs struct {
|
||||
Skip bool
|
||||
// to run a test only on certain git versions
|
||||
GitVersion GitVersionRestriction
|
||||
// width and height when running in headless mode, for testing
|
||||
// the UI in different sizes.
|
||||
// If these are set, the test must be run in headless mode
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
type GitVersionRestriction struct {
|
||||
@@ -120,6 +132,8 @@ func NewIntegrationTest(args NewIntegrationTestArgs) *IntegrationTest {
|
||||
setupConfig: args.SetupConfig,
|
||||
run: args.Run,
|
||||
gitVersion: args.GitVersion,
|
||||
width: args.Width,
|
||||
height: args.Height,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +186,18 @@ func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *IntegrationTest) HeadlessDimensions() (int, int) {
|
||||
if self.width == 0 && self.height == 0 {
|
||||
return defaultWidth, defaultHeight
|
||||
}
|
||||
|
||||
return self.width, self.height
|
||||
}
|
||||
|
||||
func (self *IntegrationTest) RequiresHeadless() bool {
|
||||
return self.width != 0 && self.height != 0
|
||||
}
|
||||
|
||||
func testNameFromCurrentFilePath() string {
|
||||
path := utils.FilePath(3)
|
||||
return TestNameFromFilePath(path)
|
||||
|
||||
@@ -82,6 +82,20 @@ func (self *ViewDriver) TopLines(matchers ...*TextMatcher) *ViewDriver {
|
||||
return self.assertLines(0, matchers...)
|
||||
}
|
||||
|
||||
// Asserts on the visible lines of the view.
|
||||
// Note, this assumes that the view's viewport is filled with lines
|
||||
func (self *ViewDriver) VisibleLines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateVisibleLineCount(matchers)
|
||||
|
||||
// Get the origin of the view and offset that.
|
||||
// Note that we don't do any retrying here so if we want to bring back retry logic
|
||||
// we'll need to update this.
|
||||
originY := self.getView().OriginY()
|
||||
|
||||
return self.assertLines(originY, matchers...)
|
||||
}
|
||||
|
||||
// asserts that somewhere in the view there are consequetive lines matching the given matchers.
|
||||
func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
@@ -212,6 +226,16 @@ func (self *ViewDriver) validateEnoughLines(matchers []*TextMatcher) {
|
||||
})
|
||||
}
|
||||
|
||||
// assumes the view's viewport is filled with lines
|
||||
func (self *ViewDriver) validateVisibleLineCount(matchers []*TextMatcher) {
|
||||
view := self.getView()
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
count := view.InnerHeight() + 1
|
||||
return count == len(matchers), fmt.Sprintf("unexpected number of visible lines in view '%s'. Expected exactly %d, got %d", view.Name(), len(matchers), count)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewDriver {
|
||||
view := self.getView()
|
||||
|
||||
|
||||
@@ -209,7 +209,9 @@ var tests = []*components.IntegrationTest{
|
||||
tag.CrudAnnotated,
|
||||
tag.CrudLightweight,
|
||||
tag.Reset,
|
||||
ui.Accordion,
|
||||
ui.DoublePopup,
|
||||
ui.EmptyMenu,
|
||||
ui.SwitchTabFromMenu,
|
||||
undo.UndoCheckoutAndDrop,
|
||||
undo.UndoDrop,
|
||||
|
||||
61
pkg/integration/tests/ui/accordion.go
Normal file
61
pkg/integration/tests/ui/accordion.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
// When in acccordion mode, Lazygit looks like this:
|
||||
//
|
||||
// ╶─Status─────────────────────────╴┌─Patch──────────────────────────────────────────────────────────┐
|
||||
// ╶─Files - Submodules──────0 of 0─╴│commit 6e56dd04b70e548976f7f2928c4d9c359574e2bc ▲
|
||||
// ╶─Local branches - Remotes1 of 1─╴│Author: CI <CI@example.com> █
|
||||
// ┌─Commits - Reflog───────────────┐│Date: Wed Jul 19 22:00:03 2023 +1000 │
|
||||
// │7fe02805 CI commit 12 ▲│ ▼
|
||||
// │6e56dd04 CI commit 11 █└────────────────────────────────────────────────────────────────┘
|
||||
// │a35c687d CI commit 10 ▼┌─Command log────────────────────────────────────────────────────┐
|
||||
// └───────────────────────10 of 20─┘│Random tip: To filter commits by path, press '<c-s>' │
|
||||
// ╶─Stash───────────────────0 of 0─╴└────────────────────────────────────────────────────────────────┘
|
||||
// <pgup>/<pgdown>: Scroll, <esc>: Cancel, q: Quit, ?: Keybindings, 1-Donate Ask Question unversioned
|
||||
|
||||
var Accordion = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Verify accordion mode kicks in when the screen height is too small",
|
||||
ExtraCmdArgs: []string{},
|
||||
Width: 100,
|
||||
Height: 10,
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(20)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
VisibleLines(
|
||||
Contains("commit 20").IsSelected(),
|
||||
Contains("commit 19"),
|
||||
Contains("commit 18"),
|
||||
).
|
||||
// go past commit 11, then come back, so that it ends up in the centre of the viewport
|
||||
NavigateToLine(Contains("commit 11")).
|
||||
NavigateToLine(Contains("commit 10")).
|
||||
NavigateToLine(Contains("commit 11")).
|
||||
VisibleLines(
|
||||
Contains("commit 12"),
|
||||
Contains("commit 11").IsSelected(),
|
||||
Contains("commit 10"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
Focus()
|
||||
|
||||
// ensure we retain the same viewport upon re-focus
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
VisibleLines(
|
||||
Contains("commit 12"),
|
||||
Contains("commit 11").IsSelected(),
|
||||
Contains("commit 10"),
|
||||
)
|
||||
},
|
||||
})
|
||||
31
pkg/integration/tests/ui/empty_menu.go
Normal file
31
pkg/integration/tests/ui/empty_menu.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EmptyMenu = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Verify that we don't crash on an empty menu",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Universal.OptionMenu)
|
||||
|
||||
t.Views().Menu().
|
||||
IsFocused().
|
||||
// a string that filters everything out
|
||||
FilterOrSearch("ljasldkjaslkdjalskdjalsdjaslkd").
|
||||
IsEmpty().
|
||||
Press(keys.Universal.Select)
|
||||
|
||||
// back in the files view, selecting the non-existing menu item was a no-op
|
||||
t.Views().Files().
|
||||
IsFocused()
|
||||
},
|
||||
})
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
type IntegrationTest interface {
|
||||
Run(GuiDriver)
|
||||
SetupConfig(config *config.AppConfig)
|
||||
RequiresHeadless() bool
|
||||
// width and height when running headless
|
||||
HeadlessDimensions() (int, int)
|
||||
}
|
||||
|
||||
// this is the interface through which our integration tests interact with the lazygit gui
|
||||
|
||||
29
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
29
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@@ -177,13 +177,26 @@ type Gui struct {
|
||||
taskManager *TaskManager
|
||||
}
|
||||
|
||||
type NewGuiOpts struct {
|
||||
OutputMode OutputMode
|
||||
SupportOverlaps bool
|
||||
PlayRecording bool
|
||||
Headless bool
|
||||
// only applicable when Headless is true
|
||||
Width int
|
||||
// only applicable when Headless is true
|
||||
Height int
|
||||
|
||||
RuneReplacements map[rune]string
|
||||
}
|
||||
|
||||
// NewGui returns a new Gui object with a given output mode.
|
||||
func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless bool, runeReplacements map[rune]string) (*Gui, error) {
|
||||
func NewGui(opts NewGuiOpts) (*Gui, error) {
|
||||
g := &Gui{}
|
||||
|
||||
var err error
|
||||
if headless {
|
||||
err = g.tcellInitSimulation()
|
||||
if opts.Headless {
|
||||
err = g.tcellInitSimulation(opts.Width, opts.Height)
|
||||
} else {
|
||||
err = g.tcellInit(runeReplacements)
|
||||
}
|
||||
@@ -191,7 +204,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if headless || runtime.GOOS == "windows" {
|
||||
if opts.Headless || runtime.GOOS == "windows" {
|
||||
g.maxX, g.maxY = g.screen.Size()
|
||||
} else {
|
||||
// TODO: find out if we actually need this bespoke logic for linux
|
||||
@@ -201,7 +214,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
|
||||
}
|
||||
}
|
||||
|
||||
g.outputMode = mode
|
||||
g.outputMode = opts.OutputMode
|
||||
|
||||
g.stop = make(chan struct{})
|
||||
|
||||
@@ -209,7 +222,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
|
||||
g.userEvents = make(chan userEvent, 20)
|
||||
g.taskManager = newTaskManager()
|
||||
|
||||
if playRecording {
|
||||
if opts.PlayRecording {
|
||||
g.ReplayedEvents = replayedEvents{
|
||||
Keys: make(chan *TcellKeyEventWrapper),
|
||||
Resizes: make(chan *TcellResizeEventWrapper),
|
||||
@@ -221,14 +234,14 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
|
||||
|
||||
// SupportOverlaps is true when we allow for view edges to overlap with other
|
||||
// view edges
|
||||
g.SupportOverlaps = supportOverlaps
|
||||
g.SupportOverlaps = opts.SupportOverlaps
|
||||
|
||||
// default keys for when searching strings in a view
|
||||
g.SearchEscapeKey = KeyEsc
|
||||
g.NextSearchMatchKey = 'n'
|
||||
g.PrevSearchMatchKey = 'N'
|
||||
|
||||
g.playRecording = playRecording
|
||||
g.playRecording = opts.PlayRecording
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
4
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
4
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
@@ -81,7 +81,7 @@ func registerRuneFallbacks(s tcell.Screen, additional map[rune]string) {
|
||||
}
|
||||
|
||||
// tcellInitSimulation initializes tcell screen for use.
|
||||
func (g *Gui) tcellInitSimulation() error {
|
||||
func (g *Gui) tcellInitSimulation(width int, height int) error {
|
||||
s := tcell.NewSimulationScreen("")
|
||||
if e := s.Init(); e != nil {
|
||||
return e
|
||||
@@ -90,7 +90,7 @@ func (g *Gui) tcellInitSimulation() error {
|
||||
Screen = s
|
||||
// setting to a larger value than the typical terminal size
|
||||
// so that during a test we're more likely to see an item to select in a view.
|
||||
s.SetSize(100, 100)
|
||||
s.SetSize(width, height)
|
||||
s.Sync()
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
2
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@@ -269,7 +269,7 @@ func (v *View) FocusPoint(cx int, cy int) {
|
||||
_, height := v.Size()
|
||||
|
||||
ly := height - 1
|
||||
if ly == -1 {
|
||||
if ly < 0 {
|
||||
ly = 0
|
||||
}
|
||||
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f
|
||||
## explicit; go 1.12
|
||||
github.com/jesseduffield/gocui
|
||||
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
|
||||
Reference in New Issue
Block a user