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:
parent
40c5cd4b4b
commit
2e05ac0c90
@ -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
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user