1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-03-05 15:15:49 +02:00

add mouse support

This commit is contained in:
Jesse Duffield 2019-11-10 16:20:35 +11:00
parent cd17b46b55
commit e85310c0a9
20 changed files with 420 additions and 125 deletions

View File

@ -18,6 +18,7 @@
- blue
commitLength:
show: true
mouseEvents: true
git:
merging:
# only applicable to unix users

View File

@ -242,7 +242,7 @@ func GetDefaultConfig() []byte {
## stuff relating to the UI
scrollHeight: 2
scrollPastBottom: true
mouseEvents: false # will default to true when the feature is complete
mouseEvents: true
theme:
lightTheme: false
activeBorderColor:

View File

@ -19,6 +19,14 @@ func (gui *Gui) getSelectedBranch() *commands.Branch {
return gui.State.Branches[selectedLine]
}
func (gui *Gui) handleBranchesClick(g *gocui.Gui, v *gocui.View) error {
itemCount := len(gui.State.Branches)
handleSelect := gui.handleBranchSelect
selectedLine := &gui.State.Panels.Branches.SelectedLine
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
}
// may want to standardise how these select methods work
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
@ -30,6 +38,9 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
gui.getMainView().Title = "Log"
// This really shouldn't happen: there should always be a master branch
if len(gui.State.Branches) == 0 {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))

View File

