1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-03-25 22:01:14 +02:00

paging keybindings for line by line panel

support searching in line by line panel

move mutexes into their own struct

add line by line panel mutex

apply LBL panel mutex

bump gocui to prevent crashing when search item count decreases
This commit is contained in:
Jesse Duffield 2020-10-02 07:56:14 +10:00
parent 40c5cd4b4b
commit 2e05ac0c90
14 changed files with 292 additions and 165 deletions

@ -15,7 +15,7 @@ func (gui *Gui) getSelectedCommitFile() *models.CommitFile {
} }
func (gui *Gui) handleCommitFileSelect() error { func (gui *Gui) handleCommitFileSelect() error {
gui.handleEscapeLineByLinePanel() gui.escapeLineByLinePanel()
commitFile := gui.getSelectedCommitFile() commitFile := gui.getSelectedCommitFile()
if commitFile == nil { if commitFile == nil {

@ -31,7 +31,7 @@ func (gui *Gui) handleCommitSelect() error {
}() }()
} }
gui.handleEscapeLineByLinePanel() gui.escapeLineByLinePanel()
var task updateTask var task updateTask
commit := gui.getSelectedLocalCommit() commit := gui.getSelectedLocalCommit()
@ -110,8 +110,8 @@ func (gui *Gui) refreshCommits() error {
} }
func (gui *Gui) refreshCommitsWithLimit() error { func (gui *Gui) refreshCommitsWithLimit() error {
gui.State.BranchCommitsMutex.Lock() gui.State.Mutexes.BranchCommitsMutex.Lock()
defer gui.State.BranchCommitsMutex.Unlock() defer gui.State.Mutexes.BranchCommitsMutex.Unlock()
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
@ -132,8 +132,8 @@ func (gui *Gui) refreshCommitsWithLimit() error {
} }
func (gui *Gui) refreshRebaseCommits() error { func (gui *Gui) refreshRebaseCommits() error {
gui.State.BranchCommitsMutex.Lock() gui.State.Mutexes.BranchCommitsMutex.Lock()
defer gui.State.BranchCommitsMutex.Unlock() defer gui.State.Mutexes.BranchCommitsMutex.Unlock()
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)

@ -81,11 +81,11 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
} }
func (gui *Gui) refreshFilesAndSubmodules() error { func (gui *Gui) refreshFilesAndSubmodules() error {
gui.State.RefreshingFilesMutex.Lock() gui.State.Mutexes.RefreshingFilesMutex.Lock()
gui.State.IsRefreshingFiles = true gui.State.IsRefreshingFiles = true
defer func() { defer func() {
gui.State.IsRefreshingFiles = false gui.State.IsRefreshingFiles = false
gui.State.RefreshingFilesMutex.Unlock() gui.State.Mutexes.RefreshingFilesMutex.Unlock()
}() }()
selectedFile := gui.getSelectedFile() selectedFile := gui.getSelectedFile()
@ -517,8 +517,8 @@ func (gui *Gui) pullFiles(opts PullFilesOptions) error {
} }
func (gui *Gui) pullWithMode(mode string, opts PullFilesOptions) error { func (gui *Gui) pullWithMode(mode string, opts PullFilesOptions) error {
gui.State.FetchMutex.Lock() gui.State.Mutexes.FetchMutex.Lock()
defer gui.State.FetchMutex.Unlock() defer gui.State.Mutexes.FetchMutex.Unlock()
err := gui.GitCommand.Fetch( err := gui.GitCommand.Fetch(
commands.FetchOptions{ commands.FetchOptions{

@ -164,8 +164,8 @@ func (gui *Gui) handleInfoClick(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) fetch(canPromptForCredentials bool) (err error) { func (gui *Gui) fetch(canPromptForCredentials bool) (err error) {
gui.State.FetchMutex.Lock() gui.State.Mutexes.FetchMutex.Lock()
defer gui.State.FetchMutex.Unlock() defer gui.State.Mutexes.FetchMutex.Unlock()
fetchOpts := commands.FetchOptions{} fetchOpts := commands.FetchOptions{}
if canPromptForCredentials { if canPromptForCredentials {

@ -285,6 +285,14 @@ type Modes struct {
Diffing Diffing Diffing Diffing
} }
type guiStateMutexes struct {
RefreshingFilesMutex sync.Mutex
RefreshingStatusMutex sync.Mutex
FetchMutex sync.Mutex
BranchCommitsMutex sync.Mutex
LineByLinePanelMutex sync.Mutex
}
type guiState struct { type guiState struct {
Files []*models.File Files []*models.File
Submodules []*models.SubmoduleConfig Submodules []*models.SubmoduleConfig
@ -298,30 +306,27 @@ type guiState struct {
// ReflogCommits are the ones used by the branches panel to obtain recency values // ReflogCommits are the ones used by the branches panel to obtain recency values
// if we're not in filtering mode, CommitFiles and FilteredReflogCommits will be // if we're not in filtering mode, CommitFiles and FilteredReflogCommits will be
// one and the same // one and the same
ReflogCommits []*models.Commit ReflogCommits []*models.Commit
SubCommits []*models.Commit SubCommits []*models.Commit
Remotes []*models.Remote Remotes []*models.Remote
RemoteBranches []*models.RemoteBranch RemoteBranches []*models.RemoteBranch
Tags []*models.Tag Tags []*models.Tag
MenuItems []*menuItem MenuItems []*menuItem
Updating bool Updating bool
Panels *panelStates Panels *panelStates
MainContext string // used to keep the main and secondary views' contexts in sync MainContext string // used to keep the main and secondary views' contexts in sync
SplitMainPanel bool SplitMainPanel bool
RetainOriginalDir bool RetainOriginalDir bool
IsRefreshingFiles bool IsRefreshingFiles bool
RefreshingFilesMutex sync.Mutex Mutexes guiStateMutexes
RefreshingStatusMutex sync.Mutex Searching searchingState
FetchMutex sync.Mutex ScreenMode int
BranchCommitsMutex sync.Mutex SideView *gocui.View
Searching searchingState Ptmx *os.File
ScreenMode int PrevMainWidth int
SideView *gocui.View PrevMainHeight int
Ptmx *os.File OldInformation string
PrevMainWidth int StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once
PrevMainHeight int
OldInformation string
StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once
Modes Modes Modes Modes

@ -1147,14 +1147,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.PrevItem), Key: gui.getKey(config.Universal.PrevItem),
Handler: gui.handleSelectPrevLine, Handler: gui.wrappedHandler(gui.handleSelectPrevLine),
Description: gui.Tr.PrevLine, Description: gui.Tr.PrevLine,
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.NextItem), Key: gui.getKey(config.Universal.NextItem),
Handler: gui.handleSelectNextLine, Handler: gui.wrappedHandler(gui.handleSelectNextLine),
Description: gui.Tr.NextLine, Description: gui.Tr.NextLine,
}, },
{ {
@ -1162,56 +1162,56 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.PrevItemAlt), Key: gui.getKey(config.Universal.PrevItemAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectPrevLine, Handler: gui.wrappedHandler(gui.handleSelectPrevLine),
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.NextItemAlt), Key: gui.getKey(config.Universal.NextItemAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectNextLine, Handler: gui.wrappedHandler(gui.handleSelectNextLine),
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gocui.MouseWheelUp, Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectPrevLine, Handler: gui.wrappedHandler(gui.handleSelectPrevLine),
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gocui.MouseWheelDown, Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectNextLine, Handler: gui.wrappedHandler(gui.handleSelectNextLine),
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.PrevBlock), Key: gui.getKey(config.Universal.PrevBlock),
Handler: gui.handleSelectPrevHunk, Handler: gui.wrappedHandler(gui.handleSelectPrevHunk),
Description: gui.Tr.PrevHunk, Description: gui.Tr.PrevHunk,
}, },
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.NextBlock),
Handler: gui.handleSelectNextHunk,
Description: gui.Tr.NextHunk,
},
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.PrevBlockAlt), Key: gui.getKey(config.Universal.PrevBlockAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectPrevHunk, Handler: gui.wrappedHandler(gui.handleSelectPrevHunk),
},
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.NextBlock),
Handler: gui.wrappedHandler(gui.handleSelectNextHunk),
Description: gui.Tr.NextHunk,
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.NextBlockAlt), Key: gui.getKey(config.Universal.NextBlockAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectNextHunk, Handler: gui.wrappedHandler(gui.handleSelectNextHunk),
}, },
{ {
ViewName: "main", ViewName: "main",
@ -1227,6 +1227,50 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleFileOpen, Handler: gui.handleFileOpen,
Description: gui.Tr.LcOpenFile, Description: gui.Tr.LcOpenFile,
}, },
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.NextPage),
Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.handleLineByLineNextPage),
Description: gui.Tr.LcNextPage,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.PrevPage),
Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.handleLineByLinePrevPage),
Description: gui.Tr.LcPrevPage,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.GotoTop),
Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.handleLineByLineGotoTop),
Description: gui.Tr.LcGotoTop,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.GotoBottom),
Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.handleLineByLineGotoBottom),
Description: gui.Tr.LcGotoBottom,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Universal.StartSearch),
Handler: gui.handleOpenSearch,
Description: gui.Tr.LcStartSearch,
Tag: "navigation",
},
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY},
@ -1238,7 +1282,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Main.ToggleDragSelect), Key: gui.getKey(config.Main.ToggleDragSelect),
Handler: gui.handleToggleSelectRange, Handler: gui.wrappedHandler(gui.handleToggleSelectRange),
Description: gui.Tr.ToggleDragSelect, Description: gui.Tr.ToggleDragSelect,
}, },
// Alias 'V' -> 'v' // Alias 'V' -> 'v'
@ -1246,14 +1290,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Main.ToggleDragSelectAlt), Key: gui.getKey(config.Main.ToggleDragSelectAlt),
Handler: gui.handleToggleSelectRange, Handler: gui.wrappedHandler(gui.handleToggleSelectRange),
Description: gui.Tr.ToggleDragSelect, Description: gui.Tr.ToggleDragSelect,
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gui.getKey(config.Main.ToggleSelectHunk), Key: gui.getKey(config.Main.ToggleSelectHunk),
Handler: gui.handleToggleSelectHunk, Handler: gui.wrappedHandler(gui.handleToggleSelectHunk),
Description: gui.Tr.ToggleSelectHunk, Description: gui.Tr.ToggleSelectHunk,
}, },
{ {
@ -1261,7 +1305,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY}, Contexts: []string{MAIN_PATCH_BUILDING_CONTEXT_KEY, MAIN_STAGING_CONTEXT_KEY},
Key: gocui.MouseLeft, Key: gocui.MouseLeft,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleMouseDown, Handler: gui.handleLBLMouseDown,
}, },
{ {
ViewName: "main", ViewName: "main",

@ -317,6 +317,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
listContextState.view.SetOnSelectItem(gui.onSelectItemWrapper(listContextState.listContext.onSearchSelect)) listContextState.view.SetOnSelectItem(gui.onSelectItemWrapper(listContextState.listContext.onSearchSelect))
} }
gui.getMainView().SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo))
mainViewWidth, mainViewHeight := gui.getMainView().Size() mainViewWidth, mainViewHeight := gui.getMainView().Size()
if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight { if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight {
gui.State.PrevMainWidth = mainViewWidth gui.State.PrevMainWidth = mainViewWidth

@ -25,6 +25,9 @@ const (
// returns whether the patch is empty so caller can escape if necessary // returns whether the patch is empty so caller can escape if necessary
// both diffs should be non-coloured because we'll parse them and colour them here // both diffs should be non-coloured because we'll parse them and colour them here
func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) { func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) {
gui.State.Mutexes.LineByLinePanelMutex.Lock()
defer gui.State.Mutexes.LineByLinePanelMutex.Unlock()
state := gui.State.Panels.LineByLine state := gui.State.Panels.LineByLine
patchParser, err := patch.NewPatchParser(gui.Log, diff) patchParser, err := patch.NewPatchParser(gui.Log, diff)
@ -98,26 +101,32 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second
return false, nil return false, nil
} }
func (gui *Gui) handleSelectPrevLine(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleSelectPrevLine() error {
return gui.handleCycleLine(-1) return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
return gui.LBLCycleLine(-1)
})
} }
func (gui *Gui) handleSelectNextLine(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleSelectNextLine() error {
return gui.handleCycleLine(+1) return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
return gui.LBLCycleLine(+1)
})
} }
func (gui *Gui) handleSelectPrevHunk(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleSelectPrevHunk() error {
state := gui.State.Panels.LineByLine return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, -1) newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, -1)
return gui.selectNewHunk(newHunk) return gui.selectNewHunk(newHunk)
})
} }
func (gui *Gui) handleSelectNextHunk(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleSelectNextHunk() error {
state := gui.State.Panels.LineByLine return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 1) newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 1)
return gui.selectNewHunk(newHunk) return gui.selectNewHunk(newHunk)
})
} }
func (gui *Gui) selectNewHunk(newHunk *patch.PatchHunk) error { func (gui *Gui) selectNewHunk(newHunk *patch.PatchHunk) error {
@ -136,7 +145,7 @@ func (gui *Gui) selectNewHunk(newHunk *patch.PatchHunk) error {
return gui.focusSelection(true) return gui.focusSelection(true)
} }
func (gui *Gui) handleCycleLine(change int) error { func (gui *Gui) LBLCycleLine(change int) error {
state := gui.State.Panels.LineByLine state := gui.State.Panels.LineByLine
if state.SelectMode == HUNK { if state.SelectMode == HUNK {
@ -144,10 +153,10 @@ func (gui *Gui) handleCycleLine(change int) error {
return gui.selectNewHunk(newHunk) return gui.selectNewHunk(newHunk)
} }
return gui.handleSelectNewLine(state.SelectedLineIdx + change) return gui.LBLSelectLine(state.SelectedLineIdx + change)
} }
func (gui *Gui) handleSelectNewLine(newSelectedLineIdx int) error { func (gui *Gui) LBLSelectLine(newSelectedLineIdx int) error {
state := gui.State.Panels.LineByLine state := gui.State.Panels.LineByLine
if newSelectedLineIdx < 0 { if newSelectedLineIdx < 0 {
@ -176,52 +185,54 @@ func (gui *Gui) handleSelectNewLine(newSelectedLineIdx int) error {
return gui.focusSelection(false) return gui.focusSelection(false)
} }
func (gui *Gui) handleMouseDown(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleLBLMouseDown(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
if gui.popupPanelFocused() {
return nil
}
if gui.popupPanelFocused() { newSelectedLineIdx := v.SelectedLineIdx()
return nil state.FirstLineIdx = newSelectedLineIdx
} state.LastLineIdx = newSelectedLineIdx
newSelectedLineIdx := v.SelectedLineIdx() state.SelectMode = RANGE
state.FirstLineIdx = newSelectedLineIdx
state.LastLineIdx = newSelectedLineIdx
state.SelectMode = RANGE return gui.LBLSelectLine(newSelectedLineIdx)
})
return gui.handleSelectNewLine(newSelectedLineIdx)
} }
func (gui *Gui) handleMouseDrag(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMouseDrag(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() { return gui.withLBLActiveCheck(func(*lineByLinePanelState) error {
return nil if gui.popupPanelFocused() {
} return nil
}
return gui.handleSelectNewLine(v.SelectedLineIdx()) return gui.LBLSelectLine(v.SelectedLineIdx())
})
} }
func (gui *Gui) handleMouseScrollUp(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMouseScrollUp(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
if gui.popupPanelFocused() {
return nil
}
if gui.popupPanelFocused() { state.SelectMode = LINE
return nil
}
state.SelectMode = LINE return gui.LBLCycleLine(-1)
})
return gui.handleCycleLine(-1)
} }
func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
if gui.popupPanelFocused() {
return nil
}
if gui.popupPanelFocused() { state.SelectMode = LINE
return nil
}
state.SelectMode = LINE return gui.LBLCycleLine(1)
})
return gui.handleCycleLine(1)
} }
func (gui *Gui) getSelectedCommitFileName() string { func (gui *Gui) getSelectedCommitFileName() string {
@ -297,65 +308,123 @@ func (gui *Gui) focusSelection(includeCurrentHunk bool) error {
return nil return nil
} }
func (gui *Gui) handleToggleSelectRange(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleToggleSelectRange() error {
state := gui.State.Panels.LineByLine return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
if state.SelectMode == RANGE { if state.SelectMode == RANGE {
state.SelectMode = LINE state.SelectMode = LINE
} else { } else {
state.SelectMode = RANGE state.SelectMode = RANGE
} }
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
return gui.refreshMainViewForLineByLine()
}
func (gui *Gui) handleToggleSelectHunk(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
if state.SelectMode == HUNK {
state.SelectMode = LINE
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
} else {
state.SelectMode = HUNK
selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
state.FirstLineIdx, state.LastLineIdx = selectedHunk.FirstLineIdx, selectedHunk.LastLineIdx()
}
if err := gui.refreshMainViewForLineByLine(); err != nil { return gui.refreshMainViewForLineByLine()
return err })
}
return gui.focusSelection(state.SelectMode == HUNK)
} }
func (gui *Gui) handleEscapeLineByLinePanel() { func (gui *Gui) handleToggleSelectHunk() error {
gui.State.Panels.LineByLine = nil return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
if state.SelectMode == HUNK {
state.SelectMode = LINE
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
} else {
state.SelectMode = HUNK
selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
state.FirstLineIdx, state.LastLineIdx = selectedHunk.FirstLineIdx, selectedHunk.LastLineIdx()
}
if err := gui.refreshMainViewForLineByLine(); err != nil {
return err
}
return gui.focusSelection(state.SelectMode == HUNK)
})
}
func (gui *Gui) escapeLineByLinePanel() {
gui.withLBLActiveCheck(func(*lineByLinePanelState) error {
gui.State.Panels.LineByLine = nil
return nil
})
} }
func (gui *Gui) handleOpenFileAtLine() error { func (gui *Gui) handleOpenFileAtLine() error {
// again, would be good to use inheritance here (or maybe even composition) return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
var filename string // again, would be good to use inheritance here (or maybe even composition)
switch gui.State.MainContext { var filename string
case gui.Contexts.PatchBuilding.Context.GetKey(): switch gui.State.MainContext {
filename = gui.getSelectedCommitFileName() case gui.Contexts.PatchBuilding.Context.GetKey():
case gui.Contexts.Staging.Context.GetKey(): filename = gui.getSelectedCommitFileName()
file := gui.getSelectedFile() case gui.Contexts.Staging.Context.GetKey():
if file == nil { file := gui.getSelectedFile()
return nil if file == nil {
return nil
}
filename = file.Name
default:
return errors.Errorf("unknown main context: %s", gui.State.MainContext)
} }
filename = file.Name
default: // need to look at current index, then work out what my hunk's header information is, and see how far my line is away from the hunk header
return errors.Errorf("unknown main context: %s", gui.State.MainContext) selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
} lineNumber := selectedHunk.LineNumberOfLine(state.SelectedLineIdx)
filenameWithLineNum := fmt.Sprintf("%s:%d", filename, lineNumber)
if err := gui.OSCommand.OpenFile(filenameWithLineNum); err != nil {
return err
}
return nil
})
}
func (gui *Gui) handleLineByLineNextPage() error {
return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
newSelectedLineIdx := state.SelectedLineIdx + gui.pageDelta(gui.getMainView())
return gui.lineByLineNavigateTo(newSelectedLineIdx)
})
}
func (gui *Gui) handleLineByLinePrevPage() error {
return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
newSelectedLineIdx := state.SelectedLineIdx - gui.pageDelta(gui.getMainView())
return gui.lineByLineNavigateTo(newSelectedLineIdx)
})
}
func (gui *Gui) handleLineByLineGotoBottom() error {
return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
newSelectedLineIdx := len(state.PatchParser.PatchLines) - 1
return gui.lineByLineNavigateTo(newSelectedLineIdx)
})
}
func (gui *Gui) handleLineByLineGotoTop() error {
return gui.lineByLineNavigateTo(0)
}
func (gui *Gui) handlelineByLineNavigateTo(selectedLineIdx int) error {
return gui.withLBLActiveCheck(func(state *lineByLinePanelState) error {
return gui.lineByLineNavigateTo(selectedLineIdx)
})
}
func (gui *Gui) lineByLineNavigateTo(selectedLineIdx int) error {
state := gui.State.Panels.LineByLine
state.SelectMode = LINE
return gui.LBLSelectLine(selectedLineIdx)
}
func (gui *Gui) withLBLActiveCheck(f func(*lineByLinePanelState) error) error {
gui.State.Mutexes.LineByLinePanelMutex.Lock()
defer gui.State.Mutexes.LineByLinePanelMutex.Unlock()
state := gui.State.Panels.LineByLine state := gui.State.Panels.LineByLine
// need to look at current index, then work out what my hunk's header information is, and see how far my line is away from the hunk header if state == nil {
selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0) return nil
lineNumber := selectedHunk.LineNumberOfLine(state.SelectedLineIdx)
filenameWithLineNum := fmt.Sprintf("%s:%d", filename, lineNumber)
if err := gui.OSCommand.OpenFile(filenameWithLineNum); err != nil {
return err
} }
return nil return f(state)
} }

