more refactoring of list panels

This commit is contained in:
Jesse Duffield 2022-10-29 09:58:54 -07:00
parent 5d498c796a
commit fc592b5806
18 changed files with 239 additions and 221 deletions

View file

@ -2,6 +2,7 @@ package gui
import ( import (
"github.com/jesseduffield/lazycore/pkg/boxlayout" "github.com/jesseduffield/lazycore/pkg/boxlayout"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/samber/lo" "github.com/samber/lo"
@ -129,12 +130,12 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
} }
func (gui *Gui) sideViewNames() []string { func (gui *Gui) sideViewNames() []string {
visibleSidePanels := lo.Filter(gui.allSidePanels(), func(panel ISideListPanel, _ int) bool { visibleSidePanels := lo.Filter(gui.allSidePanels(), func(panel panels.ISideListPanel, _ int) bool {
return !panel.IsHidden() return !panel.IsHidden()
}) })
return lo.Map(visibleSidePanels, func(panel ISideListPanel, _ int) string { return lo.Map(visibleSidePanels, func(panel panels.ISideListPanel, _ int) string {
return panel.View().Name() return panel.GetView().Name()
}) })
} }

View file

@ -11,11 +11,12 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] { func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container] {
// Standalone containers are containers which are either one-off containers, or whose service is not part of this docker-compose context. // Standalone containers are containers which are either one-off containers, or whose service is not part of this docker-compose context.
isStandaloneContainer := func(container *commands.Container) bool { isStandaloneContainer := func(container *commands.Container) bool {
if container.OneOff || container.ServiceName == "" { if container.OneOff || container.ServiceName == "" {
@ -27,34 +28,34 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
}) })
} }
return &SideListPanel[*commands.Container]{ return &panels.SideListPanel[*commands.Container]{
ContextState: &ContextState[*commands.Container]{ ContextState: &panels.ContextState[*commands.Container]{
GetContexts: func() []ContextConfig[*commands.Container] { GetContexts: func() []panels.ContextConfig[*commands.Container] {
return []ContextConfig[*commands.Container]{ return []panels.ContextConfig[*commands.Container]{
{ {
key: "logs", Key: "logs",
title: gui.Tr.LogsTitle, Title: gui.Tr.LogsTitle,
render: gui.renderContainerLogsToMain, Render: gui.renderContainerLogsToMain,
}, },
{ {
key: "stats", Key: "stats",
title: gui.Tr.StatsTitle, Title: gui.Tr.StatsTitle,
render: gui.renderContainerStats, Render: gui.renderContainerStats,
}, },
{ {
key: "env", Key: "env",
title: gui.Tr.EnvTitle, Title: gui.Tr.EnvTitle,
render: gui.renderContainerEnv, Render: gui.renderContainerEnv,
}, },
{ {
key: "config", Key: "config",
title: gui.Tr.ConfigTitle, Title: gui.Tr.ConfigTitle,
render: gui.renderContainerConfig, Render: gui.renderContainerConfig,
}, },
{ {
key: "top", Key: "top",
title: gui.Tr.TopTitle, Title: gui.Tr.TopTitle,
render: gui.renderContainerTop, Render: gui.renderContainerTop,
}, },
} }
}, },
@ -62,12 +63,12 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
return "containers-" + container.ID + "-" + container.Container.State return "containers-" + container.ID + "-" + container.Container.State
}, },
}, },
ListPanel: ListPanel[*commands.Container]{ ListPanel: panels.ListPanel[*commands.Container]{
List: NewFilteredList[*commands.Container](), List: panels.NewFilteredList[*commands.Container](),
view: gui.Views.Containers, View: gui.Views.Containers,
}, },
NoItemsMessage: gui.Tr.NoContainers, NoItemsMessage: gui.Tr.NoContainers,
gui: gui.intoInterface(), Gui: gui.intoInterface(),
// sortedContainers returns containers sorted by state if c.SortContainersByState is true (follows 1- running, 2- exited, 3- created) // sortedContainers returns containers sorted by state if c.SortContainersByState is true (follows 1- running, 2- exited, 3- created)
// and sorted by name if c.SortContainersByState is false // and sorted by name if c.SortContainersByState is false
Sort: func(a *commands.Container, b *commands.Container) bool { Sort: func(a *commands.Container, b *commands.Container) bool {
@ -282,7 +283,7 @@ func (gui *Gui) refreshContainersAndServices() error {
if i == originalSelectedLineIdx { if i == originalSelectedLineIdx {
break break
} }
gui.Panels.Services.setSelectedLineIdx(i) gui.Panels.Services.SetSelectedLineIdx(i)
gui.Panels.Services.Refocus() gui.Panels.Services.Refocus()
} }
} }

