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

399 lines
12 KiB
Go
Raw Normal View History

2020-03-29 01:31:34 +02:00
package gui
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/theme"
)
const SEARCH_PREFIX = "search: "
2020-05-18 14:00:07 +02:00
2021-04-08 15:55:18 +02:00
func (gui *Gui) createAllViews() error {
viewNameMappings := []struct {
viewPtr **gocui.View
name string
}{
{viewPtr: &gui.Views.Status, name: "status"},
{viewPtr: &gui.Views.Files, name: "files"},
{viewPtr: &gui.Views.Branches, name: "branches"},
{viewPtr: &gui.Views.Commits, name: "commits"},
{viewPtr: &gui.Views.Stash, name: "stash"},
{viewPtr: &gui.Views.CommitFiles, name: "commitFiles"},
{viewPtr: &gui.Views.Main, name: "main"},
{viewPtr: &gui.Views.Secondary, name: "secondary"},
{viewPtr: &gui.Views.Options, name: "options"},
{viewPtr: &gui.Views.AppStatus, name: "appStatus"},
{viewPtr: &gui.Views.Information, name: "information"},
{viewPtr: &gui.Views.Search, name: "search"},
{viewPtr: &gui.Views.SearchPrefix, name: "searchPrefix"},
{viewPtr: &gui.Views.CommitMessage, name: "commitMessage"},
{viewPtr: &gui.Views.Credentials, name: "credentials"},
{viewPtr: &gui.Views.Menu, name: "menu"},
{viewPtr: &gui.Views.Suggestions, name: "suggestions"},
{viewPtr: &gui.Views.Confirmation, name: "confirmation"},
{viewPtr: &gui.Views.Limit, name: "limit"},
2021-04-11 03:43:07 +02:00
{viewPtr: &gui.Views.Extras, name: "extras"},
2021-04-08 15:55:18 +02:00
}
var err error
for _, mapping := range viewNameMappings {
*mapping.viewPtr, err = gui.prepareView(mapping.name)
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
return err
}
}
gui.Views.Options.Frame = false
gui.Views.Options.FgColor = theme.OptionsColor
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
gui.Views.SearchPrefix.Frame = false
2021-04-11 02:05:19 +02:00
gui.setViewContentSync(gui.Views.SearchPrefix, SEARCH_PREFIX)
2021-04-08 15:55:18 +02:00
gui.Views.Stash.Title = gui.Tr.StashTitle
gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor
gui.Views.Commits.Title = gui.Tr.CommitsTitle
gui.Views.Commits.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitFiles.Title = gui.Tr.CommitFiles
gui.Views.CommitFiles.FgColor = theme.GocuiDefaultTextColor
gui.Views.Branches.Title = gui.Tr.BranchesTitle
gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor
gui.Views.Files.Highlight = true
gui.Views.Files.Title = gui.Tr.FilesTitle
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
gui.Views.Secondary.Title = gui.Tr.DiffTitle
gui.Views.Secondary.Wrap = true
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor
gui.Views.Secondary.IgnoreCarriageReturns = true
gui.Views.Main.Title = gui.Tr.DiffTitle
gui.Views.Main.Wrap = true
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor
gui.Views.Main.IgnoreCarriageReturns = true
gui.Views.Limit.Title = gui.Tr.NotEnoughSpace
gui.Views.Limit.Wrap = true
gui.Views.Status.Title = gui.Tr.StatusTitle
gui.Views.Status.FgColor = theme.GocuiDefaultTextColor
gui.Views.Search.BgColor = gocui.ColorDefault
gui.Views.Search.FgColor = gocui.ColorGreen
gui.Views.Search.Frame = false
gui.Views.Search.Editable = true
gui.Views.AppStatus.BgColor = gocui.ColorDefault
gui.Views.AppStatus.FgColor = gocui.ColorCyan
gui.Views.AppStatus.Frame = false
gui.Views.AppStatus.Visible = false
gui.Views.CommitMessage.Visible = false
gui.Views.CommitMessage.Title = gui.Tr.CommitMessage
gui.Views.CommitMessage.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitMessage.Editable = true
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
gui.Views.Confirmation.Visible = false
gui.Views.Credentials.Visible = false
gui.Views.Credentials.Title = gui.Tr.CredentialsUsername
gui.Views.Credentials.FgColor = theme.GocuiDefaultTextColor
gui.Views.Credentials.Editable = true
gui.Views.Suggestions.Visible = false
gui.Views.Menu.Visible = false
gui.Views.Information.BgColor = gocui.ColorDefault
gui.Views.Information.FgColor = gocui.ColorGreen
gui.Views.Information.Frame = false
2021-04-11 03:43:07 +02:00
gui.Views.Extras.Title = gui.Tr.CommandLog
gui.Views.Extras.FgColor = theme.GocuiDefaultTextColor
gui.Views.Extras.Autoscroll = true
2021-04-11 15:32:20 +02:00
gui.Views.Extras.Wrap = true
2021-04-11 13:42:18 +02:00
gui.printCommandLogHeader()
2021-04-10 05:08:51 +02:00
2021-04-08 15:55:18 +02:00
if _, err := gui.g.SetCurrentView(gui.defaultSideContext().GetViewName()); err != nil {
return err
}
gui.GitCommand.GetCmdWriter = gui.getCmdWriter
2021-04-08 15:55:18 +02:00
return nil
}
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 {
2021-04-08 15:55:18 +02:00
if !gui.ViewsSetup {
if err := gui.createAllViews(); err != nil {
return err
}
}
2020-03-29 01:31:34 +02:00
g.Highlight = true
width, height := g.Size()
minimumHeight := 9
minimumWidth := 10
2021-04-04 15:51:59 +02:00
var err error
2021-04-08 15:55:18 +02:00
_, err = g.SetView("limit", 0, 0, width-1, height-1, 0)
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
return err
2020-03-29 01:31:34 +02:00
}
2021-04-04 15:51:59 +02:00
gui.Views.Limit.Visible = height < minimumHeight || width < minimumWidth
2020-03-29 01:31:34 +02:00
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
// reading more lines into main view buffers upon resize
2021-04-04 16:31:52 +02:00
prevMainView := gui.Views.Main
if prevMainView != nil {
2020-03-29 01:31:34 +02:00
_, 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)
2021-04-04 15:51:59 +02:00
if view != nil {
view.Visible = false
}
2021-04-04 15:51:59 +02:00
return view, err
}
frameOffset := 1
if frame {
frameOffset = 0
}
2021-04-04 15:51:59 +02:00
view, err := g.SetView(
2020-05-16 04:35:19 +02:00
viewName,
dimensionsObj.X0-frameOffset,
dimensionsObj.Y0-frameOffset,
dimensionsObj.X1+frameOffset,
dimensionsObj.Y1+frameOffset,
2020-05-16 04:35:19 +02:00
0,
)
2021-04-04 15:51:59 +02:00
if view != nil {
view.Visible = true
}
return view, err
2020-05-16 04:35:19 +02:00
}
2021-04-08 15:55:18 +02:00
args := []struct {
viewName string
windowName string
frame bool
}{
{viewName: "main", windowName: "main", frame: true},
{viewName: "secondary", windowName: "secondary", frame: true},
{viewName: "status", windowName: "status", frame: true},
{viewName: "files", windowName: "files", frame: true},
{viewName: "branches", windowName: "branches", frame: true},
{viewName: "commitFiles", windowName: gui.State.Contexts.CommitFiles.GetWindowName(), frame: true},
{viewName: "commits", windowName: "commits", frame: true},
{viewName: "stash", windowName: "stash", frame: true},
{viewName: "options", windowName: "options", frame: false},
{viewName: "searchPrefix", windowName: "searchPrefix", frame: false},
{viewName: "search", windowName: "search", frame: false},
{viewName: "appStatus", windowName: "appStatus", frame: false},
{viewName: "information", windowName: "information", frame: false},
2021-04-11 03:43:07 +02:00
{viewName: "extras", windowName: "extras", frame: true},
2021-04-08 15:55:18 +02:00
}
for _, arg := range args {
_, err = setViewFromDimensions(arg.viewName, arg.windowName, arg.frame)
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
return err
}
}
2021-04-04 15:51:59 +02:00
// if the commit files view is the view to be displayed for its window, we'll display it
gui.Views.CommitFiles.Visible = gui.getViewNameForWindow(gui.State.Contexts.CommitFiles.GetWindowName()) == "commitFiles"
2020-03-29 01:31:34 +02:00
if gui.State.OldInformation != informationStr {
2021-04-11 02:05:19 +02:00
gui.setViewContentSync(gui.Views.Information, informationStr)
gui.State.OldInformation = informationStr
2020-03-29 01:31:34 +02:00
}
2021-04-03 06:56:11 +02:00
if !gui.ViewsSetup {
if err := gui.onInitialViewsCreation(); err != nil {
return err
}
2021-04-03 06:56:11 +02:00
gui.ViewsSetup = true
}
2021-04-03 06:56:11 +02:00
if !gui.State.ViewsSetup {
if err := gui.onInitialViewsCreationForRepo(); err != nil {
2020-03-29 01:31:34 +02:00
return err
}
2021-04-03 06:56:11 +02:00
gui.State.ViewsSetup = true
2020-03-29 01:31:34 +02:00
}
2021-04-03 02:32:14 +02:00
for _, listContext := range gui.getListContexts() {
view, err := gui.g.View(listContext.GetViewName())
2021-04-03 02:32:14 +02:00
if err != nil {
continue
}
2020-03-29 01:31:34 +02:00
2020-08-17 13:58:30 +02:00
// ignore contexts whose view is owned by another context right now
2021-04-04 15:51:59 +02:00
if ContextKey(view.Context) != listContext.GetKey() {
2020-03-29 01:31:34 +02:00
continue
}
2021-04-03 02:32:14 +02:00
listContext.FocusLine()
2021-04-03 02:32:14 +02:00
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
2021-04-03 02:32:14 +02:00
view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.onSearchSelect))
2020-03-29 01:31:34 +02:00
}
2021-04-04 15:51:59 +02:00
gui.Views.Main.SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo))
2021-04-04 15:51:59 +02:00
mainViewWidth, mainViewHeight := gui.Views.Main.Size()
2020-03-29 01:31:34 +02:00
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
}
2021-04-08 15:55:18 +02:00
func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {
2021-04-04 15:51:59 +02:00
// arbitrarily giving the view enough size so that we don't get an error, but
// it's expected that the view will be given the correct size before being shown
return gui.g.SetView(viewName, 0, 0, 10, 10, 0)
}
2021-04-03 06:56:11 +02:00
func (gui *Gui) onInitialViewsCreationForRepo() error {
gui.setInitialViewContexts()
2020-08-16 05:58:29 +02:00
// hide any popup views. This only applies when we've just switched repos
for _, viewName := range gui.popupViewNames() {
2021-04-04 15:51:59 +02:00
view, err := gui.g.View(viewName)
if err == nil {
view.Visible = false
2021-04-03 06:56:11 +02:00
}
}
2021-04-04 15:51:59 +02:00
initialContext := gui.currentSideContext()
2021-04-03 06:56:11 +02:00
if err := gui.pushContext(initialContext); err != nil {
return err
}
return gui.loadNewRepo()
}
func (gui *Gui) onInitialViewsCreation() error {
2021-04-04 15:51:59 +02:00
// now we order the views (in order of bottom first)
layerOneViews := []*gocui.View{
// first layer. Ordering within this layer does not matter because there are
// no overlapping views
gui.Views.Status,
gui.Views.Files,
gui.Views.Branches,
gui.Views.Commits,
gui.Views.Stash,
gui.Views.CommitFiles,
gui.Views.Main,
gui.Views.Secondary,
2021-04-11 03:43:07 +02:00
gui.Views.Extras,
2021-04-04 15:51:59 +02:00
// bottom line
gui.Views.Options,
gui.Views.AppStatus,
gui.Views.Information,
gui.Views.Search,
2021-04-08 15:55:18 +02:00
gui.Views.SearchPrefix, // this view takes up one character. Its only purpose is to show the slash when searching
2021-04-04 15:51:59 +02:00
// popups. Ordering within this layer does not matter because there should
// only be one popup shown at a time
gui.Views.CommitMessage,
gui.Views.Menu,
gui.Views.Suggestions,
gui.Views.Confirmation,
2021-04-08 16:33:39 +02:00
gui.Views.Credentials,
2021-04-04 15:51:59 +02:00
// this guy will cover everything else when it appears
gui.Views.Limit,
}
for _, view := range layerOneViews {
if _, err := gui.g.SetViewOnTop(view.Name()); err != nil {
return err
}
}
gui.g.Mutexes.ViewsMutex.Lock()
// add tabs to views
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
if err := gui.keybindings(); err != nil {
return err
}
2021-12-29 02:50:20 +02:00
if !gui.UserConfig.DisableStartupPopups {
2021-04-07 14:43:19 +02:00
popupTasks := []func(chan struct{}) error{}
storedPopupVersion := gui.Config.GetAppState().StartupPopupVersion
if storedPopupVersion < StartupPopupVersion {
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
}
gui.showInitialPopups(popupTasks)
}
if gui.showRecentRepos {
if err := gui.handleCreateRecentReposMenu(); err != nil {
return err
}
gui.showRecentRepos = false
}
2021-04-03 06:56:11 +02:00
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
gui.waitForIntro.Done()
return nil
2020-03-29 01:31:34 +02:00
}