From b80c8edcd0cdeefd5a30857826c9c5d306439072 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Oct 2022 21:35:33 -0700 Subject: [PATCH] convert images panel to use new struct --- pkg/gui/gui.go | 40 ++++----- pkg/gui/images_panel.go | 177 ++-------------------------------------- pkg/gui/keybindings.go | 4 +- pkg/gui/layout.go | 26 ++---- pkg/gui/list_panel.go | 27 +++--- pkg/gui/main_panel.go | 4 +- pkg/gui/searching.go | 4 +- pkg/gui/view_helpers.go | 2 +- pkg/gui/views.go | 2 - 9 files changed, 55 insertions(+), 231 deletions(-) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index dc4df9ef..a4b18da3 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -59,14 +59,10 @@ type Gui struct { Tr *i18n.TranslationSet Errors SentinelErrors statusManager *statusManager - waitForIntro sync.WaitGroup T *tasks.TaskManager ErrorChan chan error CyclableViews []string Views Views - // returns true if our views have been created and assigned to gui.Views. - // Views are setup only once, upon application start. - ViewsSetup bool // if we've suspended the gui (e.g. because we've switched to a subprocess) // we typically want to pause some things that are running like background @@ -111,11 +107,6 @@ type mainPanelState struct { ObjectKey string } -type imagePanelState struct { - SelectedLine int - ContextIndex int // for specifying if you are looking at logs/stats/config/etc -} - type volumePanelState struct { SelectedLine int ContextIndex int @@ -126,7 +117,6 @@ type panelStates struct { Containers *containerPanelState Menu *menuPanelState Main *mainPanelState - Images *imagePanelState Volumes *volumePanelState Project *projectState } @@ -180,7 +170,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand Panels: &panelStates{ Services: &servicePanelState{SelectedLine: -1, ContextIndex: 0}, Containers: &containerPanelState{SelectedLine: -1, ContextIndex: 0}, - Images: &imagePanelState{SelectedLine: -1, ContextIndex: 0}, Volumes: &volumePanelState{SelectedLine: -1, ContextIndex: 0}, Menu: &menuPanelState{SelectedLine: 0}, Main: &mainPanelState{ @@ -216,11 +205,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand CyclableViews: cyclableViews, } - // TODO: see if we can avoid the circular dependency - gui.Panels = Panels{ - Images: gui.getImagePanel(), - } - gui.GenerateSentinelErrors() return gui, nil @@ -273,8 +257,6 @@ func (gui *Gui) Run() error { return err } - gui.waitForIntro.Add(1) - throttledRefresh := throttle.ThrottleFunc(time.Millisecond*50, true, gui.refresh) defer throttledRefresh.Stop() @@ -285,7 +267,6 @@ func (gui *Gui) Run() error { go gui.DockerCommand.MonitorContainerStats(ctx) go func() { - gui.waitForIntro.Wait() throttledRefresh.Trigger() gui.goEvery(time.Millisecond*30, gui.reRenderMain) @@ -310,10 +291,31 @@ func (gui *Gui) Run() error { g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout())) + if err := gui.createAllViews(); err != nil { + return err + } + + // TODO: see if we can avoid the circular dependency + gui.Panels = Panels{ + Images: gui.getImagePanel(), + } + if err = gui.keybindings(g); err != nil { return err } + if gui.g.CurrentView() == nil { + viewName := gui.initiallyFocusedViewName() + view, err := gui.g.View(viewName) + if err != nil { + return err + } + + if err := gui.switchFocus(view); err != nil { + return err + } + } + err = g.MainLoop() if err == gocui.ErrQuit { return nil diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index f83b1a8c..c1444342 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -7,73 +7,12 @@ import ( "github.com/docker/docker/api/types" "github.com/fatih/color" - "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/utils" - "github.com/samber/lo" ) -// list panel functions - -func (gui *Gui) getImageContexts() []string { - return []string{"config"} -} - -func (gui *Gui) getImageContextTitles() []string { - return []string{gui.Tr.ConfigTitle} -} - -func (gui *Gui) getSelectedImage() (*commands.Image, error) { - selectedLine := gui.State.Panels.Images.SelectedLine - if selectedLine == -1 { - return &commands.Image{}, gui.Errors.ErrNoImages - } - - return gui.State.Lists.Images.Get(selectedLine), nil -} - -func (gui *Gui) handleImagesClick(g *gocui.Gui, v *gocui.View) error { - itemCount := gui.State.Lists.Images.Len() - handleSelect := gui.handleImageSelect - selectedLine := &gui.State.Panels.Images.SelectedLine - - return gui.handleClick(v, itemCount, selectedLine, handleSelect) -} - -func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error { - image, err := gui.getSelectedImage() - if err != nil { - if err != gui.Errors.ErrNoImages { - return err - } - return gui.renderStringMain(gui.Tr.NoImages) - } - - gui.focusY(gui.State.Panels.Images.SelectedLine, gui.State.Lists.Images.Len(), v) - - key := "images-" + image.ID + "-" + gui.getImageContexts()[gui.State.Panels.Images.ContextIndex] - if !gui.shouldRefresh(key) { - return nil - } - - mainView := gui.getMainView() - mainView.Tabs = gui.getImageContextTitles() - mainView.TabIndex = gui.State.Panels.Images.ContextIndex - - switch gui.getImageContexts()[gui.State.Panels.Images.ContextIndex] { - case "config": - if err := gui.renderImageConfig(image); err != nil { - return err - } - default: - return errors.New("Unknown context for Images panel") - } - - return nil -} - func (gui *Gui) renderImageConfig(image *commands.Image) error { return gui.T.NewTask(func(stop chan struct{}) { padding := 10 @@ -104,63 +43,7 @@ func (gui *Gui) reloadImages() error { return err } - return gui.rerenderImages() -} - -func (gui *Gui) rerenderImages() error { - filterString := gui.filterString(gui.Views.Images) - - gui.State.Lists.Images.Filter(func(image *commands.Image, index int) bool { - if lo.SomeBy(gui.Config.UserConfig.Ignore, func(ignore string) bool { - return strings.Contains(image.Name, ignore) - }) { - return false - } - - if filterString != "" { - return strings.Contains(image.Name, filterString) - } - - return true - }) - - noneLabel := "" - - gui.State.Lists.Images.Sort(func(a *commands.Image, b *commands.Image) bool { - if a.Name == noneLabel && b.Name != noneLabel { - return false - } - - if a.Name != noneLabel && b.Name == noneLabel { - return true - } - - return a.Name < b.Name - }) - - if gui.State.Lists.Images.Len() > 0 && gui.State.Panels.Images.SelectedLine == -1 { - gui.State.Panels.Images.SelectedLine = 0 - } - if gui.State.Lists.Images.Len()-1 < gui.State.Panels.Images.SelectedLine { - gui.State.Panels.Images.SelectedLine = gui.State.Lists.Images.Len() - 1 - } - - gui.g.Update(func(g *gocui.Gui) error { - gui.Views.Images.Clear() - isFocused := gui.g.CurrentView() == gui.Views.Images - list, err := utils.RenderList(gui.State.Lists.Images.GetItems(), utils.IsFocused(isFocused)) - if err != nil { - return err - } - fmt.Fprint(gui.Views.Images, list) - - if gui.Views.Images == g.CurrentView() { - return gui.handleImageSelect(g, gui.Views.Images) - } - return nil - }) - - return nil + return gui.Panels.Images.RerenderList() } func (gui *Gui) refreshStateImages() error { @@ -170,7 +53,7 @@ func (gui *Gui) refreshStateImages() error { } // TODO: think about also re-filtering/sorting - gui.State.Lists.Images.SetItems(images) + gui.Panels.Images.list.SetItems(images) return nil } @@ -188,54 +71,6 @@ func (gui *Gui) FilterString(view *gocui.View) string { return gui.filterString(view) } -func (gui *Gui) handleImagesNextLine(g *gocui.Gui, v *gocui.View) error { - if gui.popupPanelFocused() || gui.g.CurrentView() != v { - return nil - } - - panelState := gui.State.Panels.Images - gui.changeSelectedLine(&panelState.SelectedLine, gui.State.Lists.Images.Len(), false) - - return gui.handleImageSelect(gui.g, v) -} - -func (gui *Gui) handleImagesPrevLine(g *gocui.Gui, v *gocui.View) error { - if gui.popupPanelFocused() || gui.g.CurrentView() != v { - return nil - } - - panelState := gui.State.Panels.Images - gui.changeSelectedLine(&panelState.SelectedLine, gui.State.Lists.Images.Len(), true) - - return gui.handleImageSelect(gui.g, v) -} - -func (gui *Gui) handleImagesNextContext(g *gocui.Gui, v *gocui.View) error { - contexts := gui.getImageContexts() - if gui.State.Panels.Images.ContextIndex >= len(contexts)-1 { - gui.State.Panels.Images.ContextIndex = 0 - } else { - gui.State.Panels.Images.ContextIndex++ - } - - _ = gui.handleImageSelect(gui.g, v) - - return nil -} - -func (gui *Gui) handleImagesPrevContext(g *gocui.Gui, v *gocui.View) error { - contexts := gui.getImageContexts() - if gui.State.Panels.Images.ContextIndex <= 0 { - gui.State.Panels.Images.ContextIndex = len(contexts) - 1 - } else { - gui.State.Panels.Images.ContextIndex-- - } - - _ = gui.handleImageSelect(gui.g, v) - - return nil -} - type removeImageOption struct { description string command string @@ -249,12 +84,12 @@ func (r *removeImageOption) GetDisplayStrings(isFocused bool) []string { } func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { - Image, err := gui.getSelectedImage() + image, err := gui.Panels.Images.GetSelectedItem() if err != nil { return nil } - shortSha := Image.ID[7:17] + shortSha := image.ID[7:17] // TODO: have a way of toggling in a menu instead of showing each permutation as a separate menu item options := []*removeImageOption{ @@ -293,7 +128,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { return nil } configOptions := options[index].configOptions - if cerr := Image.Remove(configOptions); cerr != nil { + if cerr := image.Remove(configOptions); cerr != nil { return gui.createErrorPanel(cerr.Error()) } @@ -316,7 +151,7 @@ func (gui *Gui) handlePruneImages() error { } func (gui *Gui) handleImagesCustomCommand(g *gocui.Gui, v *gocui.View) error { - image, err := gui.getSelectedImage() + image, err := gui.Panels.Images.GetSelectedItem() if err != nil { return nil } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 76d66fa1..8ffa3051 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -412,14 +412,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "images", Key: '[', Modifier: gocui.ModNone, - Handler: gui.handleImagesPrevContext, + Handler: wrappedHandler(gui.Panels.Images.OnPrevContext), Description: gui.Tr.PreviousContext, }, { ViewName: "images", Key: ']', Modifier: gocui.ModNone, - Handler: gui.handleImagesNextContext, + Handler: wrappedHandler(gui.Panels.Images.OnNextContext), Description: gui.Tr.NextContext, }, { diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 1a9b2612..b6f33cf9 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -59,14 +59,6 @@ func (gui *Gui) onFocus(v *gocui.View) { // layout is called for every screen re-render e.g. when the screen is resized func (gui *Gui) layout(g *gocui.Gui) error { - if !gui.ViewsSetup { - if err := gui.createAllViews(); err != nil { - return err - } - - gui.ViewsSetup = true - } - g.Highlight = true width, height := g.Size() @@ -116,18 +108,6 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } - if gui.g.CurrentView() == nil { - viewName := gui.initiallyFocusedViewName() - view, err := gui.g.View(viewName) - if err != nil { - return err - } - - if err := gui.switchFocus(view); err != nil { - return err - } - } - // here is a good place log some stuff // if you download humanlog and do tail -f development.log | humanlog // this will let you see these branches as prettified json @@ -147,7 +127,6 @@ func (gui *Gui) focusPointInView(view *gocui.View) { listViews := map[string]listViewState{ "containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)}, - "images": {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: gui.State.Lists.Images.Len()}, "volumes": {selectedLine: gui.State.Panels.Volumes.SelectedLine, lineCount: len(gui.DockerCommand.Volumes)}, "services": {selectedLine: gui.State.Panels.Services.SelectedLine, lineCount: len(gui.DockerCommand.Services)}, "menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}, @@ -156,6 +135,11 @@ func (gui *Gui) focusPointInView(view *gocui.View) { if state, ok := listViews[view.Name()]; ok { gui.focusY(state.selectedLine, state.lineCount, view) } + + switch view.Name() { + case "images": + gui.Panels.Images.Refocus() + } } func (gui *Gui) prepareView(viewName string) (*gocui.View, error) { diff --git a/pkg/gui/list_panel.go b/pkg/gui/list_panel.go index 64335a97..bf73813d 100644 --- a/pkg/gui/list_panel.go +++ b/pkg/gui/list_panel.go @@ -20,7 +20,7 @@ type ListPanel[T comparable] struct { } func (self *ListPanel[T]) setSelectedLineIdx(value int) { - clampedValue := -1 + clampedValue := 0 if self.list.Len() > 0 { clampedValue = utils.Clamp(value, 0, self.list.Len()-1) } @@ -57,6 +57,7 @@ type SideListPanel[T comparable] struct { // returns strings that can be filtered on getSearchStrings func(item T) []string + getId func(item T) string sort func(a, b T) bool } @@ -101,7 +102,7 @@ func (gui *Gui) getImagePanel() *SideListPanel[*commands.Image] { list: NewFilteredList[*commands.Image](), view: gui.Views.Images, }, - contextIdx: -1, // TODO: see if this should be 0 + contextIdx: 0, noItemsMessge: gui.Tr.NoImages, gui: gui.intoInterface(), contexts: []ContextConfig[*commands.Image]{ @@ -116,6 +117,9 @@ func (gui *Gui) getImagePanel() *SideListPanel[*commands.Image] { getSearchStrings: func(image *commands.Image) []string { return []string{image.Name, image.Tag} }, + getId: func(image *commands.Image) string { + return image.ID + }, sort: func(a *commands.Image, b *commands.Image) bool { if a.Name == noneLabel && b.Name != noneLabel { return false @@ -148,9 +152,9 @@ func (self *SideListPanel[T]) HandleSelect() error { return self.gui.RenderStringMain(self.noItemsMessge) } - self.gui.FocusY(self.selectedIdx, self.list.Len(), self.view) + self.Refocus() - key := self.contextKeyPrefix + "-" + self.contexts[self.contextIdx].key + key := self.contextKeyPrefix + "-" + self.getId(item) + "-" + self.contexts[self.contextIdx].key if !self.gui.ShouldRefresh(key) { return nil } @@ -172,10 +176,6 @@ func (self *SideListPanel[T]) GetContextTitles() []string { func (self *SideListPanel[T]) GetSelectedItem() (T, error) { var zero T - if self.selectedIdx == -1 { - return zero, errors.New(self.noItemsMessge) - } - item, ok := self.list.TryGet(self.selectedIdx) if !ok { // could probably have a better error here @@ -221,6 +221,10 @@ func (self *SideListPanel[T]) OnPrevContext() error { return self.HandleSelect() } +func (self *SideListPanel[T]) Refocus() { + self.gui.FocusY(self.selectedIdx, self.list.Len(), self.view) +} + func (self *SideListPanel[T]) RerenderList() error { filterString := self.gui.FilterString(self.view) @@ -245,9 +249,6 @@ func (self *SideListPanel[T]) RerenderList() error { self.list.Sort(self.sort) // TODO: use clamp? - if self.list.Len() > 0 && self.selectedIdx == -1 { - self.selectedIdx = 0 - } if self.list.Len()-1 < self.selectedIdx { self.selectedIdx = self.list.Len() - 1 } @@ -269,3 +270,7 @@ func (self *SideListPanel[T]) RerenderList() error { return nil } + +func (self *SideListPanel[T]) SetContextIndex(index int) { + self.contextIdx = index +} diff --git a/pkg/gui/main_panel.go b/pkg/gui/main_panel.go index bf985d8c..b95bdb6b 100644 --- a/pkg/gui/main_panel.go +++ b/pkg/gui/main_panel.go @@ -94,8 +94,8 @@ func (gui *Gui) onMainTabClick(tabIndex int) error { gui.State.Panels.Containers.ContextIndex = tabIndex return gui.handleContainerSelect(gui.g, gui.getContainersView()) case "images": - gui.State.Panels.Images.ContextIndex = tabIndex - return gui.handleImageSelect(gui.g, gui.getImagesView()) + gui.Panels.Images.SetContextIndex(tabIndex) + return gui.Panels.Images.HandleSelect() case "volumes": gui.State.Panels.Volumes.ContextIndex = tabIndex return gui.handleVolumeSelect(gui.g, gui.getVolumesView()) diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go index eb31f93b..c5c8db71 100644 --- a/pkg/gui/searching.go +++ b/pkg/gui/searching.go @@ -18,7 +18,7 @@ func (gui *Gui) handleOpenSearch(view *gocui.View) error { func (gui *Gui) onNewSearchString(value string) error { // need to refresh the right list panel. gui.State.Searching.searchString = value - return gui.rerenderImages() + return gui.Panels.Images.RerenderList() } func (gui *Gui) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool { @@ -46,7 +46,7 @@ func (gui *Gui) clearSearch() error { gui.State.Searching.view = nil gui.Views.Search.ClearTextArea() - return gui.rerenderImages() + return gui.Panels.Images.RerenderList() } // returns to the list view with the filter still applied diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 76bc732e..a68c0e00 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -81,7 +81,7 @@ func (gui *Gui) newLineFocused(v *gocui.View) error { case "containers": return gui.handleContainerSelect(gui.g, v) case "images": - return gui.handleImageSelect(gui.g, v) + return gui.Panels.Images.HandleSelect() case "volumes": return gui.handleVolumeSelect(gui.g, v) case "confirmation": diff --git a/pkg/gui/views.go b/pkg/gui/views.go index fa27faeb..a8ea5aec 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -139,8 +139,6 @@ func (gui *Gui) createAllViews() error { gui.Views.Search.Frame = false gui.Views.Search.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor)) - gui.waitForIntro.Done() - return nil }