2022-08-12 09:19:39 +10:00
|
|
|
package components
|
2022-08-09 20:27:44 +10:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
|
|
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
|
|
|
)
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
type Input struct {
|
2022-08-13 12:56:04 +10:00
|
|
|
gui integrationTypes.GuiDriver
|
2022-08-09 20:27:44 +10:00
|
|
|
keys config.KeybindingConfig
|
2022-08-09 21:27:12 +10:00
|
|
|
assert *Assert
|
2022-08-09 20:27:44 +10:00
|
|
|
pushKeyDelay int
|
|
|
|
}
|
|
|
|
|
2022-08-13 12:56:04 +10:00
|
|
|
func NewInput(gui integrationTypes.GuiDriver, keys config.KeybindingConfig, assert *Assert, pushKeyDelay int) *Input {
|
2022-08-11 21:04:10 +10:00
|
|
|
return &Input{
|
2022-08-09 20:27:44 +10:00
|
|
|
gui: gui,
|
|
|
|
keys: keys,
|
|
|
|
assert: assert,
|
|
|
|
pushKeyDelay: pushKeyDelay,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// key is something like 'w' or '<space>'. It's best not to pass a direct value,
|
|
|
|
// but instead to go through the default user config to get a more meaningful key name
|
2022-12-24 17:48:57 +11:00
|
|
|
func (self *Input) Press(keyStrs ...string) {
|
2022-08-09 20:27:44 +10:00
|
|
|
for _, keyStr := range keyStrs {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(keyStr)
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-24 17:48:57 +11:00
|
|
|
func (self *Input) press(keyStr string) {
|
2022-08-09 20:27:44 +10:00
|
|
|
self.Wait(self.pushKeyDelay)
|
|
|
|
|
|
|
|
self.gui.PressKey(keyStr)
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) SwitchToStatusWindow() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.JumpToBlock[0])
|
2022-09-09 21:11:05 -07:00
|
|
|
self.assert.CurrentWindowName("status")
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-12-26 16:49:54 +11:00
|
|
|
// switch to status window and assert that the status view is on top
|
|
|
|
func (self *Input) SwitchToStatusView() {
|
|
|
|
self.SwitchToStatusWindow()
|
|
|
|
self.assert.CurrentView().Name("status")
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) SwitchToFilesWindow() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.JumpToBlock[1])
|
2022-09-09 21:11:05 -07:00
|
|
|
self.assert.CurrentWindowName("files")
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-12-26 16:49:54 +11:00
|
|
|
// switch to files window and assert that the files view is on top
|
|
|
|
func (self *Input) SwitchToFilesView() {
|
|
|
|
self.SwitchToFilesWindow()
|
|
|
|
self.assert.CurrentView().Name("files")
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) SwitchToBranchesWindow() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.JumpToBlock[2])
|
2022-09-09 21:11:05 -07:00
|
|
|
self.assert.CurrentWindowName("localBranches")
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-12-26 16:49:54 +11:00
|
|
|
// switch to branches window and assert that the branches view is on top
|
|
|
|
func (self *Input) SwitchToBranchesView() {
|
|
|
|
self.SwitchToBranchesWindow()
|
|
|
|
self.assert.CurrentView().Name("localBranches")
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) SwitchToCommitsWindow() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.JumpToBlock[3])
|
2022-09-09 21:11:05 -07:00
|
|
|
self.assert.CurrentWindowName("commits")
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-12-26 16:49:54 +11:00
|
|
|
// switch to commits window and assert that the commits view is on top
|
|
|
|
func (self *Input) SwitchToCommitsView() {
|
|
|
|
self.SwitchToCommitsWindow()
|
|
|
|
self.assert.CurrentView().Name("commits")
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) SwitchToStashWindow() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.JumpToBlock[4])
|
2022-09-09 21:11:05 -07:00
|
|
|
self.assert.CurrentWindowName("stash")
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-12-26 16:49:54 +11:00
|
|
|
// switch to stash window and assert that the stash view is on top
|
|
|
|
func (self *Input) SwitchToStashView() {
|
|
|
|
self.SwitchToStashWindow()
|
|
|
|
self.assert.CurrentView().Name("stash")
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) Type(content string) {
|
2022-08-09 20:27:44 +10:00
|
|
|
for _, char := range content {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(string(char))
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// i.e. pressing enter
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) Confirm() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.Confirm)
|
2022-09-17 11:58:24 -07:00
|
|
|
}
|
|
|
|
|
2022-09-16 22:15:02 -07:00
|
|
|
// i.e. same as Confirm
|
|
|
|
func (self *Input) Enter() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.Confirm)
|
2022-09-16 22:15:02 -07:00
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// i.e. pressing escape
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) Cancel() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.Return)
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// i.e. pressing space
|
2022-08-14 20:47:09 +10:00
|
|
|
func (self *Input) PrimaryAction() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.Select)
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// i.e. pressing down arrow
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) NextItem() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.NextItem)
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// i.e. pressing up arrow
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) PreviousItem() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.press(self.keys.Universal.PrevItem)
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) ContinueMerge() {
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Press(self.keys.Universal.CreateRebaseOptionsMenu)
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().SelectedLine(Contains("continue"))
|
2022-08-09 20:27:44 +10:00
|
|
|
self.Confirm()
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) ContinueRebase() {
|
2022-08-09 20:27:44 +10:00
|
|
|
self.ContinueMerge()
|
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// for when you want to allow lazygit to process something before continuing
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) Wait(milliseconds int) {
|
2022-08-09 20:27:44 +10:00
|
|
|
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) LogUI(message string) {
|
2022-08-09 20:27:44 +10:00
|
|
|
self.gui.LogUI(message)
|
|
|
|
}
|
|
|
|
|
2022-08-11 21:04:10 +10:00
|
|
|
func (self *Input) Log(message string) {
|
2022-08-09 20:27:44 +10:00
|
|
|
self.gui.LogUI(message)
|
|
|
|
}
|
|
|
|
|
2022-08-09 21:27:12 +10:00
|
|
|
// this will look for a list item in the current panel and if it finds it, it will
|
|
|
|
// enter the keypresses required to navigate to it.
|
|
|
|
// The test will fail if:
|
2022-08-12 09:24:39 +10:00
|
|
|
// - the user is not in a list item
|
|
|
|
// - no list item is found containing the given text
|
|
|
|
// - multiple list items are found containing the given text in the initial page of items
|
2022-08-09 21:27:12 +10:00
|
|
|
//
|
2022-08-09 20:27:44 +10:00
|
|
|
// NOTE: this currently assumes that ViewBufferLines returns all the lines that can be accessed.
|
|
|
|
// If this changes in future, we'll need to update this code to first attempt to find the item
|
|
|
|
// in the current page and failing that, jump to the top of the view and iterate through all of it,
|
|
|
|
// looking for the item.
|
2022-12-24 17:48:57 +11:00
|
|
|
func (self *Input) NavigateToListItem(matcher *matcher) {
|
2022-08-09 20:27:44 +10:00
|
|
|
self.assert.InListContext()
|
|
|
|
|
|
|
|
currentContext := self.gui.CurrentContext().(types.IListContext)
|
|
|
|
|
|
|
|
view := currentContext.GetView()
|
|
|
|
|
2022-09-09 21:11:05 -07:00
|
|
|
var matchIndex int
|
|
|
|
|
|
|
|
self.assert.assertWithRetries(func() (bool, string) {
|
|
|
|
matchIndex = -1
|
2022-12-24 17:48:57 +11:00
|
|
|
var matches []string
|
2022-09-09 21:11:05 -07:00
|
|
|
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
|
|
|
for i, line := range view.ViewBufferLines() {
|
2022-12-24 17:48:57 +11:00
|
|
|
ok, _ := matcher.test(line)
|
|
|
|
if ok {
|
|
|
|
matches = append(matches, line)
|
2022-09-09 21:11:05 -07:00
|
|
|
matchIndex = i
|
|
|
|
}
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
2022-12-24 17:48:57 +11:00
|
|
|
if len(matches) > 1 {
|
|
|
|
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Lines:\n%s", len(matches), matcher.name, strings.Join(matches, "\n"))
|
|
|
|
} else if len(matches) == 0 {
|
|
|
|
return false, fmt.Sprintf("Could not find item matching: %s", matcher.name)
|
2022-09-09 21:11:05 -07:00
|
|
|
} else {
|
|
|
|
return true, ""
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
selectedLineIdx := view.SelectedLineIdx()
|
|
|
|
if selectedLineIdx == matchIndex {
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().SelectedLine(matcher)
|
2022-09-09 21:11:05 -07:00
|
|
|
return
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
2022-09-09 21:11:05 -07:00
|
|
|
if selectedLineIdx < matchIndex {
|
|
|
|
for i := selectedLineIdx; i < matchIndex; i++ {
|
|
|
|
self.NextItem()
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().SelectedLine(matcher)
|
2022-09-09 21:11:05 -07:00
|
|
|
return
|
|
|
|
} else {
|
|
|
|
for i := selectedLineIdx; i > matchIndex; i-- {
|
|
|
|
self.PreviousItem()
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().SelectedLine(matcher)
|
2022-09-09 21:11:05 -07:00
|
|
|
return
|
2022-08-09 20:27:44 +10:00
|
|
|
}
|
|
|
|
}
|
2022-12-24 17:48:57 +11:00
|
|
|
|
|
|
|
func (self *Input) AcceptConfirmation(title *matcher, content *matcher) {
|
|
|
|
self.assert.InConfirm()
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Title(title)
|
|
|
|
self.assert.CurrentView().Content(content)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Confirm()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *Input) DenyConfirmation(title *matcher, content *matcher) {
|
|
|
|
self.assert.InConfirm()
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Title(title)
|
|
|
|
self.assert.CurrentView().Content(content)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *Input) Prompt(title *matcher, textToType string) {
|
|
|
|
self.assert.InPrompt()
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Title(title)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Type(textToType)
|
|
|
|
self.Confirm()
|
|
|
|
}
|
|
|
|
|
|
|
|
// type some text into a prompt, then switch to the suggestions panel and expect the first
|
|
|
|
// item to match the given matcher, then confirm that item.
|
|
|
|
func (self *Input) Typeahead(title *matcher, textToType string, expectedFirstOption *matcher) {
|
|
|
|
self.assert.InPrompt()
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Title(title)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Type(textToType)
|
|
|
|
self.Press(self.keys.Universal.TogglePanel)
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Name("suggestions")
|
|
|
|
self.assert.CurrentView().SelectedLine(expectedFirstOption)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Confirm()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *Input) Menu(title *matcher, optionToSelect *matcher) {
|
|
|
|
self.assert.InMenu()
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Title(title)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.NavigateToListItem(optionToSelect)
|
|
|
|
self.Confirm()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *Input) Alert(title *matcher, content *matcher) {
|
|
|
|
self.assert.InListContext()
|
2022-12-26 11:12:56 +11:00
|
|
|
self.assert.CurrentView().Title(title)
|
|
|
|
self.assert.CurrentView().Content(content)
|
2022-12-24 17:48:57 +11:00
|
|
|
self.Confirm()
|
|
|
|
}
|