1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-01 13:17:53 +02:00

move merge conflicts code into controller

This commit is contained in:
Jesse Duffield 2022-08-06 18:50:52 +10:00
parent 445a625b56
commit 7410acd1aa
39 changed files with 682 additions and 652 deletions

View File

@ -142,7 +142,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
bindingsByHeader,
func(header header, hBindings []*types.Binding) headerWithBindings {
uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string {
return binding.Description + keybindings.GetKeyDisplay(binding.Key)
return binding.Description + keybindings.LabelFromKey(binding.Key)
})
return headerWithBindings{
@ -202,10 +202,10 @@ func formatBinding(binding *types.Binding) string {
if binding.Alternative != "" {
return fmt.Sprintf(
" <kbd>%s</kbd>: %s (%s)\n",
keybindings.GetKeyDisplay(binding.Key),
keybindings.LabelFromKey(binding.Key),
binding.Description,
binding.Alternative,
)
}
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.GetKeyDisplay(binding.Key), binding.Description)
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
}

View File

@ -7,7 +7,8 @@ import (
"bytes"
"io"
"os/exec"
"sync"
"github.com/sasha-s/go-deadlock"
)
type Buffer struct {

View File

@ -94,7 +94,7 @@ func (gui *Gui) renderAppStatus() {
defer ticker.Stop()
for range ticker.C {
appStatus := gui.statusManager.getStatusString()
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
return gui.renderString(gui.Views.AppStatus, appStatus)
})
@ -117,7 +117,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
gui.renderAppStatus()
if err := f(); err != nil {
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
return gui.c.Error(err)
})
}

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@ -53,7 +54,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader,
gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
@ -71,7 +72,7 @@ func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig.Keybinding
formattedKey := func(key string) string {
return gui.getKeyDisplay(key)
return keybindings.Label(key)
}
tips := []string{

View File

@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -12,9 +13,9 @@ func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CommitMessageConfirm,
map[string]string{
"keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
"keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
"keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
},
)

View File

@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@ -220,22 +221,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
bindings := []*types.Binding{
{
ViewName: "confirmation",
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Key: gui.getKey(keybindingConfig.Universal.Return),
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
},
{
ViewName: "confirmation",
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
@ -245,22 +246,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
},
{
ViewName: "suggestions",
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Key: gui.getKey(keybindingConfig.Universal.Return),
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
},
{
ViewName: "suggestions",
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},
}
@ -276,12 +277,12 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.Keybinding
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
}
func (gui *Gui) refreshSuggestions() {
@ -297,8 +298,8 @@ func (gui *Gui) handleAskFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
},
)

View File

@ -89,7 +89,7 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
displayStrings := item.LabelColumns
if showKeys {
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.GetKeyDisplay(item.Key)))
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)))
}
return displayStrings
})

View File

@ -1,15 +1,20 @@
package context
import (
"math"
"sync"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/sasha-s/go-deadlock"
)
type MergeConflictsContext struct {
types.Context
viewModel *ConflictsViewModel
c *types.HelperCommon
mutex *sync.Mutex
}
type ConflictsViewModel struct {
@ -35,6 +40,7 @@ func NewMergeConflictsContext(
return &MergeConflictsContext{
viewModel: viewModel,
mutex: &sync.Mutex{},
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
@ -50,6 +56,18 @@ func NewMergeConflictsContext(
}
}
func (self *MergeConflictsContext) GetState() *mergeconflicts.State {
return self.viewModel.state
}
func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) {
self.viewModel.state = state
}
func (self *MergeConflictsContext) GetMutex() *sync.Mutex {
return self.mutex
}
func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) {
self.viewModel.userVerticalScrolling = isScrolling
}
@ -58,6 +76,43 @@ func (self *MergeConflictsContext) IsUserScrolling() bool {
return self.viewModel.userVerticalScrolling
}
func (self *MergeConflictsContext) State() *mergeconflicts.State {
return self.viewModel.state
func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
self.setContent(isFocused)
self.focusSelection()
self.c.Render()
return nil
}
func (self *MergeConflictsContext) Render(isFocused bool) error {
self.setContent(isFocused)
self.c.Render()
return nil
}
func (self *MergeConflictsContext) GetContentToRender(isFocused bool) string {
if self.GetState() == nil {
return ""
}
return mergeconflicts.ColoredConflictFile(self.GetState(), isFocused)
}
func (self *MergeConflictsContext) setContent(isFocused bool) {
self.GetView().SetContent(self.GetContentToRender(isFocused))
}
func (self *MergeConflictsContext) focusSelection() {
if !self.IsUserScrolling() {
_ = self.GetView().SetOrigin(self.GetView().OriginX(), self.GetOriginY())
}
}
func (self *MergeConflictsContext) GetOriginY() int {
view := self.GetView()
conflictMiddle := self.GetState().GetConflictMiddle()
return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
}

View File

