1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/layout.go

421 lines
13 KiB
Go
Raw Normal View History

2020-03-29 01:31:34 +02:00
package gui
import (
"fmt"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
)
const SEARCH_PREFIX = "search: "
const INFO_SECTION_PADDING = " "
2020-03-29 01:31:34 +02:00
// getFocusLayout returns a manager function for when view gain and lose focus
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
var previousView *gocui.View
return func(g *gocui.Gui) error {
newView := gui.g.CurrentView()
if err := gui.onFocusChange(); err != nil {
return err
}
// for now we don't consider losing focus to a popup panel as actually losing focus
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
if err := gui.onFocusLost(previousView, newView); err != nil {
return err
}
if err := gui.onFocus(newView); err != nil {
return err
}
previousView = newView
}
return nil
}
}
func (gui *Gui) onFocusChange() error {
currentView := gui.g.CurrentView()
for _, view := range gui.g.Views() {
view.Highlight = view == currentView
}
return nil
}
func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
if v == nil {
return nil
}
if v.IsSearching() && newView.Name() != "search" {
if err := gui.onSearchEscape(); err != nil {
return err
}
}
switch v.Name() {
case "main":
// if we have lost focus to a first-class panel, we need to do some cleanup
gui.changeMainViewsContext("normal")
case "commitFiles":
if gui.State.MainContext != "patch-building" {
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
return err
}
}
}
gui.Log.Info(v.Name() + " focus lost")
return nil
}
func (gui *Gui) onFocus(v *gocui.View) error {
if v == nil {
return nil
}
gui.Log.Info(v.Name() + " focus gained")
return nil
}
2020-05-18 14:00:07 +02:00
func (gui *Gui) informationStr() string {
if gui.inDiffMode() {
return utils.ColoredString(fmt.Sprintf("%s %s %s", gui.Tr.SLocalize("showingGitDiff"), "git diff "+gui.diffStr(), utils.ColoredString(gui.Tr.SLocalize("(reset)"), color.Underline)), color.FgMagenta)
} else if gui.inFilterMode() {
return utils.ColoredString(fmt.Sprintf("%s '%s' %s", gui.Tr.SLocalize("filteringBy"), gui.State.FilterPath, utils.ColoredString(gui.Tr.SLocalize("(reset)"), color.Underline)), color.FgRed, color.Bold)
} else if len(gui.State.CherryPickedCommits) > 0 {
return utils.ColoredString(fmt.Sprintf("%d commits copied", len(gui.State.CherryPickedCommits)), color.FgCyan)
} else if gui.g.Mouse {
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
return donate + " " + gui.Config.GetVersion()
} else {
return gui.Config.GetVersion()
}
}
2020-03-29 01:31:34 +02:00
// layout is called for every screen re-render e.g. when the screen is resized
func (gui *Gui) layout(g *gocui.Gui) error {
g.Highlight = true
width, height := g.Size()
minimumHeight := 9
minimumWidth := 10
if height < minimumHeight || width < minimumWidth {
v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
if err != nil {
if err.Error() != "unknown view" {
return err
}
v.Title = gui.Tr.SLocalize("NotEnoughSpace")
v.Wrap = true
_, _ = g.SetViewOnTop("limit")
}
return nil
}
informationStr := gui.informationStr()
2020-03-29 01:31:34 +02:00
appStatus := gui.statusManager.getStatusString()
viewDimensions := gui.getViewDimensions(informationStr, appStatus)
2020-03-29 01:31:34 +02:00
_, _ = g.SetViewOnBottom("limit")
_ = g.DeleteView("limit")
textColor := theme.GocuiDefaultTextColor
// reading more lines into main view buffers upon resize
prevMainView, err := gui.g.View("main")
if err == nil {
_, prevMainHeight := prevMainView.Size()
newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 - 1
2020-05-16 04:35:19 +02:00
heightDiff := newMainHeight - prevMainHeight
2020-03-29 01:31:34 +02:00
if heightDiff > 0 {
if manager, ok := gui.viewBufferManagerMap["main"]; ok {
manager.ReadLines(heightDiff)
}
if manager, ok := gui.viewBufferManagerMap["secondary"]; ok {
manager.ReadLines(heightDiff)
}
}
}
setViewFromDimensions := func(viewName string, boxName string, frame bool) (*gocui.View, error) {
2020-05-16 04:35:19 +02:00
dimensionsObj := viewDimensions[boxName]
frameOffset := 1
if frame {
frameOffset = 0
}
2020-05-16 04:35:19 +02:00
return g.SetView(
viewName,
dimensionsObj.X0-frameOffset,
dimensionsObj.Y0-frameOffset,
dimensionsObj.X1+frameOffset,
dimensionsObj.Y1+frameOffset,
2020-05-16 04:35:19 +02:00
0,
)
}
v, err := setViewFromDimensions("main", "main", true)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
v.Title = gui.Tr.SLocalize("DiffTitle")
v.Wrap = true
v.FgColor = textColor
v.IgnoreCarriageReturns = true
}
secondaryView, err := setViewFromDimensions("secondary", "secondary", true)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
secondaryView.Title = gui.Tr.SLocalize("DiffTitle")
secondaryView.Wrap = true
2020-05-16 04:35:19 +02:00
secondaryView.FgColor = textColor
2020-03-29 01:31:34 +02:00
secondaryView.IgnoreCarriageReturns = true
}
2020-05-16 04:35:19 +02:00
hiddenViewOffset := 9999
if v, err := setViewFromDimensions("status", "status", true); err != nil {
2020-03-29 01:31:34 +02:00
if err.Error() != "unknown view" {
return err
}
v.Title = gui.Tr.SLocalize("StatusTitle")
v.FgColor = textColor
}
filesView, err := setViewFromDimensions("files", "files", true)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
filesView.Highlight = true
filesView.Title = gui.Tr.SLocalize("FilesTitle")
filesView.ContainsList = true
}
branchesView, err := setViewFromDimensions("branches", "branches", true)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
branchesView.FgColor = textColor
branchesView.ContainsList = true
}
commitFilesView, err := setViewFromDimensions("commitFiles", "commits", true)
if err != nil {
2020-03-29 01:31:34 +02:00
if err.Error() != "unknown view" {
return err
}
commitFilesView.Title = gui.Tr.SLocalize("CommitFiles")
commitFilesView.FgColor = textColor
commitFilesView.ContainsList = true
2020-03-29 01:31:34 +02:00
}
commitsView, err := setViewFromDimensions("commits", "commits", true)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
commitsView.Title = gui.Tr.SLocalize("CommitsTitle")
commitsView.Tabs = []string{"Commits", "Reflog"}
commitsView.FgColor = textColor
commitsView.ContainsList = true
}
stashView, err := setViewFromDimensions("stash", "stash", true)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
stashView.Title = gui.Tr.SLocalize("StashTitle")
stashView.FgColor = textColor
stashView.ContainsList = true
}
if gui.getCommitMessageView() == nil {
// doesn't matter where this view starts because it will be hidden
if commitMessageView, err := g.SetView("commitMessage", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
_, _ = g.SetViewOnBottom("commitMessage")
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
commitMessageView.FgColor = textColor
commitMessageView.Editable = true
commitMessageView.Editor = gocui.EditorFunc(gui.commitMessageEditor)
}
}
if check, _ := g.View("credentials"); check == nil {
// doesn't matter where this view starts because it will be hidden
if credentialsView, err := g.SetView("credentials", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
_, err := g.SetViewOnBottom("credentials")
if err != nil {
return err
}
credentialsView.Title = gui.Tr.SLocalize("CredentialsUsername")
credentialsView.FgColor = textColor
credentialsView.Editable = true
}
}
if v, err := setViewFromDimensions("options", "options", false); err != nil {
if err.Error() != "unknown view" {
return err
}
v.Frame = false
v.FgColor = theme.OptionsColor
// doing this here because it'll only happen once
if err := gui.onInitialViewsCreation(); err != nil {
return err
}
2020-03-29 01:31:34 +02:00
}
// this view takes up one character. Its only purpose is to show the slash when searching
if searchPrefixView, err := setViewFromDimensions("searchPrefix", "searchPrefix", false); err != nil {
2020-03-29 01:31:34 +02:00
if err.Error() != "unknown view" {
return err
}
searchPrefixView.BgColor = gocui.ColorDefault
searchPrefixView.FgColor = gocui.ColorGreen
searchPrefixView.Frame = false
gui.setViewContent(searchPrefixView, SEARCH_PREFIX)
2020-03-29 01:31:34 +02:00
}
if searchView, err := setViewFromDimensions("search", "search", false); err != nil {
2020-03-29 01:31:34 +02:00
if err.Error() != "unknown view" {
return err
}
searchView.BgColor = gocui.ColorDefault
searchView.FgColor = gocui.ColorGreen
searchView.Frame = false
searchView.Editable = true
}
if appStatusView, err := setViewFromDimensions("appStatus", "appStatus", false); err != nil {
2020-03-29 01:31:34 +02:00
if err.Error() != "unknown view" {
return err
}
appStatusView.BgColor = gocui.ColorDefault
appStatusView.FgColor = gocui.ColorCyan
appStatusView.Frame = false
if _, err := g.SetViewOnBottom("appStatus"); err != nil {
return err
}
}
informationView, err := setViewFromDimensions("information", "information", false)
2020-03-29 01:31:34 +02:00
if err != nil {
if err.Error() != "unknown view" {
return err
}
informationView.BgColor = gocui.ColorDefault
informationView.FgColor = gocui.ColorGreen
informationView.Frame = false
2020-08-15 08:36:39 +02:00
gui.renderString("information", INFO_SECTION_PADDING+informationStr)
2020-03-29 01:31:34 +02:00
}
if gui.State.OldInformation != informationStr {
gui.setViewContent(informationView, informationStr)
gui.State.OldInformation = informationStr
2020-03-29 01:31:34 +02:00
}
if gui.g.CurrentView() == nil {
initialView := gui.getFilesView()
if gui.inFilterMode() {
initialView = gui.getCommitsView()
}
if _, err := gui.g.SetCurrentView(initialView.Name()); err != nil {
return err
}
2020-08-15 08:36:39 +02:00
if err := gui.switchFocus(nil, initialView); err != nil {
2020-03-29 01:31:34 +02:00
return err
}
}
type listViewState struct {
selectedLine int
lineCount int
view *gocui.View
context string
2020-08-16 01:18:57 +02:00
listView *listView
2020-03-29 01:31:34 +02:00
}
2020-08-16 01:18:57 +02:00
listViewStates := []listViewState{
{view: filesView, context: "", selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files), listView: gui.filesListView()},
{view: branchesView, context: "local-branches", selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches), listView: gui.branchesListView()},
{view: branchesView, context: "remotes", selectedLine: gui.State.Panels.Remotes.SelectedLine, lineCount: len(gui.State.Remotes), listView: gui.remotesListView()},
{view: branchesView, context: "remote-branches", selectedLine: gui.State.Panels.RemoteBranches.SelectedLine, lineCount: len(gui.State.Remotes), listView: gui.remoteBranchesListView()},
{view: branchesView, context: "tags", selectedLine: gui.State.Panels.Tags.SelectedLine, lineCount: len(gui.State.Tags), listView: gui.tagsListView()},
{view: commitsView, context: "branch-commits", selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits), listView: gui.branchCommitsListView()},
{view: commitsView, context: "reflog-commits", selectedLine: gui.State.Panels.ReflogCommits.SelectedLine, lineCount: len(gui.State.FilteredReflogCommits), listView: gui.reflogCommitsListView()},
{view: stashView, context: "", selectedLine: gui.State.Panels.Stash.SelectedLine, lineCount: len(gui.State.StashEntries), listView: gui.stashListView()},
{view: commitFilesView, context: "", selectedLine: gui.State.Panels.CommitFiles.SelectedLine, lineCount: len(gui.State.CommitFiles), listView: gui.commitFilesListView()},
2020-03-29 01:31:34 +02:00
}
// menu view might not exist so we check to be safe
if menuView, err := gui.g.View("menu"); err == nil {
2020-08-16 01:18:57 +02:00
listViewStates = append(listViewStates, listViewState{view: menuView, context: "", selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount, listView: gui.menuListView()})
2020-03-29 01:31:34 +02:00
}
2020-08-16 01:18:57 +02:00
for _, listViewState := range listViewStates {
2020-03-29 01:31:34 +02:00
// ignore views where the context doesn't match up with the selected line we're trying to focus
2020-08-16 01:18:57 +02:00
if listViewState.context != "" && (listViewState.view.Context != listViewState.context) {
2020-03-29 01:31:34 +02:00
continue
}
// check if the selected line is now out of view and if so refocus it
2020-08-16 01:18:57 +02:00
listViewState.view.FocusPoint(0, listViewState.selectedLine)
2020-08-16 01:18:57 +02:00
listViewState.view.SelBgColor = theme.GocuiSelectedLineBgColor
// I doubt this is expensive though it's admittedly redundant after the first render
listViewState.view.SetOnSelectItem(gui.onSelectItemWrapper(listViewState.listView.onSearchSelect))
2020-03-29 01:31:34 +02:00
}
mainViewWidth, mainViewHeight := gui.getMainView().Size()
if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight {
gui.State.PrevMainWidth = mainViewWidth
gui.State.PrevMainHeight = mainViewHeight
if err := gui.onResize(); err != nil {
return err
}
}
// here is a good place log some stuff
// if you download humanlog and do tail -f development.log | humanlog
// this will let you see these branches as prettified json
// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
2020-08-15 09:23:16 +02:00
return gui.resizeCurrentPopupPanel()
2020-03-29 01:31:34 +02:00
}
func (gui *Gui) onInitialViewsCreation() error {
gui.changeMainViewsContext("normal")
gui.getBranchesView().Context = "local-branches"
gui.getCommitsView().Context = "branch-commits"
if gui.showRecentRepos {
if err := gui.handleCreateRecentReposMenu(); err != nil {
return err
}
gui.showRecentRepos = false
}
2020-03-29 01:31:34 +02:00
return gui.loadNewRepo()
}
func max(a, b int) int {
if a > b {
return a
}
return b
}