1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-10 04:07:18 +02:00
lazygit/pkg/gui/layout.go

381 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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
return err
}
filesView.Highlight = true
2020-10-04 02:00:48 +02:00
filesView.Title = gui.Tr.FilesTitle
2020-10-09 15:13:04 +02:00
filesView.FgColor = textColor
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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
}
2021-04-03 06:56:11 +02:00
commitFilesView, err := setViewFromDimensions("commitFiles", gui.State.Contexts.CommitFiles.GetWindowName(), true)
if err != nil {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
return err
}
2020-10-04 02:00:48 +02:00
commitFilesView.Title = gui.Tr.CommitFiles
commitFilesView.FgColor = textColor
commitFilesView.ContainsList = true
_, _ = gui.g.SetViewOnBottom("commitFiles")
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
return err
}
v.Frame = false
v.FgColor = theme.OptionsColor
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-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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 {
2020-11-16 11:38:26 +02:00
if err.Error() != UNKNOWN_VIEW_ERROR_MSG {
2020-03-29 01:31:34 +02:00
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
}
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.ViewName)
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-03 02:32:14 +02:00
if view.Context != listContext.GetKey() {
2020-03-29 01:31:34 +02:00
continue
}
2021-04-03 02:32:14 +02:00
2020-03-29 01:31:34 +02:00
// check if the selected line is now out of view and if so refocus it
2021-04-03 02:32:14 +02:00
view.FocusPoint(0, listContext.GetPanelState().GetSelectedLineIdx())
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
}
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
}
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() {
_, _ = gui.g.SetViewOnBottom(viewName)
}
2021-04-03 06:56:11 +02:00
// the status panel is not actually a list context at the moment, so it is excluded
// here. Arguably that's quite convenient because it means we're back to starting
// in the files panel when landing in a new repo, but when returning from a submodule
// we'll be back in the submodules context. This still seems awkward though, and it's
// definitely going to break when (if) we make the status context a list context
initialContext := gui.currentSideContext()
if initialContext == nil {
if gui.State.Modes.Filtering.Active() {
initialContext = gui.State.Contexts.BranchCommits
} else {
initialContext = gui.State.Contexts.Files
}
}
if err := gui.pushContext(initialContext); err != nil {
return err
}
return gui.loadNewRepo()
}
func (gui *Gui) onInitialViewsCreation() error {
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
}
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
}