@ -67,19 +67,16 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
}
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
self.GetView().SetContent(self.GetContentToRender(isFocused))
if err := self.focusSelection(); err != nil {
return err
}
self.setContent(isFocused)
self.focusSelection()
self.c.Render()
return nil
}
func (self *PatchExplorerContext) Render(isFocused bool) error {
self.GetView().SetContent(self.GetContentToRender(isFocused))
self.setContent(isFocused)
self.c.Render()
@ -87,16 +84,17 @@ func (self *PatchExplorerContext) Render(isFocused bool) error {
}
func (self *PatchExplorerContext) Focus() error {
if err := self.focusSelection(); err != nil {
return err
}
self.focusSelection()
self.c.Render()
return nil
}
func (self *PatchExplorerContext) focusSelection() error {
func (self *PatchExplorerContext) setContent(isFocused bool) {
self.GetView().SetContent(self.GetContentToRender(isFocused))
}
func (self *PatchExplorerContext) focusSelection() {
view := self.GetView()
state := self.GetState()
_, viewHeight := view.Size()
@ -107,11 +105,8 @@ func (self *PatchExplorerContext) focusSelection() error {
newOrigin := state.CalculateOrigin(origin, bufferHeight)
if err := view.SetOriginY(newOrigin); err != nil {
return err
}
return view.SetCursor(0, selectedLineIdx-newOrigin)
_ = view.SetOriginY(newOrigin)
_ = view.SetCursor(0, selectedLineIdx-newOrigin)
}
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {

View File

@ -164,16 +164,21 @@ func (gui *Gui) contextTree() *context.ContextTree {
OnFocus: OnFocusWrapper(func() error {
gui.Views.MergeConflicts.Wrap = false
return gui.renderConflictsWithLock(true)
return gui.refreshMergePanel(true)
}),
OnFocusLost: func(types.OnFocusLostOpts) error {
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
gui.Views.MergeConflicts.Wrap = true
return nil
},
},
gui.c,
gui.getMergingOptions,
func() map[string]string {
// wrapping in a function because contexts are initialized before helpers
return gui.helpers.MergeConflicts.GetMergingOptions()
},
),
Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
@ -217,12 +222,11 @@ func (gui *Gui) contextTree() *context.ContextTree {
),
CommandLog: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT,
View: gui.Views.Extras,
WindowName: "extras",
Key: context.COMMAND_LOG_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions,
Focusable: true,
Kind: types.EXTRAS_CONTEXT,
View: gui.Views.Extras,
WindowName: "extras",
Key: context.COMMAND_LOG_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocusLost: func(opts types.OnFocusLostOpts) error {

View File

@ -35,6 +35,7 @@ func (gui *Gui) resetControllers() {
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
GPG: helpers.NewGpgHelper(helperCommon, gui.os, gui.git),
MergeAndRebase: rebaseHelper,
MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
CherryPick: helpers.NewCherryPickHelper(
helperCommon,
gui.git,
@ -51,7 +52,6 @@ func (gui *Gui) resetControllers() {
gui.git,
gui.State.Contexts,
gui.helpers,
gui.getKey,
)
common := controllers.NewControllerCommon(
@ -112,8 +112,8 @@ func (gui *Gui) resetControllers() {
gui.enterSubmodule,
setCommitMessage,
getSavedCommitMessage,
gui.switchToMerge,
)
mergeConflictsController := controllers.NewMergeConflictsController(common)
remotesController := controllers.NewRemotesController(
common,
func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches },
@ -187,6 +187,10 @@ func (gui *Gui) resetControllers() {
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
)
controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
mergeConflictsController,
)
controllers.AttachControllers(gui.State.Contexts.Files,
filesController,
filesRemoveController,

View File

@ -22,7 +22,6 @@ type FilesController struct {
enterSubmodule func(submodule *models.SubmoduleConfig) error
setCommitMessage func(message string)
getSavedCommitMessage func() string
switchToMergeFn func(path string) error
}
var _ types.IController = &FilesController{}
@ -32,14 +31,12 @@ func NewFilesController(
enterSubmodule func(submodule *models.SubmoduleConfig) error,
setCommitMessage func(message string),
getSavedCommitMessage func() string,
switchToMergeFn func(path string) error,
) *FilesController {
return &FilesController{
controllerCommon: common,
enterSubmodule: enterSubmodule,
setCommitMessage: setCommitMessage,
getSavedCommitMessage: getSavedCommitMessage,
switchToMergeFn: switchToMergeFn,
}
}
@ -268,10 +265,6 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
if node.IsFile() {
file := node.File
if file.HasInlineMergeConflicts {
return self.c.PushContext(self.contexts.MergeConflicts)
}
if file.HasUnstagedChanges {
self.c.LogAction(self.c.Tr.Actions.StageFile)
@ -328,6 +321,10 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
}
func (self *FilesController) press(node *filetree.FileNode) error {
if node.IsFile() && node.File.HasInlineMergeConflicts {
return self.switchToMerge()
}
if err := self.pressWithLock(node); err != nil {
return err
}
@ -750,7 +747,7 @@ func (self *FilesController) switchToMerge() error {
return nil
}
return self.switchToMergeFn(file.Name)
return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
}
func (self *FilesController) createStashMenu() error {

View File

@ -8,6 +8,7 @@ type Helpers struct {
WorkingTree *WorkingTreeHelper
Tags *TagsHelper
MergeAndRebase *MergeAndRebaseHelper
MergeConflicts *MergeConflictsHelper
CherryPick *CherryPickHelper
Host *HostHelper
PatchBuilding *PatchBuildingHelper
@ -24,6 +25,7 @@ func NewStubHelpers() *Helpers {
WorkingTree: &WorkingTreeHelper{},
Tags: &TagsHelper{},
MergeAndRebase: &MergeAndRebaseHelper{},
MergeConflicts: &MergeConflictsHelper{},
CherryPick: &CherryPickHelper{},
Host: &HostHelper{},
PatchBuilding: &PatchBuildingHelper{},

View File

@ -186,8 +186,6 @@ func (self *MergeAndRebaseHelper) workingTreeStateNoun() string {
// PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
self.contexts.MergeConflicts.SetUserScrolling(false)
return self.c.Confirm(types.ConfirmOpts{
Title: "continue",
Prompt: self.c.Tr.ConflictsResolved,

View File

@ -0,0 +1,115 @@
package helpers
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type MergeConflictsHelper struct {
c *types.HelperCommon
contexts *context.ContextTree
git *commands.GitCommand
}
func NewMergeConflictsHelper(
c *types.HelperCommon,
contexts *context.ContextTree,
git *commands.GitCommand,
) *MergeConflictsHelper {
return &MergeConflictsHelper{
c: c,
contexts: contexts,
git: git,
}
}
func (self *MergeConflictsHelper) GetMergingOptions() map[string]string {
keybindingConfig := self.c.UserConfig.Keybinding
return map[string]string{
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcSelectHunk,
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock)): self.c.Tr.LcNavigateConflicts,
keybindings.Label(keybindingConfig.Universal.Select): self.c.Tr.LcPickHunk,
keybindings.Label(keybindingConfig.Main.PickBothHunks): self.c.Tr.LcPickAllHunks,
keybindings.Label(keybindingConfig.Universal.Undo): self.c.Tr.LcUndo,
}
}
func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
return self.setMergeStateWithoutLock(path)
}
func (self *MergeConflictsHelper) setMergeStateWithoutLock(path string) (bool, error) {
content, err := self.git.File.Cat(path)
if err != nil {
return false, err
}
if path != self.context().GetState().GetPath() {
self.context().SetUserScrolling(false)
}
self.context().GetState().SetContent(content, path)
return !self.context().GetState().NoConflicts(), nil
}
func (self *MergeConflictsHelper) ResetMergeState() {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
self.resetMergeState()
}
func (self *MergeConflictsHelper) resetMergeState() {
self.context().SetUserScrolling(false)
self.context().GetState().Reset()
}
func (self *MergeConflictsHelper) EscapeMerge() error {
self.resetMergeState()
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
self.c.OnUIThread(func() error {
return self.c.PushContext(self.contexts.Files)
})
return nil
}
func (self *MergeConflictsHelper) SetConflictsAndRender(path string, isFocused bool) (bool, error) {
hasConflicts, err := self.setMergeStateWithoutLock(path)
if err != nil {
return false, err
}
if hasConflicts {
return true, self.context().Render(isFocused)
}
return false, nil
}
func (self *MergeConflictsHelper) SwitchToMerge(path string) error {
if self.context().GetState().GetPath() != path {
hasConflicts, err := self.SetMergeState(path)
if err != nil {
return err
}
if !hasConflicts {
return nil
}
}
return self.c.PushContext(self.contexts.MergeConflicts)
}
func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
return self.contexts.MergeConflicts
}

View File

@ -1,7 +1,11 @@
package controllers
import (
"io/ioutil"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -25,14 +29,125 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.EditFile,
Handler: self.HandleEditFile,
Description: self.c.Tr.LcEditFile,
},
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.HandleOpenFile,
Description: self.c.Tr.LcOpenFile,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.withRenderAndFocus(self.PrevConflict),
Description: self.c.Tr.PrevConflict,
},
{
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.withRenderAndFocus(self.NextConflict),
Description: self.c.Tr.NextConflict,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
Description: self.c.Tr.SelectPrevHunk,
},
{
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.withRenderAndFocus(self.NextConflictHunk),
Description: self.c.Tr.SelectNextHunk,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
Handler: self.withRenderAndFocus(self.PrevConflict),
},
{
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
Handler: self.withRenderAndFocus(self.NextConflict),
},
{
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
},
{
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Handler: self.withRenderAndFocus(self.NextConflictHunk),
},
{
Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
Handler: self.withRenderAndFocus(self.HandleScrollLeft),
Description: self.c.Tr.LcScrollLeft,
Tag: "navigation",
},
{
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
Handler: self.withRenderAndFocus(self.HandleScrollRight),
Description: self.c.Tr.LcScrollRight,
Tag: "navigation",
},
{
Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.withRenderAndFocus(self.HandleUndo),
Description: self.c.Tr.LcUndo,
},
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.helpers.WorkingTree.OpenMergeTool,
Description: self.c.Tr.LcOpenMergeTool,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withRenderAndFocus(self.HandlePickHunk),
Description: self.c.Tr.PickHunk,
},
{
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
Description: self.c.Tr.PickAllHunks,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.Escape,
Description: self.c.Tr.ReturnToFilesPanel,
},
}
return bindings
}
func (self *MergeConflictsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.context().GetViewName(),
Key: gocui.MouseWheelUp,
Handler: func(gocui.ViewMouseBindingOpts) error {
return self.HandleScrollUp()
},
},
{
ViewName: self.context().GetViewName(),
Key: gocui.MouseWheelDown,
Handler: func(gocui.ViewMouseBindingOpts) error {
return self.HandleScrollDown()
},
},
}
}
func (self *MergeConflictsController) HandleScrollUp() error {
self.context().SetUserScrolling(true)
self.context().GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
return nil
}
func (self *MergeConflictsController) HandleScrollDown() error {
self.context().SetUserScrolling(true)
self.context().GetViewTrait().ScrollDown(self.c.UserConfig.Gui.ScrollHeight)
return nil
}
func (self *MergeConflictsController) Context() types.Context {
return self.context()
}
@ -41,14 +156,162 @@ func (self *MergeConflictsController) context() *context.MergeConflictsContext {
return self.contexts.MergeConflicts
}
func (self *MergeConflictsController) EditFile() error {
lineNumber := self.context().State().GetSelectedLine()
return self.helpers.Files.EditFileAtLine(self.context().State().GetPath(), lineNumber)
func (self *MergeConflictsController) Escape() error {
return self.c.PushContext(self.contexts.Files)
}
func (self *MergeConflictsController) withMergeConflictLock(f func() error) error {
self.context().State().Lock()
defer self.context().State().Unlock()
return f()
func (self *MergeConflictsController) HandleEditFile() error {
lineNumber := self.context().GetState().GetSelectedLine()
return self.helpers.Files.EditFileAtLine(self.context().GetState().GetPath(), lineNumber)
}
func (self *MergeConflictsController) HandleOpenFile() error {
lineNumber := self.context().GetState().GetSelectedLine()
return self.helpers.Files.OpenFileAtLine(self.context().GetState().GetPath(), lineNumber)
}
func (self *MergeConflictsController) HandleScrollLeft() error {
self.context().GetViewTrait().ScrollLeft()
return nil
}
func (self *MergeConflictsController) HandleScrollRight() error {
self.context().GetViewTrait().ScrollRight()
return nil
}
func (self *MergeConflictsController) HandleUndo() error {
state := self.context().GetState()
ok := state.Undo()
if !ok {
return nil
}
self.c.LogAction("Restoring file to previous state")
self.c.LogCommand("Undoing last conflict resolution", false)
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
return err
}
return nil
}
func (self *MergeConflictsController) PrevConflictHunk() error {
self.context().SetUserScrolling(false)
self.context().GetState().SelectPrevConflictHunk()
return nil
}
func (self *MergeConflictsController) NextConflictHunk() error {
self.context().SetUserScrolling(false)
self.context().GetState().SelectNextConflictHunk()
return nil
}
func (self *MergeConflictsController) NextConflict() error {
self.context().SetUserScrolling(false)
self.context().GetState().SelectNextConflict()
return nil
}
func (self *MergeConflictsController) PrevConflict() error {
self.context().SetUserScrolling(false)
self.context().GetState().SelectPrevConflict()
return nil
}
func (self *MergeConflictsController) HandlePickHunk() error {
return self.pickSelection(self.context().GetState().Selection())
}
func (self *MergeConflictsController) HandlePickAllHunks() error {
return self.pickSelection(mergeconflicts.ALL)
}
func (self *MergeConflictsController) pickSelection(selection mergeconflicts.Selection) error {
ok, err := self.resolveConflict(selection)
if err != nil {
return err
}
if !ok {
return nil
}
if self.context().GetState().AllConflictsResolved() {
return self.onLastConflictResolved()
}
return nil
}
func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
self.context().SetUserScrolling(false)
state := self.context().GetState()
ok, content, err := state.ContentAfterConflictResolve(selection)
if err != nil {
return false, err
}
if !ok {
return false, nil
}
var logStr string
switch selection {
case mergeconflicts.TOP:
logStr = "Picking top hunk"
case mergeconflicts.MIDDLE:
logStr = "Picking middle hunk"
case mergeconflicts.BOTTOM:
logStr = "Picking bottom hunk"
case mergeconflicts.ALL:
logStr = "Picking all hunks"
}
self.c.LogAction("Resolve merge conflict")
self.c.LogCommand(logStr, false)
state.PushContent(content)
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
}
func (self *MergeConflictsController) onLastConflictResolved() error {
// as part of refreshing files, we handle the situation where a file has had
// its merge conflicts resolved.
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}
func (self *MergeConflictsController) isFocused() bool {
return self.c.CurrentContext().GetKey() == self.context().GetKey()
}
func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error {
return self.withLock(func() error {
if err := f(); err != nil {
return err
}
return self.context().RenderAndFocus(self.isFocused())
})
}
func (self *MergeConflictsController) withLock(f func() error) func() error {
return func() error {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
if self.context().GetState() == nil {
return nil
}
return f()
}
}

View File

@ -4,10 +4,11 @@ import (
"unicode"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
)
func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool {
newlineKey, ok := gui.getKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
newlineKey, ok := keybindings.GetKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
if !ok {
newlineKey = gocui.KeyAltEnter
}

View File

@ -6,8 +6,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
)
// list panel functions
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
return gui.State.Contexts.Files.GetSelected()
}
@ -20,15 +18,6 @@ func (gui *Gui) getSelectedFile() *models.File {
return node.File
}
func (gui *Gui) getSelectedPath() string {
node := gui.getSelectedFileNode()
if node == nil {
return ""
}
return node.GetPath()
}
func (gui *Gui) filesRenderToMain() error {
node := gui.getSelectedFileNode()
@ -43,16 +32,17 @@ func (gui *Gui) filesRenderToMain() error {
}
if node.File != nil && node.File.HasInlineMergeConflicts {
ok, err := gui.setConflictsAndRenderWithLock(node.GetPath(), false)
hasConflicts, err := gui.helpers.MergeConflicts.SetMergeState(node.GetPath())
if err != nil {
return err
}
if ok {
return nil
if hasConflicts {
return gui.refreshMergePanel(false)
}
}
gui.resetMergeStateWithLock()
gui.helpers.MergeConflicts.ResetMergeState()
pair := gui.normalMainContextPair()
if node.File != nil {
@ -92,13 +82,6 @@ func (gui *Gui) filesRenderToMain() error {
return gui.refreshMainViews(refreshOpts)
}
func (gui *Gui) onFocusFile() error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
return nil
}
// test
func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
return func(text string) {
// using a getView function so that we don't need to worry about when the view is created
@ -108,5 +91,3 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
view.RenderTextArea()
}
}
// test

View File

@ -75,10 +75,6 @@ func (gui *Gui) scrollDownView(view *gocui.View) {
}
func (gui *Gui) scrollUpMain() error {
if gui.renderingConflicts() {
gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
}
var view *gocui.View
if gui.c.CurrentContext().GetWindowName() == "secondary" {
view = gui.secondaryView()
@ -86,16 +82,20 @@ func (gui *Gui) scrollUpMain() error {
view = gui.mainView()
}
if view.Name() == "mergeConflicts" {
// although we have this same logic in the controller, this method can be invoked
// via the global scroll up/down keybindings, as opposed to just the mouse wheel keybinding.
// It would be nice to have a concept of a global keybinding that runs on the top context in a
// window but that might be overkill for this one use case.
gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
}
gui.scrollUpView(view)
return nil
}
func (gui *Gui) scrollDownMain() error {
if gui.renderingConflicts() {
gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
}
var view *gocui.View
if gui.c.CurrentContext().GetWindowName() == "secondary" {
view = gui.secondaryView()
@ -103,6 +103,10 @@ func (gui *Gui) scrollDownMain() error {
view = gui.mainView()
}
if view.Name() == "mergeConflicts" {
gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
}
gui.scrollDownView(view)
return nil
@ -120,27 +124,6 @@ func (gui *Gui) secondaryView() *gocui.View {
return view
}
func (gui *Gui) scrollLeftMain() error {
gui.scrollLeft(gui.mainView())
return nil
}
func (gui *Gui) scrollRightMain() error {
gui.scrollRight(gui.mainView())
return nil
}
func (gui *Gui) scrollLeft(view *gocui.View) {
newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
_ = view.SetOriginX(newOriginX)
}
func (gui *Gui) scrollRight(view *gocui.View) {
_ = view.SetOriginX(view.OriginX() + view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR)
}
func (gui *Gui) scrollUpSecondary() error {
gui.scrollUpView(gui.secondaryView())

View File

@ -19,6 +19,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/modes/filtering"
@ -481,6 +482,13 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
gui.g = g
defer gui.g.Close()
// if the deadlock package wants to report a deadlock, we first need to
// close the gui so that we can actually read what it prints.
deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
gui.g.Close()
})
deadlock.Opts.Disable = !gui.Debug
if replaying() {
gui.g.RecordingConfig = gocui.RecordingConfig{
Speed: getRecordingSpeed(),
@ -504,9 +512,9 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
return nil
}
userConfig := gui.UserConfig
gui.g.SearchEscapeKey = gui.getKey(userConfig.Keybinding.Universal.Return)
gui.g.NextSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.NextMatch)
gui.g.PrevSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.PrevMatch)
gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return)
gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch)
gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch)
gui.g.ShowListFooter = userConfig.Gui.ShowListFooter
@ -771,7 +779,7 @@ func (gui *Gui) setColorScheme() error {
return nil
}
func (gui *Gui) OnUIThread(f func() error) {
func (gui *Gui) onUIThread(f func() error) {
gui.g.Update(func(*gocui.Gui) error {
return f()
})

View File

@ -87,5 +87,5 @@ func (self *guiCommon) OpenSearch() {
}
func (self *guiCommon) OnUIThread(f func() error) {
self.gui.OnUIThread(f)
self.gui.onUIThread(f)
}

View File

@ -2,37 +2,13 @@ package gui
import (
"log"
"strings"
"unicode/utf8"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) getKeyDisplay(name string) string {
key := gui.getKey(name)
return keybindings.GetKeyDisplay(key)
}
func (gui *Gui) getKey(key string) types.Key {
runeCount := utf8.RuneCountInString(key)
if runeCount > 1 {
binding := keybindings.Keymap[strings.ToLower(key)]
if binding == nil {
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
} else {
return binding
}
} else if runeCount == 1 {
return []rune(key)[0]
}
log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
return nil
}
func (gui *Gui) noPopupPanel(f func() error) func() error {
return func() error {
if gui.popupPanelFocused() {
@ -68,7 +44,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}
opts := types.KeybindingsOpts{
GetKey: self.getKey,
GetKey: keybindings.GetKey,
Config: config,
Guards: guards,
}
@ -325,110 +301,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Modifier: gocui.ModNone,
Handler: self.scrollUpSecondary,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
Handler: self.scrollLeftMain,
Description: self.c.Tr.LcScrollLeft,
Tag: "navigation",
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
Handler: self.scrollRightMain,
Description: self.c.Tr.LcScrollRight,
Tag: "navigation",
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.handleEscapeMerge,
Description: self.c.Tr.ReturnToFilesPanel,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.helpers.WorkingTree.OpenMergeTool,
Description: self.c.Tr.LcOpenMergeTool,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.handlePickHunk,
Description: self.c.Tr.PickHunk,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.handlePickAllHunks,
Description: self.c.Tr.PickAllHunks,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.handleSelectPrevConflict,
Description: self.c.Tr.PrevConflict,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.handleSelectNextConflict,
Description: self.c.Tr.NextConflict,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.handleSelectPrevConflictHunk,
Description: self.c.Tr.SelectPrevHunk,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.handleSelectNextConflictHunk,
Description: self.c.Tr.SelectNextHunk,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectPrevConflict,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectNextConflict,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectPrevConflictHunk,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectNextConflictHunk,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.handleMergeConflictEditFileAtLine,
Description: self.c.Tr.LcEditFile,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.handleMergeConflictOpenFileAtLine,
Description: self.c.Tr.LcOpenFile,
},
{
ViewName: "mergeConflicts",
Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.handleMergeConflictUndo,
Description: self.c.Tr.LcUndo,
},
{
ViewName: "status",
Key: gocui.MouseLeft,

View File

@ -2,12 +2,16 @@ package keybindings
import (
"fmt"
"log"
"strings"
"unicode/utf8"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
var KeyMapReversed = map[gocui.Key]string{
var keyMapReversed = map[gocui.Key]string{
gocui.KeyF1: "f1",
gocui.KeyF2: "f2",
gocui.KeyF3: "f3",
@ -66,11 +70,11 @@ var KeyMapReversed = map[gocui.Key]string{
gocui.KeyCtrl5: "ctrl+5", // ctrl+]
gocui.KeyCtrl6: "ctrl+6",
gocui.KeyCtrl8: "ctrl+8",
gocui.MouseWheelUp: "mouse wheel up",
gocui.MouseWheelDown: "mouse wheel down",
gocui.MouseWheelUp: "mouse wheel ",
gocui.MouseWheelDown: "mouse wheel ",
}
var Keymap = map[string]types.Key{
var keyMap = map[string]types.Key{
"<c-a>": gocui.KeyCtrlA,
"<c-b>": gocui.KeyCtrlB,
"<c-c>": gocui.KeyCtrlC,
@ -142,14 +146,18 @@ var Keymap = map[string]types.Key{
"<right>": gocui.KeyArrowRight,
}
func GetKeyDisplay(key types.Key) string {
func Label(name string) string {
return LabelFromKey(GetKey(name))
}
func LabelFromKey(key types.Key) string {
keyInt := 0
switch key := key.(type) {
case rune:
keyInt = int(key)
case gocui.Key:
value, ok := KeyMapReversed[key]
value, ok := keyMapReversed[key]
if ok {
return value
}
@ -158,3 +166,19 @@ func GetKeyDisplay(key types.Key) string {
return fmt.Sprintf("%c", keyInt)
}
func GetKey(key string) types.Key {
runeCount := utf8.RuneCountInString(key)
if runeCount > 1 {
binding := keyMap[strings.ToLower(key)]
if binding == nil {
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
} else {
return binding
}
} else if runeCount == 1 {
return []rune(key)[0]
}
log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
return nil
}

View File

@ -33,7 +33,7 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext {
return []string{line}
})
},
OnFocusWrapper(gui.onFocusFile),
nil,
gui.withDiffModeCheck(gui.filesRenderToMain),
nil,
gui.c,

View File

@ -43,6 +43,18 @@ func NewRenderStringWithoutScrollTask(str string) *renderStringWithoutScrollTask
return &renderStringWithoutScrollTask{str: str}
}
type renderStringWithScrollTask struct {
str string
originX int
originY int
}
func (t *renderStringWithScrollTask) IsUpdateTask() {}
func NewRenderStringWithScrollTask(str string, originX int, originY int) *renderStringWithScrollTask {
return &renderStringWithScrollTask{str: str, originX: originX, originY: originY}
}
type runCommandTask struct {
cmd *exec.Cmd
prefix string
@ -69,11 +81,6 @@ func NewRunPtyTask(cmd *exec.Cmd) *runPtyTask {
return &runPtyTask{cmd: cmd}
}
// currently unused
// func (gui *Gui) createRunPtyTaskWithPrefix(cmd *exec.Cmd, prefix string) *runPtyTask {
// return &runPtyTask{cmd: cmd, prefix: prefix}
// }
func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
switch v := task.(type) {
case *renderStringTask:
@ -82,6 +89,9 @@ func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
case *renderStringWithoutScrollTask:
return gui.newStringTaskWithoutScroll(view, v.str)
case *renderStringWithScrollTask:
return gui.newStringTaskWithScroll(view, v.str, v.originX, v.originY)
case *runCommandTask:
return gui.newCmdTask(view, v.cmd, v.prefix)

View File

@ -3,6 +3,7 @@ package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@ -13,9 +14,9 @@ func (gui *Gui) getMenuOptions() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
keybindings.Label(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
}
}

View File

@ -1,315 +0,0 @@
// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future
package gui
import (
"fmt"
"io/ioutil"
"math"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) handleSelectPrevConflictHunk() error {
return gui.withMergeConflictLock(func() error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.State().SelectPrevConflictHunk()
return gui.renderConflictsWithFocus()
})
}
func (gui *Gui) handleSelectNextConflictHunk() error {
return gui.withMergeConflictLock(func() error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.State().SelectNextConflictHunk()
return gui.renderConflictsWithFocus()
})
}
func (gui *Gui) handleSelectNextConflict() error {
return gui.withMergeConflictLock(func() error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.State().SelectNextConflict()
return gui.renderConflictsWithFocus()
})
}
func (gui *Gui) handleSelectPrevConflict() error {
return gui.withMergeConflictLock(func() error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.State().SelectPrevConflict()
return gui.renderConflictsWithFocus()
})
}
func (gui *Gui) handleMergeConflictUndo() error {
state := gui.State.Contexts.MergeConflicts.State()
ok := state.Undo()
if !ok {
return nil
}
gui.c.LogAction("Restoring file to previous state")
gui.LogCommand("Undoing last conflict resolution", false)
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
return err
}
return gui.renderConflictsWithFocus()
}
func (gui *Gui) handlePickHunk() error {
return gui.withMergeConflictLock(func() error {
ok, err := gui.resolveConflict(gui.State.Contexts.MergeConflicts.State().Selection())
if err != nil {
return err
}
if !ok {
return nil
}
if gui.State.Contexts.MergeConflicts.State().AllConflictsResolved() {
return gui.onLastConflictResolved()
}
return gui.renderConflictsWithFocus()
})
}
func (gui *Gui) handlePickAllHunks() error {
return gui.withMergeConflictLock(func() error {
ok, err := gui.resolveConflict(mergeconflicts.ALL)
if err != nil {
return err
}
if !ok {
return nil
}
if gui.State.Contexts.MergeConflicts.State().AllConflictsResolved() {
return gui.onLastConflictResolved()
}
return gui.renderConflictsWithFocus()
})
}
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
state := gui.State.Contexts.MergeConflicts.State()
ok, content, err := state.ContentAfterConflictResolve(selection)
if err != nil {
return false, err
}
if !ok {
return false, nil
}
var logStr string
switch selection {
case mergeconflicts.TOP:
logStr = "Picking top hunk"
case mergeconflicts.MIDDLE:
logStr = "Picking middle hunk"
case mergeconflicts.BOTTOM:
logStr = "Picking bottom hunk"
case mergeconflicts.ALL:
logStr = "Picking all hunks"
}
gui.c.LogAction("Resolve merge conflict")
gui.LogCommand(logStr, false)
state.PushContent(content)
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
}
// precondition: we actually have conflicts to render
func (gui *Gui) renderConflicts(hasFocus bool) error {
state := gui.State.Contexts.MergeConflicts.State()
content := mergeconflicts.ColoredConflictFile(state, hasFocus)
if !gui.State.Contexts.MergeConflicts.IsUserScrolling() {
// TODO: find a way to not have to do this OnUIThread thing. Why doesn't it work
// without it given that we're calling the 'no scroll' variant below?
gui.c.OnUIThread(func() error {
gui.State.Contexts.MergeConflicts.State().Lock()
defer gui.State.Contexts.MergeConflicts.State().Unlock()
if !state.Active() {
return nil
}
gui.centerYPos(gui.Views.MergeConflicts, state.GetConflictMiddle())
return nil
})
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.mergingMainContextPair(),
main: &viewUpdateOpts{
task: NewRenderStringWithoutScrollTask(content),
},
})
}
func (gui *Gui) renderConflictsWithFocus() error {
return gui.renderConflicts(true)
}
func (gui *Gui) renderConflictsWithLock(hasFocus bool) error {
return gui.withMergeConflictLock(func() error {
return gui.renderConflicts(hasFocus)
})
}
func (gui *Gui) centerYPos(view *gocui.View, y int) {
ox, _ := view.Origin()
_, height := view.Size()
newOriginY := int(math.Max(0, float64(y-(height/2))))
_ = view.SetOrigin(ox, newOriginY)
}
func (gui *Gui) getMergingOptions() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcSelectHunk,
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.c.Tr.LcNavigateConflicts,
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcPickHunk,
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.c.Tr.LcPickAllHunks,
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.c.Tr.LcUndo,
}
}
func (gui *Gui) handleEscapeMerge() error {
if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
return gui.escapeMerge()
}
func (gui *Gui) onLastConflictResolved() error {
// as part of refreshing files, we handle the situation where a file has had
// its merge conflicts resolved.
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}
func (gui *Gui) resetMergeState() {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.State().Reset()
}
func (gui *Gui) setMergeState(path string) (bool, error) {
content, err := gui.git.File.Cat(path)
if err != nil {
return false, err
}
gui.State.Contexts.MergeConflicts.State().SetContent(content, path)
return !gui.State.Contexts.MergeConflicts.State().NoConflicts(), nil
}
func (gui *Gui) setMergeStateWithLock(path string) (bool, error) {
gui.State.Contexts.MergeConflicts.State().Lock()
defer gui.State.Contexts.MergeConflicts.State().Unlock()
return gui.setMergeState(path)
}
func (gui *Gui) resetMergeStateWithLock() {
gui.State.Contexts.MergeConflicts.State().Lock()
defer gui.State.Contexts.MergeConflicts.State().Unlock()
gui.resetMergeState()
}
func (gui *Gui) escapeMerge() error {
gui.resetMergeState()
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
gui.OnUIThread(func() error {
return gui.c.PushContext(gui.State.Contexts.Files)
})
return nil
}
func (gui *Gui) renderingConflicts() bool {
currentView := gui.g.CurrentView()
if currentView != gui.Views.MergeConflicts && currentView != gui.Views.Files {
return false
}
return gui.State.Contexts.MergeConflicts.State().Active()
}
func (gui *Gui) withMergeConflictLock(f func() error) error {
gui.State.Contexts.MergeConflicts.State().Lock()
defer gui.State.Contexts.MergeConflicts.State().Unlock()
return f()
}
func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error) {
hasConflicts, err := gui.setMergeState(path)
if err != nil {
return false, err
}
if hasConflicts {
return true, gui.renderConflicts(hasFocus)
}
return false, nil
}
func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, error) {
gui.State.Contexts.MergeConflicts.State().Lock()
defer gui.State.Contexts.MergeConflicts.State().Unlock()
return gui.setConflictsAndRender(path, hasFocus)
}
func (gui *Gui) switchToMerge(path string) error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
if gui.State.Contexts.MergeConflicts.State().GetPath() != path {
hasConflicts, err := gui.setMergeStateWithLock(path)
if err != nil {
return err
}
if !hasConflicts {
return nil
}
}
return gui.c.PushContext(gui.State.Contexts.MergeConflicts)
}
func (gui *Gui) handleMergeConflictEditFileAtLine() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
lineNumber := gui.State.Contexts.MergeConflicts.State().GetSelectedLine()
return gui.helpers.Files.EditFileAtLine(file.GetPath(), lineNumber)
}
func (gui *Gui) handleMergeConflictOpenFileAtLine() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
lineNumber := gui.State.Contexts.MergeConflicts.State().GetSelectedLine()
return gui.helpers.Files.OpenFileAtLine(file.GetPath(), lineNumber)
}

View File

@ -1,15 +1,11 @@
package mergeconflicts
import (
"sync"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// State represents the selection state of the merge conflict context.
type State struct {
sync.Mutex
// path of the file with the conflicts
path string
@ -28,7 +24,6 @@ type State struct {
func NewState() *State {
return &State{
Mutex: sync.Mutex{},
conflictIndex: 0,
selectionIndex: 0,
conflicts: []*mergeConflict{},
@ -151,6 +146,12 @@ func (s *State) Reset() {
s.path = ""
}
// we're not resetting selectedIndex here because the user typically would want
// to pick either all top hunks or all bottom hunks so we retain that selection
func (s *State) ResetConflictSelection() {
s.conflictIndex = 0
}
func (s *State) Active() bool {
return s.path != ""
}

View File

@ -20,7 +20,7 @@ func (gui *Gui) getBindings(context types.Context) []*types.Binding {
bindings = append(customBindings, bindings...)
for _, binding := range bindings {
if keybindings.GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
if keybindings.LabelFromKey(binding.Key) != "" && binding.Description != "" {
if binding.ViewName == "" {
bindingsGlobal = append(bindingsGlobal, binding)
} else if binding.Tag == "navigation" {

View File

@ -22,17 +22,18 @@ import (
func getScopeNames(scopes []types.RefreshableView) []string {
scopeNameMap := map[types.RefreshableView]string{
types.COMMITS: "commits",
types.BRANCHES: "branches",
types.FILES: "files",
types.SUBMODULES: "submodules",
types.STASH: "stash",
types.REFLOG: "reflog",
types.TAGS: "tags",
types.REMOTES: "remotes",
types.STATUS: "status",
types.BISECT_INFO: "bisect",
types.STAGING: "staging",
types.COMMITS: "commits",
types.BRANCHES: "branches",
types.FILES: "files",
types.SUBMODULES: "submodules",
types.STASH: "stash",
types.REFLOG: "reflog",
types.TAGS: "tags",
types.REMOTES: "remotes",
types.STATUS: "status",
types.BISECT_INFO: "bisect",
types.STAGING: "staging",
types.MERGE_CONFLICTS: "mergeConflicts",
}
return slices.Map(scopes, func(scope types.RefreshableView) string {
@ -138,6 +139,10 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
refresh(func() { _ = gui.refreshPatchBuildingPanel(types.OnFocusOpts{}) })
}
if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) {
refresh(func() { _ = gui.refreshMergeState() })
}
wg.Wait()
gui.refreshStatus()
@ -148,7 +153,7 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
}
if options.Mode == types.BLOCK_UI {
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
f()
return nil
})
@ -323,21 +328,15 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
gui.Mutexes.RefreshingFilesMutex.Unlock()
}()
prevSelectedPath := gui.getSelectedPath()
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
return err
}
if err := gui.refreshMergeState(); err != nil {
return err
}
if err := gui.refreshStateFiles(); err != nil {
return err
}
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
gui.c.Log.Error(err)
}
@ -346,14 +345,6 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
gui.c.Log.Error(err)
}
if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY {
currentSelectedPath := gui.getSelectedPath()
alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath
if !alreadySelected {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
}
}
return nil
})
@ -361,20 +352,20 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
}
func (gui *Gui) refreshMergeState() error {
gui.State.Contexts.MergeConflicts.State().Lock()
defer gui.State.Contexts.MergeConflicts.State().Unlock()
gui.State.Contexts.MergeConflicts.GetMutex().Lock()
defer gui.State.Contexts.MergeConflicts.GetMutex().Unlock()
if gui.currentContext().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY {
return nil
}
hasConflicts, err := gui.setConflictsAndRender(gui.State.Contexts.MergeConflicts.State().GetPath(), true)
hasConflicts, err := gui.helpers.MergeConflicts.SetConflictsAndRender(gui.State.Contexts.MergeConflicts.GetState().GetPath(), true)
if err != nil {
return gui.c.Error(err)
}
if !hasConflicts {
return gui.escapeMerge()
return gui.helpers.MergeConflicts.EscapeMerge()
}
return nil
@ -426,7 +417,7 @@ func (gui *Gui) refreshStateFiles() error {
}
if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
gui.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
gui.c.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
}
fileTreeViewModel.RWMutex.Lock()
@ -701,3 +692,22 @@ func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
},
})
}
func (gui *Gui) refreshMergePanel(isFocused bool) error {
content := gui.State.Contexts.MergeConflicts.GetContentToRender(isFocused)
var task updateTask
if gui.State.Contexts.MergeConflicts.IsUserScrolling() {
task = NewRenderStringWithoutScrollTask(content)
} else {
originY := gui.State.Contexts.MergeConflicts.GetOriginY()
task = NewRenderStringWithScrollTask(content, 0, originY)
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.mergingMainContextPair(),
main: &viewUpdateOpts{
task: task,
},
})
}

