You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
8.9 KiB
Go
376 lines
8.9 KiB
Go
package gui
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
|
"github.com/samber/lo"
|
|
"github.com/spkg/bom"
|
|
)
|
|
|
|
func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
|
|
sideViewNames := gui.sideViewNames()
|
|
var focusedViewName string
|
|
if v == nil || v.Name() == sideViewNames[len(sideViewNames)-1] {
|
|
focusedViewName = sideViewNames[0]
|
|
} else {
|
|
viewName := v.Name()
|
|
for i := range sideViewNames {
|
|
if viewName == sideViewNames[i] {
|
|
focusedViewName = sideViewNames[i+1]
|
|
break
|
|
}
|
|
if i == len(sideViewNames)-1 {
|
|
gui.Log.Info("not in list of views")
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
focusedView, err := g.View(focusedViewName)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
gui.resetMainView()
|
|
return gui.switchFocus(focusedView)
|
|
}
|
|
|
|
func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
|
|
sideViewNames := gui.sideViewNames()
|
|
var focusedViewName string
|
|
if v == nil || v.Name() == sideViewNames[0] {
|
|
focusedViewName = sideViewNames[len(sideViewNames)-1]
|
|
} else {
|
|
viewName := v.Name()
|
|
for i := range sideViewNames {
|
|
if viewName == sideViewNames[i] {
|
|
focusedViewName = sideViewNames[i-1]
|
|
break
|
|
}
|
|
if i == len(sideViewNames)-1 {
|
|
gui.Log.Info("not in list of views")
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
focusedView, err := g.View(focusedViewName)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
gui.resetMainView()
|
|
return gui.switchFocus(focusedView)
|
|
}
|
|
|
|
func (gui *Gui) resetMainView() {
|
|
gui.State.Panels.Main.ObjectKey = ""
|
|
gui.Views.Main.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
}
|
|
|
|
// if the cursor down past the last item, move it to the last line
|
|
// nolint:unparam
|
|
func (gui *Gui) focusPoint(selectedX int, selectedY int, lineCount int, v *gocui.View) {
|
|
if selectedY < 0 || selectedY > lineCount {
|
|
return
|
|
}
|
|
ox, oy := v.Origin()
|
|
originalOy := oy
|
|
cx, cy := v.Cursor()
|
|
originalCy := cy
|
|
_, height := v.Size()
|
|
|
|
ly := utils.Max(height-1, 0)
|
|
|
|
windowStart := oy
|
|
windowEnd := oy + ly
|
|
|
|
if selectedY < windowStart {
|
|
oy = utils.Max(oy-(windowStart-selectedY), 0)
|
|
} else if selectedY > windowEnd {
|
|
oy += (selectedY - windowEnd)
|
|
}
|
|
|
|
if windowEnd > lineCount-1 {
|
|
shiftAmount := (windowEnd - (lineCount - 1))
|
|
oy = utils.Max(oy-shiftAmount, 0)
|
|
}
|
|
|
|
if originalOy != oy {
|
|
_ = v.SetOrigin(ox, oy)
|
|
}
|
|
|
|
cy = selectedY - oy
|
|
if originalCy != cy {
|
|
_ = v.SetCursor(cx, selectedY-oy)
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) FocusY(selectedY int, lineCount int, v *gocui.View) {
|
|
gui.focusPoint(0, selectedY, lineCount, v)
|
|
}
|
|
|
|
func (gui *Gui) ResetOrigin(v *gocui.View) {
|
|
_ = v.SetOrigin(0, 0)
|
|
_ = v.SetCursor(0, 0)
|
|
}
|
|
|
|
func (gui *Gui) cleanString(s string) string {
|
|
output := string(bom.Clean([]byte(s)))
|
|
return utils.NormalizeLinefeeds(output)
|
|
}
|
|
|
|
func (gui *Gui) setViewContent(v *gocui.View, s string) error {
|
|
v.Clear()
|
|
fmt.Fprint(v, gui.cleanString(s))
|
|
return nil
|
|
}
|
|
|
|
// renderString resets the origin of a view and sets its content
|
|
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
|
g.Update(func(*gocui.Gui) error {
|
|
v, err := g.View(viewName)
|
|
if err != nil {
|
|
return nil // return gracefully if view has been deleted
|
|
}
|
|
if err := v.SetOrigin(0, 0); err != nil {
|
|
return err
|
|
}
|
|
if err := v.SetCursor(0, 0); err != nil {
|
|
return err
|
|
}
|
|
return gui.setViewContent(v, s)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) RenderStringMain(s string) {
|
|
_ = gui.renderString(gui.g, "main", s)
|
|
}
|
|
|
|
// reRenderString sets the main view's content, without changing its origin
|
|
func (gui *Gui) reRenderStringMain(s string) {
|
|
gui.reRenderString("main", s)
|
|
}
|
|
|
|
// reRenderString sets the view's content, without changing its origin
|
|
func (gui *Gui) reRenderString(viewName, s string) {
|
|
gui.g.Update(func(*gocui.Gui) error {
|
|
v, err := gui.g.View(viewName)
|
|
if err != nil {
|
|
return nil // return gracefully if view has been deleted
|
|
}
|
|
return gui.setViewContent(v, s)
|
|
})
|
|
}
|
|
|
|
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, ", ")
|
|
}
|
|
|
|
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
|
|
return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
|
|
}
|
|
|
|
func (gui *Gui) GetMainView() *gocui.View {
|
|
return gui.Views.Main
|
|
}
|
|
|
|
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
|
return strings.TrimSpace(v.Buffer())
|
|
}
|
|
|
|
func (gui *Gui) currentViewName() string {
|
|
currentView := gui.g.CurrentView()
|
|
// this can happen when the app is first starting up
|
|
if currentView == nil {
|
|
return gui.initiallyFocusedViewName()
|
|
}
|
|
return currentView.Name()
|
|
}
|
|
|
|
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
|
|
v := g.CurrentView()
|
|
if gui.isPopupPanel(v.Name()) {
|
|
return gui.resizePopupPanel(v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) resizePopupPanel(v *gocui.View) error {
|
|
// If the confirmation panel is already displayed, just resize the width,
|
|
// otherwise continue
|
|
content := v.Buffer()
|
|
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(v.Wrap, content)
|
|
vx0, vy0, vx1, vy1 := v.Dimensions()
|
|
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
|
|
return nil
|
|
}
|
|
_, err := gui.g.SetView(v.Name(), x0, y0, x1, y1, 0)
|
|
return err
|
|
}
|
|
|
|
func (gui *Gui) renderPanelOptions() error {
|
|
currentView := gui.g.CurrentView()
|
|
switch currentView.Name() {
|
|
case "menu":
|
|
return gui.renderMenuOptions()
|
|
case "confirmation":
|
|
return gui.renderConfirmationOptions()
|
|
}
|
|
return gui.renderGlobalOptions()
|
|
}
|
|
|
|
func (gui *Gui) isPopupPanel(viewName string) bool {
|
|
return lo.Contains(gui.popupViewNames(), viewName)
|
|
}
|
|
|
|
func (gui *Gui) popupPanelFocused() bool {
|
|
return gui.isPopupPanel(gui.currentViewName())
|
|
}
|
|
|
|
func (gui *Gui) clearMainView() {
|
|
mainView := gui.Views.Main
|
|
mainView.Clear()
|
|
_ = mainView.SetOrigin(0, 0)
|
|
_ = mainView.SetCursor(0, 0)
|
|
}
|
|
|
|
func (gui *Gui) HandleClick(v *gocui.View, itemCount int, selectedLine *int, handleSelect func() error) error {
|
|
wrappedHandleSelect := func(g *gocui.Gui, v *gocui.View) error {
|
|
return handleSelect()
|
|
}
|
|
return gui.handleClickAux(v, itemCount, selectedLine, wrappedHandleSelect)
|
|
}
|
|
|
|
func (gui *Gui) handleClickAux(v *gocui.View, itemCount int, selectedLine *int, handleSelect func(*gocui.Gui, *gocui.View) error) error {
|
|
if gui.popupPanelFocused() && v != nil && !gui.isPopupPanel(v.Name()) {
|
|
return nil
|
|
}
|
|
|
|
_, cy := v.Cursor()
|
|
_, oy := v.Origin()
|
|
|
|
newSelectedLine := cy + oy
|
|
|
|
if newSelectedLine < 0 {
|
|
newSelectedLine = 0
|
|
}
|
|
|
|
if newSelectedLine > itemCount-1 {
|
|
newSelectedLine = itemCount - 1
|
|
}
|
|
|
|
*selectedLine = newSelectedLine
|
|
|
|
if gui.currentViewName() != v.Name() {
|
|
if err := gui.switchFocus(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return handleSelect(gui.g, v)
|
|
}
|
|
|
|
func (gui *Gui) nextScreenMode() error {
|
|
if gui.currentViewName() == "main" {
|
|
gui.State.ScreenMode = prevIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
|
|
|
return nil
|
|
}
|
|
|
|
gui.State.ScreenMode = nextIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) prevScreenMode() error {
|
|
if gui.currentViewName() == "main" {
|
|
gui.State.ScreenMode = nextIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
|
|
|
return nil
|
|
}
|
|
|
|
gui.State.ScreenMode = prevIntInCycle([]WindowMaximisation{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
|
|
|
return nil
|
|
}
|
|
|
|
func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
|
|
for i, val := range sl {
|
|
if val == current {
|
|
if i == len(sl)-1 {
|
|
return sl[0]
|
|
}
|
|
return sl[i+1]
|
|
}
|
|
}
|
|
return sl[0]
|
|
}
|
|
|
|
func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
|
|
for i, val := range sl {
|
|
if val == current {
|
|
if i > 0 {
|
|
return sl[i-1]
|
|
}
|
|
return sl[len(sl)-1]
|
|
}
|
|
}
|
|
return sl[len(sl)-1]
|
|
}
|
|
|
|
func (gui *Gui) CurrentView() *gocui.View {
|
|
return gui.g.CurrentView()
|
|
}
|
|
|
|
func (gui *Gui) currentSidePanel() (panels.ISideListPanel, bool) {
|
|
viewName := gui.currentViewName()
|
|
|
|
for _, sidePanel := range gui.allSidePanels() {
|
|
if sidePanel.GetView().Name() == viewName {
|
|
return sidePanel, true
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// returns the current list panel. If no list panel is focused, returns false.
|
|
func (gui *Gui) currentListPanel() (panels.ISideListPanel, bool) {
|
|
viewName := gui.currentViewName()
|
|
|
|
for _, sidePanel := range gui.allListPanels() {
|
|
if sidePanel.GetView().Name() == viewName {
|
|
return sidePanel, true
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func (gui *Gui) allSidePanels() []panels.ISideListPanel {
|
|
return []panels.ISideListPanel{
|
|
gui.Panels.Projects,
|
|
gui.Panels.Services,
|
|
gui.Panels.Containers,
|
|
gui.Panels.Images,
|
|
gui.Panels.Volumes,
|
|
gui.Panels.Networks,
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) allListPanels() []panels.ISideListPanel {
|
|
return append(gui.allSidePanels(), gui.Panels.Menu)
|
|
}
|
|
|
|
func (gui *Gui) IsCurrentView(view *gocui.View) bool {
|
|
return view == gui.CurrentView()
|
|
}
|