@ -15,11 +15,21 @@ func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
return gui.State.CommitFiles[selectedLine]
}
func (gui *Gui) handleCommitFilesClick(g *gocui.Gui, v *gocui.View) error {
itemCount := len(gui.State.CommitFiles)
handleSelect := gui.handleCommitFileSelect
selectedLine := &gui.State.Panels.CommitFiles.SelectedLine
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
}
func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
gui.getMainView().Title = "Patch"
commitFile := gui.getSelectedCommitFile(g)
if commitFile == nil {
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
@ -96,7 +106,7 @@ func (gui *Gui) refreshCommitFilesView() error {
return err
}
if err := gui.refreshPatchBuildingPanel(); err != nil {
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
return err
}
@ -177,16 +187,20 @@ func (gui *Gui) startPatchManager() error {
}
func (gui *Gui) handleEnterCommitFile(g *gocui.Gui, v *gocui.View) error {
return gui.enterCommitFile(-1)
}
func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
return err
}
commitFile := gui.getSelectedCommitFile(g)
commitFile := gui.getSelectedCommitFile(gui.g)
if commitFile == nil {
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
}
enterTheFile := func() error {
enterTheFile := func(selectedLineIdx int) error {
if !gui.GitCommand.PatchManager.CommitSelected() {
if err := gui.startPatchManager(); err != nil {
return err
@ -196,18 +210,21 @@ func (gui *Gui) handleEnterCommitFile(g *gocui.Gui, v *gocui.View) error {
if err := gui.changeContext("main", "patch-building"); err != nil {
return err
}
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
if err := gui.changeContext("secondary", "patch-building"); err != nil {
return err
}
return gui.refreshPatchBuildingPanel()
if err := gui.switchFocus(gui.g, gui.getCommitFilesView(), gui.getMainView()); err != nil {
return err
}
return gui.refreshPatchBuildingPanel(selectedLineIdx)
}
if gui.GitCommand.PatchManager.CommitSelected() && gui.GitCommand.PatchManager.CommitSha != commitFile.Sha {
return gui.createConfirmationPanel(g, v, false, gui.Tr.SLocalize("DiscardPatch"), gui.Tr.SLocalize("DiscardPatchConfirm"), func(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(gui.g, gui.getCommitFilesView(), false, gui.Tr.SLocalize("DiscardPatch"), gui.Tr.SLocalize("DiscardPatchConfirm"), func(g *gocui.Gui, v *gocui.View) error {
gui.GitCommand.PatchManager.Reset()
return enterTheFile()
return enterTheFile(selectedLineIdx)
}, nil)
}
return enterTheFile()
return enterTheFile(selectedLineIdx)
}

View File

@ -23,6 +23,14 @@ func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit {
return gui.State.Commits[selectedLine]
}
func (gui *Gui) handleCommitsClick(g *gocui.Gui, v *gocui.View) error {
itemCount := len(gui.State.Commits)
handleSelect := gui.handleCommitSelect
selectedLine := &gui.State.Panels.Commits.SelectedLine
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
}
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
@ -36,6 +44,10 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
gui.getMainView().Title = "Patch"
gui.getSecondaryView().Title = "Custom Patch"
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
@ -458,7 +470,7 @@ func (gui *Gui) handleSwitchToCommitFilesPanel(g *gocui.Gui, v *gocui.View) erro
return err
}
return gui.switchFocus(g, v, gui.getCommitFilesView())
return gui.switchFocus(g, gui.getCommitsView(), gui.getCommitFilesView())
}
func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {

View File

@ -1,44 +1,5 @@
package gui
func (gui *Gui) titleMap() map[string]string {
return map[string]string{
"commits": gui.Tr.SLocalize("DiffTitle"),
"branches": gui.Tr.SLocalize("LogTitle"),
"files": gui.Tr.SLocalize("DiffTitle"),
"status": "",
"stash": gui.Tr.SLocalize("DiffTitle"),
}
}
func (gui *Gui) contextTitleMap() map[string]map[string]string {
return map[string]map[string]string{
"main": {
"staging": gui.Tr.SLocalize("StagingMainTitle"),
"patch-building": gui.Tr.SLocalize("PatchBuildingMainTitle"),
"merging": gui.Tr.SLocalize("MergingMainTitle"),
"normal": "",
},
}
}
func (gui *Gui) setMainTitle() error {
currentView := gui.g.CurrentView()
if currentView == nil {
return nil
}
currentViewName := currentView.Name()
var newTitle string
if context, ok := gui.State.Contexts[currentViewName]; ok {
newTitle = gui.contextTitleMap()[currentViewName][context]
} else if title, ok := gui.titleMap()[currentViewName]; ok {
newTitle = title
} else {
return nil
}
gui.getMainView().Title = newTitle
return nil
}
func (gui *Gui) changeContext(viewName, context string) error {
if gui.State.Contexts[viewName] == context {
return nil
@ -50,19 +11,20 @@ func (gui *Gui) changeContext(viewName, context string) error {
bindings := contextMap[viewName][context]
for _, binding := range bindings {
if err := gui.g.SetKeybinding(viewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err
}
}
gui.State.Contexts[viewName] = context
return gui.setMainTitle()
return nil
}
func (gui *Gui) setInitialContexts() error {
contextMap := gui.GetContextMap()
initialContexts := map[string]string{
"main": "normal",
"main": "normal",
"secondary": "normal",
}
for viewName, context := range initialContexts {

View File

@ -27,24 +27,21 @@ func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
return gui.State.Files[selectedLine], nil
}
func (gui *Gui) handleFilesFocus(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleFilesClick(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
cx, cy := v.Cursor()
_, oy := v.Origin()
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLine
newSelectedLineIdx := v.SelectedLineIdx()
prevSelectedLine := gui.State.Panels.Files.SelectedLine
newSelectedLine := cy - oy
if newSelectedLine > len(gui.State.Files)-1 || len(utils.Decolorise(gui.State.Files[newSelectedLine].DisplayString)) < cx {
if newSelectedLineIdx > len(gui.State.Files)-1 {
return gui.handleFileSelect(gui.g, v, false)
}
gui.State.Panels.Files.SelectedLine = newSelectedLine
gui.State.Panels.Files.SelectedLine = newSelectedLineIdx
if prevSelectedLine == newSelectedLine && gui.currentViewName() == v.Name() {
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
return gui.handleFilePress(gui.g, v)
} else {
return gui.handleFileSelect(gui.g, v, true)
@ -77,12 +74,16 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bo
leftContent := content
if file.HasStagedChanges && file.HasUnstagedChanges {
gui.State.SplitMainPanel = true
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
} else {
gui.State.SplitMainPanel = false
if file.HasUnstagedChanges {
leftContent = content
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
} else {
leftContent = contentCached
gui.getMainView().Title = gui.Tr.SLocalize("StagedChanges")
}
}
@ -189,7 +190,11 @@ func (gui *Gui) stageSelectedFile(g *gocui.Gui) error {
}
func (gui *Gui) handleEnterFile(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
return gui.enterFile(false, -1)
}
func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error {
file, err := gui.getSelectedFile(gui.g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
@ -197,18 +202,21 @@ func (gui *Gui) handleEnterFile(g *gocui.Gui, v *gocui.View) error {
return nil
}
if file.HasInlineMergeConflicts {
return gui.handleSwitchToMerge(g, v)
return gui.handleSwitchToMerge(gui.g, gui.getFilesView())
}
if file.HasMergeConflicts {
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements"))
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FileStagingRequirements"))
}
if err := gui.changeContext("main", "staging"); err != nil {
return err
}
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
if err := gui.changeContext("secondary", "staging"); err != nil {
return err
}
return gui.refreshStagingPanel()
if err := gui.switchFocus(gui.g, gui.getFilesView(), gui.getMainView()); err != nil {
return err
}
return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx)
}
func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {

View File

@ -118,12 +118,18 @@ type stashPanelState struct {
type menuPanelState struct {
SelectedLine int
OnPress func(g *gocui.Gui, v *gocui.View) error
}
type commitFilesPanelState struct {
SelectedLine int
}
type statusPanelState struct {
pushables string
pullables string
}
type panelStates struct {
Files *filePanelState
Branches *branchPanelState
@ -133,6 +139,7 @@ type panelStates struct {
LineByLine *lineByLinePanelState
Merging *mergingPanelState
CommitFiles *commitFilesPanelState
Status *statusPanelState
}
type guiState struct {
@ -179,6 +186,7 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
Conflicts: []commands.Conflict{},
EditHistory: stack.New(),
},
Status: &statusPanelState{},
},
}
@ -257,7 +265,7 @@ func (gui *Gui) onFocusChange() error {
for _, view := range gui.g.Views() {
view.Highlight = view == currentView
}
return gui.setMainTitle()
return nil
}
func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
@ -683,6 +691,8 @@ func (gui *Gui) Run() error {
}
defer g.Close()
g.Log = gui.Log
if gui.Config.GetUserConfig().GetBool("gui.mouseEvents") {
g.Mouse = true
}
@ -795,3 +805,31 @@ func (gui *Gui) setColorScheme() error {
return nil
}
func (gui *Gui) handleMouseDownMain(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
switch g.CurrentView().Name() {
case "files":
return gui.enterFile(false, v.SelectedLineIdx())
case "commitFiles":
return gui.enterCommitFile(v.SelectedLineIdx())
}
return nil
}
func (gui *Gui) handleMouseDownSecondary(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
switch g.CurrentView().Name() {
case "files":
return gui.enterFile(true, v.SelectedLineIdx())
}
return nil
}

View File

@ -144,6 +144,11 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: 'x',
Modifier: gocui.ModNone,
Handler: gui.handleCreateOptionsMenu,
}, {
ViewName: "",
Key: gocui.MouseMiddle,
Modifier: gocui.ModNone,
Handler: gui.handleCreateOptionsMenu,
}, {
ViewName: "",
Key: gocui.KeyCtrlP,
@ -558,15 +563,15 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
listPanelMap := map[string]struct {
prevLine func(*gocui.Gui, *gocui.View) error
nextLine func(*gocui.Gui, *gocui.View) error
focus func(*gocui.Gui, *gocui.View) error
onClick func(*gocui.Gui, *gocui.View) error
}{
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine, focus: gui.handleMenuSelect},
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine, focus: gui.handleFilesFocus},
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine, focus: gui.handleBranchSelect},
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine, focus: gui.handleCommitSelect},
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine, focus: gui.handleStashEntrySelect},
"status": {focus: gui.handleStatusSelect},
"commitFiles": {prevLine: gui.handleCommitFilesPrevLine, nextLine: gui.handleCommitFilesNextLine, focus: gui.handleCommitFileSelect},
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine, onClick: gui.handleMenuClick},
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine, onClick: gui.handleFilesClick},
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine, onClick: gui.handleBranchesClick},
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine, onClick: gui.handleCommitsClick},
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine, onClick: gui.handleStashEntrySelect},
"status": {onClick: gui.handleStatusClick},
"commitFiles": {prevLine: gui.handleCommitFilesPrevLine, nextLine: gui.handleCommitFilesNextLine, onClick: gui.handleCommitFilesClick},
}
for viewName, functions := range listPanelMap {
@ -577,7 +582,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: functions.focus},
{ViewName: viewName, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: functions.onClick},
}...)
}
@ -610,6 +615,24 @@ func (gui *Gui) keybindings(g *gocui.Gui) error {
func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
return map[string]map[string][]*Binding{
"secondary": {
"normal": {
{
ViewName: "secondary",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleMouseDownSecondary,
},
},
"staging": {
{
ViewName: "secondary",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleTogglePanelClick,
},
},
},
"main": {
"normal": {
{
@ -626,6 +649,11 @@ func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
Handler: gui.scrollUpMain,
Description: gui.Tr.SLocalize("ScrollUp"),
Alternative: "fn+down",
}, {
ViewName: "main",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleMouseDownMain,
},
},
"staging": {
@ -657,16 +685,6 @@ func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
Key: 'j',
Modifier: gocui.ModNone,
Handler: gui.handleSelectNextLine,
}, {
ViewName: "main",
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
Handler: gui.handleSelectPrevLine,
}, {
ViewName: "main",
Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone,
Handler: gui.handleSelectNextLine,
}, {
ViewName: "main",
Key: gocui.KeyArrowLeft,
@ -719,6 +737,26 @@ func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleTogglePanel,
Description: gui.Tr.SLocalize("TogglePanel"),
}, {
ViewName: "main",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleMouseDown,
}, {
ViewName: "main",
Key: gocui.MouseLeft,
Modifier: gocui.ModMotion,
Handler: gui.handleMouseDrag,
}, {
ViewName: "main",
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
Handler: gui.handleMouseScrollUp,
}, {
ViewName: "main",
Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone,
Handler: gui.handleMouseScrollDown,
},
},
"patch-building": {
@ -806,6 +844,26 @@ func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleToggleSelectHunk,
Description: gui.Tr.SLocalize("ToggleSelectHunk"),
}, {
ViewName: "main",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleMouseDown,
}, {
ViewName: "main",
Key: gocui.MouseLeft,
Modifier: gocui.ModMotion,
Handler: gui.handleMouseDrag,
}, {
ViewName: "main",
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
Handler: gui.handleMouseScrollUp,
}, {
ViewName: "main",
Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone,
Handler: gui.handleMouseScrollDown,
},
},
"merging": {

View File

@ -21,7 +21,7 @@ const (
// 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
func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool) (bool, error) {
func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) {
state := gui.State.Panels.LineByLine
patchParser, err := commands.NewPatchParser(gui.Log, diff)
@ -33,11 +33,14 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second
return true, nil
}
var selectedLineIdx int
var firstLineIdx int
var lastLineIdx int
selectMode := LINE
if state != nil {
// if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line
if selectedLineIdx >= 0 {
selectMode = RANGE
firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx
} else if state != nil {
if state.SelectMode == HUNK {
// this is tricky: we need to find out which hunk we just staged based on our old `state.PatchParser` (as opposed to the new `patchParser`)
// we do this by getting the first line index of the original hunk, then
@ -96,20 +99,25 @@ func (gui *Gui) handleSelectPrevLine(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleSelectNextLine(g *gocui.Gui, v *gocui.View) error {
return gui.handleCycleLine(1)
return gui.handleCycleLine(+1)
}
func (gui *Gui) handleSelectPrevHunk(g *gocui.Gui, v *gocui.View) error {
return gui.handleCycleHunk(-1)
state := gui.State.Panels.LineByLine
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, -1)
return gui.selectNewHunk(newHunk)
}
func (gui *Gui) handleSelectNextHunk(g *gocui.Gui, v *gocui.View) error {
return gui.handleCycleHunk(1)
state := gui.State.Panels.LineByLine
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 1)
return gui.selectNewHunk(newHunk)
}
func (gui *Gui) handleCycleHunk(change int) error {
func (gui *Gui) selectNewHunk(newHunk *commands.PatchHunk) error {
state := gui.State.Panels.LineByLine
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, change)
state.SelectedLineIdx = state.PatchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx)
if state.SelectMode == HUNK {
state.FirstLineIdx, state.LastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx
@ -128,10 +136,16 @@ func (gui *Gui) handleCycleLine(change int) error {
state := gui.State.Panels.LineByLine
if state.SelectMode == HUNK {
return gui.handleCycleHunk(change)
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, change)
return gui.selectNewHunk(newHunk)
}
newSelectedLineIdx := state.SelectedLineIdx + change
return gui.handleSelectNewLine(state.SelectedLineIdx + change)
}
func (gui *Gui) handleSelectNewLine(newSelectedLineIdx int) error {
state := gui.State.Panels.LineByLine
if newSelectedLineIdx < 0 {
newSelectedLineIdx = 0
} else if newSelectedLineIdx > len(state.PatchParser.PatchLines)-1 {
@ -158,6 +172,54 @@ func (gui *Gui) handleCycleLine(change int) error {
return gui.focusSelection(false)
}
func (gui *Gui) handleMouseDown(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
if gui.popupPanelFocused() {
return nil
}
newSelectedLineIdx := v.SelectedLineIdx()
state.FirstLineIdx = newSelectedLineIdx
state.LastLineIdx = newSelectedLineIdx
state.SelectMode = RANGE
return gui.handleSelectNewLine(newSelectedLineIdx)
}
func (gui *Gui) handleMouseDrag(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
return gui.handleSelectNewLine(v.SelectedLineIdx())
}
func (gui *Gui) handleMouseScrollUp(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
if gui.popupPanelFocused() {
return nil
}
state.SelectMode = LINE
return gui.handleCycleLine(-1)
}
func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
if gui.popupPanelFocused() {
return nil
}
state.SelectMode = LINE
return gui.handleCycleLine(1)
}
func (gui *Gui) refreshMainView() error {
state := gui.State.Panels.LineByLine

View File

@ -82,7 +82,9 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
return gui.returnFocus(gui.g, menuView)
}
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter} {
gui.State.Panels.Menu.OnPress = wrappedHandlePress
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} {
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
if err := gui.g.SetKeybinding("menu", key, gocui.ModNone, wrappedHandlePress); err != nil {
@ -101,3 +103,15 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
})
return nil
}
func (gui *Gui) handleMenuClick(g *gocui.Gui, v *gocui.View) error {
itemCount := gui.State.MenuItemCount
handleSelect := gui.handleMenuSelect
selectedLine := &gui.State.Panels.Menu.SelectedLine
if err := gui.handleClick(v, itemCount, selectedLine, handleSelect); err != nil {
return err
}
return gui.State.Panels.Menu.OnPress(g, v)
}

View File

@ -4,13 +4,16 @@ import (
"github.com/jesseduffield/gocui"
)
func (gui *Gui) refreshPatchBuildingPanel() error {
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
if !gui.GitCommand.PatchManager.CommitSelected() {
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
}
gui.State.SplitMainPanel = true
gui.getMainView().Title = "Patch"
gui.getSecondaryView().Title = "Custom Patch"
// get diff from commit file that's currently selected
commitFile := gui.getSelectedCommitFile(gui.g)
if commitFile == nil {
@ -27,7 +30,7 @@ func (gui *Gui) refreshPatchBuildingPanel() error {
return err
}
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, false)
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, false, selectedLineIdx)
if err != nil {
return err
}
@ -54,7 +57,7 @@ func (gui *Gui) handleAddSelectionToPatch(g *gocui.Gui, v *gocui.View) error {
return err
}
if err := gui.refreshPatchBuildingPanel(); err != nil {
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
return err
}
@ -76,7 +79,7 @@ func (gui *Gui) handleRemoveSelectionFromPatch(g *gocui.Gui, v *gocui.View) erro
return err
}
if err := gui.refreshPatchBuildingPanel(); err != nil {
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
return err
}

View File

@ -26,8 +26,13 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
options = append(options, &option{value: "skip"})
}
options = append(options, &option{value: "cancel"})
handleMenuPress := func(index int) error {
command := options[index].value
if command == "cancel" {
return nil
}
return gui.genericMergeCommand(command)
}

View File

@ -7,7 +7,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands"
)
func (gui *Gui) refreshStagingPanel() error {
func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error {
gui.State.SplitMainPanel = true
state := gui.State.Panels.LineByLine
@ -25,14 +25,26 @@ func (gui *Gui) refreshStagingPanel() error {
}
secondaryFocused := false
if state != nil {
secondaryFocused = state.SecondaryFocused
if forceSecondaryFocused {
secondaryFocused = true
} else {
if state != nil {
secondaryFocused = state.SecondaryFocused
}
}
if (secondaryFocused && !file.HasStagedChanges) || (!secondaryFocused && !file.HasUnstagedChanges) {
secondaryFocused = !secondaryFocused
}
if secondaryFocused {
gui.getMainView().Title = gui.Tr.SLocalize("StagedChanges")
gui.getSecondaryView().Title = gui.Tr.SLocalize("UnstagedChanges")
} else {
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
}
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
diff := gui.GitCommand.Diff(file, true, secondaryFocused)
secondaryDiff := gui.GitCommand.Diff(file, true, !secondaryFocused)
@ -47,7 +59,7 @@ func (gui *Gui) refreshStagingPanel() error {
diff, secondaryDiff = secondaryDiff, diff
}
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, secondaryFocused)
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, secondaryFocused, selectedLineIdx)
if err != nil {
return err
}
@ -59,11 +71,19 @@ func (gui *Gui) refreshStagingPanel() error {
return nil
}
func (gui *Gui) handleTogglePanelClick(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
state.SecondaryFocused = !state.SecondaryFocused
return gui.refreshStagingPanel(false, v.SelectedLineIdx())
}
func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
state.SecondaryFocused = !state.SecondaryFocused
return gui.refreshStagingPanel()
return gui.refreshStagingPanel(false, -1)
}
func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
@ -116,8 +136,16 @@ func (gui *Gui) applySelection(reverse bool) error {
if err := gui.refreshFiles(); err != nil {
return err
}
if err := gui.refreshStagingPanel(); err != nil {
if err := gui.refreshStagingPanel(false, -1); err != nil {
return err
}
return nil
}
func (gui *Gui) handleMouseDownSecondaryWhileStaging(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.LineByLine
state.SecondaryFocused = !state.SecondaryFocused
return gui.refreshStagingPanel(false, -1)
}

View File

@ -29,6 +29,9 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
gui.getMainView().Title = "Stash"
stashEntry := gui.getSelectedStashEntry(v)
if stashEntry == nil {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))

