mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
create filtered list struct
This commit is contained in:
parent
57379252a5
commit
cb38e48add
10 changed files with 387 additions and 63 deletions
|
|
@ -40,11 +40,11 @@ type DockerCommand struct {
|
||||||
ErrorChan chan error
|
ErrorChan chan error
|
||||||
ContainerMutex sync.Mutex
|
ContainerMutex sync.Mutex
|
||||||
ServiceMutex sync.Mutex
|
ServiceMutex sync.Mutex
|
||||||
Services []*Service
|
|
||||||
Containers []*Container
|
Services []*Service
|
||||||
|
Containers []*Container
|
||||||
// DisplayContainers is the array of containers we will display in the containers panel. If Gui.ShowAllContainers is false, this will only be those containers which aren't based on a service. This reduces clutter and duplication in the UI
|
// DisplayContainers is the array of containers we will display in the containers panel. If Gui.ShowAllContainers is false, this will only be those containers which aren't based on a service. This reduces clutter and duplication in the UI
|
||||||
DisplayContainers []*Container
|
DisplayContainers []*Container
|
||||||
Images []*Image
|
|
||||||
Volumes []*Volume
|
Volumes []*Volume
|
||||||
Closers []io.Closer
|
Closers []io.Closer
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
|
@ -103,7 +101,7 @@ func (i *Image) RenderHistory() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshImages returns a slice of docker images
|
// RefreshImages returns a slice of docker images
|
||||||
func (c *DockerCommand) RefreshImages(filterString string) ([]*Image, error) {
|
func (c *DockerCommand) RefreshImages() ([]*Image, error) {
|
||||||
images, err := c.Client.ImageList(context.Background(), types.ImageListOptions{})
|
images, err := c.Client.ImageList(context.Background(), types.ImageListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -145,32 +143,6 @@ func (c *DockerCommand) RefreshImages(filterString string) ([]*Image, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ownImages = lo.Filter(ownImages, func(image *Image, _ int) bool {
|
|
||||||
return !lo.SomeBy(c.Config.UserConfig.Ignore, func(ignore string) bool {
|
|
||||||
return strings.Contains(image.Name, ignore)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if filterString != "" {
|
|
||||||
ownImages = lo.Filter(ownImages, func(image *Image, _ int) bool {
|
|
||||||
return strings.Contains(image.Name, filterString)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
noneLabel := "<none>"
|
|
||||||
|
|
||||||
sort.Slice(ownImages, func(i, j int) bool {
|
|
||||||
if ownImages[i].Name == noneLabel && ownImages[j].Name != noneLabel {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ownImages[i].Name != noneLabel && ownImages[j].Name == noneLabel {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return ownImages[i].Name < ownImages[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
return ownImages, nil
|
return ownImages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
88
pkg/gui/filtered_list.go
Normal file
88
pkg/gui/filtered_list.go
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilteredList[T comparable] struct {
|
||||||
|
allItems []T
|
||||||
|
// indices of items in the allItems slice that are included in the filtered list
|
||||||
|
indices []int
|
||||||
|
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFilteredList[T comparable]() *FilteredList[T] {
|
||||||
|
return &FilteredList[T]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) SetItems(items []T) {
|
||||||
|
self.mutex.Lock()
|
||||||
|
defer self.mutex.Unlock()
|
||||||
|
|
||||||
|
self.allItems = items
|
||||||
|
self.indices = make([]int, len(items))
|
||||||
|
for i := range self.indices {
|
||||||
|
self.indices[i] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) Filter(filter func(T, int) bool) {
|
||||||
|
self.mutex.Lock()
|
||||||
|
defer self.mutex.Unlock()
|
||||||
|
|
||||||
|
self.indices = self.indices[:0]
|
||||||
|
for i, item := range self.allItems {
|
||||||
|
if filter(item, i) {
|
||||||
|
self.indices = append(self.indices, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) Sort(less func(T, T) bool) {
|
||||||
|
self.mutex.Lock()
|
||||||
|
defer self.mutex.Unlock()
|
||||||
|
|
||||||
|
sort.Slice(self.indices, func(i, j int) bool {
|
||||||
|
return less(self.allItems[self.indices[i]], self.allItems[self.indices[j]])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) Get(index int) T {
|
||||||
|
self.mutex.RLock()
|
||||||
|
defer self.mutex.RUnlock()
|
||||||
|
|
||||||
|
return self.allItems[self.indices[index]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the length of the filtered list
|
||||||
|
func (self *FilteredList[T]) Len() int {
|
||||||
|
self.mutex.RLock()
|
||||||
|
defer self.mutex.RUnlock()
|
||||||
|
|
||||||
|
return len(self.indices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) GetIndex(item T) int {
|
||||||
|
self.mutex.RLock()
|
||||||
|
defer self.mutex.RUnlock()
|
||||||
|
|
||||||
|
for i, index := range self.indices {
|
||||||
|
if self.allItems[index] == item {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FilteredList[T]) GetItems() []T {
|
||||||
|
self.mutex.RLock()
|
||||||
|
defer self.mutex.RUnlock()
|
||||||
|
|
||||||
|
result := make([]T, len(self.indices))
|
||||||
|
for i, index := range self.indices {
|
||||||
|
result[i] = self.allItems[index]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
183
pkg/gui/filtered_list_test.go
Normal file
183
pkg/gui/filtered_list_test.go
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilteredListGet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
args int
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: 1,
|
||||||
|
want: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: 2,
|
||||||
|
want: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{1}},
|
||||||
|
args: 0,
|
||||||
|
want: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := tt.f.Get(tt.args); got != tt.want {
|
||||||
|
t.Errorf("FilteredList.Get() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredListLen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
want: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{1}},
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := tt.f.Len(); got != tt.want {
|
||||||
|
t.Errorf("FilteredList.Len() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredListFilter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
args func(int, int) bool
|
||||||
|
want FilteredList[int]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: func(i int, _ int) bool { return i%2 == 0 },
|
||||||
|
want: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: func(i int, _ int) bool { return i%2 == 1 },
|
||||||
|
want: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 2}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt.f.Filter(tt.args)
|
||||||
|
assert.EqualValues(t, tt.f.indices, tt.want.indices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredListSort(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
args func(int, int) bool
|
||||||
|
want FilteredList[int]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: func(i int, j int) bool { return i < j },
|
||||||
|
want: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: func(i int, j int) bool { return i > j },
|
||||||
|
want: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{2, 1, 0}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt.f.Sort(tt.args)
|
||||||
|
assert.EqualValues(t, tt.f.indices, tt.want.indices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredListGetIndex(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
args int
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: 1,
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: 2,
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{1}},
|
||||||
|
args: 0,
|
||||||
|
want: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := tt.f.GetIndex(tt.args); got != tt.want {
|
||||||
|
t.Errorf("FilteredList.GetIndex() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredListGetItems(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
want []int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
want: []int{1, 2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{1}},
|
||||||
|
want: []int{2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := tt.f.GetItems()
|
||||||
|
assert.EqualValues(t, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredListSetItems(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f FilteredList[int]
|
||||||
|
args []int
|
||||||
|
want FilteredList[int]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{0, 1, 2}},
|
||||||
|
args: []int{4, 5, 6},
|
||||||
|
want: FilteredList[int]{allItems: []int{4, 5, 6}, indices: []int{0, 1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
f: FilteredList[int]{allItems: []int{1, 2, 3}, indices: []int{1}},
|
||||||
|
args: []int{4},
|
||||||
|
want: FilteredList[int]{allItems: []int{4}, indices: []int{0}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt.f.SetItems(tt.args)
|
||||||
|
assert.EqualValues(t, tt.f.indices, tt.want.indices)
|
||||||
|
assert.EqualValues(t, tt.f.allItems, tt.want.allItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -137,6 +137,16 @@ type guiState struct {
|
||||||
ScreenMode WindowMaximisation
|
ScreenMode WindowMaximisation
|
||||||
|
|
||||||
Searching searchingState
|
Searching searchingState
|
||||||
|
|
||||||
|
Lists Lists
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are the items we display, after filtering is applied.
|
||||||
|
type Lists struct {
|
||||||
|
Containers *FilteredList[*commands.Container]
|
||||||
|
Services *FilteredList[*commands.Service]
|
||||||
|
Images *FilteredList[*commands.Image]
|
||||||
|
Volumes *FilteredList[*commands.Volume]
|
||||||
}
|
}
|
||||||
|
|
||||||
type searchingState struct {
|
type searchingState struct {
|
||||||
|
|
@ -173,6 +183,12 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
|
||||||
Project: &projectState{ContextIndex: 0},
|
Project: &projectState{ContextIndex: 0},
|
||||||
},
|
},
|
||||||
ViewStack: []string{},
|
ViewStack: []string{},
|
||||||
|
Lists: Lists{
|
||||||
|
Containers: NewFilteredList[*commands.Container](),
|
||||||
|
Services: NewFilteredList[*commands.Service](),
|
||||||
|
Images: NewFilteredList[*commands.Image](),
|
||||||
|
Volumes: NewFilteredList[*commands.Volume](),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cyclableViews := []string{"project", "containers", "images", "volumes"}
|
cyclableViews := []string{"project", "containers", "images", "volumes"}
|
||||||
|
|
@ -313,7 +329,7 @@ func (gui *Gui) refresh() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
if err := gui.refreshImages(); err != nil {
|
if err := gui.reloadImages(); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.Log.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"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/utils"
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
|
|
@ -30,11 +31,11 @@ func (gui *Gui) getSelectedImage() (*commands.Image, error) {
|
||||||
return &commands.Image{}, gui.Errors.ErrNoImages
|
return &commands.Image{}, gui.Errors.ErrNoImages
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.DockerCommand.Images[selectedLine], nil
|
return gui.State.Lists.Images.Get(selectedLine), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleImagesClick(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleImagesClick(g *gocui.Gui, v *gocui.View) error {
|
||||||
itemCount := len(gui.DockerCommand.Images)
|
itemCount := gui.State.Lists.Images.Len()
|
||||||
handleSelect := gui.handleImageSelect
|
handleSelect := gui.handleImageSelect
|
||||||
selectedLine := &gui.State.Panels.Images.SelectedLine
|
selectedLine := &gui.State.Panels.Images.SelectedLine
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@ func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.renderString(g, "main", gui.Tr.NoImages)
|
return gui.renderString(g, "main", gui.Tr.NoImages)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.focusY(gui.State.Panels.Images.SelectedLine, len(gui.DockerCommand.Images), v)
|
gui.focusY(gui.State.Panels.Images.SelectedLine, gui.State.Lists.Images.Len(), v)
|
||||||
|
|
||||||
key := "images-" + Image.ID + "-" + gui.getImageContexts()[gui.State.Panels.Images.ContextIndex]
|
key := "images-" + Image.ID + "-" + gui.getImageContexts()[gui.State.Panels.Images.ContextIndex]
|
||||||
if !gui.shouldRefresh(key) {
|
if !gui.shouldRefresh(key) {
|
||||||
|
|
@ -97,34 +98,63 @@ func (gui *Gui) renderImageConfig(mainView *gocui.View, image *commands.Image) e
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshImages() error {
|
func (gui *Gui) reloadImages() error {
|
||||||
ImagesView := gui.getImagesView()
|
|
||||||
if ImagesView == nil {
|
|
||||||
// if the ImagesView hasn't been instantiated yet we just return
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := gui.refreshStateImages(); err != nil {
|
if err := gui.refreshStateImages(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gui.DockerCommand.Images) > 0 && gui.State.Panels.Images.SelectedLine == -1 {
|
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 := "<none>"
|
||||||
|
|
||||||
|
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
|
gui.State.Panels.Images.SelectedLine = 0
|
||||||
}
|
}
|
||||||
if len(gui.DockerCommand.Images)-1 < gui.State.Panels.Images.SelectedLine {
|
if gui.State.Lists.Images.Len()-1 < gui.State.Panels.Images.SelectedLine {
|
||||||
gui.State.Panels.Images.SelectedLine = len(gui.DockerCommand.Images) - 1
|
gui.State.Panels.Images.SelectedLine = gui.State.Lists.Images.Len() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
ImagesView.Clear()
|
gui.Views.Images.Clear()
|
||||||
isFocused := gui.g.CurrentView() == gui.Views.Images
|
isFocused := gui.g.CurrentView() == gui.Views.Images
|
||||||
list, err := utils.RenderList(gui.DockerCommand.Images, utils.IsFocused(isFocused))
|
list, err := utils.RenderList(gui.State.Lists.Images.GetItems(), utils.IsFocused(isFocused))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprint(ImagesView, list)
|
fmt.Fprint(gui.Views.Images, list)
|
||||||
|
|
||||||
if ImagesView == g.CurrentView() {
|
if gui.Views.Images == g.CurrentView() {
|
||||||
return gui.handleImageSelect(g, ImagesView)
|
return gui.handleImageSelect(g, gui.Views.Images)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
@ -132,14 +162,14 @@ func (gui *Gui) refreshImages() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: leave this to DockerCommand
|
|
||||||
func (gui *Gui) refreshStateImages() error {
|
func (gui *Gui) refreshStateImages() error {
|
||||||
Images, err := gui.DockerCommand.RefreshImages(gui.filterString(gui.Views.Images))
|
images, err := gui.DockerCommand.RefreshImages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.DockerCommand.Images = Images
|
// TODO: think about also re-filtering/sorting
|
||||||
|
gui.State.Lists.Images.SetItems(images)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +188,7 @@ func (gui *Gui) handleImagesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
panelState := gui.State.Panels.Images
|
panelState := gui.State.Panels.Images
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Images), false)
|
gui.changeSelectedLine(&panelState.SelectedLine, gui.State.Lists.Images.Len(), false)
|
||||||
|
|
||||||
return gui.handleImageSelect(gui.g, v)
|
return gui.handleImageSelect(gui.g, v)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +199,7 @@ func (gui *Gui) handleImagesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
panelState := gui.State.Panels.Images
|
panelState := gui.State.Panels.Images
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Images), true)
|
gui.changeSelectedLine(&panelState.SelectedLine, gui.State.Lists.Images.Len(), true)
|
||||||
|
|
||||||
return gui.handleImageSelect(gui.g, v)
|
return gui.handleImageSelect(gui.g, v)
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +304,7 @@ func (gui *Gui) handlePruneImages() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.createErrorPanel(err.Error())
|
return gui.createErrorPanel(err.Error())
|
||||||
}
|
}
|
||||||
return gui.refreshImages()
|
return gui.reloadImages()
|
||||||
})
|
})
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -516,6 +516,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.scrollRightMain,
|
Handler: gui.scrollRightMain,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "search",
|
||||||
|
Key: gocui.KeyEnter,
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: wrappedHandler(gui.commitSearch),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ViewName: "search",
|
||||||
|
Key: gocui.KeyEsc,
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: wrappedHandler(gui.escapeSearchPrompt),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
Key: 'J',
|
Key: 'J',
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ func (gui *Gui) focusPointInView(view *gocui.View) {
|
||||||
|
|
||||||
listViews := map[string]listViewState{
|
listViews := map[string]listViewState{
|
||||||
"containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)},
|
"containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)},
|
||||||
"images": {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: len(gui.DockerCommand.Images)},
|
"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)},
|
"volumes": {selectedLine: gui.State.Panels.Volumes.SelectedLine, lineCount: len(gui.DockerCommand.Volumes)},
|
||||||
"services": {selectedLine: gui.State.Panels.Services.SelectedLine, lineCount: len(gui.DockerCommand.Services)},
|
"services": {selectedLine: gui.State.Panels.Services.SelectedLine, lineCount: len(gui.DockerCommand.Services)},
|
||||||
"menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
|
"menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package gui
|
package gui
|
||||||
|
|
||||||
import "github.com/jesseduffield/gocui"
|
import (
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
)
|
||||||
|
|
||||||
func (gui *Gui) handleOpenImageSearch() error {
|
func (gui *Gui) handleOpenImageSearch() error {
|
||||||
return gui.handleOpenSearch(gui.Views.Images)
|
return gui.handleOpenSearch(gui.Views.Images)
|
||||||
|
|
@ -10,23 +12,44 @@ func (gui *Gui) handleOpenSearch(view *gocui.View) error {
|
||||||
gui.State.Searching.isSearching = true
|
gui.State.Searching.isSearching = true
|
||||||
gui.State.Searching.view = view
|
gui.State.Searching.view = view
|
||||||
|
|
||||||
gui.Views.Search.ClearTextArea()
|
|
||||||
|
|
||||||
return gui.switchFocus(gui.Views.Search)
|
return gui.switchFocus(gui.Views.Search)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) onNewSearchString(value string) error {
|
func (gui *Gui) onNewSearchString(value string) error {
|
||||||
// need to refresh the right list panel.
|
// need to refresh the right list panel.
|
||||||
gui.State.Searching.searchString = value
|
gui.State.Searching.searchString = value
|
||||||
return gui.refreshImages()
|
return gui.rerenderImages()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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 {
|
||||||
return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
|
return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
|
||||||
matched := f(v, key, ch, mod)
|
matched := f(v, key, ch, mod)
|
||||||
if matched {
|
if matched {
|
||||||
gui.onNewSearchString(v.TextArea.GetContent())
|
// TODO: handle error
|
||||||
|
_ = gui.onNewSearchString(v.TextArea.GetContent())
|
||||||
}
|
}
|
||||||
return matched
|
return matched
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) escapeSearchPrompt() error {
|
||||||
|
if err := gui.clearSearch(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.returnFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) clearSearch() error {
|
||||||
|
gui.State.Searching.searchString = ""
|
||||||
|
gui.State.Searching.isSearching = false
|
||||||
|
gui.State.Searching.view = nil
|
||||||
|
gui.Views.Search.ClearTextArea()
|
||||||
|
|
||||||
|
return gui.rerenderImages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns to the list view with the filter still applied
|
||||||
|
func (gui *Gui) commitSearch() error {
|
||||||
|
return gui.returnFocus()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ func (gui *Gui) createAllViews() error {
|
||||||
gui.Views.Search.FgColor = gocui.ColorGreen
|
gui.Views.Search.FgColor = gocui.ColorGreen
|
||||||
gui.Views.Search.Editable = true
|
gui.Views.Search.Editable = true
|
||||||
gui.Views.Search.Frame = false
|
gui.Views.Search.Frame = false
|
||||||
// gui.Views.Search.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
|
gui.Views.Search.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
|
||||||
|
|
||||||
gui.waitForIntro.Done()
|
gui.waitForIntro.Done()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue