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

376 lines
11 KiB
Go
Raw Normal View History

2020-03-29 01:31:34 +02:00
package gui
import (
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/theme"
)
const SEARCH_PREFIX = "search: "
const INFO_SECTION_PADDING = " "
2020-05-18 14:00:07 +02:00
func (gui *Gui) informationStr() string {
for _, mode := range gui.modeStatuses() {
if mode.isActive() {
return mode.description()
}
}
if gui.g.Mouse {
2020-10-04 02:00:48 +02:00
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.Donate)
2020-05-18 14:00:07 +02:00
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
}
2020-10-04 02:00:48 +02:00
v.Title = gui.Tr.NotEnoughSpace
2020-03-29 01:31:34 +02:00
v.Wrap = true
_, _ = g.SetViewOnTop("limit")
}
return nil
}
informationStr := gui.informationStr()
2020-03-29 01:31:34 +02:00
appStatus := gui.statusManager.getStatusString()
2020-08-21 11:53:45 +02:00
viewDimensions := gui.getWindowDimensions(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, windowName string, frame bool) (*gocui.View, error) {
dimensionsObj, ok := viewDimensions[windowName]
if !ok {
// view not specified in dimensions object: so create the view and hide it
2020-08-23 03:53:05 +02:00
// making the view take up the whole space in the background in case it needs
// to render content as soon as it appears, because lazyloaded content (via a pty task)
// cares about the size of the view.
view, err := g.SetView(viewName, 0, 0, width, height, 0)
if err != nil {
return view, err
}
return g.SetViewOnBottom(viewName)
}
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
}
2020-10-04 02:00:48 +02:00
v.Title = gui.Tr.DiffTitle
2020-03-29 01:31:34 +02:00
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
}
2020-10-04 02:00:48 +02:00
secondaryView.Title = gui.Tr.DiffTitle
2020-03-29 01:31:34 +02:00
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
}
2020-10-04 02:00:48 +02:00
v.Title = gui.Tr.StatusTitle
2020-03-29 01:31:34 +02:00
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
2020-10-04 02:00:48 +02:00
filesView.Title = gui.Tr.FilesTitle
2020-03-29 01:31:34 +02:00
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
}
2020-10-04 02:00:48 +02:00
branchesView.Title = gui.Tr.BranchesTitle
2020-03-29 01:31:34 +02:00
branchesView.FgColor = textColor
branchesView.ContainsList = true
}
2020-08-21 11:53:45 +02:00
commitFilesView, err := setViewFromDimensions("commitFiles", gui.Contexts.CommitFiles.Context.GetWindowName(), true)
if err != nil {
2020-03-29 01:31:34 +02:00
if err.Error() != "unknown view" {
return err
}
2020-10-04 02:00:48 +02:00
commitFilesView.Title = gui.Tr.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
}
2020-10-04 02:00:48 +02:00
commitsView.Title = gui.Tr.CommitsTitle
2020-03-29 01:31:34 +02:00
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
}
2020-10-04 02:00:48 +02:00
stashView.Title = gui.Tr.StashTitle
2020-03-29 01:31:34 +02:00
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")
2020-10-04 02:00:48 +02:00
commitMessageView.Title = gui.Tr.CommitMessage
2020-03-29 01:31:34 +02:00
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
}
_, _ = g.SetViewOnBottom("credentials")
2020-10-04 02:00:48 +02:00
credentialsView.Title = gui.Tr.CredentialsUsername
2020-03-29 01:31:34 +02:00
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
_, _ = g.SetViewOnBottom("appStatus")
2020-03-29 01:31:34 +02:00
}
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 {
2020-08-16 05:58:29 +02:00
initialContext := gui.Contexts.Files.Context
if gui.State.Modes.Filtering.Active() {
2020-08-16 05:58:29 +02:00
initialContext = gui.Contexts.BranchCommits.Context
2020-03-29 01:31:34 +02:00
}
2020-08-16 05:58:29 +02:00
if err := gui.switchContext(initialContext); err != nil {
2020-03-29 01:31:34 +02:00
return err
}
}
2020-08-17 13:58:30 +02:00
type listContextState struct {
2020-08-20 00:24:35 +02:00
view *gocui.View
listContext *ListContext
2020-03-29 01:31:34 +02:00
}
2020-09-30 00:27:23 +02:00
// TODO: don't we already have the view included in the context object itself? Or might that change in a way we don't want reflected here?
2020-08-17 13:58:30 +02:00
listContextStates := []listContextState{
2020-08-20 00:24:35 +02:00
{view: filesView, listContext: gui.filesListContext()},
2020-09-30 00:27:23 +02:00
{view: filesView, listContext: gui.submodulesListContext()},
2020-08-20 00:24:35 +02:00
{view: branchesView, listContext: gui.branchesListContext()},
{view: branchesView, listContext: gui.remotesListContext()},
{view: branchesView, listContext: gui.remoteBranchesListContext()},
{view: branchesView, listContext: gui.tagsListContext()},
{view: commitsView, listContext: gui.branchCommitsListContext()},
{view: commitsView, listContext: gui.reflogCommitsListContext()},
{view: stashView, listContext: gui.stashListContext()},
{view: commitFilesView, listContext: gui.commitFilesListContext()},
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-20 00:24:35 +02:00
listContextStates = append(listContextStates, listContextState{view: menuView, listContext: gui.menuListContext()})
2020-03-29 01:31:34 +02:00
}
2020-08-17 13:58:30 +02:00
for _, listContextState := range listContextStates {
// ignore contexts whose view is owned by another context right now
2020-08-20 00:24:35 +02:00
if listContextState.view.Context != listContextState.listContext.GetKey() {
2020-03-29 01:31:34 +02:00
continue
}
// check if the selected line is now out of view and if so refocus it
listContextState.view.FocusPoint(0, listContextState.listContext.GetPanelState().GetSelectedLineIdx())
2020-08-17 13:58:30 +02:00
listContextState.view.SelBgColor = theme.GocuiSelectedLineBgColor
2020-08-16 01:18:57 +02:00
// I doubt this is expensive though it's admittedly redundant after the first render
2020-08-17 13:58:30 +02:00
listContextState.view.SetOnSelectItem(gui.onSelectItemWrapper(listContextState.listContext.onSearchSelect))
2020-03-29 01:31:34 +02:00
}
gui.getMainView().SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo))
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 run `lazygit --logs`
2020-03-29 01:31:34 +02:00
// 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.setInitialViewContexts()
2020-08-16 05:58:29 +02:00
2020-09-30 00:27:12 +02:00
// add tabs to views
gui.g.Mutexes.ViewsMutex.Lock()
2020-09-30 00:27:12 +02:00
for _, view := range gui.g.Views() {
tabs := gui.viewTabNames(view.Name())
if len(tabs) == 0 {
continue
}
view.Tabs = tabs
}
gui.g.Mutexes.ViewsMutex.Unlock()
2020-09-30 00:27:12 +02:00
2020-08-22 00:49:02 +02:00
if err := gui.switchContext(gui.defaultSideContext()); err != nil {
2020-08-17 13:58:30 +02:00
return err
}
2020-08-16 05:58:29 +02:00
if err := gui.keybindings(); err != nil {
return err
}
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
}