View File

@ -10,6 +10,8 @@ import (
)
func (gui *Gui) refreshStatus(g *gocui.Gui) error {
state := gui.State.Panels.Status
v, err := g.View("status")
if err != nil {
panic(err)
@ -19,34 +21,69 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
// contents end up cleared
g.Update(func(*gocui.Gui) error {
v.Clear()
pushables, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
branches := gui.State.Branches
state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
if err := gui.updateWorkTreeState(); err != nil {
return err
}
status := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables)
branches := gui.State.Branches
if gui.State.WorkingTreeState != "normal" {
fmt.Fprint(v, utils.ColoredString(fmt.Sprintf(" (%s)", gui.State.WorkingTreeState), color.FgYellow))
status += utils.ColoredString(fmt.Sprintf(" (%s)", gui.State.WorkingTreeState), color.FgYellow)
}
if len(branches) == 0 {
return nil
if len(branches) > 0 {
branch := branches[0]
name := utils.ColoredString(branch.Name, branch.GetColor())
repoName := utils.GetCurrentRepoName()
status += fmt.Sprintf(" %s → %s", repoName, name)
}
branch := branches[0]
name := utils.ColoredString(branch.Name, branch.GetColor())
repo := utils.GetCurrentRepoName()
fmt.Fprint(v, " "+repo+" → "+name)
fmt.Fprint(v, status)
return nil
})
return nil
}
func runeCount(str string) int {
return len([]rune(str))
}
func cursorInSubstring(cx int, prefix string, substring string) bool {
return cx >= runeCount(prefix) && cx < runeCount(prefix+substring)
}
func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error {
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
return gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("CheckingForUpdates"))
}
func (gui *Gui) handleStatusClick(g *gocui.Gui, v *gocui.View) error {
state := gui.State.Panels.Status
cx, _ := v.Cursor()
upstreamStatus := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables)
repoName := utils.GetCurrentRepoName()
gui.Log.Warn(gui.State.WorkingTreeState)
switch gui.State.WorkingTreeState {
case "rebasing", "merging":
workingTreeStatus := fmt.Sprintf("(%s)", gui.State.WorkingTreeState)
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
return gui.handleCreateRebaseOptionsMenu(gui.g, v)
}
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
return gui.handleCreateRecentReposMenu(gui.g, v)
}
default:
if cursorInSubstring(cx, upstreamStatus+" ", repoName) {
return gui.handleCreateRecentReposMenu(gui.g, v)
}
}
return gui.handleStatusSelect(gui.g, v)
}
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
@ -57,6 +94,9 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
gui.getMainView().Title = ""
magenta := color.New(color.FgMagenta)
dashboardString := strings.Join(
@ -67,7 +107,7 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
"Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md",
"Tutorial: https://youtu.be/VDXvbHZYeKY",
"Raise an Issue: https://github.com/jesseduffield/lazygit/issues",
magenta.Sprint("Buy Jesse a coffee: https://donorbox.org/lazygit"), // caffeine ain't free
magenta.Sprint("Become a sponsor (github is matching all donations for 12 months): https://github.com/sponsors/jesseduffield"), // caffeine ain't free
}, "\n\n")
return gui.renderString(g, "main", dashboardString)

