mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	making a start on unidirectional data binding to fix these UI bugs
This commit is contained in:
		| @@ -52,6 +52,8 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { | ||||
| 	} else { | ||||
| 		log = newProductionLogger(config) | ||||
| 	} | ||||
| 	log.Formatter = &logrus.JSONFormatter{} | ||||
|  | ||||
| 	if config.GetUserConfig().GetString("reporting") == "on" { | ||||
| 		// this isn't really a secret token: it only has permission to push new rollbar items | ||||
| 		hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", environment) | ||||
|   | ||||
| @@ -130,6 +130,17 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { | ||||
|  | ||||
| // GetStatusFiles git status files | ||||
| func (c *GitCommand) GetStatusFiles() []*File { | ||||
|  | ||||
| 	// files := []*File{} | ||||
| 	// for i := 0; i < 100; i++ { | ||||
| 	// 	files = append(files, &File{ | ||||
| 	// 		Name:          strconv.Itoa(i), | ||||
| 	// 		DisplayString: strconv.Itoa(i), | ||||
| 	// 		Type:          "file", | ||||
| 	// 	}) | ||||
| 	// } | ||||
| 	// return files | ||||
|  | ||||
| 	statusOutput, _ := c.GitStatus() | ||||
| 	statusStrings := utils.SplitLines(statusOutput) | ||||
| 	files := []*File{} | ||||
|   | ||||
| @@ -131,30 +131,30 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { | ||||
| } | ||||
|  | ||||
| func (gui *Gui) getSelectedBranch(v *gocui.View) *commands.Branch { | ||||
| 	lineNumber := gui.getItemPosition(v) | ||||
| 	return gui.State.Branches[lineNumber] | ||||
| } | ||||
| 	selectedLine := gui.State.Panels.Branches.SelectedLine | ||||
| 	if selectedLine == -1 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error { | ||||
| 	return gui.renderGlobalOptions(g) | ||||
| 	return gui.State.Branches[selectedLine] | ||||
| } | ||||
|  | ||||
| // may want to standardise how these select methods work | ||||
| func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { | ||||
| 	if err := gui.renderBranchesOptions(g); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// 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")) | ||||
| 	} | ||||
| 	branch := gui.getSelectedBranch(v) | ||||
| 	if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, v); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	go func() { | ||||
| 		branch := gui.getSelectedBranch(v) | ||||
| 		diff, err := gui.GitCommand.GetBranchGraph(branch.Name) | ||||
| 		if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { | ||||
| 			diff = gui.Tr.SLocalize("NoTrackingThisBranch") | ||||
| 		graph, err := gui.GitCommand.GetBranchGraph(branch.Name) | ||||
| 		if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") { | ||||
| 			graph = gui.Tr.SLocalize("NoTrackingThisBranch") | ||||
| 		} | ||||
| 		gui.renderString(g, "main", diff) | ||||
| 		_ = gui.renderString(g, "main", graph) | ||||
| 	}() | ||||
| 	return nil | ||||
| } | ||||
| @@ -173,6 +173,8 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { | ||||
| 		} | ||||
| 		gui.State.Branches = builder.Build() | ||||
|  | ||||
| 		gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches)) | ||||
|  | ||||
| 		v.Clear() | ||||
| 		list, err := utils.RenderList(gui.State.Branches) | ||||
| 		if err != nil { | ||||
| @@ -186,3 +188,17 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { | ||||
| 	}) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	panelState := gui.State.Panels.Branches | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false) | ||||
|  | ||||
| 	return gui.handleBranchSelect(gui.g, v) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	panelState := gui.State.Panels.Branches | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), true) | ||||
|  | ||||
| 	return gui.handleBranchSelect(gui.g, v) | ||||
| } | ||||
|   | ||||
| @@ -22,6 +22,8 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits)) | ||||
|  | ||||
| 		v.Clear() | ||||
|  | ||||
| 		list, err := utils.RenderList(gui.State.Commits) | ||||
| @@ -41,9 +43,9 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { | ||||
|  | ||||
| func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { | ||||
| 	return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error { | ||||
| 		commit, err := gui.getSelectedCommit(g) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		commit := gui.getSelectedCommit(g) | ||||
| 		if commit == nil { | ||||
| 			panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch"))) | ||||
| 		} | ||||
| 		if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil { | ||||
| 			return gui.createErrorPanel(g, err.Error()) | ||||
| @@ -59,21 +61,15 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error | ||||
| 	}, nil) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error { | ||||
| 	return gui.renderGlobalOptions(g) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { | ||||
| 	if err := gui.renderCommitsOptions(g); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	commit, err := gui.getSelectedCommit(g) | ||||
| 	if err != nil { | ||||
| 		if err.Error() != gui.Tr.SLocalize("NoCommitsThisBranch") { | ||||
| 			return err | ||||
| 		} | ||||
| 	commit := gui.getSelectedCommit(g) | ||||
| 	if commit == nil { | ||||
| 		return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch")) | ||||
| 	} | ||||
|  | ||||
| 	if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, v); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	commitText, err := gui.GitCommand.Show(commit.Sha) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -85,12 +81,12 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { | ||||
| 	if gui.getItemPosition(v) != 0 { | ||||
| 		return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit")) | ||||
| 	} | ||||
| 	if len(gui.State.Commits) == 1 { | ||||
| 	if len(gui.State.Commits) <= 1 { | ||||
| 		return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash")) | ||||
| 	} | ||||
| 	commit, err := gui.getSelectedCommit(g) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	commit := gui.getSelectedCommit(g) | ||||
| 	if commit == nil { | ||||
| 		return errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) | ||||
| 	} | ||||
| 	if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil { | ||||
| 		return gui.createErrorPanel(g, err.Error()) | ||||
| @@ -113,16 +109,16 @@ func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool { | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { | ||||
| 	if len(gui.State.Commits) == 1 { | ||||
| 	if len(gui.State.Commits) <= 1 { | ||||
| 		return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash")) | ||||
| 	} | ||||
| 	if gui.anyUnStagedChanges(gui.State.Files) { | ||||
| 		return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges")) | ||||
| 	} | ||||
| 	branch := gui.State.Branches[0] | ||||
| 	commit, err := gui.getSelectedCommit(g) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	commit := gui.getSelectedCommit(g) | ||||
| 	if commit == nil { | ||||
| 		return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitsThisBranch")) | ||||
| 	} | ||||
| 	message := gui.Tr.SLocalize("SureFixupThisCommit") | ||||
| 	gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), message, func(g *gocui.Gui, v *gocui.View) error { | ||||
| @@ -165,18 +161,27 @@ func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) getSelectedCommit(g *gocui.Gui) (*commands.Commit, error) { | ||||
| 	v, err := g.View("commits") | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit { | ||||
| 	selectedLine := gui.State.Panels.Commits.SelectedLine | ||||
| 	if selectedLine == -1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if len(gui.State.Commits) == 0 { | ||||
| 		return &commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) | ||||
| 	} | ||||
| 	lineNumber := gui.getItemPosition(v) | ||||
| 	if lineNumber > len(gui.State.Commits)-1 { | ||||
| 		gui.Log.Info(gui.Tr.SLocalize("PotentialErrInGetselectedCommit"), gui.State.Commits, lineNumber) | ||||
| 		return gui.State.Commits[len(gui.State.Commits)-1], nil | ||||
| 	} | ||||
| 	return gui.State.Commits[lineNumber], nil | ||||
|  | ||||
| 	return gui.State.Commits[selectedLine] | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCommitsNextLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	gui.Log.Info(utils.AsJson(gui.State.Panels)) | ||||
| 	panelState := gui.State.Panels.Commits | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), false) | ||||
|  | ||||
| 	return gui.handleCommitSelect(gui.g, v) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCommitsPrevLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	gui.Log.Info(utils.AsJson(gui.State.Panels)) | ||||
| 	panelState := gui.State.Panels.Commits | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), true) | ||||
|  | ||||
| 	return gui.handleCommitSelect(gui.g, v) | ||||
| } | ||||
|   | ||||
| @@ -140,15 +140,12 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { | ||||
| } | ||||
|  | ||||
| func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) { | ||||
| 	if len(gui.State.Files) == 0 { | ||||
| 	selectedLine := gui.State.Panels.Files.SelectedLine | ||||
| 	if selectedLine == -1 { | ||||
| 		return &commands.File{}, gui.Errors.ErrNoFiles | ||||
| 	} | ||||
| 	filesView, err := g.View("files") | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	lineNumber := gui.getItemPosition(filesView) | ||||
| 	return gui.State.Files[lineNumber], nil | ||||
|  | ||||
| 	return gui.State.Files[selectedLine], nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { | ||||
| @@ -194,26 +191,23 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { | ||||
| 	return gui.refreshFiles(g) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error { | ||||
| 	return gui.renderGlobalOptions(g) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { | ||||
| 	file, err := gui.getSelectedFile(g) | ||||
| 	if err != nil { | ||||
| 		if err != gui.Errors.ErrNoFiles { | ||||
| 			return err | ||||
| 		} | ||||
| 		gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) | ||||
| 		return gui.renderfilesOptions(g, nil) | ||||
| 	} | ||||
| 	if err := gui.renderfilesOptions(g, file); err != nil { | ||||
| 		return err | ||||
| 		return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) | ||||
| 	} | ||||
|  | ||||
| 	if file.HasMergeConflicts { | ||||
| 		return gui.refreshMergePanel(g) | ||||
| 	} | ||||
|  | ||||
| 	if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, v); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	content := gui.GitCommand.Diff(file, false) | ||||
| 	return gui.renderString(g, "main", content) | ||||
| } | ||||
| @@ -309,6 +303,7 @@ func (gui *Gui) refreshStateFiles() { | ||||
| 	// get files to stage | ||||
| 	files := gui.GitCommand.GetStatusFiles() | ||||
| 	gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) | ||||
| 	gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files)) | ||||
| 	gui.updateHasMergeConflictStatus() | ||||
| } | ||||
|  | ||||
| @@ -340,6 +335,20 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { | ||||
| 	return cat, nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleFilesNextLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	panelState := gui.State.Panels.Files | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false) | ||||
|  | ||||
| 	return gui.handleFileSelect(gui.g, v) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error { | ||||
| 	panelState := gui.State.Panels.Files | ||||
| 	gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true) | ||||
|  | ||||
| 	return gui.handleFileSelect(gui.g, v) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) refreshFiles(g *gocui.Gui) error { | ||||
| 	filesView, err := g.View("files") | ||||
| 	if err != nil { | ||||
| @@ -347,17 +356,21 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { | ||||
| 	} | ||||
| 	gui.refreshStateFiles() | ||||
|  | ||||
| 	filesView.Clear() | ||||
| 	list, err := utils.RenderList(gui.State.Files) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprint(filesView, list) | ||||
| 	gui.g.Update(func(g *gocui.Gui) error { | ||||
|  | ||||
| 		filesView.Clear() | ||||
| 		list, err := utils.RenderList(gui.State.Files) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprint(filesView, list) | ||||
|  | ||||
| 		if filesView == g.CurrentView() { | ||||
| 			gui.handleFileSelect(g, filesView) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	gui.correctCursor(filesView) | ||||
| 	if filesView == g.CurrentView() { | ||||
| 		gui.handleFileSelect(g, filesView) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -72,11 +72,35 @@ type Gui struct { | ||||
| 	statusManager *statusManager | ||||
| } | ||||
|  | ||||
| type stagingState struct { | ||||
| 	StageableLines   []int | ||||
| 	HunkStarts       []int | ||||
| 	CurrentLineIndex int | ||||
| 	Diff             string | ||||
| type stagingPanelState struct { | ||||
| 	SelectedLine   int | ||||
| 	StageableLines []int | ||||
| 	HunkStarts     []int | ||||
| 	Diff           string | ||||
| } | ||||
|  | ||||
| type filePanelState struct { | ||||
| 	SelectedLine int | ||||
| } | ||||
|  | ||||
| type branchPanelState struct { | ||||
| 	SelectedLine int | ||||
| } | ||||
|  | ||||
| type commitPanelState struct { | ||||
| 	SelectedLine int | ||||
| } | ||||
|  | ||||
| type stashPanelState struct { | ||||
| 	SelectedLine int | ||||
| } | ||||
|  | ||||
| type panelStates struct { | ||||
| 	Files    *filePanelState | ||||
| 	Staging  *stagingPanelState | ||||
| 	Branches *branchPanelState | ||||
| 	Commits  *commitPanelState | ||||
| 	Stash    *stashPanelState | ||||
| } | ||||
|  | ||||
| type guiState struct { | ||||
| @@ -92,7 +116,7 @@ type guiState struct { | ||||
| 	EditHistory       *stack.Stack | ||||
| 	Platform          commands.Platform | ||||
| 	Updating          bool | ||||
| 	StagingState      *stagingState | ||||
| 	Panels            *panelStates | ||||
| } | ||||
|  | ||||
| // NewGui builds a new gui handler | ||||
| @@ -108,6 +132,12 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma | ||||
| 		Conflicts:     make([]commands.Conflict, 0), | ||||
| 		EditHistory:   stack.New(), | ||||
| 		Platform:      *oSCommand.Platform, | ||||
| 		Panels: &panelStates{ | ||||
| 			Files:    &filePanelState{SelectedLine: -1}, | ||||
| 			Branches: &branchPanelState{SelectedLine: 0}, | ||||
| 			Commits:  &commitPanelState{SelectedLine: -1}, | ||||
| 			Stash:    &stashPanelState{SelectedLine: -1}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	gui := &Gui{ | ||||
| @@ -193,9 +223,11 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 			} | ||||
| 			v.Title = gui.Tr.SLocalize("NotEnoughSpace") | ||||
| 			v.Wrap = true | ||||
| 			g.SetCurrentView(v.Name()) | ||||
| 			g.SetViewOnTop("limit") | ||||
| 		} | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		g.SetViewOnBottom("limit") | ||||
| 	} | ||||
|  | ||||
| 	g.DeleteView("limit") | ||||
| @@ -247,12 +279,13 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 		v.FgColor = gocui.ColorWhite | ||||
| 	} | ||||
|  | ||||
| 	if v, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM); err != nil { | ||||
| 	branchesView, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM) | ||||
| 	if err != nil { | ||||
| 		if err != gocui.ErrUnknownView { | ||||
| 			return err | ||||
| 		} | ||||
| 		v.Title = gui.Tr.SLocalize("BranchesTitle") | ||||
| 		v.FgColor = gocui.ColorWhite | ||||
| 		branchesView.Title = gui.Tr.SLocalize("BranchesTitle") | ||||
| 		branchesView.FgColor = gocui.ColorWhite | ||||
| 	} | ||||
|  | ||||
| 	if v, err := g.SetView("commits", 0, commitsBranchesBoundary+panelSpacing, leftSideWidth, commitsStashBoundary, gocui.TOP|gocui.BOTTOM); err != nil { | ||||
| @@ -325,11 +358,14 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		gui.handleFileSelect(g, filesView) | ||||
| 		gui.g.SetCurrentView(filesView.Name()) | ||||
| 		gui.refreshFiles(g) | ||||
| 		gui.refreshBranches(g) | ||||
| 		gui.refreshCommits(g) | ||||
| 		gui.refreshStashEntries(g) | ||||
| 		if err := gui.renderGlobalOptions(g); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := gui.switchFocus(g, nil, filesView); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -341,6 +377,17 @@ func (gui *Gui) layout(g *gocui.Gui) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	listViews := map[*gocui.View]int{ | ||||
| 		filesView:    gui.State.Panels.Files.SelectedLine, | ||||
| 		branchesView: gui.State.Panels.Branches.SelectedLine, | ||||
| 	} | ||||
| 	for view, selectedLine := range listViews { | ||||
| 		// check if the selected line is now out of view and if so refocus it | ||||
| 		if err := gui.focusPoint(0, selectedLine, view); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return gui.resizeCurrentPopupPanel(g) | ||||
| } | ||||
|  | ||||
| @@ -411,7 +458,7 @@ func (gui *Gui) Run() error { | ||||
| 	} | ||||
|  | ||||
| 	gui.goEvery(g, time.Second*60, gui.fetch) | ||||
| 	gui.goEvery(g, time.Second*10, gui.refreshFiles) | ||||
| 	// gui.goEvery(g, time.Second*2, gui.refreshFiles) // TODO: comment back in | ||||
| 	gui.goEvery(g, time.Millisecond*50, gui.updateLoader) | ||||
| 	gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus) | ||||
|  | ||||
|   | ||||
| @@ -219,7 +219,12 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Handler:     gui.handleSwitchToStagingPanel, | ||||
| 			Description: gui.Tr.SLocalize("StageLines"), | ||||
| 			KeyReadable: "enter", | ||||
| 		}, { | ||||
| 		}, | ||||
| 		{ViewName: "files", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleFilesPrevLine}, | ||||
| 		{ViewName: "files", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleFilesPrevLine}, | ||||
| 		{ViewName: "files", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleFilesNextLine}, | ||||
| 		{ViewName: "files", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleFilesNextLine}, | ||||
| 		{ | ||||
| 			ViewName: "main", | ||||
| 			Key:      gocui.KeyEsc, | ||||
| 			Modifier: gocui.ModNone, | ||||
| @@ -322,7 +327,12 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Modifier:    gocui.ModNone, | ||||
| 			Handler:     gui.handleMerge, | ||||
| 			Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"), | ||||
| 		}, { | ||||
| 		}, | ||||
| 		{ViewName: "branches", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, | ||||
| 		{ViewName: "branches", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, | ||||
| 		{ViewName: "branches", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, | ||||
| 		{ViewName: "branches", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, | ||||
| 		{ | ||||
| 			ViewName:    "commits", | ||||
| 			Key:         's', | ||||
| 			Modifier:    gocui.ModNone, | ||||
| @@ -352,7 +362,12 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
| 			Modifier:    gocui.ModNone, | ||||
| 			Handler:     gui.handleCommitFixup, | ||||
| 			Description: gui.Tr.SLocalize("fixupCommit"), | ||||
| 		}, { | ||||
| 		}, | ||||
| 		{ViewName: "commits", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleCommitsPrevLine}, | ||||
| 		{ViewName: "commits", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleCommitsPrevLine}, | ||||
| 		{ViewName: "commits", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleCommitsNextLine}, | ||||
| 		{ViewName: "commits", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleCommitsNextLine}, | ||||
| 		{ | ||||
| 			ViewName:    "stash", | ||||
| 			Key:         gocui.KeySpace, | ||||
| 			Modifier:    gocui.ModNone, | ||||
| @@ -455,17 +470,22 @@ func (gui *Gui) GetKeybindings() []*Binding { | ||||
|  | ||||
| 	// Would make these keybindings global but that interferes with editing | ||||
| 	// input in the confirmation panel | ||||
| 	for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} { | ||||
| 	for _, viewName := range []string{"status", "commits", "stash", "menu"} { | ||||
| 		bindings = append(bindings, []*Binding{ | ||||
| 			{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.cursorUp}, | ||||
| 			{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.cursorDown}, | ||||
| 			{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: gui.cursorUp}, | ||||
| 			{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: gui.cursorDown}, | ||||
| 		}...) | ||||
| 	} | ||||
|  | ||||
| 	for _, viewName := range []string{"status", "branches", "files", "commits", "stash", "menu"} { | ||||
| 		bindings = append(bindings, []*Binding{ | ||||
| 			{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView}, | ||||
| 			{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView}, | ||||
| 			{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView}, | ||||
| 			{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.cursorUp}, | ||||
| 			{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.cursorDown}, | ||||
| 			{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView}, | ||||
| 			{ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView}, | ||||
| 			{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: gui.cursorUp}, | ||||
| 			{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: gui.cursorDown}, | ||||
| 		}...) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -40,23 +40,23 @@ func (gui *Gui) refreshStagingPanel() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var currentLineIndex int | ||||
| 	if gui.State.StagingState != nil { | ||||
| 	var selectedLine int | ||||
| 	if gui.State.Panels.Staging != nil { | ||||
| 		end := len(stageableLines) - 1 | ||||
| 		if end < gui.State.StagingState.CurrentLineIndex { | ||||
| 			currentLineIndex = end | ||||
| 		if end < gui.State.Panels.Staging.SelectedLine { | ||||
| 			selectedLine = end | ||||
| 		} else { | ||||
| 			currentLineIndex = gui.State.StagingState.CurrentLineIndex | ||||
| 			selectedLine = gui.State.Panels.Staging.SelectedLine | ||||
| 		} | ||||
| 	} else { | ||||
| 		currentLineIndex = 0 | ||||
| 		selectedLine = 0 | ||||
| 	} | ||||
|  | ||||
| 	gui.State.StagingState = &stagingState{ | ||||
| 		StageableLines:   stageableLines, | ||||
| 		HunkStarts:       hunkStarts, | ||||
| 		CurrentLineIndex: currentLineIndex, | ||||
| 		Diff:             diff, | ||||
| 	gui.State.Panels.Staging = &stagingPanelState{ | ||||
| 		StageableLines: stageableLines, | ||||
| 		HunkStarts:     hunkStarts, | ||||
| 		SelectedLine:   selectedLine, | ||||
| 		Diff:           diff, | ||||
| 	} | ||||
|  | ||||
| 	if len(stageableLines) == 0 { | ||||
| @@ -74,7 +74,7 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	gui.State.StagingState = nil | ||||
| 	gui.State.Panels.Staging = nil | ||||
|  | ||||
| 	return gui.switchFocus(gui.g, nil, gui.getFilesView(gui.g)) | ||||
| } | ||||
| @@ -96,9 +96,9 @@ func (gui *Gui) handleStagingNextHunk(g *gocui.Gui, v *gocui.View) error { | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCycleHunk(prev bool) error { | ||||
| 	state := gui.State.StagingState | ||||
| 	state := gui.State.Panels.Staging | ||||
| 	lineNumbers := state.StageableLines | ||||
| 	currentLine := lineNumbers[state.CurrentLineIndex] | ||||
| 	currentLine := lineNumbers[state.SelectedLine] | ||||
| 	currentHunkIndex := utils.PrevIndex(state.HunkStarts, currentLine) | ||||
| 	var newHunkIndex int | ||||
| 	if prev { | ||||
| @@ -115,22 +115,22 @@ func (gui *Gui) handleCycleHunk(prev bool) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	state.CurrentLineIndex = utils.NextIndex(lineNumbers, state.HunkStarts[newHunkIndex]) | ||||
| 	state.SelectedLine = utils.NextIndex(lineNumbers, state.HunkStarts[newHunkIndex]) | ||||
|  | ||||
| 	return gui.focusLineAndHunk() | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCycleLine(prev bool) error { | ||||
| 	state := gui.State.StagingState | ||||
| 	state := gui.State.Panels.Staging | ||||
| 	lineNumbers := state.StageableLines | ||||
| 	currentLine := lineNumbers[state.CurrentLineIndex] | ||||
| 	currentLine := lineNumbers[state.SelectedLine] | ||||
| 	var newIndex int | ||||
| 	if prev { | ||||
| 		newIndex = utils.PrevIndex(lineNumbers, currentLine) | ||||
| 	} else { | ||||
| 		newIndex = utils.NextIndex(lineNumbers, currentLine) | ||||
| 	} | ||||
| 	state.CurrentLineIndex = newIndex | ||||
| 	state.SelectedLine = newIndex | ||||
|  | ||||
| 	return gui.focusLineAndHunk() | ||||
| } | ||||
| @@ -139,9 +139,9 @@ func (gui *Gui) handleCycleLine(prev bool) error { | ||||
| // selected line and size of the hunk | ||||
| func (gui *Gui) focusLineAndHunk() error { | ||||
| 	stagingView := gui.getStagingView(gui.g) | ||||
| 	state := gui.State.StagingState | ||||
| 	state := gui.State.Panels.Staging | ||||
|  | ||||
| 	lineNumber := state.StageableLines[state.CurrentLineIndex] | ||||
| 	lineNumber := state.StageableLines[state.SelectedLine] | ||||
|  | ||||
| 	// we want the bottom line of the view buffer to ideally be the bottom line | ||||
| 	// of the hunk, but if the hunk is too big we'll just go three lines beyond | ||||
| @@ -170,23 +170,7 @@ func (gui *Gui) focusLineAndHunk() error { | ||||
| 		bottomLine = lineNumber + 3 | ||||
| 	} | ||||
|  | ||||
| 	return gui.focusLine(lineNumber, bottomLine, stagingView) | ||||
| } | ||||
|  | ||||
| // focusLine takes a lineNumber to focus, and a bottomLine to ensure we can see | ||||
| func (gui *Gui) focusLine(lineNumber int, bottomLine int, v *gocui.View) error { | ||||
| 	_, height := v.Size() | ||||
| 	overScroll := bottomLine - height + 1 | ||||
| 	if overScroll < 0 { | ||||
| 		overScroll = 0 | ||||
| 	} | ||||
| 	if err := v.SetOrigin(0, overScroll); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := v.SetCursor(0, lineNumber-overScroll); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| 	return gui.generalFocusLine(lineNumber, bottomLine, stagingView) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleStageHunk(g *gocui.Gui, v *gocui.View) error { | ||||
| @@ -198,13 +182,13 @@ func (gui *Gui) handleStageLine(g *gocui.Gui, v *gocui.View) error { | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleStageLineOrHunk(hunk bool) error { | ||||
| 	state := gui.State.StagingState | ||||
| 	state := gui.State.Panels.Staging | ||||
| 	p, err := git.NewPatchModifier(gui.Log) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	currentLine := state.StageableLines[state.CurrentLineIndex] | ||||
| 	currentLine := state.StageableLines[state.SelectedLine] | ||||
| 	var patch string | ||||
| 	if hunk { | ||||
| 		patch, err = p.ModifyPatchForHunk(state.Diff, state.HunkStarts, currentLine) | ||||
|   | ||||
| @@ -37,14 +37,7 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { | ||||
| 	return gui.State.StashEntries[lineNumber] | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderStashOptions(g *gocui.Gui) error { | ||||
| 	return gui.renderGlobalOptions(g) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { | ||||
| 	if err := gui.renderStashOptions(g); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	go func() { | ||||
| 		stashEntry := gui.getSelectedStashEntry(v) | ||||
| 		if stashEntry == nil { | ||||
|   | ||||
| @@ -42,10 +42,6 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) renderStatusOptions(g *gocui.Gui) error { | ||||
| 	return gui.renderGlobalOptions(g) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error { | ||||
| 	gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true) | ||||
| 	return gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("CheckingForUpdates")) | ||||
| @@ -63,10 +59,7 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { | ||||
| 			"Buy Jesse a coffee: https://donorbox.org/lazygit", | ||||
| 		}, "\n\n") | ||||
|  | ||||
| 	if err := gui.renderString(g, "main", dashboardString); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return gui.renderStatusOptions(g) | ||||
| 	return gui.renderString(g, "main", dashboardString) | ||||
| } | ||||
|  | ||||
| func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error { | ||||
|   | ||||
| @@ -230,19 +230,46 @@ func (gui *Gui) resetOrigin(v *gocui.View) error { | ||||
| // if the cursor down past the last item, move it to the last line | ||||
| func (gui *Gui) correctCursor(v *gocui.View) error { | ||||
| 	cx, cy := v.Cursor() | ||||
| 	ox, oy := v.Origin() | ||||
| 	_, height := v.Size() | ||||
| 	maxY := height - 1 | ||||
| 	ly := v.LinesHeight() - 1 | ||||
| 	if oy+cy <= ly { | ||||
| 	return gui.focusPoint(cx, cy, v) | ||||
| } | ||||
|  | ||||
| // if the cursor down past the last item, move it to the last line | ||||
| func (gui *Gui) focusPoint(cx int, cy int, v *gocui.View) error { | ||||
| 	if cy < 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	newCy := utils.Min(ly, maxY) | ||||
| 	if err := v.SetCursor(cx, newCy); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := v.SetOrigin(ox, ly-newCy); err != nil { | ||||
| 		return err | ||||
| 	ox, oy := v.Origin() | ||||
| 	_, height := v.Size() | ||||
| 	ly := height - 1 | ||||
|  | ||||
| 	// if line is above origin, move origin and set cursor to zero | ||||
| 	// if line is below origin + height, move origin and set cursor to max | ||||
| 	// otherwise set cursor to value - origin | ||||
| 	if ly > v.LinesHeight() { | ||||
| 		if err := v.SetCursor(cx, cy); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := v.SetOrigin(ox, 0); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else if cy < oy { | ||||
| 		if err := v.SetCursor(cx, 0); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := v.SetOrigin(ox, cy); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else if cy > oy+ly { | ||||
| 		if err := v.SetCursor(cx, ly); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := v.SetOrigin(ox, cy-ly); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		if err := v.SetCursor(cx, cy-oy); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -334,3 +361,59 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { | ||||
| 	_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // focusLine focuses and selects the given line | ||||
| func (gui *Gui) focusLine(lineNumber int, v *gocui.View) error { | ||||
| 	_, height := v.Size() | ||||
| 	overScroll := lineNumber - height + 1 | ||||
| 	if overScroll < 0 { | ||||
| 		overScroll = 0 | ||||
| 	} | ||||
| 	if err := v.SetOrigin(0, overScroll); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := v.SetCursor(0, lineNumber-overScroll); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see | ||||
| func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error { | ||||
| 	_, height := v.Size() | ||||
| 	overScroll := bottomLine - height + 1 | ||||
| 	if overScroll < 0 { | ||||
| 		overScroll = 0 | ||||
| 	} | ||||
| 	if err := v.SetOrigin(0, overScroll); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := v.SetCursor(0, lineNumber-overScroll); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gui *Gui) changeSelectedLine(line *int, total int, up bool) { | ||||
| 	if up { | ||||
| 		if *line == -1 || *line == 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		*line -= 1 | ||||
| 	} else { | ||||
| 		if *line == -1 || *line == total-1 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		*line += 1 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gui *Gui) refreshSelectedLine(line *int, total int) { | ||||
| 	if *line == -1 && total > 0 { | ||||
| 		*line = 0 | ||||
| 	} else if total-1 < *line { | ||||
| 		*line = total - 1 | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package utils | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| @@ -235,3 +236,8 @@ func PrevIndex(numbers []int, currentNumber int) int { | ||||
| 	} | ||||
| 	return end | ||||
| } | ||||
|  | ||||
| func AsJson(i interface{}) string { | ||||
| 	bytes, _ := json.MarshalIndent(i, "", "    ") | ||||
| 	return string(bytes) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user