diff --git a/docs/Config.md b/docs/Config.md index 92a8a3ed..fecb7d3c 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -28,6 +28,14 @@ gui: - blue returnImmediately: false wrapMainPanel: true + # Side panel width as a ratio of the screen's width + sidePanelWidth: 0.333 + # Determines whether we show the bottom line (the one containing keybinding + # info and the status of the app). + showBottomLine: true + # When true, increases vertical space used by focused side panel, + # creating an accordion effect + expandFocusedSidePanel: false logs: timestamps: false since: '60m' diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 9094104f..78064067 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -122,7 +122,8 @@ type GuiConfig struct { // info and the status of the app). ShowBottomLine bool `yaml:"showBottomLine"` - // When true, ensures that the focused side panel always takes up the full vertical space + // When true, increases vertical space used by focused side panel, + // creating an accordion effect ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"` } diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go index 20a81120..f806f72c 100644 --- a/pkg/gui/app_status_manager.go +++ b/pkg/gui/app_status_manager.go @@ -73,7 +73,7 @@ func (gui *Gui) WithWaitingStatus(name string, f func() error) error { if err := f(); err != nil { gui.g.Update(func(g *gocui.Gui) error { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) }) } }() diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index df717341..f507685b 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -63,17 +63,6 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map return boxlayout.ArrangeWindows(root, 0, 0, width, height) } -func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { - result := map[K]V{} - for _, currMap := range maps { - for key, value := range currMap { - result[key] = value - } - } - - return result -} - func (gui *Gui) getMidSectionWeights() (int, int) { currentWindow := gui.currentStaticWindowName() @@ -203,16 +192,3 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box { }) } } - -func (gui *Gui) currentSideWindowName() string { - // we expect that there is a side window somewhere in the view stack, so we will search from top to bottom - for idx := range gui.State.ViewStack { - reversedIdx := len(gui.State.ViewStack) - 1 - idx - viewName := gui.State.ViewStack[reversedIdx] - if lo.Contains(gui.sideViewNames(), viewName) { - return viewName - } - } - - return gui.initiallyFocusedViewName() -} diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index c515fd54..f9e1ec74 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -62,14 +62,14 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt s height/2 + panelHeight/2 } -func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { +func (gui *Gui) createPromptPanel(title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { gui.onNewPopupPanel() err := gui.prepareConfirmationPanel(title, "", false) if err != nil { return err } gui.Views.Confirmation.Editable = true - return gui.setKeyBindings(g, handleConfirm, nil) + return gui.setKeyBindings(gui.g, handleConfirm, nil) } func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool) error { @@ -143,7 +143,7 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go return nil } -func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error { +func (gui *Gui) createErrorPanel(message string) error { colorFunction := color.New(color.FgRed).SprintFunc() coloredMessage := colorFunction(strings.TrimSpace(message)) return gui.createConfirmationPanel(gui.Tr.ErrorTitle, coloredMessage, nil, nil) diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 3dc9bd2f..4f6bb941 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -197,7 +197,7 @@ func (gui *Gui) renderContainerStats(container *commands.Container) error { contents, err := container.RenderStats(width) if err != nil { - _ = gui.createErrorPanel(gui.g, err.Error()) + _ = gui.createErrorPanel(err.Error()) } gui.reRenderStringMain(contents) @@ -415,7 +415,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error { }) }, nil) } - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil }) @@ -433,7 +433,7 @@ func (gui *Gui) PauseContainer(container *commands.Container) error { } if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.refreshContainersAndServices() @@ -458,7 +458,7 @@ func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.StopContainer, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error { if err := container.Stop(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil @@ -474,7 +474,7 @@ func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { if err := container.Restart(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil @@ -489,7 +489,7 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error { c, err := container.Attach() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.runSubprocess(c) @@ -500,7 +500,7 @@ func (gui *Gui) handlePruneContainers() error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { err := gui.DockerCommand.PruneContainers() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil }) diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index da03eed9..6038b6b2 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -58,7 +58,7 @@ func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, command return gui.WithWaitingStatus(waitingStatus, func() error { if err := gui.OSCommand.RunCommand(option.command); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil }) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 94aa4c0d..69245453 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -269,7 +269,7 @@ func (gui *Gui) Run() error { gui.Log.Warn(err) continue } - _ = gui.createErrorPanel(gui.g, err.Error()) + _ = gui.createErrorPanel(err.Error()) } }() @@ -407,7 +407,7 @@ func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) editFile(filename string) error { cmd, err := gui.OSCommand.EditFile(filename) if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.runSubprocess(cmd) @@ -415,13 +415,13 @@ func (gui *Gui) editFile(filename string) error { func (gui *Gui) openFile(filename string) error { if err := gui.OSCommand.OpenFile(filename); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil } func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error { - return gui.createPromptPanel(g, v, gui.Tr.CustomCommandTitle, func(g *gocui.Gui, v *gocui.View) error { + return gui.createPromptPanel(gui.Tr.CustomCommandTitle, func(g *gocui.Gui, v *gocui.View) error { command := gui.trimmedContent(v) return gui.runSubprocess(gui.OSCommand.RunCustomCommand(command)) }) diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index 2f513ab5..9e2bd8cb 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -250,7 +250,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { } configOptions := options[index].configOptions if cerr := Image.Remove(configOptions); cerr != nil { - return gui.createErrorPanel(gui.g, cerr.Error()) + return gui.createErrorPanel(cerr.Error()) } return nil @@ -264,7 +264,7 @@ func (gui *Gui) handlePruneImages() error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { err := gui.DockerCommand.PruneImages() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.refreshImages() }) diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 11e86758..134076ce 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -109,7 +109,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return view, err } - for _, viewName := range []string{"project", "services", "containers", "images", "volumes", "options", "information", "appStatus", "main", "limit"} { + for _, viewName := range gui.controlledBoundsViewNames() { _, err := setViewFromDimensions(viewName, viewName) if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { return err diff --git a/pkg/gui/project_panel.go b/pkg/gui/project_panel.go index 3dd14175..0ae98e9d 100644 --- a/pkg/gui/project_panel.go +++ b/pkg/gui/project_panel.go @@ -221,7 +221,7 @@ func (gui *Gui) handleProjectPrevContext(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error { c, err := gui.DockerCommand.ViewAllLogs() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.runSubprocess(c) diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 16accf28..76d0116b 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -240,7 +240,7 @@ func (gui *Gui) handleServiceStop(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.StopService, func(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error { if err := service.Stop(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil @@ -256,7 +256,7 @@ func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { if err := service.Restart(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil @@ -271,7 +271,7 @@ func (gui *Gui) handleServiceStart(g *gocui.Gui, v *gocui.View) error { return gui.WithWaitingStatus(gui.Tr.StartingStatus, func() error { if err := service.Start(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil @@ -285,12 +285,12 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error { } if service.Container == nil { - return gui.createErrorPanel(gui.g, gui.Tr.NoContainers) + return gui.createErrorPanel(gui.Tr.NoContainers) } c, err := service.Attach() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.runSubprocess(c) @@ -304,7 +304,7 @@ func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error c, err := service.ViewLogs() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return gui.runSubprocess(c) @@ -336,7 +336,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { f: func() error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { if err := service.Restart(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil }) @@ -351,7 +351,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { f: func() error { return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error { if err := gui.OSCommand.RunCommand(recreateCommand); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil }) @@ -385,7 +385,7 @@ func (gui *Gui) createServiceCommandMenu(options []*commandOption, status string } return gui.WithWaitingStatus(status, func() error { if err := gui.OSCommand.RunCommand(options[index].command); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil @@ -447,7 +447,7 @@ func (gui *Gui) handleServicesExecShell(g *gocui.Gui, v *gocui.View) error { container := service.Container if container == nil { - return gui.createErrorPanel(gui.g, gui.Tr.NoContainers) + return gui.createErrorPanel(gui.Tr.NoContainers) } return gui.containerExecShell(container) @@ -461,7 +461,7 @@ func (gui *Gui) handleServicesOpenInBrowserCommand(g *gocui.Gui, v *gocui.View) container := service.Container if container == nil { - return gui.createErrorPanel(gui.g, gui.Tr.NoContainers) + return gui.createErrorPanel(gui.Tr.NoContainers) } return gui.openContainerInBrowser(container) diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go index 54af269d..8366393c 100644 --- a/pkg/gui/subprocess.go +++ b/pkg/gui/subprocess.go @@ -17,27 +17,23 @@ func (gui *Gui) runSubprocess(cmd *exec.Cmd) error { defer gui.Mutexes.SubprocessMutex.Unlock() if err := gui.g.Suspend(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } gui.PauseBackgroundThreads = true - cmdErr := gui.runCommand(cmd) + gui.runCommand(cmd) if err := gui.g.Resume(); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } gui.PauseBackgroundThreads = false - if cmdErr != nil { - return gui.createErrorPanel(gui.g, cmdErr.Error()) - } - return nil } -func (gui *Gui) runCommand(cmd *exec.Cmd) error { +func (gui *Gui) runCommand(cmd *exec.Cmd) { cmd.Stdout = os.Stdout cmd.Stderr = os.Stdout cmd.Stdin = os.Stdin @@ -67,6 +63,4 @@ func (gui *Gui) runCommand(cmd *exec.Cmd) error { cmd.Stderr = ioutil.Discard gui.promptToReturn() - - return nil } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 740ccbc1..000118d3 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -153,7 +153,6 @@ func (gui *Gui) pushView(name string) { }) gui.State.ViewStack = append(gui.State.ViewStack, name) - gui.Log.Warnf("Just pushed %s. View stack is now "+strings.Join(gui.State.ViewStack, ", "), name) } // excludes popups @@ -170,6 +169,22 @@ func (gui *Gui) currentStaticViewName() string { return gui.initiallyFocusedViewName() } +func (gui *Gui) currentSideViewName() string { + gui.Mutexes.ViewStackMutex.Lock() + defer gui.Mutexes.ViewStackMutex.Unlock() + + // we expect that there is a side window somewhere in the view stack, so we will search from top to bottom + for idx := range gui.State.ViewStack { + reversedIdx := len(gui.State.ViewStack) - 1 - idx + viewName := gui.State.ViewStack[reversedIdx] + if lo.Contains(gui.sideViewNames(), viewName) { + return viewName + } + } + + return gui.initiallyFocusedViewName() +} + // 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) { diff --git a/pkg/gui/views.go b/pkg/gui/views.go index 6a7ed3e6..4b568ed0 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -3,7 +3,6 @@ package gui import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" - "github.com/samber/lo" ) type Views struct { @@ -28,12 +27,6 @@ type viewNameMapping struct { name string } -func (gui *Gui) orderedViews() []*gocui.View { - return lo.Map(gui.orderedViewNameMappings(), func(v viewNameMapping, _ int) *gocui.View { - return *v.viewPtr - }) -} - func (gui *Gui) orderedViewNameMappings() []viewNameMapping { return []viewNameMapping{ // first layer. Ordering within this layer does not matter because there are @@ -131,3 +124,8 @@ func (gui *Gui) getInformationContent() string { func (gui *Gui) popupViewNames() []string { return []string{"confirmation", "menu"} } + +// these views have their position and size determined by arrangement.go +func (gui *Gui) controlledBoundsViewNames() []string { + return []string{"project", "services", "containers", "images", "volumes", "options", "information", "appStatus", "main", "limit"} +} diff --git a/pkg/gui/volumes_panel.go b/pkg/gui/volumes_panel.go index d052856d..7d2cf936 100644 --- a/pkg/gui/volumes_panel.go +++ b/pkg/gui/volumes_panel.go @@ -229,7 +229,7 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error { } return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { if cerr := volume.Remove(options[index].force); cerr != nil { - return gui.createErrorPanel(gui.g, cerr.Error()) + return gui.createErrorPanel(cerr.Error()) } return nil }) @@ -243,7 +243,7 @@ func (gui *Gui) handlePruneVolumes() error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { err := gui.DockerCommand.PruneVolumes() if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) + return gui.createErrorPanel(err.Error()) } return nil }) diff --git a/pkg/gui/window.go b/pkg/gui/window.go index f596a2b2..d58d132f 100644 --- a/pkg/gui/window.go +++ b/pkg/gui/window.go @@ -1,12 +1,16 @@ package gui -func (gui *Gui) currentWindow() string { - // at the moment, we only have one view per window in lazydocker, so we - // are using the view name as the window name - return gui.currentViewName() -} +// func (gui *Gui) currentWindow() string { +// // at the moment, we only have one view per window in lazydocker, so we +// // are using the view name as the window name +// return gui.currentViewName() +// } // excludes popups func (gui *Gui) currentStaticWindowName() string { return gui.currentStaticViewName() } + +func (gui *Gui) currentSideWindowName() string { + return gui.currentSideViewName() +}