1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-23 12:18:51 +02:00
lazygit/pkg/gui/view_helpers.go
Jesse Duffield 3b2353b5ae remove redundant call to refreshCommitFilesView
We already call this function inside the refreshCommitsView function.
We call it there because it's logical that A) one occurs whenever the other does and
B) the commit files only get refreshed after we've updated the commits themselves
2019-03-16 10:20:27 +11:00

409 lines
9.8 KiB
Go

package gui
import (
"fmt"
"sort"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom"
)
var cyclableViews = []string{"status", "files", "branches", "commits", "stash"}
func (gui *Gui) refreshSidePanels(g *gocui.Gui) error {
if err := gui.refreshBranches(g); err != nil {
return err
}
if err := gui.refreshFiles(); err != nil {
return err
}
if err := gui.refreshCommits(g); err != nil {
return err
}
return gui.refreshStashEntries(g)
}
func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
var focusedViewName string
if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] {
focusedViewName = cyclableViews[0]
} else {
// if we're in the commitFiles view we'll act like we're in the commits view
viewName := v.Name()
if viewName == "commitFiles" {
viewName = "commits"
}
for i := range cyclableViews {
if viewName == cyclableViews[i] {
focusedViewName = cyclableViews[i+1]
break
}
if i == len(cyclableViews)-1 {
message := gui.Tr.TemplateLocalize(
"IssntListOfViews",
Teml{
"name": viewName,
},
)
gui.Log.Info(message)
return nil
}
}
}
focusedView, err := g.View(focusedViewName)
if err != nil {
panic(err)
}
return gui.switchFocus(g, v, focusedView)
}
func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
var focusedViewName string
if v == nil || v.Name() == cyclableViews[0] {
focusedViewName = cyclableViews[len(cyclableViews)-1]
} else {
// if we're in the commitFiles view we'll act like we're in the commits view
viewName := v.Name()
if viewName == "commitFiles" {
viewName = "commits"
}
for i := range cyclableViews {
if viewName == cyclableViews[i] {
focusedViewName = cyclableViews[i-1] // TODO: make this work properly
break
}
if i == len(cyclableViews)-1 {
message := gui.Tr.TemplateLocalize(
"IssntListOfViews",
Teml{
"name": viewName,
},
)
gui.Log.Info(message)
return nil
}
}
}
focusedView, err := g.View(focusedViewName)
if err != nil {
panic(err)
}
return gui.switchFocus(g, v, focusedView)
}
func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
switch v.Name() {
case "menu":
return gui.handleMenuSelect(g, v)
case "status":
return gui.handleStatusSelect(g, v)
case "files":
return gui.handleFileSelect(g, v, false)
case "branches":
return gui.handleBranchSelect(g, v)
case "commits":
return gui.handleCommitSelect(g, v)
case "commitFiles":
return gui.handleCommitFileSelect(g, v)
case "stash":
return gui.handleStashEntrySelect(g, v)
case "confirmation":
return nil
case "commitMessage":
return gui.handleCommitFocused(g, v)
case "credentials":
return gui.handleCredentialsViewFocused(g, v)
case "main":
if gui.State.Contexts["main"] == "merging" {
return gui.refreshMergePanel()
}
v.Highlight = false
return nil
default:
panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement"))
}
}
func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error {
previousView, err := g.View(gui.State.PreviousView)
if err != nil {
// always fall back to files view if there's no 'previous' view stored
previousView, err = g.View("files")
if err != nil {
gui.Log.Error(err)
}
}
return gui.switchFocus(g, v, previousView)
}
// pass in oldView = nil if you don't want to be able to return to your old view
// TODO: move some of this logic into our onFocusLost and onFocus hooks
func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
// we assume we'll never want to return focus to a popup panel i.e.
// we should never stack popup panels
if oldView != nil && !gui.isPopupPanel(oldView.Name()) {
gui.State.PreviousView = oldView.Name()
}
gui.Log.Info("setting highlight to true for view" + newView.Name())
message := gui.Tr.TemplateLocalize(
"newFocusedViewIs",
Teml{
"newFocusedView": newView.Name(),
},
)
gui.Log.Info(message)
if _, err := g.SetCurrentView(newView.Name()); err != nil {
return err
}
if _, err := g.SetViewOnTop(newView.Name()); err != nil {
return err
}
g.Cursor = newView.Editable
if err := gui.renderPanelOptions(); err != nil {
return err
}
return gui.newLineFocused(g, newView)
}
func (gui *Gui) resetOrigin(v *gocui.View) error {
if err := v.SetCursor(0, 0); err != nil {
return err
}
return v.SetOrigin(0, 0)
}
// 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
}
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
}
func (gui *Gui) cleanString(s string) string {
output := string(bom.Clean([]byte(s)))
return utils.NormalizeLinefeeds(output)
}
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error {
v.Clear()
fmt.Fprint(v, gui.cleanString(s))
return nil
}
// renderString resets the origin of a view and sets its content
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
g.Update(func(*gocui.Gui) error {
v, err := g.View(viewName)
if err != nil {
return nil // return gracefully if view has been deleted
}
if err := v.SetOrigin(0, 0); err != nil {
return err
}
return gui.setViewContent(gui.g, v, s)
})
return nil
}
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
optionsArray := make([]string, 0)
for key, description := range optionsMap {
optionsArray = append(optionsArray, key+": "+description)
}
sort.Strings(optionsArray)
return strings.Join(optionsArray, ", ")
}
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
}
// TODO: refactor properly
// i'm so sorry but had to add this getBranchesView
func (gui *Gui) getFilesView() *gocui.View {
v, _ := gui.g.View("files")
return v
}
func (gui *Gui) getCommitsView() *gocui.View {
v, _ := gui.g.View("commits")
return v
}
func (gui *Gui) getCommitMessageView() *gocui.View {
v, _ := gui.g.View("commitMessage")
return v
}
func (gui *Gui) getBranchesView() *gocui.View {
v, _ := gui.g.View("branches")
return v
}
func (gui *Gui) getMainView() *gocui.View {
v, _ := gui.g.View("main")
return v
}
func (gui *Gui) getStashView() *gocui.View {
v, _ := gui.g.View("stash")
return v
}
func (gui *Gui) getCommitFilesView() *gocui.View {
v, _ := gui.g.View("commitFiles")
return v
}
func (gui *Gui) trimmedContent(v *gocui.View) string {
return strings.TrimSpace(v.Buffer())
}
func (gui *Gui) currentViewName() string {
currentView := gui.g.CurrentView()
return currentView.Name()
}
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
v := g.CurrentView()
if gui.isPopupPanel(v.Name()) {
return gui.resizePopupPanel(g, v)
}
return nil
}
func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
content := v.Buffer()
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Wrap, content)
vx0, vy0, vx1, vy1 := v.Dimensions()
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
return nil
}
gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel"))
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
return err
}
// 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
}
}
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
gui.g.Update(func(g *gocui.Gui) error {
isFocused := gui.g.CurrentView().Name() == v.Name()
list, err := utils.RenderList(items, isFocused)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
v.Clear()
fmt.Fprint(v, list)
return nil
})
return nil
}
func (gui *Gui) renderPanelOptions() error {
currentView := gui.g.CurrentView()
switch currentView.Name() {
case "menu":
return gui.renderMenuOptions()
case "main":
if gui.State.Contexts["main"] == "merging" {
return gui.renderMergeOptions()
}
}
return gui.renderGlobalOptions()
}
func (gui *Gui) handleFocusView(g *gocui.Gui, v *gocui.View) error {
_, err := gui.g.SetCurrentView(v.Name())
return err
}
func (gui *Gui) isPopupPanel(viewName string) bool {
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
}
func (gui *Gui) popupPanelFocused() bool {
return gui.isPopupPanel(gui.currentViewName())
}