View file

@ -24,7 +24,7 @@ func (gui *Gui) handleOpenFilter() error {
func (gui *Gui) onNewFilterNeedle(value string) error { func (gui *Gui) onNewFilterNeedle(value string) error {
gui.State.Filter.needle = value gui.State.Filter.needle = value
gui.ResetOrigin(gui.State.Filter.panel.View()) gui.ResetOrigin(gui.State.Filter.panel.GetView())
return gui.State.Filter.panel.RerenderList() return gui.State.Filter.panel.RerenderList()
} }
@ -59,7 +59,7 @@ func (gui *Gui) clearFilter() error {
return nil return nil
} }
gui.ResetOrigin(panel.View()) gui.ResetOrigin(panel.GetView())
return panel.RerenderList() return panel.RerenderList()
} }

View file

@ -52,7 +52,7 @@ func (gui *Gui) switchFocusAux(newView *gocui.View) error {
newViewStack := gui.State.ViewStack newViewStack := gui.State.ViewStack
if gui.State.Filter.panel != nil && !lo.Contains(newViewStack, gui.State.Filter.panel.View().Name()) { if gui.State.Filter.panel != nil && !lo.Contains(newViewStack, gui.State.Filter.panel.GetView().Name()) {
if err := gui.clearFilter(); err != nil { if err := gui.clearFilter(); err != nil {
return err return err
} }

View file

@ -15,6 +15,7 @@ import (
lcUtils "github.com/jesseduffield/lazycore/pkg/utils" lcUtils "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/i18n" "github.com/jesseduffield/lazydocker/pkg/i18n"
"github.com/jesseduffield/lazydocker/pkg/tasks" "github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
@ -49,12 +50,12 @@ type Gui struct {
} }
type Panels struct { type Panels struct {
Projects *SideListPanel[*commands.Project] Projects *panels.SideListPanel[*commands.Project]
Services *SideListPanel[*commands.Service] Services *panels.SideListPanel[*commands.Service]
Containers *SideListPanel[*commands.Container] Containers *panels.SideListPanel[*commands.Container]
Images *SideListPanel[*commands.Image] Images *panels.SideListPanel[*commands.Image]
Volumes *SideListPanel[*commands.Volume] Volumes *panels.SideListPanel[*commands.Volume]
Menu *SideListPanel[*MenuItem] Menu *panels.SideListPanel[*MenuItem]
} }
type Mutexes struct { type Mutexes struct {
@ -94,7 +95,7 @@ type filterState struct {
// or we've committed the filter and we're back in the list view // or we've committed the filter and we're back in the list view
active bool active bool
// The panel that we're filtering. // The panel that we're filtering.
panel ISideListPanel panel panels.ISideListPanel
// The string that we're filtering on // The string that we're filtering on
needle string needle string
} }

View file

@ -10,21 +10,22 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] { func (gui *Gui) getImagesPanel() *panels.SideListPanel[*commands.Image] {
noneLabel := "<none>" noneLabel := "<none>"
return &SideListPanel[*commands.Image]{ return &panels.SideListPanel[*commands.Image]{
ContextState: &ContextState[*commands.Image]{ ContextState: &panels.ContextState[*commands.Image]{
GetContexts: func() []ContextConfig[*commands.Image] { GetContexts: func() []panels.ContextConfig[*commands.Image] {
return []ContextConfig[*commands.Image]{ return []panels.ContextConfig[*commands.Image]{
{ {
key: "config", Key: "config",
title: gui.Tr.ConfigTitle, Title: gui.Tr.ConfigTitle,
render: func(image *commands.Image) error { Render: func(image *commands.Image) error {
return gui.renderImageConfig(image) return gui.renderImageConfig(image)
}, },
}, },
@ -34,12 +35,12 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] {
return "images-" + image.ID return "images-" + image.ID
}, },
}, },
ListPanel: ListPanel[*commands.Image]{ ListPanel: panels.ListPanel[*commands.Image]{
List: NewFilteredList[*commands.Image](), List: panels.NewFilteredList[*commands.Image](),
view: gui.Views.Images, View: gui.Views.Images,
}, },
NoItemsMessage: gui.Tr.NoImages, NoItemsMessage: gui.Tr.NoImages,
gui: gui.intoInterface(), Gui: gui.intoInterface(),
Sort: func(a *commands.Image, b *commands.Image) bool { Sort: func(a *commands.Image, b *commands.Image) bool {
if a.Name == noneLabel && b.Name != noneLabel { if a.Name == noneLabel && b.Name != noneLabel {
return false return false
@ -114,7 +115,7 @@ func (gui *Gui) refreshStateImages() error {
} }
func (gui *Gui) FilterString(view *gocui.View) string { func (gui *Gui) FilterString(view *gocui.View) string {
if gui.State.Filter.panel != nil && gui.State.Filter.panel.View() != view { if gui.State.Filter.panel != nil && gui.State.Filter.panel.GetView() != view {
return "" return ""
} }

View file

@ -492,12 +492,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
for _, panel := range gui.allSidePanels() { for _, panel := range gui.allSidePanels() {
bindings = append(bindings, []*Binding{ bindings = append(bindings, []*Binding{
{ViewName: panel.View().Name(), Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: panel.GetView().Name(), Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView},
{ViewName: panel.View().Name(), Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: panel.GetView().Name(), Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: panel.View().Name(), Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: panel.GetView().Name(), Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView},
{ViewName: panel.View().Name(), Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: panel.GetView().Name(), Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: panel.View().Name(), Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: panel.GetView().Name(), Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: panel.View().Name(), Key: gocui.KeyBacktab, Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: panel.GetView().Name(), Key: gocui.KeyBacktab, Modifier: gocui.ModNone, Handler: gui.previousView},
}...) }...)
} }
@ -514,7 +514,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
} }
for _, panel := range gui.allListPanels() { for _, panel := range gui.allListPanels() {
setUpDownClickBindings(panel.View().Name(), panel.HandlePrevLine, panel.HandleNextLine, panel.HandleClick) setUpDownClickBindings(panel.GetView().Name(), panel.HandlePrevLine, panel.HandleNextLine, panel.HandleClick)
} }
setUpDownClickBindings("main", gui.scrollUpMain, gui.scrollDownMain, gui.handleMainClick) setUpDownClickBindings("main", gui.scrollUpMain, gui.scrollDownMain, gui.handleMainClick)
@ -522,21 +522,21 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
for _, panel := range gui.allSidePanels() { for _, panel := range gui.allSidePanels() {
bindings = append(bindings, bindings = append(bindings,
&Binding{ &Binding{
ViewName: panel.View().Name(), ViewName: panel.GetView().Name(),
Key: gocui.KeyEnter, Key: gocui.KeyEnter,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleEnterMain, Handler: gui.handleEnterMain,
Description: gui.Tr.FocusMain, Description: gui.Tr.FocusMain,
}, },
&Binding{ &Binding{
ViewName: panel.View().Name(), ViewName: panel.GetView().Name(),
Key: '[', Key: '[',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: wrappedHandler(panel.HandlePrevContext), Handler: wrappedHandler(panel.HandlePrevContext),
Description: gui.Tr.PreviousContext, Description: gui.Tr.PreviousContext,
}, },
&Binding{ &Binding{
ViewName: panel.View().Name(), ViewName: panel.GetView().Name(),
Key: ']', Key: ']',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: wrappedHandler(panel.HandleNextContext), Handler: wrappedHandler(panel.HandleNextContext),
@ -547,7 +547,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
for _, panel := range gui.allListPanels() { for _, panel := range gui.allListPanels() {
bindings = append(bindings, &Binding{ bindings = append(bindings, &Binding{
ViewName: panel.View().Name(), ViewName: panel.GetView().Name(),
Key: '/', Key: '/',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: wrappedHandler(gui.handleOpenFilter), Handler: wrappedHandler(gui.handleOpenFilter),

View file

@ -1,6 +1,7 @@
package gui package gui
import ( import (
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
) )
@ -22,14 +23,14 @@ type CreateMenuOptions struct {
HideCancel bool HideCancel bool
} }
func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] { func (gui *Gui) getMenuPanel() *panels.SideListPanel[*MenuItem] {
return &SideListPanel[*MenuItem]{ return &panels.SideListPanel[*MenuItem]{
ListPanel: ListPanel[*MenuItem]{ ListPanel: panels.ListPanel[*MenuItem]{
List: NewFilteredList[*MenuItem](), List: panels.NewFilteredList[*MenuItem](),
view: gui.Views.Menu, View: gui.Views.Menu,
}, },
NoItemsMessage: "", NoItemsMessage: "",
gui: gui.intoInterface(), Gui: gui.intoInterface(),
OnClick: gui.onMenuPress, OnClick: gui.onMenuPress,
Sort: nil, Sort: nil,
GetDisplayStrings: func(menuItem *MenuItem) []string { GetDisplayStrings: func(menuItem *MenuItem) []string {
@ -101,7 +102,7 @@ func (gui *Gui) Menu(opts CreateMenuOptions) error {
} }
gui.Panels.Menu.SetItems(opts.Items) gui.Panels.Menu.SetItems(opts.Items)
gui.Panels.Menu.setSelectedLineIdx(0) gui.Panels.Menu.SetSelectedLineIdx(0)
if err := gui.Panels.Menu.RerenderList(); err != nil { if err := gui.Panels.Menu.RerenderList(); err != nil {
return err return err

7
pkg/gui/panels.go Normal file
View file

@ -0,0 +1,7 @@
package gui
import "github.com/jesseduffield/lazydocker/pkg/gui/panels"
func (gui *Gui) intoInterface() panels.IGui {
return gui
}

View file

@ -1,4 +1,4 @@
package gui package panels
import ( import (
"sort" "sort"

View file

@ -1,4 +1,4 @@
package gui package panels
import ( import (
"testing" "testing"

View file

@ -0,0 +1,42 @@
package panels
import (
"github.com/jesseduffield/gocui"
lcUtils "github.com/jesseduffield/lazycore/pkg/utils"
)
type ListPanel[T comparable] struct {
SelectedIdx int
List *FilteredList[T]
View *gocui.View
}
func (self *ListPanel[T]) SetSelectedLineIdx(value int) {
clampedValue := 0
if self.List.Len() > 0 {
clampedValue = lcUtils.Clamp(value, 0, self.List.Len()-1)
}
self.SelectedIdx = clampedValue
}
func (self *ListPanel[T]) clampSelectedLineIdx() {
clamped := lcUtils.Clamp(self.SelectedIdx, 0, self.List.Len()-1)
if clamped != self.SelectedIdx {
self.SelectedIdx = clamped
}
}
// moves the cursor up or down by the given amount (up for negative values)
func (self *ListPanel[T]) moveSelectedLine(delta int) {
self.SetSelectedLineIdx(self.SelectedIdx + delta)
}
func (self *ListPanel[T]) SelectNextLine() {
self.moveSelectedLine(1)
}
func (self *ListPanel[T]) SelectPrevLine() {
self.moveSelectedLine(-1)
}

View file

@ -1,4 +1,4 @@
package gui package panels
import ( import (
"fmt" "fmt"
@ -6,53 +6,23 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
lcUtils "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
type ListPanel[T comparable] struct { type ISideListPanel interface {
SelectedIdx int SetContextIndex(int)
List *FilteredList[T] HandleSelect() error
view *gocui.View GetView() *gocui.View
} Refocus()
RerenderList() error
func (self *ListPanel[T]) setSelectedLineIdx(value int) { IsFilterDisabled() bool
clampedValue := 0 IsHidden() bool
if self.List.Len() > 0 { HandleNextLine() error
clampedValue = lcUtils.Clamp(value, 0, self.List.Len()-1) HandlePrevLine() error
} HandleClick() error
HandlePrevContext() error
self.SelectedIdx = clampedValue HandleNextContext() error
}
func (self *ListPanel[T]) clampSelectedLineIdx() {
clamped := lcUtils.Clamp(self.SelectedIdx, 0, self.List.Len()-1)
if clamped != self.SelectedIdx {
self.SelectedIdx = clamped
}
}
// moves the cursor up or down by the given amount (up for negative values)
func (self *ListPanel[T]) moveSelectedLine(delta int) {
self.setSelectedLineIdx(self.SelectedIdx + delta)
}
func (self *ListPanel[T]) SelectNextLine() {
self.moveSelectedLine(1)
}
func (self *ListPanel[T]) SelectPrevLine() {
self.moveSelectedLine(-1)
}
type ContextState[T any] struct {
contextIdx int
// contexts []ContextConfig[T]
GetContexts func() []ContextConfig[T]
// this tells us whether we need to re-render to the main panel
GetContextCacheKey func(item T) string
} }
// list panel at the side of the screen that renders content to the main panel // list panel at the side of the screen that renders content to the main panel
@ -65,7 +35,7 @@ type SideListPanel[T comparable] struct {
// and it has focus. Leave empty if you don't want to render anything // and it has focus. Leave empty if you don't want to render anything
NoItemsMessage string NoItemsMessage string
gui IGui Gui IGui
// this Filter is applied on top of additional default filters // this Filter is applied on top of additional default filters
Filter func(T) bool Filter func(T) bool
@ -85,27 +55,20 @@ type SideListPanel[T comparable] struct {
Hide func() bool Hide func() bool
} }
type ISideListPanel interface {
SetContextIndex(int)
HandleSelect() error
View() *gocui.View
Refocus()
RerenderList() error
IsFilterDisabled() bool
IsHidden() bool
HandleNextLine() error
HandlePrevLine() error
HandleClick() error
HandlePrevContext() error
HandleNextContext() error
}
var _ ISideListPanel = &SideListPanel[int]{} var _ ISideListPanel = &SideListPanel[int]{}
type ContextState[T any] struct {
contextIdx int
// contexts []ContextConfig[T]
GetContexts func() []ContextConfig[T]
// this tells us whether we need to re-render to the main panel
GetContextCacheKey func(item T) string
}
type ContextConfig[T any] struct { type ContextConfig[T any] struct {
key string Key string
title string Title string
render func(item T) error Render func(item T) error
} }
type IGui interface { type IGui interface {
@ -121,16 +84,12 @@ type IGui interface {
Update(func() error) Update(func() error)
} }
func (gui *Gui) intoInterface() IGui {
return gui
}
func (self *SideListPanel[T]) HandleClick() error { func (self *SideListPanel[T]) HandleClick() error {
itemCount := self.List.Len() itemCount := self.List.Len()
handleSelect := self.HandleSelect handleSelect := self.HandleSelect
selectedLine := &self.SelectedIdx selectedLine := &self.SelectedIdx
if err := self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect); err != nil { if err := self.Gui.HandleClick(self.View, itemCount, selectedLine, handleSelect); err != nil {
return err return err
} }
@ -144,8 +103,8 @@ func (self *SideListPanel[T]) HandleClick() error {
return nil return nil
} }
func (self *SideListPanel[T]) View() *gocui.View { func (self *SideListPanel[T]) GetView() *gocui.View {
return self.view return self.View
} }
func (self *SideListPanel[T]) HandleSelect() error { func (self *SideListPanel[T]) HandleSelect() error {
@ -156,7 +115,7 @@ func (self *SideListPanel[T]) HandleSelect() error {
} }
if self.NoItemsMessage != "" { if self.NoItemsMessage != "" {
return self.gui.RenderStringMain(self.NoItemsMessage) return self.Gui.RenderStringMain(self.NoItemsMessage)
} }
return nil return nil
@ -173,25 +132,25 @@ func (self *SideListPanel[T]) renderContext(item T) error {
} }
key := self.ContextState.GetCurrentContextKey(item) key := self.ContextState.GetCurrentContextKey(item)
if !self.gui.ShouldRefresh(key) { if !self.Gui.ShouldRefresh(key) {
return nil return nil
} }
mainView := self.gui.GetMainView() mainView := self.Gui.GetMainView()
mainView.Tabs = self.ContextState.GetContextTitles() mainView.Tabs = self.ContextState.GetContextTitles()
mainView.TabIndex = self.ContextState.contextIdx mainView.TabIndex = self.ContextState.contextIdx
return self.ContextState.GetCurrentContext().render(item) return self.ContextState.GetCurrentContext().Render(item)
} }
func (self *ContextState[T]) GetContextTitles() []string { func (self *ContextState[T]) GetContextTitles() []string {
return lo.Map(self.GetContexts(), func(context ContextConfig[T], _ int) string { return lo.Map(self.GetContexts(), func(context ContextConfig[T], _ int) string {
return context.title return context.Title
}) })
} }
func (self *ContextState[T]) GetCurrentContextKey(item T) string { func (self *ContextState[T]) GetCurrentContextKey(item T) string {
return self.GetContextCacheKey(item) + "-" + self.GetCurrentContext().key return self.GetContextCacheKey(item) + "-" + self.GetCurrentContext().Key
} }
func (self *ContextState[T]) GetCurrentContext() ContextConfig[T] { func (self *ContextState[T]) GetCurrentContext() ContextConfig[T] {
@ -263,7 +222,7 @@ func (self *SideListPanel[T]) HandlePrevContext() error {
} }
func (self *SideListPanel[T]) Refocus() { func (self *SideListPanel[T]) Refocus() {
self.gui.FocusY(self.SelectedIdx, self.List.Len(), self.view) self.Gui.FocusY(self.SelectedIdx, self.List.Len(), self.View)
} }
func (self *SideListPanel[T]) SetItems(items []T) { func (self *SideListPanel[T]) SetItems(items []T) {
@ -272,14 +231,14 @@ func (self *SideListPanel[T]) SetItems(items []T) {
} }
func (self *SideListPanel[T]) FilterAndSort() { func (self *SideListPanel[T]) FilterAndSort() {
filterString := self.gui.FilterString(self.view) filterString := self.Gui.FilterString(self.View)
self.List.Filter(func(item T, index int) bool { self.List.Filter(func(item T, index int) bool {
if self.Filter != nil && !self.Filter(item) { if self.Filter != nil && !self.Filter(item) {
return false return false
} }
if lo.SomeBy(self.gui.IgnoreStrings(), func(ignore string) bool { if lo.SomeBy(self.Gui.IgnoreStrings(), func(ignore string) bool {
return lo.SomeBy(self.GetDisplayStrings(item), func(searchString string) bool { return lo.SomeBy(self.GetDisplayStrings(item), func(searchString string) bool {
return strings.Contains(searchString, ignore) return strings.Contains(searchString, ignore)
}) })
@ -304,8 +263,8 @@ func (self *SideListPanel[T]) FilterAndSort() {
func (self *SideListPanel[T]) RerenderList() error { func (self *SideListPanel[T]) RerenderList() error {
self.FilterAndSort() self.FilterAndSort()
self.gui.Update(func() error { self.Gui.Update(func() error {
self.view.Clear() self.View.Clear()
table := lo.Map(self.List.GetItems(), func(item T, index int) []string { table := lo.Map(self.List.GetItems(), func(item T, index int) []string {
return self.GetDisplayStrings(item) return self.GetDisplayStrings(item)
}) })
@ -313,7 +272,7 @@ func (self *SideListPanel[T]) RerenderList() error {
if err != nil { if err != nil {
return err return err
} }
fmt.Fprint(self.view, renderedTable) fmt.Fprint(self.View, renderedTable)
if self.OnRerender != nil { if self.OnRerender != nil {
if err := self.OnRerender(); err != nil { if err := self.OnRerender(); err != nil {
@ -321,7 +280,7 @@ func (self *SideListPanel[T]) RerenderList() error {
} }
} }
if self.view == self.gui.CurrentView() { if self.View == self.Gui.CurrentView() {
return self.HandleSelect() return self.HandleSelect()
} }
return nil return nil

View file

@ -8,6 +8,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/jesseduffield/yaml" "github.com/jesseduffield/yaml"
) )
@ -15,35 +16,35 @@ import (
// Although at the moment we'll only have one project, in future we could have // Although at the moment we'll only have one project, in future we could have
// a list of projects in the project panel. // a list of projects in the project panel.
func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] { func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
return &SideListPanel[*commands.Project]{ return &panels.SideListPanel[*commands.Project]{
ContextState: &ContextState[*commands.Project]{ ContextState: &panels.ContextState[*commands.Project]{
GetContexts: func() []ContextConfig[*commands.Project] { GetContexts: func() []panels.ContextConfig[*commands.Project] {
if gui.DockerCommand.InDockerComposeProject { if gui.DockerCommand.InDockerComposeProject {
return []ContextConfig[*commands.Project]{ return []panels.ContextConfig[*commands.Project]{
{ {
key: "logs", Key: "logs",
title: gui.Tr.LogsTitle, Title: gui.Tr.LogsTitle,
render: gui.renderAllLogs, Render: gui.renderAllLogs,
}, },
{ {
key: "config", Key: "config",
title: gui.Tr.DockerComposeConfigTitle, Title: gui.Tr.DockerComposeConfigTitle,
render: gui.renderDockerComposeConfig, Render: gui.renderDockerComposeConfig,
}, },
{ {
key: "credits", Key: "credits",
title: gui.Tr.CreditsTitle, Title: gui.Tr.CreditsTitle,
render: gui.renderCredits, Render: gui.renderCredits,
}, },
} }
} }
return []ContextConfig[*commands.Project]{ return []panels.ContextConfig[*commands.Project]{
{ {
key: "credits", Key: "credits",
title: gui.Tr.CreditsTitle, Title: gui.Tr.CreditsTitle,
render: gui.renderCredits, Render: gui.renderCredits,
}, },
} }
}, },
@ -52,12 +53,12 @@ func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] {
}, },
}, },
ListPanel: ListPanel[*commands.Project]{ ListPanel: panels.ListPanel[*commands.Project]{
List: NewFilteredList[*commands.Project](), List: panels.NewFilteredList[*commands.Project](),
view: gui.Views.Project, View: gui.Views.Project,
}, },
NoItemsMessage: "", NoItemsMessage: "",
gui: gui.intoInterface(), Gui: gui.intoInterface(),
Sort: func(a *commands.Project, b *commands.Project) bool { Sort: func(a *commands.Project, b *commands.Project) bool {
return false return false

View file

@ -8,39 +8,40 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] { func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
return &SideListPanel[*commands.Service]{ return &panels.SideListPanel[*commands.Service]{
ContextState: &ContextState[*commands.Service]{ ContextState: &panels.ContextState[*commands.Service]{
GetContexts: func() []ContextConfig[*commands.Service] { GetContexts: func() []panels.ContextConfig[*commands.Service] {
return []ContextConfig[*commands.Service]{ return []panels.ContextConfig[*commands.Service]{
{ {
key: "logs", Key: "logs",
title: gui.Tr.LogsTitle, Title: gui.Tr.LogsTitle,
render: gui.renderServiceLogs, Render: gui.renderServiceLogs,
}, },
{ {
key: "stats", Key: "stats",
title: gui.Tr.StatsTitle, Title: gui.Tr.StatsTitle,
render: gui.renderServiceStats, Render: gui.renderServiceStats,
}, },
{ {
key: "container-env", Key: "container-env",
title: gui.Tr.ContainerEnvTitle, Title: gui.Tr.ContainerEnvTitle,
render: gui.renderServiceContainerEnv, Render: gui.renderServiceContainerEnv,
}, },
{ {
key: "container-config", Key: "container-config",
title: gui.Tr.ContainerConfigTitle, Title: gui.Tr.ContainerConfigTitle,
render: gui.renderServiceContainerConfig, Render: gui.renderServiceContainerConfig,
}, },
{ {
key: "top", Key: "top",
title: gui.Tr.TopTitle, Title: gui.Tr.TopTitle,
render: gui.renderServiceTop, Render: gui.renderServiceTop,
}, },
} }
}, },
@ -51,13 +52,12 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] {
return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State
}, },
}, },
ListPanel: ListPanel[*commands.Service]{ ListPanel: panels.ListPanel[*commands.Service]{
List: NewFilteredList[*commands.Service](), List: panels.NewFilteredList[*commands.Service](),
view: gui.Views.Services, View: gui.Views.Services,
}, },
// TODO: i18n NoItemsMessage: gui.Tr.NoServices,
NoItemsMessage: "no service selected", Gui: gui.intoInterface(),
gui: gui.intoInterface(),
// sort services first by whether they have a linked container, and second by alphabetical order // sort services first by whether they have a linked container, and second by alphabetical order
Sort: func(a *commands.Service, b *commands.Service) bool { Sort: func(a *commands.Service, b *commands.Service) bool {
if a.Container != nil && b.Container == nil { if a.Container != nil && b.Container == nil {

View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/spkg/bom" "github.com/spkg/bom"
@ -329,11 +330,11 @@ func (gui *Gui) CurrentView() *gocui.View {
return gui.g.CurrentView() return gui.g.CurrentView()
} }
func (gui *Gui) currentSidePanel() (ISideListPanel, bool) { func (gui *Gui) currentSidePanel() (panels.ISideListPanel, bool) {
viewName := gui.currentViewName() viewName := gui.currentViewName()
for _, sidePanel := range gui.allSidePanels() { for _, sidePanel := range gui.allSidePanels() {
if sidePanel.View().Name() == viewName { if sidePanel.GetView().Name() == viewName {
return sidePanel, true return sidePanel, true
} }
} }
@ -342,11 +343,11 @@ func (gui *Gui) currentSidePanel() (ISideListPanel, bool) {
} }
// returns the current list panel. If no list panel is focused, returns false. // returns the current list panel. If no list panel is focused, returns false.
func (gui *Gui) currentListPanel() (ISideListPanel, bool) { func (gui *Gui) currentListPanel() (panels.ISideListPanel, bool) {
viewName := gui.currentViewName() viewName := gui.currentViewName()
for _, sidePanel := range gui.allListPanels() { for _, sidePanel := range gui.allListPanels() {
if sidePanel.View().Name() == viewName { if sidePanel.GetView().Name() == viewName {
return sidePanel, true return sidePanel, true
} }
} }
@ -354,8 +355,8 @@ func (gui *Gui) currentListPanel() (ISideListPanel, bool) {
return nil, false return nil, false
} }
func (gui *Gui) allSidePanels() []ISideListPanel { func (gui *Gui) allSidePanels() []panels.ISideListPanel {
return []ISideListPanel{ return []panels.ISideListPanel{
gui.Panels.Projects, gui.Panels.Projects,
gui.Panels.Services, gui.Panels.Services,
gui.Panels.Containers, gui.Panels.Containers,
@ -364,6 +365,6 @@ func (gui *Gui) allSidePanels() []ISideListPanel {
} }
} }
func (gui *Gui) allListPanels() []ISideListPanel { func (gui *Gui) allListPanels() []panels.ISideListPanel {
return append(gui.allSidePanels(), gui.Panels.Menu) return append(gui.allSidePanels(), gui.Panels.Menu)
} }

View file

@ -7,19 +7,20 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] { func (gui *Gui) getVolumesPanel() *panels.SideListPanel[*commands.Volume] {
return &SideListPanel[*commands.Volume]{ return &panels.SideListPanel[*commands.Volume]{
ContextState: &ContextState[*commands.Volume]{ ContextState: &panels.ContextState[*commands.Volume]{
GetContexts: func() []ContextConfig[*commands.Volume] { GetContexts: func() []panels.ContextConfig[*commands.Volume] {
return []ContextConfig[*commands.Volume]{ return []panels.ContextConfig[*commands.Volume]{
{ {
key: "config", Key: "config",
title: gui.Tr.ConfigTitle, Title: gui.Tr.ConfigTitle,
render: gui.renderVolumeConfig, Render: gui.renderVolumeConfig,
}, },
} }
}, },
@ -27,12 +28,12 @@ func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] {
return "volumes-" + volume.Name return "volumes-" + volume.Name
}, },
}, },
ListPanel: ListPanel[*commands.Volume]{ ListPanel: panels.ListPanel[*commands.Volume]{
List: NewFilteredList[*commands.Volume](), List: panels.NewFilteredList[*commands.Volume](),
view: gui.Views.Volumes, View: gui.Views.Volumes,
}, },
NoItemsMessage: gui.Tr.NoVolumes, NoItemsMessage: gui.Tr.NoVolumes,
gui: gui.intoInterface(), Gui: gui.intoInterface(),
// we're sorting these volumes based on whether they have labels defined, // we're sorting these volumes based on whether they have labels defined,
// because those are the ones you typically care about. // because those are the ones you typically care about.
// Within that, we also sort them alphabetically // Within that, we also sort them alphabetically

View file

@ -76,6 +76,7 @@ type TranslationSet struct {
NoContainer string NoContainer string
NoImages string NoImages string
NoVolumes string NoVolumes string
NoServices string
RemoveImage string RemoveImage string
RemoveVolume string RemoveVolume string
RemoveWithoutPrune string RemoveWithoutPrune string
@ -225,6 +226,7 @@ func englishSet() TranslationSet {
NoContainer: "No container", NoContainer: "No container",
NoImages: "No images", NoImages: "No images",
NoVolumes: "No volumes", NoVolumes: "No volumes",
NoServices: "No services",
ConfirmQuit: "Are you sure you want to quit?", ConfirmQuit: "Are you sure you want to quit?",
ConfirmUpProject: "Are you sure you want to 'up' your docker compose project?", ConfirmUpProject: "Are you sure you want to 'up' your docker compose project?",