View File

@ -425,3 +425,27 @@ func (gui *Gui) isPopupPanel(viewName string) bool {
func (gui *Gui) popupPanelFocused() bool {
return gui.isPopupPanel(gui.currentViewName())
}
func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, handleSelect func(*gocui.Gui, *gocui.View) error) error {
if gui.popupPanelFocused() && v != nil && !gui.isPopupPanel(v.Name()) {
return nil
}
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
newSelectedLine := v.SelectedLineIdx()
if newSelectedLine < 0 {
newSelectedLine = 0
}
if newSelectedLine > itemCount-1 {
newSelectedLine = itemCount - 1
}
*selectedLine = newSelectedLine
return handleSelect(gui.g, v)
}

View File

@ -38,8 +38,11 @@ func addDutch(i18nObject *i18n.Bundle) error {
ID: "StashTitle",
Other: "Stash",
}, &i18n.Message{
ID: "StagingMainTitle",
Other: `Stage Lines/Hunks`,
ID: "UnstagedChanges",
Other: `Unstaged Changes`,
}, &i18n.Message{
ID: "StagedChanges",
Other: `Staged Changes`,
}, &i18n.Message{
ID: "MergingMainTitle",
Other: "Resolve merge conflicts",

View File

@ -46,8 +46,11 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "StashTitle",
Other: "Stash",
}, &i18n.Message{
ID: "StagingMainTitle",
Other: `Stage Lines/Hunks`,
ID: "UnstagedChanges",
Other: `Unstaged Changes`,
}, &i18n.Message{
ID: "StagedChanges",
Other: `Staged Changes`,
}, &i18n.Message{
ID: "PatchBuildingMainTitle",
Other: `Add Lines/Hunks To Patch`,

View File

@ -36,8 +36,11 @@ func addPolish(i18nObject *i18n.Bundle) error {
ID: "StashTitle",
Other: "Schowek",
}, &i18n.Message{
ID: "StagingMainTitle",
Other: `Stage Lines/Hunks`,
ID: "UnstagedChanges",
Other: `Unstaged Changes`,
}, &i18n.Message{
ID: "StagedChanges",
Other: `Staged Changes`,
}, &i18n.Message{
ID: "MergingMainTitle",
Other: "Resolve merge conflicts",