View File

@ -3,6 +3,7 @@ package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@ -52,7 +53,7 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
fmt.Sprintf(
"no matches for '%s' %s",
gui.State.Searching.searchString,
theme.OptionsFgColor.Sprintf("%s: exit search mode", gui.getKeyDisplay(keybindingConfig.Universal.Return)),
theme.OptionsFgColor.Sprintf("%s: exit search mode", keybindings.Label(keybindingConfig.Universal.Return)),
),
)
}
@ -65,9 +66,9 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
total,
theme.OptionsFgColor.Sprintf(
"%s: next match, %s: previous match, %s: exit search mode",
gui.getKeyDisplay(keybindingConfig.Universal.NextMatch),
gui.getKeyDisplay(keybindingConfig.Universal.PrevMatch),
gui.getKeyDisplay(keybindingConfig.Universal.Return),
keybindings.Label(keybindingConfig.Universal.NextMatch),
keybindings.Label(keybindingConfig.Universal.PrevMatch),
keybindings.Label(keybindingConfig.Universal.Return),
),
),
)

View File

@ -23,11 +23,10 @@ func NewClient(
git *commands.GitCommand,
contexts *context.ContextTree,
helpers *helpers.Helpers,
getKey func(string) types.Key,
) *Client {
sessionStateLoader := NewSessionStateLoader(contexts, helpers)
handlerCreator := NewHandlerCreator(c, os, git, sessionStateLoader)
keybindingCreator := NewKeybindingCreator(contexts, getKey)
keybindingCreator := NewKeybindingCreator(contexts)
customCommands := c.UserConfig.CustomCommands
return &Client{

View File

@ -8,19 +8,18 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// KeybindingCreator takes a custom command along with its handler and returns a corresponding keybinding
type KeybindingCreator struct {
contexts *context.ContextTree
getKey func(string) types.Key
}
func NewKeybindingCreator(contexts *context.ContextTree, getKey func(string) types.Key) *KeybindingCreator {
func NewKeybindingCreator(contexts *context.ContextTree) *KeybindingCreator {
return &KeybindingCreator{
contexts: contexts,
getKey: getKey,
}
}
@ -41,7 +40,7 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
return &types.Binding{
ViewName: viewName,
Key: self.getKey(customCommand.Key),
Key: keybindings.GetKey(customCommand.Key),
Modifier: gocui.ModNone,
Handler: handler,
Description: description,

View File

@ -64,6 +64,22 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
return nil
}
func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error {
manager := gui.getManager(view)
f := func(stop chan struct{}) error {
gui.setViewContent(view, str)
_ = view.SetOrigin(originX, originY)
return nil
}
if err := manager.NewTask(f, ""); err != nil {
return err
}
return nil
}
func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error {
manager := gui.getManager(view)

View File

@ -16,6 +16,7 @@ const (
SUBMODULES
STAGING
PATCH_BUILDING
MERGE_CONFLICTS
COMMIT_FILES
// not actually a view. Will refactor this later
BISECT_INFO

View File

@ -58,7 +58,7 @@ func (gui *Gui) startUpdating(newVersion string) {
func (gui *Gui) onUpdateFinish(statusId int, err error) error {
gui.State.Updating = false
gui.statusManager.removeStatus(statusId)
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
_ = gui.renderString(gui.Views.AppStatus, "")
if err != nil {
errMessage := utils.ResolvePlaceholderString(

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom"
@ -99,13 +100,13 @@ func (gui *Gui) globalOptionsMap() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock), gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
keybindings.Label(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
keybindings.Label(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
}
}
@ -192,5 +193,5 @@ func getTabbedView(gui *Gui) *gocui.View {
}
func (gui *Gui) render() {
gui.OnUIThread(func() error { return nil })
gui.c.OnUIThread(func() error { return nil })
}

View File

@ -202,7 +202,7 @@ func RunTests(
// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos)
func validateSameRepos(expectedDir string, actualDir string) error {
// iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir
// iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir
expectedFiles, err := ioutil.ReadDir(expectedDir)
if err != nil {
return err