@ -186,11 +186,8 @@ func (lc *ListContext) handleNextPage(g *gocui.Gui, v *gocui.View) error {
if err != nil { if err != nil {
return nil return nil
} }
_, height := view.Size() delta := lc.Gui.pageDelta(view)
delta := height - 1
if delta == 0 {
delta = 1
}
return lc.handleLineChange(delta) return lc.handleLineChange(delta)
} }
@ -207,11 +204,9 @@ func (lc *ListContext) handlePrevPage(g *gocui.Gui, v *gocui.View) error {
if err != nil { if err != nil {
return nil return nil
} }
_, height := view.Size()
delta := height - 1 delta := lc.Gui.pageDelta(view)
if delta == 0 {
delta = 1
}
return lc.handleLineChange(-delta) return lc.handleLineChange(-delta)
} }

@ -92,7 +92,7 @@ func (gui *Gui) handleToggleSelectionForPatch(g *gocui.Gui, v *gocui.View) error
} }
func (gui *Gui) handleEscapePatchBuildingPanel() error { func (gui *Gui) handleEscapePatchBuildingPanel() error {
gui.handleEscapeLineByLinePanel() gui.escapeLineByLinePanel()
if gui.GitCommand.PatchManager.IsEmpty() { if gui.GitCommand.PatchManager.IsEmpty() {
gui.GitCommand.PatchManager.Reset() gui.GitCommand.PatchManager.Reset()

@ -158,8 +158,8 @@ func (gui *Gui) handleFetchRemote(g *gocui.Gui, v *gocui.View) error {
} }
return gui.WithWaitingStatus(gui.Tr.FetchingRemoteStatus, func() error { return gui.WithWaitingStatus(gui.Tr.FetchingRemoteStatus, func() error {
gui.State.FetchMutex.Lock() gui.State.Mutexes.FetchMutex.Lock()
defer gui.State.FetchMutex.Unlock() defer gui.State.Mutexes.FetchMutex.Unlock()
// TODO: test this // TODO: test this
err := gui.GitCommand.FetchRemote(remote.Name, gui.promptUserForCredential) err := gui.GitCommand.FetchRemote(remote.Name, gui.promptUserForCredential)

@ -84,7 +84,7 @@ func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleStagingEscape() error { func (gui *Gui) handleStagingEscape() error {
gui.handleEscapeLineByLinePanel() gui.escapeLineByLinePanel()
return gui.switchContext(gui.Contexts.Files.Context) return gui.switchContext(gui.Contexts.Files.Context)
} }

@ -12,8 +12,8 @@ import (
// never call this on its own, it should only be called from within refreshCommits() // never call this on its own, it should only be called from within refreshCommits()
func (gui *Gui) refreshStatus() { func (gui *Gui) refreshStatus() {
gui.State.RefreshingStatusMutex.Lock() gui.State.Mutexes.RefreshingStatusMutex.Lock()
defer gui.State.RefreshingStatusMutex.Unlock() defer gui.State.Mutexes.RefreshingStatusMutex.Unlock()
currentBranch := gui.currentBranch() currentBranch := gui.currentBranch()
if currentBranch == nil { if currentBranch == nil {

@ -433,3 +433,15 @@ func (gui *Gui) handlePrevTab(g *gocui.Gui, v *gocui.View) error {
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)), utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
) )
} }
// this is the distance we will move the cursor when paging up or down in a view
func (gui *Gui) pageDelta(view *gocui.View) int {
_, height := view.Size()
delta := height - 1
if delta == 0 {
return 1
}
return delta
}