From 158ef683728214164440451dd43460548f81ff41 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 22 Oct 2022 11:14:28 -0700 Subject: [PATCH] convert project panel to use new struct --- pkg/commands/project.go | 9 +++ pkg/gui/containers_panel.go | 54 +++++++------ pkg/gui/gui.go | 8 +- pkg/gui/images_panel.go | 16 ++-- pkg/gui/keybindings.go | 16 ++-- pkg/gui/list_panel.go | 50 +++++++++--- pkg/gui/main_panel.go | 23 ++---- pkg/gui/project_panel.go | 157 +++++++++++++----------------------- pkg/gui/services_panel.go | 54 +++++++------ pkg/gui/view_helpers.go | 37 ++++++--- pkg/gui/volumes_panel.go | 14 ++-- 11 files changed, 221 insertions(+), 217 deletions(-) create mode 100644 pkg/commands/project.go diff --git a/pkg/commands/project.go b/pkg/commands/project.go new file mode 100644 index 00000000..fe44a7d9 --- /dev/null +++ b/pkg/commands/project.go @@ -0,0 +1,9 @@ +package commands + +type Project struct { + Name string +} + +func (self *Project) GetDisplayStrings(isFocused bool) []string { + return []string{self.Name} +} diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index e73404e2..41ca8fec 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -42,32 +42,34 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] { contextIdx: 0, noItemsMessge: gui.Tr.NoContainers, gui: gui.intoInterface(), - contexts: []ContextConfig[*commands.Container]{ - { - key: "logs", - title: gui.Tr.LogsTitle, - render: gui.renderContainerLogsToMain, - }, - { - key: "stats", - title: gui.Tr.StatsTitle, - render: gui.renderContainerStats, - }, - { - key: "env", - title: gui.Tr.EnvTitle, - render: gui.renderContainerEnv, - }, - { - key: "config", - title: gui.Tr.ConfigTitle, - render: gui.renderContainerConfig, - }, - { - key: "top", - title: gui.Tr.TopTitle, - render: gui.renderContainerTop, - }, + getContexts: func() []ContextConfig[*commands.Container] { + return []ContextConfig[*commands.Container]{ + { + key: "logs", + title: gui.Tr.LogsTitle, + render: gui.renderContainerLogsToMain, + }, + { + key: "stats", + title: gui.Tr.StatsTitle, + render: gui.renderContainerStats, + }, + { + key: "env", + title: gui.Tr.EnvTitle, + render: gui.renderContainerEnv, + }, + { + key: "config", + title: gui.Tr.ConfigTitle, + render: gui.renderContainerConfig, + }, + { + key: "top", + title: gui.Tr.TopTitle, + render: gui.renderContainerTop, + }, + } }, getSearchStrings: func(container *commands.Container) []string { // TODO: think about more things to search on diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 1a9d9d00..b4898875 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -48,6 +48,7 @@ type Gui struct { } type Panels struct { + Projects *SideListPanel[*commands.Project] Services *SideListPanel[*commands.Service] Containers *SideListPanel[*commands.Container] Images *SideListPanel[*commands.Image] @@ -239,6 +240,7 @@ func (gui *Gui) Run() error { // TODO: see if we can avoid the circular dependency gui.Panels = Panels{ + Projects: gui.getProjectPanel(), Services: gui.getServicesPanel(), Containers: gui.getContainersPanel(), Images: gui.getImagesPanel(), @@ -294,7 +296,11 @@ func (gui *Gui) rerenderContainersAndServices() error { } func (gui *Gui) refresh() { - go gui.refreshProject() + go func() { + if err := gui.refreshProject(); err != nil { + gui.Log.Error(err) + } + }() go func() { if err := gui.refreshContainersAndServices(); err != nil { gui.Log.Error(err) diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index 490847a9..673e87ac 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -25,14 +25,16 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] { contextIdx: 0, noItemsMessge: gui.Tr.NoImages, gui: gui.intoInterface(), - contexts: []ContextConfig[*commands.Image]{ - { - key: "config", - title: gui.Tr.ConfigTitle, - render: func(image *commands.Image) error { - return gui.renderImageConfig(image) + getContexts: func() []ContextConfig[*commands.Image] { + return []ContextConfig[*commands.Image]{ + { + key: "config", + title: gui.Tr.ConfigTitle, + render: func(image *commands.Image) error { + return gui.renderImageConfig(image) + }, }, - }, + } }, getSearchStrings: func(image *commands.Image) []string { return []string{image.Name, image.Tag} diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 04b3baeb..dfa2721c 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -151,22 +151,16 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "project", Key: '[', Modifier: gocui.ModNone, - Handler: gui.handleProjectPrevContext, + Handler: wrappedHandler(gui.Panels.Projects.OnPrevContext), Description: gui.Tr.PreviousContext, }, { ViewName: "project", Key: ']', Modifier: gocui.ModNone, - Handler: gui.handleProjectNextContext, + Handler: wrappedHandler(gui.Panels.Projects.OnNextContext), Description: gui.Tr.NextContext, }, - { - ViewName: "project", - Key: gocui.MouseLeft, - Modifier: gocui.ModNone, - Handler: gui.handleProjectClick, - }, { ViewName: "project", Key: 'm', @@ -178,7 +172,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "project", Key: gocui.MouseLeft, Modifier: gocui.ModNone, - Handler: gui.handleProjectSelect, + Handler: wrappedHandler(gui.Panels.Projects.OnClick), }, { ViewName: "menu", @@ -603,9 +597,9 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { }...) } - for _, viewName := range []string{"project", "services", "containers", "images", "volumes"} { + for _, sidePanel := range gui.allSidepanels() { bindings = append(bindings, &Binding{ - ViewName: viewName, + ViewName: sidePanel.View().Name(), Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: gui.handleEnterMain, diff --git a/pkg/gui/list_panel.go b/pkg/gui/list_panel.go index 57dc02d8..bbf5caf5 100644 --- a/pkg/gui/list_panel.go +++ b/pkg/gui/list_panel.go @@ -49,28 +49,34 @@ func (self *ListPanel[T]) SelectPrevLine() { // list panel at the side of the screen that renders content to the main panel type SideListPanel[T comparable] struct { contextKeyPrefix string + 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 ListPanel[T] - contextIdx int - noItemsMessge string gui IGui - contexts []ContextConfig[T] - // returns strings that can be filtered on getSearchStrings func(item T) []string - // this tells us whether we need to re-render to the main panel - getContextCacheKey func(item T) string - - sort func(a, b T) bool // this filter is applied on top of additional default filters filter func(T) bool + sort func(a, b T) bool } +type ISideListPanel interface { + SetContextIndex(int) + HandleSelect() error + View() *gocui.View +} + +var _ ISideListPanel = &SideListPanel[int]{} + type ContextConfig[T any] struct { key string title string @@ -103,6 +109,10 @@ func (self *SideListPanel[T]) OnClick() error { return self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect) } +func (self *SideListPanel[T]) View() *gocui.View { + return self.view +} + func (self *SideListPanel[T]) HandleSelect() error { item, err := self.GetSelectedItem() if err != nil { @@ -115,7 +125,9 @@ func (self *SideListPanel[T]) HandleSelect() error { self.Refocus() - key := self.contextKeyPrefix + "-" + self.getContextCacheKey(item) + "-" + self.contexts[self.contextIdx].key + contexts := self.getContexts() + + key := self.contextKeyPrefix + "-" + self.getContextCacheKey(item) + "-" + contexts[self.contextIdx].key if !self.gui.ShouldRefresh(key) { return nil } @@ -125,11 +137,11 @@ func (self *SideListPanel[T]) HandleSelect() error { mainView.TabIndex = self.contextIdx // now I have an item. What do I do with it? - return self.contexts[self.contextIdx].render(item) + return contexts[self.contextIdx].render(item) } func (self *SideListPanel[T]) GetContextTitles() []string { - return lo.Map(self.contexts, func(context ContextConfig[T], _ int) string { + return lo.Map(self.getContexts(), func(context ContextConfig[T], _ int) string { return context.title }) } @@ -171,13 +183,25 @@ func (self *SideListPanel[T]) ignoreKeypress() bool { } func (self *SideListPanel[T]) OnNextContext() error { - self.contextIdx = (self.contextIdx + 1) % len(self.contexts) + contexts := self.getContexts() + + if len(contexts) == 0 { + return nil + } + + self.contextIdx = (self.contextIdx + 1) % len(contexts) return self.HandleSelect() } func (self *SideListPanel[T]) OnPrevContext() error { - self.contextIdx = (self.contextIdx - 1 + len(self.contexts)) % len(self.contexts) + contexts := self.getContexts() + + if len(contexts) == 0 { + return nil + } + + self.contextIdx = (self.contextIdx - 1 + len(contexts)) % len(contexts) return self.HandleSelect() } diff --git a/pkg/gui/main_panel.go b/pkg/gui/main_panel.go index db0092aa..d5045406 100644 --- a/pkg/gui/main_panel.go +++ b/pkg/gui/main_panel.go @@ -83,25 +83,14 @@ func (gui *Gui) onMainTabClick(tabIndex int) error { viewName = mainView.ParentView.Name() } - switch viewName { - case "project": - gui.State.Panels.Project.ContextIndex = tabIndex - return gui.handleProjectSelect(gui.g, gui.getProjectView()) - case "services": - gui.Panels.Services.SetContextIndex(tabIndex) - return gui.Panels.Services.HandleSelect() - case "containers": - gui.Panels.Containers.SetContextIndex(tabIndex) - return gui.Panels.Containers.HandleSelect() - case "images": - gui.Panels.Images.SetContextIndex(tabIndex) - return gui.Panels.Images.HandleSelect() - case "volumes": - gui.Panels.Volumes.SetContextIndex(tabIndex) - return gui.Panels.Volumes.HandleSelect() + currentSidePanel, ok := gui.currentSidePanel() + + if !ok { + return nil } - return nil + currentSidePanel.SetContextIndex(tabIndex) + return currentSidePanel.HandleSelect() } func (gui *Gui) handleEnterMain(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/project_panel.go b/pkg/gui/project_panel.go index ddc60b2f..5dc98f79 100644 --- a/pkg/gui/project_panel.go +++ b/pkg/gui/project_panel.go @@ -2,42 +2,73 @@ package gui import ( "bytes" - "fmt" "path" "strings" "github.com/fatih/color" - "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/yaml" ) -func (gui *Gui) getProjectContexts() []string { - if gui.DockerCommand.InDockerComposeProject { - return []string{"logs", "config", "credits"} +// Although at the moment we'll only have one project, in future we could have +// a list of projects in the project panel. + +func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] { + return &SideListPanel[*commands.Project]{ + contextKeyPrefix: "project", + ListPanel: ListPanel[*commands.Project]{ + list: NewFilteredList[*commands.Project](), + view: gui.Views.Project, + }, + contextIdx: 0, + noItemsMessge: "no projects", // we don't expect to ever actually see this + gui: gui.intoInterface(), + getContexts: func() []ContextConfig[*commands.Project] { + if gui.DockerCommand.InDockerComposeProject { + return []ContextConfig[*commands.Project]{ + { + key: "logs", + title: gui.Tr.LogsTitle, + render: gui.renderAllLogs, + }, + { + key: "config", + title: gui.Tr.DockerComposeConfigTitle, + render: gui.renderDockerComposeConfig, + }, + { + key: "credits", + title: gui.Tr.CreditsTitle, + render: gui.renderCredits, + }, + } + } + + return []ContextConfig[*commands.Project]{ + { + key: "credits", + title: gui.Tr.CreditsTitle, + render: gui.renderCredits, + }, + } + }, + getSearchStrings: func(project *commands.Project) []string { + return []string{project.Name} + }, + getContextCacheKey: func(project *commands.Project) string { + return project.Name + }, + sort: func(a *commands.Project, b *commands.Project) bool { + return false + }, } - return []string{"credits"} } -func (gui *Gui) getProjectContextTitles() []string { - if gui.DockerCommand.InDockerComposeProject { - return []string{gui.Tr.LogsTitle, gui.Tr.DockerComposeConfigTitle, gui.Tr.CreditsTitle} - } - return []string{gui.Tr.CreditsTitle} -} - -func (gui *Gui) refreshProject() { - v := gui.getProjectView() - - projectName := gui.getProjectName() - - gui.g.Update(func(*gocui.Gui) error { - v.Clear() - fmt.Fprint(v, projectName) - return nil - }) +func (gui *Gui) refreshProject() error { + gui.Panels.Projects.SetItems([]*commands.Project{{Name: gui.getProjectName()}}) + return gui.Panels.Projects.RerenderList() } func (gui *Gui) getProjectName() string { @@ -54,55 +85,7 @@ func (gui *Gui) getProjectName() string { return projectName } -func (gui *Gui) handleProjectClick(g *gocui.Gui, v *gocui.View) error { - if gui.popupPanelFocused() { - return nil - } - - if _, err := gui.g.SetCurrentView(v.Name()); err != nil { - return err - } - - return gui.handleProjectSelect(g, v) -} - -func (gui *Gui) handleProjectSelect(g *gocui.Gui, v *gocui.View) error { - if gui.popupPanelFocused() { - return nil - } - - key := gui.getProjectContexts()[gui.State.Panels.Project.ContextIndex] - if !gui.shouldRefresh(key) { - return nil - } - - gui.clearMainView() - - mainView := gui.getMainView() - mainView.Tabs = gui.getProjectContextTitles() - mainView.TabIndex = gui.State.Panels.Project.ContextIndex - - switch gui.getProjectContexts()[gui.State.Panels.Project.ContextIndex] { - case "credits": - if err := gui.renderCredits(); err != nil { - return err - } - case "logs": - if err := gui.renderAllLogs(); err != nil { - return err - } - case "config": - if err := gui.renderDockerComposeConfig(); err != nil { - return err - } - default: - return errors.New("Unknown context for status panel") - } - - return nil -} - -func (gui *Gui) renderCredits() error { +func (gui *Gui) renderCredits(_project *commands.Project) error { return gui.T.NewTask(func(stop chan struct{}) { mainView := gui.getMainView() mainView.Autoscroll = false @@ -127,7 +110,7 @@ func (gui *Gui) renderCredits() error { }) } -func (gui *Gui) renderAllLogs() error { +func (gui *Gui) renderAllLogs(_project *commands.Project) error { return gui.T.NewTask(func(stop chan struct{}) { mainView := gui.getMainView() mainView.Autoscroll = true @@ -159,7 +142,7 @@ func (gui *Gui) renderAllLogs() error { }) } -func (gui *Gui) renderDockerComposeConfig() error { +func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) error { return gui.T.NewTask(func(stop chan struct{}) { mainView := gui.getMainView() mainView.Autoscroll = false @@ -191,32 +174,6 @@ func lazydockerTitle() string { ` } -func (gui *Gui) handleProjectNextContext(g *gocui.Gui, v *gocui.View) error { - contexts := gui.getProjectContexts() - if gui.State.Panels.Project.ContextIndex >= len(contexts)-1 { - gui.State.Panels.Project.ContextIndex = 0 - } else { - gui.State.Panels.Project.ContextIndex++ - } - - _ = gui.handleProjectSelect(gui.g, v) - - return nil -} - -func (gui *Gui) handleProjectPrevContext(g *gocui.Gui, v *gocui.View) error { - contexts := gui.getProjectContexts() - if gui.State.Panels.Project.ContextIndex <= 0 { - gui.State.Panels.Project.ContextIndex = len(contexts) - 1 - } else { - gui.State.Panels.Project.ContextIndex-- - } - - _ = gui.handleProjectSelect(gui.g, v) - - return nil -} - // handleViewAllLogs switches to a subprocess viewing all the logs from docker-compose func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error { c, err := gui.DockerCommand.ViewAllLogs() diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 959ede0c..27c3a358 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -22,32 +22,34 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] { // TODO: i18n noItemsMessge: "no service selected", gui: gui.intoInterface(), - contexts: []ContextConfig[*commands.Service]{ - { - key: "logs", - title: gui.Tr.LogsTitle, - render: gui.renderServiceLogs, - }, - { - key: "stats", - title: gui.Tr.StatsTitle, - render: gui.renderServiceStats, - }, - { - key: "container-env", - title: gui.Tr.ContainerEnvTitle, - render: gui.renderServiceContainerEnv, - }, - { - key: "container-config", - title: gui.Tr.ContainerConfigTitle, - render: gui.renderServiceContainerConfig, - }, - { - key: "top", - title: gui.Tr.TopTitle, - render: gui.renderServiceTop, - }, + getContexts: func() []ContextConfig[*commands.Service] { + return []ContextConfig[*commands.Service]{ + { + key: "logs", + title: gui.Tr.LogsTitle, + render: gui.renderServiceLogs, + }, + { + key: "stats", + title: gui.Tr.StatsTitle, + render: gui.renderServiceStats, + }, + { + key: "container-env", + title: gui.Tr.ContainerEnvTitle, + render: gui.renderServiceContainerEnv, + }, + { + key: "container-config", + title: gui.Tr.ContainerConfigTitle, + render: gui.renderServiceContainerConfig, + }, + { + key: "top", + title: gui.Tr.TopTitle, + render: gui.renderServiceTop, + }, + } }, getSearchStrings: func(service *commands.Service) []string { // TODO: think about more things to search on diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 82fc1cf4..9c7928bd 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -71,19 +71,14 @@ func (gui *Gui) newLineFocused(v *gocui.View) error { return nil } + currentSidePanel, ok := gui.currentSidePanel() + if ok { + return currentSidePanel.HandleSelect() + } + switch v.Name() { case "menu": return gui.handleMenuSelect(gui.g, v) - case "project": - return gui.handleProjectSelect(gui.g, v) - case "services": - return gui.Panels.Services.HandleSelect() - case "containers": - return gui.Panels.Containers.HandleSelect() - case "images": - return gui.Panels.Images.HandleSelect() - case "volumes": - return gui.Panels.Volumes.HandleSelect() case "confirmation": return nil case "main": @@ -501,3 +496,25 @@ func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowM func (gui *Gui) CurrentView() *gocui.View { return gui.g.CurrentView() } + +func (gui *Gui) currentSidePanel() (ISideListPanel, bool) { + viewName := gui.currentViewName() + + for _, sidePanel := range gui.allSidepanels() { + if sidePanel.View().Name() == viewName { + return sidePanel, true + } + } + + return nil, false +} + +func (gui *Gui) allSidepanels() []ISideListPanel { + return []ISideListPanel{ + gui.Panels.Projects, + gui.Panels.Services, + gui.Panels.Containers, + gui.Panels.Images, + gui.Panels.Volumes, + } +} diff --git a/pkg/gui/volumes_panel.go b/pkg/gui/volumes_panel.go index 6f52b98b..b1d34355 100644 --- a/pkg/gui/volumes_panel.go +++ b/pkg/gui/volumes_panel.go @@ -20,12 +20,14 @@ func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] { contextIdx: 0, noItemsMessge: gui.Tr.NoVolumes, gui: gui.intoInterface(), - contexts: []ContextConfig[*commands.Volume]{ - { - key: "config", - title: gui.Tr.ConfigTitle, - render: gui.renderVolumeConfig, - }, + getContexts: func() []ContextConfig[*commands.Volume] { + return []ContextConfig[*commands.Volume]{ + { + key: "config", + title: gui.Tr.ConfigTitle, + render: gui.renderVolumeConfig, + }, + } }, getSearchStrings: func(volume *commands.Volume) []string { // TODO: think about more things to search on