1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-24 05:36:19 +02:00
lazygit/pkg/gui/view_helpers.go

342 lines
9.0 KiB
Go
Raw Normal View History

2018-08-14 11:05:26 +02:00
package gui
2018-05-26 13:23:39 +10:00
import (
"fmt"
"sort"
"strings"
2020-03-26 23:48:11 +11:00
"sync"
2018-05-26 13:23:39 +10:00
"github.com/jesseduffield/gocui"
2022-01-28 20:44:36 +11:00
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom"
2018-05-26 13:23:39 +10:00
)
2020-08-16 13:58:29 +10:00
func (gui *Gui) getCyclableWindows() []string {
2020-05-17 21:54:51 +10:00
return []string{"status", "files", "branches", "commits", "stash"}
}
2018-06-06 12:17:49 +10:00
2022-01-28 20:44:36 +11:00
func getScopeNames(scopes []types.RefreshableView) []string {
scopeNameMap := map[types.RefreshableView]string{
types.COMMITS: "commits",
types.BRANCHES: "branches",
types.FILES: "files",
types.SUBMODULES: "submodules",
types.STASH: "stash",
types.REFLOG: "reflog",
types.TAGS: "tags",
types.REMOTES: "remotes",
types.STATUS: "status",
types.BISECT_INFO: "bisect",
}
scopeNames := make([]string, len(scopes))
for i, scope := range scopes {
scopeNames[i] = scopeNameMap[scope]
}
return scopeNames
}
2022-01-28 20:44:36 +11:00
func getModeName(mode types.RefreshMode) string {
switch mode {
2022-01-28 20:44:36 +11:00
case types.SYNC:
return "sync"
2022-01-28 20:44:36 +11:00
case types.ASYNC:
return "async"
2022-01-28 20:44:36 +11:00
case types.BLOCK_UI:
return "block-ui"
default:
return "unknown mode"
}
}
2022-01-28 20:44:36 +11:00
func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool {
output := map[types.RefreshableView]bool{}
for _, el := range arr {
output[el] = true
}
return output
}
func (gui *Gui) Refresh(options types.RefreshOptions) error {
2022-01-28 20:44:36 +11:00
if options.Scope == nil {
gui.c.Log.Infof(
"refreshing all scopes in %s mode",
2022-01-28 20:44:36 +11:00
getModeName(options.Mode),
)
} else {
gui.c.Log.Infof(
"refreshing the following scopes in %s mode: %s",
2022-01-28 20:44:36 +11:00
getModeName(options.Mode),
strings.Join(getScopeNames(options.Scope), ","),
)
}
2020-03-26 23:48:11 +11:00
wg := sync.WaitGroup{}
f := func() {
2022-01-28 20:44:36 +11:00
var scopeMap map[types.RefreshableView]bool
if len(options.Scope) == 0 {
scopeMap = arrToMap([]types.RefreshableView{
types.COMMITS,
types.BRANCHES,
types.FILES,
types.STASH,
types.REFLOG,
types.TAGS,
types.REMOTES,
types.STATUS,
types.BISECT_INFO,
})
} else {
2022-01-28 20:44:36 +11:00
scopeMap = arrToMap(options.Scope)
}
refresh := func(f func()) {
wg.Add(1)
func() {
2022-01-28 20:44:36 +11:00
if options.Mode == types.ASYNC {
go utils.Safe(f)
} else {
f()
}
wg.Done()
}()
}
2020-03-26 23:48:11 +11:00
if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] {
refresh(gui.refreshCommits)
} else if scopeMap[types.REBASE_COMMITS] {
// the above block handles rebase commits so we only need to call this one
// if we've asked specifically for rebase commits and not those other things
refresh(func() { _ = gui.refreshRebaseCommits() })
}
2022-01-28 20:44:36 +11:00
if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] {
refresh(func() { _ = gui.refreshFilesAndSubmodules() })
}
2022-01-28 20:44:36 +11:00
if scopeMap[types.STASH] {
refresh(func() { _ = gui.refreshStashEntries() })
}
2020-03-26 23:48:11 +11:00
2022-01-28 20:44:36 +11:00
if scopeMap[types.TAGS] {
refresh(func() { _ = gui.refreshTags() })
}
2022-01-28 20:44:36 +11:00
if scopeMap[types.REMOTES] {
refresh(func() { _ = gui.refreshRemotes() })
}
wg.Wait()
2020-03-26 23:48:11 +11:00
gui.refreshStatus()
2022-01-28 20:44:36 +11:00
if options.Then != nil {
options.Then()
}
}
2022-01-28 20:44:36 +11:00
if options.Mode == types.BLOCK_UI {
2022-01-15 12:04:00 +11:00
gui.OnUIThread(func() error {
f()
return nil
})
} else {
f()
}
2020-03-26 23:48:11 +11:00
return nil
2018-06-06 12:17:49 +10:00
}
2018-08-14 11:05:26 +02:00
func (gui *Gui) resetOrigin(v *gocui.View) error {
2019-04-25 21:37:19 +02:00
_ = v.SetCursor(0, 0)
return v.SetOrigin(0, 0)
2018-06-09 19:06:33 +10:00
}
func (gui *Gui) cleanString(s string) string {
output := string(bom.Clean([]byte(s)))
return utils.NormalizeLinefeeds(output)
}
2021-04-11 10:05:19 +10:00
func (gui *Gui) setViewContent(v *gocui.View, s string) {
2022-01-15 12:04:00 +11:00
v.SetContent(gui.cleanString(s))
2021-04-11 10:05:19 +10:00
}
// renderString resets the origin of a view and sets its content
2022-01-15 12:04:00 +11:00
func (gui *Gui) renderString(view *gocui.View, s string) error {
2021-04-05 00:31:52 +10:00
if err := view.SetOrigin(0, 0); err != nil {
return err
}
2021-04-05 00:31:52 +10:00
if err := view.SetCursor(0, 0); err != nil {
return err
}
2022-01-15 12:04:00 +11:00
gui.setViewContent(view, s)
return nil
}
2018-08-14 11:05:26 +02:00
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, ", ")
2018-06-09 19:06:33 +10:00
}
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
2022-01-15 12:04:00 +11:00
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
2018-06-09 19:06:33 +10:00
}
2019-02-25 22:11:35 +11:00
func (gui *Gui) currentViewName() string {
currentView := gui.g.CurrentView()
2020-05-16 12:35:19 +10:00
if currentView == nil {
return ""
}
2018-08-14 11:05:26 +02:00
return currentView.Name()
}
2018-09-05 19:07:46 +10:00
2020-08-15 17:23:16 +10:00
func (gui *Gui) resizeCurrentPopupPanel() error {
v := gui.g.CurrentView()
2020-08-18 22:41:14 +10:00
if v == nil {
return nil
}
if gui.isPopupPanel(v.Name()) {
2021-10-17 13:00:44 +11:00
return gui.resizePopupPanel(v, v.Buffer())
2018-09-05 19:07:46 +10:00
}
return nil
}
2021-10-17 13:00:44 +11:00
func (gui *Gui) resizePopupPanel(v *gocui.View, content string) error {
2018-09-05 19:07:46 +10:00
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
2020-08-15 17:23:16 +10:00
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(v.Wrap, content)
2018-09-05 19:07:46 +10:00
vx0, vy0, vx1, vy1 := v.Dimensions()
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
return nil
}
2020-08-15 17:23:16 +10:00
_, err := gui.g.SetView(v.Name(), x0, y0, x1, y1, 0)
2018-09-05 19:07:46 +10:00
return err
}
func (gui *Gui) changeSelectedLine(panelState types.IListPanelState, total int, change int) {
2019-11-16 14:00:27 +11:00
// TODO: find out why we're doing this
line := panelState.GetSelectedLineIdx()
if line == -1 {
2019-11-16 14:00:27 +11:00
return
}
var newLine int
if line+change < 0 {
newLine = 0
} else if line+change >= total {
newLine = total - 1
} else {
newLine = line + change
}
panelState.SetSelectedLineIdx(newLine)
}
func (gui *Gui) refreshSelectedLine(panelState types.IListPanelState, total int) {
line := panelState.GetSelectedLineIdx()
if line == -1 && total > 0 {
panelState.SetSelectedLineIdx(0)
} else if total-1 < line {
panelState.SetSelectedLineIdx(total - 1)
}
}
2018-12-07 18:52:31 +11:00
2020-02-25 20:11:07 +11:00
func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) {
list := utils.RenderDisplayStrings(displayStrings)
v.SetContent(list)
2020-02-25 20:11:07 +11:00
}
2021-11-02 16:39:15 +11:00
func (gui *Gui) renderDisplayStringsAtPos(v *gocui.View, y int, displayStrings [][]string) {
list := utils.RenderDisplayStrings(displayStrings)
v.OverwriteLines(y, list)
}
func (gui *Gui) globalOptionsMap() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
2020-10-03 14:54:55 +10:00
return map[string]string{
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock), gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
}
2020-03-29 10:31:34 +11:00
}
func (gui *Gui) isPopupPanel(viewName string) bool {
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
}
2019-02-25 22:11:35 +11:00
func (gui *Gui) popupPanelFocused() bool {
return gui.isPopupPanel(gui.currentViewName())
2019-02-25 22:11:35 +11:00
}
2019-11-10 16:20:35 +11:00
2020-05-18 22:00:07 +10:00
// secondaryViewFocused tells us whether it appears that the secondary view is focused. The view is actually never focused for real: we just swap the main and secondary views and then you're still focused on the main view so that we can give you access to all its keybindings for free. I will probably regret this design decision soon enough.
func (gui *Gui) secondaryViewFocused() bool {
2020-10-08 08:01:04 +11:00
state := gui.State.Panels.LineByLine
return state != nil && state.SecondaryFocused
2020-05-18 22:00:07 +10:00
}
2020-08-17 21:58:30 +10:00
2020-08-19 19:31:58 +10:00
func (gui *Gui) onViewTabClick(viewName string, tabIndex int) error {
context := gui.State.ViewTabContextMap[viewName][tabIndex].Contexts[0]
2020-08-19 19:31:58 +10:00
return gui.c.PushContext(context)
2020-08-19 19:31:58 +10:00
}
func (gui *Gui) handleNextTab() error {
2021-06-06 12:41:55 +02:00
v := getTabbedView(gui)
2021-04-02 20:51:52 +11:00
if v == nil {
return nil
}
2020-08-19 19:31:58 +10:00
return gui.onViewTabClick(
v.Name(),
utils.ModuloWithWrap(v.TabIndex+1, len(v.Tabs)),
)
}
func (gui *Gui) handlePrevTab() error {
2021-06-06 12:41:55 +02:00
v := getTabbedView(gui)
2021-04-02 20:51:52 +11:00
if v == nil {
return nil
}
2020-08-19 19:31:58 +10:00
return gui.onViewTabClick(
v.Name(),
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
)
}
// this is the distance we will move the cursor when paging up or down in a view
func (gui *Gui) pageDelta(view *gocui.View) int {
_, height := view.Size()
delta := height - 1
if delta == 0 {
return 1
}
return delta
}
2021-06-06 12:41:55 +02:00
func getTabbedView(gui *Gui) *gocui.View {
2021-06-15 19:58:43 +02:00
// It safe assumption that only static contexts have tabs
context := gui.currentStaticContext()
view, _ := gui.g.View(context.GetViewName())
return view
2021-06-06 12:41:55 +02:00
}
func (gui *Gui) render() {
2022-01-15 12:04:00 +11:00
gui.OnUIThread(func() error { return nil })
}