update prompt. Fix #649

mine
derailed 2020-04-04 21:53:57 -06:00
parent 31020fcbad
commit a6ec74ff6c
28 changed files with 697 additions and 709 deletions

View File

@ -1,5 +1,5 @@
# Build...
FROM golang:1.13.6-alpine3.11 AS build
FROM golang:1.14.1 AS build
WORKDIR /k9s
COPY go.mod go.sum main.go Makefile ./

6
go.mod
View File

@ -2,10 +2,12 @@ module github.com/derailed/k9s
go 1.13
replace helm.sh/helm/v3 => /Users/fernand/go_wk/derailed/src/github.com/derailed/tmp/helm
require (
github.com/atotto/clipboard v0.1.2
github.com/derailed/popeye v0.8.0
github.com/derailed/tview v0.3.9
github.com/derailed/popeye v0.8.1
github.com/derailed/tview v0.3.10
github.com/drone/envsubst v1.0.2 // indirect
github.com/fatih/color v1.9.0
github.com/fsnotify/fsnotify v1.4.7

10
go.sum
View File

@ -126,8 +126,12 @@ github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/derailed/popeye v0.8.0 h1:D+5fHiMmuXqaF5J2bJTI+YLrD77ag5Wb1dkAuR4bPGI=
github.com/derailed/popeye v0.8.0/go.mod h1:OBHcJDa50VpE9QNyOU243bNOtHb29MyLlVHJolwlwas=
github.com/derailed/popeye v0.8.1 h1:N69XH0NZTBkrNj8qvUzy6Z6bP7+jx0AwollETqvc3dc=
github.com/derailed/popeye v0.8.1/go.mod h1:OBHcJDa50VpE9QNyOU243bNOtHb29MyLlVHJolwlwas=
github.com/derailed/tview v0.3.9 h1:6iUtOmzN6gdk6yx1KNSwhMgrsLYjgldduulKPqHnqwk=
github.com/derailed/tview v0.3.9/go.mod h1:GJ3k/TIzEE+sj1L09/usk6HrkjsdadSsb03eHgPbcII=
github.com/derailed/tview v0.3.10 h1:n+iQwYh9Ff9STdR5hBhp+rTJRlu59q2xP2pHvwQbYPw=
github.com/derailed/tview v0.3.10/go.mod h1:GJ3k/TIzEE+sj1L09/usk6HrkjsdadSsb03eHgPbcII=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -371,6 +375,7 @@ github.com/mattn/go-runewidth v0.0.5/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
@ -691,6 +696,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
@ -726,10 +732,13 @@ k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ=
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss=
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
k8s.io/apiextensions-apiserver v0.18.0 h1:HN4/P8vpGZFvB5SOMuPPH2Wt9Y/ryX+KRvIyAkchu1Q=
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE=
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=
k8s.io/cli-runtime v0.18.0 h1:jG8XpSqQ5TrV0N+EZ3PFz6+gqlCk71dkggWCCq9Mq34=
k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ=
@ -767,6 +776,7 @@ modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=

View File

@ -1,124 +1,124 @@
package dao
// BOZO!! v1.18.0
// import (
// "context"
// "fmt"
// "os"
import (
"context"
"fmt"
"os"
// "github.com/derailed/k9s/internal/client"
// "github.com/derailed/k9s/internal/render"
// "github.com/rs/zerolog/log"
// "helm.sh/helm/v3/pkg/action"
// "k8s.io/apimachinery/pkg/runtime"
// )
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
"helm.sh/helm/v3/pkg/action"
"k8s.io/apimachinery/pkg/runtime"
)
// var (
// _ Accessor = (*Chart)(nil)
// _ Nuker = (*Chart)(nil)
// _ Describer = (*Chart)(nil)
// )
var (
_ Accessor = (*Chart)(nil)
_ Nuker = (*Chart)(nil)
_ Describer = (*Chart)(nil)
)
// // Chart represents a helm chart.
// type Chart struct {
// NonResource
// }
// Chart represents a helm chart.
type Chart struct {
NonResource
}
// // List returns a collection of resources.
// func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
// cfg, err := c.EnsureHelmConfig(ns)
// if err != nil {
// return nil, err
// }
// List returns a collection of resources.
func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) {
cfg, err := c.EnsureHelmConfig(ns)
if err != nil {
return nil, err
}
// rr, err := action.NewList(cfg).Run()
// if err != nil {
// return nil, err
// }
rr, err := action.NewList(cfg).Run()
if err != nil {
return nil, err
}
// oo := make([]runtime.Object, 0, len(rr))
// for _, r := range rr {
// oo = append(oo, render.ChartRes{Release: r})
// }
oo := make([]runtime.Object, 0, len(rr))
for _, r := range rr {
oo = append(oo, render.ChartRes{Release: r})
}
// return oo, nil
// }
return oo, nil
}
// // Get returns a resource.
// func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) {
// ns, n := client.Namespaced(path)
// cfg, err := c.EnsureHelmConfig(ns)
// if err != nil {
// return nil, err
// }
// resp, err := action.NewGet(cfg).Run(n)
// if err != nil {
// return nil, err
// }
// Get returns a resource.
func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) {
ns, n := client.Namespaced(path)
cfg, err := c.EnsureHelmConfig(ns)
if err != nil {
return nil, err
}
resp, err := action.NewGet(cfg).Run(n)
if err != nil {
return nil, err
}
// return render.ChartRes{Release: resp}, nil
// }
return render.ChartRes{Release: resp}, nil
}
// // Describe returns the chart notes.
// func (c *Chart) Describe(path string) (string, error) {
// ns, n := client.Namespaced(path)
// cfg, err := c.EnsureHelmConfig(ns)
// if err != nil {
// return "", err
// }
// resp, err := action.NewGet(cfg).Run(n)
// if err != nil {
// return "", err
// }
// Describe returns the chart notes.
func (c *Chart) Describe(path string) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := c.EnsureHelmConfig(ns)
if err != nil {
return "", err
}
resp, err := action.NewGet(cfg).Run(n)
if err != nil {
return "", err
}
// return resp.Info.Notes, nil
// }
return resp.Info.Notes, nil
}
// // ToYAML returns the chart manifest.
// func (c *Chart) ToYAML(path string) (string, error) {
// ns, n := client.Namespaced(path)
// cfg, err := c.EnsureHelmConfig(ns)
// if err != nil {
// return "", err
// }
// resp, err := action.NewGet(cfg).Run(n)
// if err != nil {
// return "", err
// }
// ToYAML returns the chart manifest.
func (c *Chart) ToYAML(path string) (string, error) {
ns, n := client.Namespaced(path)
cfg, err := c.EnsureHelmConfig(ns)
if err != nil {
return "", err
}
resp, err := action.NewGet(cfg).Run(n)
if err != nil {
return "", err
}
// return resp.Manifest, nil
// }
return resp.Manifest, nil
}
// // Delete uninstall a Chart.
// func (c *Chart) Delete(path string, cascade, force bool) error {
// ns, n := client.Namespaced(path)
// cfg, err := c.EnsureHelmConfig(ns)
// if err != nil {
// return err
// }
// Delete uninstall a Chart.
func (c *Chart) Delete(path string, cascade, force bool) error {
ns, n := client.Namespaced(path)
cfg, err := c.EnsureHelmConfig(ns)
if err != nil {
return err
}
// res, err := action.NewUninstall(cfg).Run(n)
// if err != nil {
// return err
// }
res, err := action.NewUninstall(cfg).Run(n)
if err != nil {
return err
}
// if res != nil && res.Info != "" {
// return fmt.Errorf("%s", res.Info)
// }
if res != nil && res.Info != "" {
return fmt.Errorf("%s", res.Info)
}
// return nil
// }
return nil
}
// // EnsureHelmConfig return a new configuration.
// func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) {
// cfg := new(action.Configuration)
// flags := c.Client().Config().Flags()
// if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil {
// return nil, err
// }
// return cfg, nil
// }
// EnsureHelmConfig return a new configuration.
func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) {
cfg := new(action.Configuration)
flags := c.Client().Config().Flags()
if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil {
return nil, err
}
return cfg, nil
}
// func helmLogger(s string, args ...interface{}) {
// log.Debug().Msgf("%s %v", s, args)
// }
func helmLogger(s string, args ...interface{}) {
log.Debug().Msgf("%s %v", s, args)
}

View File

@ -50,6 +50,9 @@ func (readWriteCloser) Close() error {
func (p *Popeye) List(ctx context.Context, _ string) ([]runtime.Object, error) {
defer func(t time.Time) {
log.Debug().Msgf("Popeye -- Elapsed %v", time.Since(t))
if err := recover(); err != nil {
log.Debug().Msgf("POPEYE DIED!")
}
}(time.Now())
flags := config.NewFlags()

View File

@ -4,9 +4,9 @@ const maxBuff = 10
const (
// Command represents a command buffer.
Command BufferKind = 1 << iota
CommandBuffer BufferKind = 1 << iota
// Filter represents a filter buffer.
Filter
FilterBuffer
)
// BufferKind indicates a buffer type
@ -40,6 +40,29 @@ func NewCmdBuff(key rune, kind BufferKind) *CmdBuff {
}
}
func (c *CmdBuff) CurrentSuggestion() (string, bool) {
return "", false
}
func (c *CmdBuff) NextSuggestion() (string, bool) {
return "", false
}
func (c *CmdBuff) PrevSuggestion() (string, bool) {
return "", false
}
func (c *CmdBuff) ClearSuggestions() {}
func (c *CmdBuff) AutoSuggests() bool {
return false
}
// Suggestions returns suggestions.
func (c *CmdBuff) Suggestions() []string {
return nil
}
// Notify notifies all listener of current suggestions.
func (c *CmdBuff) Notify() {}
// InCmdMode checks if a command exists and the buffer is active.
func (c *CmdBuff) InCmdMode() bool {
return c.active || len(c.buff) > 0
@ -56,21 +79,21 @@ func (c *CmdBuff) SetActive(b bool) {
c.fireActive(c.active)
}
// String turns rune to string (Stringer protocol)
func (c *CmdBuff) String() string {
// GetText returns the current text.
func (c *CmdBuff) GetText() string {
return string(c.buff)
}
// Set initializes the buffer with a command.
func (c *CmdBuff) Set(cmd string) {
func (c *CmdBuff) SetText(cmd string) {
c.buff = []rune(cmd)
c.fireChanged()
c.fireBufferChanged()
}
// Add adds a new charater to the buffer.
func (c *CmdBuff) Add(r rune) {
c.buff = append(c.buff, r)
c.fireChanged()
c.fireBufferChanged()
}
// Delete removes the last character from the buffer.
@ -79,19 +102,19 @@ func (c *CmdBuff) Delete() {
return
}
c.buff = c.buff[:len(c.buff)-1]
c.fireChanged()
c.fireBufferChanged()
}
// Clear clears out command buffer.
func (c *CmdBuff) Clear() {
// ClearText clears out command buffer.
func (c *CmdBuff) ClearText() {
c.buff = make([]rune, 0, maxBuff)
c.fireChanged()
c.fireBufferChanged()
}
// Reset clears out the command buffer and deactives it.
// Reset clears out the command buffer and deactivates it.
func (c *CmdBuff) Reset() {
c.Clear()
c.fireChanged()
c.ClearText()
c.fireBufferChanged()
c.SetActive(false)
}
@ -104,8 +127,8 @@ func (c *CmdBuff) Empty() bool {
// Event Listeners...
// AddListener registers a cmd buffer listener.
func (c *CmdBuff) AddListener(w ...BuffWatcher) {
c.listeners = append(c.listeners, w...)
func (c *CmdBuff) AddListener(w BuffWatcher) {
c.listeners = append(c.listeners, w)
}
// RemoveListener unregisters a listener.
@ -124,9 +147,9 @@ func (c *CmdBuff) RemoveListener(l BuffWatcher) {
c.listeners = append(c.listeners[:victim], c.listeners[victim+1:]...)
}
func (c *CmdBuff) fireChanged() {
func (c *CmdBuff) fireBufferChanged() {
for _, l := range c.listeners {
l.BufferChanged(c.String())
l.BufferChanged(c.GetText())
}
}

View File

@ -26,7 +26,7 @@ func (l *testListener) BufferActive(s bool, _ model.BufferKind) {
}
func TestCmdBuffActivate(t *testing.T) {
b, l := model.NewCmdBuff('>', model.Command), testListener{}
b, l := model.NewCmdBuff('>', model.CommandBuffer), testListener{}
b.AddListener(&l)
b.SetActive(true)
@ -36,7 +36,7 @@ func TestCmdBuffActivate(t *testing.T) {
}
func TestCmdBuffDeactivate(t *testing.T) {
b, l := model.NewCmdBuff('>', model.Command), testListener{}
b, l := model.NewCmdBuff('>', model.CommandBuffer), testListener{}
b.AddListener(&l)
b.SetActive(false)
@ -46,39 +46,39 @@ func TestCmdBuffDeactivate(t *testing.T) {
}
func TestCmdBuffChanged(t *testing.T) {
b, l := model.NewCmdBuff('>', model.Command), testListener{}
b, l := model.NewCmdBuff('>', model.CommandBuffer), testListener{}
b.AddListener(&l)
b.Add('b')
assert.Equal(t, 0, l.act)
assert.Equal(t, 0, l.inact)
assert.Equal(t, "b", l.text)
assert.Equal(t, "b", b.String())
assert.Equal(t, "b", b.GetText())
b.Delete()
assert.Equal(t, 0, l.act)
assert.Equal(t, 0, l.inact)
assert.Equal(t, "", l.text)
assert.Equal(t, "", b.String())
assert.Equal(t, "", b.GetText())
b.Add('c')
b.Clear()
b.ClearText()
assert.Equal(t, 0, l.act)
assert.Equal(t, 0, l.inact)
assert.Equal(t, "", l.text)
assert.Equal(t, "", b.String())
assert.Equal(t, "", b.GetText())
b.Add('c')
b.Reset()
assert.Equal(t, 0, l.act)
assert.Equal(t, 1, l.inact)
assert.Equal(t, "", l.text)
assert.Equal(t, "", b.String())
assert.Equal(t, "", b.GetText())
assert.True(t, b.Empty())
}
func TestCmdBuffAdd(t *testing.T) {
b := model.NewCmdBuff('>', model.Command)
b := model.NewCmdBuff('>', model.CommandBuffer)
uu := []struct {
runes []rune
@ -93,13 +93,13 @@ func TestCmdBuffAdd(t *testing.T) {
for _, r := range u.runes {
b.Add(r)
}
assert.Equal(t, u.cmd, b.String())
assert.Equal(t, u.cmd, b.GetText())
b.Reset()
}
}
func TestCmdBuffDel(t *testing.T) {
b := model.NewCmdBuff('>', model.Command)
b := model.NewCmdBuff('>', model.CommandBuffer)
uu := []struct {
runes []rune
@ -115,13 +115,13 @@ func TestCmdBuffDel(t *testing.T) {
b.Add(r)
}
b.Delete()
assert.Equal(t, u.cmd, b.String())
assert.Equal(t, u.cmd, b.GetText())
b.Reset()
}
}
func TestCmdBuffEmpty(t *testing.T) {
b := model.NewCmdBuff('>', model.Command)
b := model.NewCmdBuff('>', model.CommandBuffer)
uu := []struct {
runes []rune

View File

@ -9,22 +9,72 @@ type SuggestionListener interface {
BuffWatcher
// SuggestionChanged notifies suggestion changes.
SuggestionChanged([]string)
SuggestionChanged(text, sugg string)
}
// SuggestionFunc produces suggestions.
type SuggestionFunc func(s string) sort.StringSlice
type SuggestionFunc func(text string) sort.StringSlice
// FishBuff represents a suggestion buffer.
type FishBuff struct {
*CmdBuff
suggestionFn SuggestionFunc
suggestionFn SuggestionFunc
suggestion string
suggestions []string
suggestionIndex int
}
// NewFishBuff returns a new command buffer.
func NewFishBuff(key rune, kind BufferKind) *FishBuff {
return &FishBuff{CmdBuff: NewCmdBuff(key, kind)}
return &FishBuff{
CmdBuff: NewCmdBuff(key, kind),
suggestionIndex: -1,
}
}
func (c *FishBuff) PrevSuggestion() (string, bool) {
if c.suggestionIndex < 0 {
return "", false
}
c.suggestionIndex--
if c.suggestionIndex < 0 {
c.suggestionIndex = len(c.suggestions) - 1
}
return c.suggestions[c.suggestionIndex], true
}
func (c *FishBuff) NextSuggestion() (string, bool) {
if c.suggestionIndex < 0 {
return "", false
}
c.suggestionIndex++
if c.suggestionIndex >= len(c.suggestions) {
c.suggestionIndex = 0
}
return c.suggestions[c.suggestionIndex], true
}
func (c *FishBuff) ClearSuggestions() {
c.suggestion, c.suggestionIndex = "", -1
}
func (c *FishBuff) CurrentSuggestion() (string, bool) {
if c.suggestionIndex < 0 {
return "", false
}
return c.suggestions[c.suggestionIndex], true
}
func (c *FishBuff) AutoSuggests() bool {
return true
}
func (f *FishBuff) Suggestions() []string {
if f.suggestionFn != nil {
return f.suggestionFn(string(f.buff))
}
return nil
}
// SetSuggestionFn sets up suggestions.
@ -32,23 +82,13 @@ func (f *FishBuff) SetSuggestionFn(fn SuggestionFunc) {
f.suggestionFn = fn
}
// Activate activates the command.
func (f *FishBuff) Activate() {
// Notify publish suggestions to all listeners.
func (f *FishBuff) Notify() {
if f.suggestionFn == nil {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggest(cc)
}
// Delete removes the last character from the buffer.
func (f *FishBuff) Delete() {
f.CmdBuff.Delete()
if f.suggestionFn == nil {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggest(cc)
f.fireSuggestionChanged(cc)
}
// Add adds a new charater to the buffer.
@ -58,13 +98,30 @@ func (f *FishBuff) Add(r rune) {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggest(cc)
f.fireSuggestionChanged(cc)
}
func (f *FishBuff) fireSuggest(cc []string) {
// Delete removes the last character from the buffer.
func (f *FishBuff) Delete() {
f.CmdBuff.Delete()
if f.suggestionFn == nil {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggestionChanged(cc)
}
func (f *FishBuff) fireSuggestionChanged(ss []string) {
f.suggestions, f.suggestionIndex = ss, 0
if ss == nil {
f.suggestionIndex, f.suggestion = -1, ""
return
}
f.suggestion = ss[f.suggestionIndex]
for _, l := range f.listeners {
if s, ok := l.(SuggestionListener); ok {
s.SuggestionChanged(cc)
if listener, ok := l.(SuggestionListener); ok {
listener.SuggestionChanged(f.GetText(), f.suggestion)
}
}
}

View File

@ -14,31 +14,29 @@ type App struct {
*tview.Application
Configurator
Main *Pages
flash *model.Flash
actions KeyActions
views map[string]tview.Primitive
cmdBuff *model.FishBuff
Main *Pages
flash *model.Flash
actions KeyActions
views map[string]tview.Primitive
cmdModel *model.FishBuff
}
// NewApp returns a new app.
func NewApp(cfg *config.Config, context string) *App {
a := App{
Application: tview.NewApplication(),
actions: make(KeyActions),
Configurator: Configurator{
Config: cfg,
},
Main: NewPages(),
flash: model.NewFlash(model.DefaultFlashDelay),
cmdBuff: model.NewFishBuff(':', model.Command),
Application: tview.NewApplication(),
actions: make(KeyActions),
Configurator: Configurator{Config: cfg},
Main: NewPages(),
flash: model.NewFlash(model.DefaultFlashDelay),
cmdModel: model.NewFishBuff(':', model.CommandBuffer),
}
a.ReloadStyles(context)
a.views = map[string]tview.Primitive{
"menu": NewMenu(a.Styles),
"logo": NewLogo(a.Styles),
"cmd": NewCommand(a.Config.K9s.NoIcons, a.Styles, a.cmdBuff),
"prompt": NewPrompt(a.Config.K9s.NoIcons, a.Styles),
"crumbs": NewCrumbs(a.Styles),
}
@ -48,9 +46,9 @@ func NewApp(cfg *config.Config, context string) *App {
// Init initializes the application.
func (a *App) Init() {
a.bindKeys()
a.cmdBuff.AddListener(a.Cmd())
a.Prompt().SetModel(a.cmdModel)
a.cmdModel.AddListener(a)
a.Styles.AddListener(a)
a.CmdBuff().AddListener(a)
a.SetRoot(a.Main, true)
}
@ -59,16 +57,17 @@ func (a *App) Init() {
func (a *App) BufferChanged(s string) {}
// BufferActive indicates the buff activity changed.
func (a *App) BufferActive(state bool, _ model.BufferKind) {
func (a *App) BufferActive(state bool, kind model.BufferKind) {
flex, ok := a.Main.GetPrimitive("main").(*tview.Flex)
if !ok {
return
}
if state && flex.ItemAt(1) != a.Cmd() {
flex.AddItemAtIndex(1, a.Cmd(), 3, 1, false)
} else if !state && flex.ItemAt(1) == a.Cmd() {
if state && flex.ItemAt(1) != a.Prompt() {
flex.AddItemAtIndex(1, a.Prompt(), 3, 1, false)
} else if !state && flex.ItemAt(1) == a.Prompt() {
flex.RemoveItemAtIndex(1)
a.SetFocus(flex)
}
a.Draw()
}
@ -103,15 +102,11 @@ func (a *App) Conn() client.Connection {
func (a *App) bindKeys() {
a.actions = KeyActions{
KeyColon: NewKeyAction("Cmd", a.activateCmd, false),
tcell.KeyCtrlR: NewKeyAction("Redraw", a.redrawCmd, false),
tcell.KeyCtrlC: NewKeyAction("Quit", a.quitCmd, false),
tcell.KeyEscape: NewKeyAction("Escape", a.escapeCmd, false),
tcell.KeyBackspace2: NewKeyAction("Erase", a.eraseCmd, false),
tcell.KeyBackspace: NewKeyAction("Erase", a.eraseCmd, false),
tcell.KeyDelete: NewKeyAction("Erase", a.eraseCmd, false),
tcell.KeyCtrlU: NewSharedKeyAction("Clear Filter", a.clearCmd, false),
tcell.KeyCtrlQ: NewSharedKeyAction("Clear Filter", a.clearCmd, false),
KeyColon: NewKeyAction("Cmd", a.activateCmd, false),
tcell.KeyCtrlR: NewKeyAction("Redraw", a.redrawCmd, false),
tcell.KeyCtrlC: NewKeyAction("Quit", a.quitCmd, false),
tcell.KeyCtrlU: NewSharedKeyAction("Clear Filter", a.clearCmd, false),
tcell.KeyCtrlQ: NewSharedKeyAction("Clear Filter", a.clearCmd, false),
}
}
@ -120,29 +115,35 @@ func (a *App) BailOut() {
a.Stop()
}
func (a *App) ResetPrompt(m PromptModel) {
a.Prompt().SetModel(m)
a.SetFocus(a.Prompt())
m.SetActive(true)
}
// ResetCmd clear out user command.
func (a *App) ResetCmd() {
a.cmdBuff.Reset()
a.cmdModel.Reset()
}
// ActivateCmd toggle command mode.
func (a *App) ActivateCmd(b bool) {
a.cmdBuff.SetActive(b)
a.cmdModel.SetActive(b)
}
// GetCmd retrieves user command.
func (a *App) GetCmd() string {
return a.cmdBuff.String()
return a.cmdModel.GetText()
}
// CmdBuff returns a cmd buffer.
func (a *App) CmdBuff() *model.FishBuff {
return a.cmdBuff
return a.cmdModel
}
// HasCmd check if cmd buffer is active and has a command.
func (a *App) HasCmd() bool {
return a.cmdBuff.IsActive() && !a.cmdBuff.Empty()
return a.cmdModel.IsActive() && !a.cmdModel.Empty()
}
func (a *App) quitCmd(evt *tcell.EventKey) *tcell.EventKey {
@ -156,7 +157,7 @@ func (a *App) quitCmd(evt *tcell.EventKey) *tcell.EventKey {
// InCmdMode check if command mode is active.
func (a *App) InCmdMode() bool {
return a.Cmd().InCmdMode()
return a.Prompt().InCmdMode()
}
// HasAction checks if key matches a registered binding.
@ -186,7 +187,7 @@ func (a *App) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
if !a.CmdBuff().IsActive() {
return evt
}
a.CmdBuff().Clear()
a.CmdBuff().ClearText()
return nil
}
@ -195,38 +196,19 @@ func (a *App) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.InCmdMode() {
return evt
}
a.cmdBuff.SetActive(true)
a.cmdBuff.Clear()
a.SetFocus(a.Cmd())
a.ResetPrompt(a.cmdModel)
a.cmdModel.ClearText()
return nil
}
// EraseCmd removes the last char from a command.
func (a *App) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.IsActive() {
a.cmdBuff.Delete()
return nil
}
return evt
}
// EscapeCmd dismiss cmd mode.
func (a *App) escapeCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.IsActive() {
a.cmdBuff.Reset()
a.SetFocus(a.Main.GetPrimitive("main"))
}
return evt
}
// RedrawCmd forces a redraw.
func (a *App) redrawCmd(evt *tcell.EventKey) *tcell.EventKey {
a.Draw()
return evt
}
// View Accessora...
// View Accessors...
// Crumbs return app crumba.
func (a *App) Crumbs() *Crumbs {
@ -239,8 +221,8 @@ func (a *App) Logo() *Logo {
}
// Cmd returns app cmd.
func (a *App) Cmd() *Command {
return a.views["cmd"].(*Command)
func (a *App) Prompt() *Prompt {
return a.views["prompt"].(*Prompt)
}
// Menu returns app menu.
@ -258,6 +240,9 @@ func (a *App) Flash() *model.Flash {
// AsKey converts rune to keyboard key.,
func AsKey(evt *tcell.EventKey) tcell.Key {
if evt.Key() != tcell.KeyRune {
return evt.Key()
}
key := tcell.Key(evt.Rune())
if evt.Modifiers() == tcell.ModAlt {
key = tcell.Key(int16(evt.Rune()) * int16(evt.Modifiers()))

View File

@ -11,7 +11,7 @@ import (
func TestAppGetCmd(t *testing.T) {
a := ui.NewApp(config.NewConfig(nil), "")
a.Init()
a.CmdBuff().Set("blee")
a.CmdBuff().SetText("blee")
assert.Equal(t, "blee", a.GetCmd())
}
@ -19,21 +19,21 @@ func TestAppGetCmd(t *testing.T) {
func TestAppInCmdMode(t *testing.T) {
a := ui.NewApp(config.NewConfig(nil), "")
a.Init()
a.CmdBuff().Set("blee")
a.CmdBuff().SetText("blee")
assert.False(t, a.InCmdMode())
a.CmdBuff().SetActive(true)
assert.True(t, a.InCmdMode())
a.CmdBuff().SetActive(false)
assert.False(t, a.InCmdMode())
}
func TestAppResetCmd(t *testing.T) {
a := ui.NewApp(config.NewConfig(nil), "")
a.Init()
a.CmdBuff().Set("blee")
a.CmdBuff().SetText("blee")
a.ResetCmd()
assert.Equal(t, "", a.CmdBuff().String())
assert.Equal(t, "", a.CmdBuff().GetText())
}
func TestAppHasCmd(t *testing.T) {
@ -43,7 +43,7 @@ func TestAppHasCmd(t *testing.T) {
a.ActivateCmd(true)
assert.False(t, a.HasCmd())
a.CmdBuff().Set("blee")
a.CmdBuff().SetText("blee")
assert.True(t, a.InCmdMode())
}
@ -53,14 +53,14 @@ func TestAppGetActions(t *testing.T) {
a.AddActions(ui.KeyActions{ui.KeyZ: ui.KeyAction{Description: "zorg"}})
assert.Equal(t, 10, len(a.GetActions()))
assert.Equal(t, 6, len(a.GetActions()))
}
func TestAppViews(t *testing.T) {
a := ui.NewApp(config.NewConfig(nil), "")
a.Init()
vv := []string{"crumbs", "logo", "cmd", "menu"}
vv := []string{"crumbs", "logo", "prompt", "menu"}
for i := range vv {
v := vv[i]
t.Run(v, func(t *testing.T) {
@ -70,6 +70,6 @@ func TestAppViews(t *testing.T) {
assert.NotNil(t, a.Crumbs())
assert.NotNil(t, a.Logo())
assert.NotNil(t, a.Cmd())
assert.NotNil(t, a.Prompt())
assert.NotNil(t, a.Menu())
}

View File

@ -1,200 +0,0 @@
package ui
import (
"fmt"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
)
const (
defaultPrompt = "%c> [::b]%s"
defaultSpacer = 4
)
// Command captures users free from command input.
type Command struct {
*tview.TextView
activated bool
noIcons bool
icon rune
text string
suggestion string
styles *config.Styles
model *model.FishBuff
suggestions []string
suggestionIndex int
spacer int
}
// NewCommand returns a new command view.
func NewCommand(noIcons bool, styles *config.Styles, m *model.FishBuff) *Command {
c := Command{
styles: styles,
noIcons: noIcons,
TextView: tview.NewTextView(),
spacer: defaultSpacer,
model: m,
suggestionIndex: -1,
}
if noIcons {
c.spacer--
}
c.SetWordWrap(true)
c.ShowCursor(true)
c.SetWrap(true)
c.SetDynamicColors(true)
c.SetBorder(true)
c.SetBorderPadding(0, 0, 1, 1)
c.SetBackgroundColor(styles.BgColor())
c.SetTextColor(styles.FgColor())
styles.AddListener(&c)
c.SetInputCapture(c.keyboard)
return &c
}
func (c *Command) keyboard(evt *tcell.EventKey) *tcell.EventKey {
switch evt.Key() {
case tcell.KeyEnter, tcell.KeyCtrlE:
if c.suggestionIndex >= 0 {
c.model.Set(c.text + c.suggestions[c.suggestionIndex])
}
case tcell.KeyCtrlW, tcell.KeyCtrlU:
c.model.Clear()
case tcell.KeyDown:
if c.suggestionIndex < 0 {
return evt
}
c.suggestionIndex++
if c.suggestionIndex >= len(c.suggestions) {
c.suggestionIndex = 0
}
c.suggest(c.model.String(), c.suggestions[c.suggestionIndex])
case tcell.KeyUp:
if c.suggestionIndex < 0 {
return evt
}
c.suggestionIndex--
if c.suggestionIndex < 0 {
c.suggestionIndex = len(c.suggestions) - 1
}
c.suggest(c.model.String(), c.suggestions[c.suggestionIndex])
case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF:
if c.suggestionIndex >= 0 {
c.model.Set(c.model.String() + c.suggestions[c.suggestionIndex])
c.suggestionIndex = -1
}
}
return evt
}
// StylesChanged notifies skin changed.
func (c *Command) StylesChanged(s *config.Styles) {
c.styles = s
c.SetBackgroundColor(s.BgColor())
c.SetTextColor(s.FgColor())
}
// InCmdMode returns true if command is active, false otherwise.
func (c *Command) InCmdMode() bool {
return c.activated
}
func (c *Command) activate() {
c.SetCursorIndex(len(c.text))
c.write(false, c.text, "")
c.model.Activate()
}
func (c *Command) update(s string) {
if c.text == s {
return
}
c.text = s
c.Clear()
c.write(false, s, "")
}
func (c *Command) suggest(text, suggestion string) {
c.Clear()
c.write(false, text, suggestion)
}
func (c *Command) write(append bool, text, suggest string) {
c.suggestion = suggest
c.SetCursorIndex(c.spacer + len(text))
txt := text
if suggest != "" {
txt += "[gray::-]" + suggest
}
if append {
fmt.Fprintf(c, "[gray::-]%s", suggest)
return
}
fmt.Fprintf(c, defaultPrompt, c.icon, txt)
}
// ----------------------------------------------------------------------------
// Event Listener protocol...
// SuggestionChanged indicates the suggestions changed.
func (c *Command) SuggestionChanged(ss []string) {
c.suggestions, c.suggestionIndex = ss, 0
if ss == nil {
c.suggestionIndex = -1
return
}
if c.suggestion == ss[c.suggestionIndex] {
return
}
c.write(true, c.text, ss[c.suggestionIndex])
}
// BufferChanged indicates the buffer was changed.
func (c *Command) BufferChanged(s string) {
c.update(s)
}
// BufferActive indicates the buff activity changed.
func (c *Command) BufferActive(f bool, k model.BufferKind) {
if c.activated = f; f {
c.SetBorder(true)
c.SetTextColor(c.styles.FgColor())
c.SetBorderColor(colorFor(k))
c.icon = c.iconFor(k)
c.activate()
} else {
c.SetBorder(false)
c.SetBackgroundColor(c.styles.BgColor())
c.Clear()
}
}
func (c *Command) iconFor(k model.BufferKind) rune {
if c.noIcons {
return ' '
}
switch k {
case model.Command:
return '🐶'
default:
return '🐩'
}
}
// ----------------------------------------------------------------------------
// Helpers...
func colorFor(k model.BufferKind) tcell.Color {
switch k {
case model.Command:
return tcell.ColorAqua
default:
return tcell.ColorSeaGreen
}
}

247
internal/ui/prompt.go Normal file
View File

@ -0,0 +1,247 @@
package ui
import (
"fmt"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
)
const (
defaultPrompt = "%c> [::b]%s"
defaultSpacer = 4
)
var _ PromptModel = (*model.CmdBuff)(nil)
var _ Suggester = (*model.CmdBuff)(nil)
var _ PromptModel = (*model.FishBuff)(nil)
var _ Suggester = (*model.FishBuff)(nil)
type Suggester interface {
CurrentSuggestion() (string, bool)
NextSuggestion() (string, bool)
PrevSuggestion() (string, bool)
ClearSuggestions()
}
type PromptModel interface {
// AutoSuggests returns true if model implements auto suggestions.
AutoSuggests() bool
// Suggestions returns suggestions.
Suggestions() []string
// SetText sets the model text.
SetText(string)
// GetText returns the current text.
GetText() string
// ClearText clears out model text.
ClearText()
// Notify notifies all listener of current suggestions.
Notify()
// AddListener registers a command listener.
AddListener(model.BuffWatcher)
// RemoveListener removes a listener.
RemoveListener(model.BuffWatcher)
IsActive() bool
SetActive(bool)
Add(rune)
Delete()
}
// Prompt captures users free from command input.
type Prompt struct {
*tview.TextView
noIcons bool
icon rune
styles *config.Styles
model PromptModel
spacer int
}
// NewPrompt returns a new command view.
func NewPrompt(noIcons bool, styles *config.Styles) *Prompt {
c := Prompt{
styles: styles,
noIcons: noIcons,
TextView: tview.NewTextView(),
spacer: defaultSpacer,
}
if noIcons {
c.spacer--
}
c.SetWordWrap(true)
c.SetWrap(true)
c.SetDynamicColors(true)
c.SetBorder(true)
c.SetBorderPadding(0, 0, 1, 1)
c.SetBackgroundColor(styles.BgColor())
c.SetTextColor(styles.FgColor())
styles.AddListener(&c)
c.SetInputCapture(c.keyboard)
return &c
}
// SendKey sends an keyboard event (testing only!).
func (p *Prompt) SendKey(evt *tcell.EventKey) {
p.keyboard(evt)
}
// SendStrokes (testing only!)
func (p *Prompt) SendStrokes(s string) {
for _, r := range s {
p.keyboard(tcell.NewEventKey(tcell.KeyRune, r, tcell.ModNone))
}
}
func (c *Prompt) SetModel(m PromptModel) {
if c.model != nil {
c.model.RemoveListener(c)
}
c.model = m
c.model.AddListener(c)
}
func (c *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey {
m, ok := c.model.(Suggester)
if !ok {
return nil
}
switch evt.Key() {
case tcell.KeyBackspace2, tcell.KeyBackspace, tcell.KeyDelete:
c.model.Delete()
case tcell.KeyRune:
c.model.Add(evt.Rune())
case tcell.KeyEscape:
c.model.ClearText()
c.model.SetActive(false)
case tcell.KeyEnter, tcell.KeyCtrlE:
if curr, ok := m.CurrentSuggestion(); ok {
c.model.SetText(c.model.GetText() + curr)
}
c.model.SetActive(false)
case tcell.KeyCtrlW, tcell.KeyCtrlU:
c.model.ClearText()
case tcell.KeyDown:
if next, ok := m.NextSuggestion(); ok {
c.suggest(c.model.GetText(), next)
}
case tcell.KeyUp:
if prev, ok := m.PrevSuggestion(); ok {
c.suggest(c.model.GetText(), prev)
}
case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF:
if curr, ok := m.CurrentSuggestion(); ok {
c.model.SetText(c.model.GetText() + curr)
m.ClearSuggestions()
}
}
return evt
}
// StylesChanged notifies skin changed.
func (c *Prompt) StylesChanged(s *config.Styles) {
c.styles = s
c.SetBackgroundColor(s.BgColor())
c.SetTextColor(s.FgColor())
}
// InCmdMode returns true if command is active, false otherwise.
func (c *Prompt) InCmdMode() bool {
if c.model == nil {
return false
}
return c.model.IsActive()
}
func (c *Prompt) activate() {
c.SetCursorIndex(len(c.model.GetText()))
c.write(c.model.GetText(), "")
c.model.Notify()
}
func (c *Prompt) update(s string) {
c.Clear()
c.write(s, "")
}
func (c *Prompt) suggest(text, suggestion string) {
c.Clear()
c.write(text, suggestion)
}
func (c *Prompt) write(text, suggest string) {
c.SetCursorIndex(c.spacer + len(text))
txt := text
if suggest != "" {
txt += "[gray::-]" + suggest
}
fmt.Fprintf(c, defaultPrompt, c.icon, txt)
}
// ----------------------------------------------------------------------------
// Event Listener protocol...
// BufferChanged indicates the buffer was changed.
func (c *Prompt) BufferChanged(s string) {
c.update(s)
}
func (c *Prompt) SuggestionChanged(text, sugg string) {
c.Clear()
c.write(text, sugg)
}
// BufferActive indicates the buff activity changed.
func (c *Prompt) BufferActive(activate bool, kind model.BufferKind) {
if activate {
c.ShowCursor(true)
c.SetBorder(true)
c.SetTextColor(c.styles.FgColor())
c.SetBorderColor(colorFor(kind))
c.icon = c.iconFor(kind)
c.activate()
return
}
c.ShowCursor(false)
c.SetBorder(false)
c.SetBackgroundColor(c.styles.BgColor())
c.Clear()
}
func (c *Prompt) iconFor(k model.BufferKind) rune {
if c.noIcons {
return ' '
}
switch k {
case model.CommandBuffer:
return '🐶'
default:
return '🐩'
}
}
// ----------------------------------------------------------------------------
// Helpers...
func colorFor(k model.BufferKind) tcell.Color {
switch k {
case model.CommandBuffer:
return tcell.ColorAqua
default:
return tcell.ColorSeaGreen
}
}

View File

@ -10,21 +10,22 @@ import (
)
func TestCmdNew(t *testing.T) {
model := model.NewFishBuff(':', model.Command)
v := ui.NewCommand(true, config.NewStyles(), model)
v := ui.NewPrompt(true, config.NewStyles())
model := model.NewFishBuff(':', model.CommandBuffer)
v.SetModel(model)
model.AddListener(v)
model.Set("blee")
model.SetText("blee")
assert.Equal(t, "\x00> [::b]blee\n", v.GetText(false))
}
func TestCmdUpdate(t *testing.T) {
model := model.NewFishBuff(':', model.Command)
v := ui.NewCommand(true, config.NewStyles(), model)
model := model.NewFishBuff(':', model.CommandBuffer)
v := ui.NewPrompt(true, config.NewStyles())
v.SetModel(model)
model.AddListener(v)
model.Set("blee")
model.SetText("blee")
model.Add('!')
assert.Equal(t, "\x00> [::b]blee!\n", v.GetText(false))
@ -32,8 +33,9 @@ func TestCmdUpdate(t *testing.T) {
}
func TestCmdMode(t *testing.T) {
model := model.NewFishBuff(':', model.Command)
v := ui.NewCommand(true, config.NewStyles(), model)
model := model.NewFishBuff(':', model.CommandBuffer)
v := ui.NewPrompt(true, config.NewStyles())
v.SetModel(model)
model.AddListener(v)
for _, f := range []bool{false, true} {

View File

@ -57,7 +57,7 @@ func NewTable(gvr client.GVR) *Table {
},
gvr: gvr,
actions: make(KeyActions),
cmdBuff: model.NewCmdBuff('/', model.Filter),
cmdBuff: model.NewCmdBuff('/', model.FilterBuffer),
sortCol: SortColumn{asc: true},
}
}
@ -148,6 +148,13 @@ func (t *Table) FilterInput(r rune) bool {
return true
}
func (t *Table) Filter(s string) {
t.ClearSelection()
t.doUpdate(t.filtered(t.GetModel().Peek()))
t.UpdateTitle()
t.SelectFirstRow()
}
// Hints returns the view hints.
func (t *Table) Hints() model.MenuHints {
return t.actions.Hints()
@ -363,26 +370,26 @@ func (t *Table) filtered(data render.TableData) render.TableData {
if t.toast {
filtered = filterToast(data)
}
if t.cmdBuff.Empty() || IsLabelSelector(t.cmdBuff.String()) {
if t.cmdBuff.Empty() || IsLabelSelector(t.cmdBuff.GetText()) {
return filtered
}
q := t.cmdBuff.String()
q := t.cmdBuff.GetText()
if IsFuzzySelector(q) {
return fuzzyFilter(q[2:], filtered)
}
filtered, err := rxFilter(t.cmdBuff.String(), filtered)
filtered, err := rxFilter(t.cmdBuff.GetText(), filtered)
if err != nil {
log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp")
t.cmdBuff.Clear()
t.cmdBuff.ClearText()
}
return filtered
}
// SearchBuff returns the associated command buffer.
func (t *Table) SearchBuff() *model.CmdBuff {
// CmdBuff returns the associated command buffer.
func (t *Table) CmdBuff() *model.CmdBuff {
return t.cmdBuff
}
@ -430,7 +437,7 @@ func (t *Table) styleTitle() string {
title = SkinTitle(fmt.Sprintf(NSTitleFmt, base, ns, rc), t.styles.Frame())
}
buff := t.cmdBuff.String()
buff := t.cmdBuff.GetText()
if buff == "" {
return title
}

View File

@ -29,7 +29,7 @@ func NewTree() *Tree {
TreeView: tview.NewTreeView(),
expandNodes: true,
actions: make(KeyActions),
cmdBuff: model.NewCmdBuff('/', model.Filter),
cmdBuff: model.NewCmdBuff('/', model.FilterBuffer),
}
}
@ -95,20 +95,7 @@ func (t *Tree) BindKeys() {
}
func (t *Tree) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if key == tcell.KeyRune {
if t.cmdBuff.IsActive() {
t.cmdBuff.Add(evt.Rune())
t.ClearSelection()
if t.keyListener != nil {
t.keyListener()
}
return nil
}
key = mapKey(evt)
}
if a, ok := t.actions[key]; ok {
if a, ok := t.actions[AsKey(evt)]; ok {
return a.Action(evt)
}
@ -135,14 +122,3 @@ func (t *Tree) ClearSelection() {
t.selectedItem = ""
t.SetCurrentNode(nil)
}
// ----------------------------------------------------------------------------
// Helpers...
func mapKey(evt *tcell.EventKey) tcell.Key {
key := tcell.Key(evt.Rune())
if evt.Modifiers() == tcell.ModAlt {
key = tcell.Key(int16(evt.Rune()) * int16(evt.Modifiers()))
}
return key
}

View File

@ -66,7 +66,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
if a.GetTable().SearchBuff().IsActive() {
if a.GetTable().CmdBuff().IsActive() {
return a.GetTable().activateCmd(evt)
}
return evt

View File

@ -30,10 +30,9 @@ func TestAliasSearch(t *testing.T) {
v := view.NewAlias(client.NewGVR("aliases"))
assert.Nil(t, v.Init(makeContext()))
v.GetTable().SetModel(&testModel{})
v.GetTable().SearchBuff().SetActive(true)
v.GetTable().SearchBuff().Set("dump")
v.GetTable().SendKey(tcell.NewEventKey(tcell.KeyRune, 'd', tcell.ModNone))
v.GetTable().Refresh()
v.App().Prompt().SetModel(v.GetTable().CmdBuff())
v.App().Prompt().SendStrokes("blee")
assert.Equal(t, 3, v.GetTable().GetColumnCount())
assert.Equal(t, 1, v.GetTable().GetRowCount())
@ -45,11 +44,11 @@ func TestAliasGoto(t *testing.T) {
v.GetTable().Select(0, 0)
b := buffL{}
v.GetTable().SearchBuff().SetActive(true)
v.GetTable().SearchBuff().AddListener(&b)
v.GetTable().CmdBuff().SetActive(true)
v.GetTable().CmdBuff().AddListener(&b)
v.GetTable().SendKey(tcell.NewEventKey(tcell.KeyEnter, 256, tcell.ModNone))
assert.True(t, v.GetTable().SearchBuff().IsActive())
assert.True(t, v.GetTable().CmdBuff().IsActive())
}
// ----------------------------------------------------------------------------
@ -97,7 +96,13 @@ func (k ks) NamespaceNames(nn []v1.Namespace) []string {
type testModel struct{}
var _ ui.Tabular = &testModel{}
var _ ui.Tabular = (*testModel)(nil)
var _ ui.Suggester = (*testModel)(nil)
func (t *testModel) CurrentSuggestion() (string, bool) { return "", false }
func (t *testModel) NextSuggestion() (string, bool) { return "", false }
func (t *testModel) PrevSuggestion() (string, bool) { return "", false }
func (t *testModel) ClearSuggestions() {}
func (t *testModel) SetInstance(string) {}
func (t *testModel) Empty() bool { return false }

View File

@ -50,8 +50,8 @@ type App struct {
func NewApp(cfg *config.Config) *App {
a := App{
App: ui.NewApp(cfg, cfg.K9s.CurrentContext),
Content: NewPageStack(),
history: model.NewHistory(model.MaxHistory),
Content: NewPageStack(),
}
a.Views()["statusIndicator"] = ui.NewStatusIndicator(a.App, a.Styles)
@ -94,14 +94,14 @@ func (a *App) Init(version string, rate int) error {
a.clusterModel.AddListener(a.clusterInfo())
a.clusterModel.AddListener(a.statusIndicator())
a.clusterModel.Refresh()
a.clusterInfo().Init()
a.command = NewCommand(a)
if err := a.command.Init(); err != nil {
return err
}
a.CmdBuff().SetSuggestionFn(a.suggestCommand())
a.clusterInfo().Init()
a.CmdBuff().AddListener(a)
flash := ui.NewFlash(a.App)
go flash.Watch(ctx, a.Flash().Channel())
@ -119,7 +119,7 @@ func (a *App) Init(version string, rate int) error {
return nil
}
func (a *App) suggestCommand() func(s string) (entries sort.StringSlice) {
func (a *App) suggestCommand() model.SuggestionFunc {
return func(s string) (entries sort.StringSlice) {
if s == "" {
if a.history.Empty() {
@ -147,16 +147,7 @@ func (a *App) suggestCommand() func(s string) (entries sort.StringSlice) {
}
func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if key == tcell.KeyRune {
if a.CmdBuff().IsActive() && evt.Modifiers() == tcell.ModNone {
a.CmdBuff().Add(evt.Rune())
return nil
}
key = ui.AsKey(evt)
}
if k, ok := a.HasAction(key); ok && !a.Content.IsTopDialog() {
if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() {
return k.Action(evt)
}
@ -431,7 +422,7 @@ func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (a *App) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.Cmd().InCmdMode() {
if a.Prompt().InCmdMode() {
return evt
}

View File

@ -12,5 +12,5 @@ func TestAppNew(t *testing.T) {
a := view.NewApp(config.NewConfig(ks{}))
a.Init("blee", 10)
assert.Equal(t, 13, len(a.GetActions()))
assert.Equal(t, 9, len(a.GetActions()))
}

View File

@ -183,32 +183,28 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (b *Browser) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !b.SearchBuff().InCmdMode() {
b.SearchBuff().Reset()
if !b.CmdBuff().InCmdMode() {
b.CmdBuff().Reset()
return b.App().PrevCmd(evt)
}
cmd := b.SearchBuff().String()
b.SearchBuff().Reset()
if ui.IsLabelSelector(cmd) {
b.CmdBuff().Reset()
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
b.Start()
} else {
b.Refresh()
return nil
}
b.Refresh()
return nil
}
func (b *Browser) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
if !b.SearchBuff().IsActive() {
if !b.CmdBuff().IsActive() {
return evt
}
b.SearchBuff().SetActive(false)
cmd := b.SearchBuff().String()
if ui.IsLabelSelector(cmd) {
b.CmdBuff().SetActive(false)
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
b.Start()
return nil
}
@ -355,8 +351,8 @@ func (b *Browser) defaultContext() context.Context {
ctx = context.WithValue(ctx, internal.KeyPath, b.Path)
ctx = context.WithValue(ctx, internal.KeyLabels, "")
if ui.IsLabelSelector(b.SearchBuff().String()) {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.SearchBuff().String()))
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText()))
}
ctx = context.WithValue(ctx, internal.KeyFields, "")
ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace()))

View File

@ -37,7 +37,7 @@ func NewDetails(app *App, title, subject string, searchable bool) *Details {
title: title,
subject: subject,
actions: make(ui.KeyActions),
cmdBuff: model.NewCmdBuff('/', model.Filter),
cmdBuff: model.NewCmdBuff('/', model.FilterBuffer),
model: model.NewText(),
searchable: searchable,
}
@ -63,7 +63,7 @@ func (d *Details) Init(_ context.Context) error {
d.app.Styles.AddListener(d)
d.StylesChanged(d.app.Styles)
d.cmdBuff.AddListener(d.app.Cmd())
d.app.Prompt().SetModel(d.cmdBuff)
d.cmdBuff.AddListener(d)
d.bindKeys()
@ -100,7 +100,10 @@ func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) {
}
// BufferChanged indicates the buffer was changed.
func (d *Details) BufferChanged(s string) {}
func (d *Details) BufferChanged(s string) {
d.model.Filter(s)
d.updateTitle()
}
// BufferActive indicates the buff activity changed.
func (d *Details) BufferActive(state bool, k model.BufferKind) {
@ -109,18 +112,14 @@ func (d *Details) BufferActive(state bool, k model.BufferKind) {
func (d *Details) bindKeys() {
d.actions.Set(ui.KeyActions{
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", d.filterCmd, false),
tcell.KeyEscape: ui.NewKeyAction("Back", d.resetCmd, false),
tcell.KeyCtrlS: ui.NewKeyAction("Save", d.saveCmd, false),
ui.KeyC: ui.NewKeyAction("Copy", d.cpCmd, true),
ui.KeyN: ui.NewKeyAction("Next Match", d.nextCmd, true),
ui.KeyShiftN: ui.NewKeyAction("Prev Match", d.prevCmd, true),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", d.activateCmd, false),
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", d.clearCmd, false),
tcell.KeyCtrlW: ui.NewSharedKeyAction("Clear Filter", d.clearCmd, false),
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", d.filterCmd, false),
tcell.KeyEscape: ui.NewKeyAction("Back", d.resetCmd, false),
tcell.KeyCtrlS: ui.NewKeyAction("Save", d.saveCmd, false),
ui.KeyC: ui.NewKeyAction("Copy", d.cpCmd, true),
ui.KeyN: ui.NewKeyAction("Next Match", d.nextCmd, true),
ui.KeyShiftN: ui.NewKeyAction("Prev Match", d.prevCmd, true),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", d.activateCmd, false),
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
})
if !d.searchable {
@ -129,35 +128,13 @@ func (d *Details) bindKeys() {
}
func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if key == tcell.KeyUp || key == tcell.KeyDown {
return evt
}
if key == tcell.KeyRune {
if d.filterInput(evt.Rune()) {
return nil
}
key = ui.AsKey(evt)
}
if a, ok := d.actions[key]; ok {
if a, ok := d.actions[ui.AsKey(evt)]; ok {
return a.Action(evt)
}
return evt
}
func (d *Details) filterInput(r rune) bool {
if !d.cmdBuff.IsActive() {
return false
}
d.cmdBuff.Add(r)
d.updateTitle()
return true
}
// StylesChanged notifies the skin changed.
func (d *Details) StylesChanged(s *config.Styles) {
d.SetBackgroundColor(d.app.Styles.BgColor())
@ -236,7 +213,7 @@ func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (d *Details) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
d.model.Filter(d.cmdBuff.String())
d.model.Filter(d.cmdBuff.GetText())
d.cmdBuff.SetActive(false)
d.updateTitle()
@ -247,16 +224,7 @@ func (d *Details) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if d.app.InCmdMode() {
return evt
}
d.cmdBuff.SetActive(true)
return nil
}
func (d *Details) clearCmd(*tcell.EventKey) *tcell.EventKey {
if !d.app.InCmdMode() {
return nil
}
d.cmdBuff.Clear()
d.app.ResetPrompt(d.cmdBuff)
return nil
}
@ -276,7 +244,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return d.app.PrevCmd(evt)
}
if d.cmdBuff.String() != "" {
if d.cmdBuff.GetText() != "" {
d.model.ClearFilter()
}
d.cmdBuff.SetActive(false)
@ -311,16 +279,15 @@ func (d *Details) updateTitle() {
}
fmat := fmt.Sprintf(detailsTitleFmt, d.title, d.subject)
buff := d.cmdBuff.String()
buff := d.cmdBuff.GetText()
if buff == "" {
d.SetTitle(ui.SkinTitle(fmat, d.app.Styles.Frame()))
return
}
search := d.cmdBuff.String()
if d.maxRegions != 0 {
search += fmt.Sprintf("[%d:%d]", d.currentRegion+1, d.maxRegions)
buff += fmt.Sprintf("[%d:%d]", d.currentRegion+1, d.maxRegions)
}
fmat += fmt.Sprintf(ui.SearchFmt, search)
fmat += fmt.Sprintf(ui.SearchFmt, buff)
d.SetTitle(ui.SkinTitle(fmat, d.app.Styles.Frame()))
}

View File

@ -58,9 +58,9 @@ func (h *Help) Init(ctx context.Context) error {
func (h *Help) bindKeys() {
h.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlS)
h.Actions().Set(ui.KeyActions{
tcell.KeyEsc: ui.NewKeyAction("Back", h.app.PrevCmd, false),
ui.KeyHelp: ui.NewKeyAction("Back", h.app.PrevCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Back", h.app.PrevCmd, false),
tcell.KeyEscape: ui.NewKeyAction("Back", h.app.PrevCmd, false),
ui.KeyHelp: ui.NewKeyAction("Back", h.app.PrevCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Back", h.app.PrevCmd, false),
})
}

View File

@ -123,7 +123,7 @@ func (l *Log) LogChanged(lines dao.LogItems) {
// BufferChanged indicates the buffer was changed.
func (l *Log) BufferChanged(s string) {
if err := l.model.Filter(l.logs.cmdBuff.String()); err != nil {
if err := l.model.Filter(l.logs.cmdBuff.GetText()); err != nil {
l.app.Flash().Err(err)
}
l.updateTitle()
@ -167,7 +167,7 @@ func (l *Log) Stop() {
l.model.RemoveListener(l)
l.app.Styles.RemoveListener(l)
l.logs.cmdBuff.RemoveListener(l)
l.logs.cmdBuff.RemoveListener(l.app.Cmd())
l.logs.cmdBuff.RemoveListener(l.app.Prompt())
}
// Name returns the component name.
@ -193,9 +193,7 @@ func (l *Log) bindKeys() {
// SendStrokes (testing only!)
func (l *Log) SendStrokes(s string) {
for _, r := range s {
l.logs.keyboard(tcell.NewEventKey(tcell.KeyRune, r, tcell.ModNone))
}
l.app.Prompt().SendStrokes(s)
}
// SendKeys (testing only!)
@ -226,7 +224,7 @@ func (l *Log) updateTitle() {
title = ui.SkinTitle(fmt.Sprintf(logCoFmt, path, co, since), l.app.Styles.Frame())
}
buff := l.logs.cmdBuff.String()
buff := l.logs.cmdBuff.GetText()
if buff != "" {
title += ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), l.app.Styles.Frame())
}
@ -270,7 +268,7 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
l.logs.cmdBuff.SetActive(false)
if err := l.model.Filter(l.logs.cmdBuff.String()); err != nil {
if err := l.model.Filter(l.logs.cmdBuff.GetText()); err != nil {
l.app.Flash().Err(err)
}
l.updateTitle()

View File

@ -116,8 +116,8 @@ func (p *PortForward) runBenchmark() {
}
func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
if !p.GetTable().SearchBuff().Empty() {
p.GetTable().SearchBuff().Reset()
if !p.GetTable().CmdBuff().Empty() {
p.GetTable().CmdBuff().Reset()
return nil
}

View File

@ -97,14 +97,9 @@ func (s *Sanitizer) SetInstance(string) {}
func (s *Sanitizer) bindKeys() {
s.Actions().Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Goto", s.gotoCmd, true),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", s.activateCmd, false),
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", s.eraseCmd, false),
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", s.eraseCmd, false),
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", s.eraseCmd, false),
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", s.clearCmd, false),
tcell.KeyCtrlW: ui.NewSharedKeyAction("Clear Filter", s.clearCmd, false),
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", s.resetCmd, false),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", s.activateCmd, false),
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", s.resetCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Goto", s.gotoCmd, true),
})
}
@ -113,8 +108,7 @@ func (s *Sanitizer) keyEntered() {
s.update(s.filter(s.model.Peek()))
}
func (s *Sanitizer) refreshActions() {
}
func (s *Sanitizer) refreshActions() {}
// GetSelectedPath returns the current selection as string.
func (s *Sanitizer) GetSelectedPath() string {
@ -153,7 +147,7 @@ func (s *Sanitizer) k9sEnv() Env {
return env
}
env["FILTER"] = s.CmdBuff().String()
env["FILTER"] = s.CmdBuff().GetText()
if env["FILTER"] == "" {
ns, n := client.Namespaced(spec.Path())
env["NAMESPACE"], env["FILTER"] = ns, n
@ -182,27 +176,7 @@ func (s *Sanitizer) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if s.app.InCmdMode() {
return evt
}
s.CmdBuff().SetActive(true)
return nil
}
func (s *Sanitizer) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
if !s.CmdBuff().IsActive() {
return evt
}
s.CmdBuff().Clear()
s.model.ClearFilter()
s.Start()
return nil
}
func (s *Sanitizer) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if s.CmdBuff().IsActive() {
s.CmdBuff().Delete()
}
s.UpdateTitle()
s.app.ResetPrompt(s.CmdBuff())
return nil
}
@ -221,7 +195,7 @@ func (s *Sanitizer) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
func (s *Sanitizer) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if s.CmdBuff().IsActive() {
if ui.IsLabelSelector(s.CmdBuff().String()) {
if ui.IsLabelSelector(s.CmdBuff().GetText()) {
s.Start()
}
s.CmdBuff().SetActive(false)
@ -251,7 +225,7 @@ func (s *Sanitizer) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (s *Sanitizer) filter(root *xray.TreeNode) *xray.TreeNode {
q := s.CmdBuff().String()
q := s.CmdBuff().GetText()
if s.CmdBuff().Empty() || ui.IsLabelSelector(q) {
return root
}
@ -338,11 +312,12 @@ func (s *Sanitizer) hydrate(parent *tview.TreeNode, n *xray.TreeNode) {
func (s *Sanitizer) SetEnvFn(EnvFunc) {}
// Refresh updates the view
func (s *Sanitizer) Refresh() {
}
func (s *Sanitizer) Refresh() {}
// BufferChanged indicates the buffer was changed.
func (s *Sanitizer) BufferChanged(t string) {}
func (s *Sanitizer) BufferChanged(q string) {
s.update(s.filter(s.model.Peek()))
}
// BufferActive indicates the buff activity changed.
func (s *Sanitizer) BufferActive(state bool, k model.BufferKind) {
@ -355,7 +330,7 @@ func (s *Sanitizer) defaultContext() context.Context {
if s.CmdBuff().Empty() {
ctx = context.WithValue(ctx, internal.KeyLabels, "")
} else {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(s.CmdBuff().String()))
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(s.CmdBuff().GetText()))
}
return ctx
@ -364,8 +339,6 @@ func (s *Sanitizer) defaultContext() context.Context {
// Start initializes resource watch loop.
func (s *Sanitizer) Start() {
s.Stop()
s.CmdBuff().AddListener(s.app.Cmd())
s.CmdBuff().AddListener(s)
ctx := s.defaultContext()
@ -384,8 +357,6 @@ func (s *Sanitizer) Stop() {
}
s.cancelFn()
s.cancelFn = nil
s.CmdBuff().RemoveListener(s.app.Cmd())
s.CmdBuff().RemoveListener(s)
}
@ -426,17 +397,17 @@ func (s *Sanitizer) styleTitle() string {
ns = client.NamespaceAll
}
buff := s.CmdBuff().String()
var title string
if ns == client.ClusterScope {
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, s.Count), s.app.Styles.Frame())
} else {
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, s.Count), s.app.Styles.Frame())
}
buff := s.CmdBuff().GetText()
if buff == "" {
return title
}
if ui.IsLabelSelector(buff) {
buff = ui.TrimLabelSelector(buff)
}

View File

@ -47,13 +47,14 @@ func (t *Table) Init(ctx context.Context) (err error) {
t.SetInputCapture(t.keyboard)
t.bindKeys()
t.GetModel().SetRefreshRate(time.Duration(t.app.Config.K9s.GetRefreshRate()) * time.Second)
t.CmdBuff().AddListener(t)
return nil
}
// SendKey sends an keyboard event (testing only!).
func (t *Table) SendKey(evt *tcell.EventKey) {
t.keyboard(evt)
t.app.Prompt().SendKey(evt)
}
func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
@ -62,14 +63,7 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
if key == tcell.KeyRune {
if t.FilterInput(evt.Rune()) {
return nil
}
key = ui.AsKey(evt)
}
if a, ok := t.Actions()[key]; ok && !t.app.Content.IsTopDialog() {
if a, ok := t.Actions()[ui.AsKey(evt)]; ok && !t.app.Content.IsTopDialog() {
return a.Action(evt)
}
@ -97,7 +91,7 @@ func (t *Table) defaultEnv() Env {
log.Error().Msgf("unable to locate selected row for %q", path)
}
env := defaultEnv(t.app.Conn().Config(), path, t.GetModel().Peek().Header, row)
env["FILTER"] = t.SearchBuff().String()
env["FILTER"] = t.CmdBuff().GetText()
if env["FILTER"] == "" {
env["NAMESPACE"], env["FILTER"] = client.Namespaced(path)
}
@ -113,15 +107,13 @@ func (t *Table) App() *App {
// Start runs the component.
func (t *Table) Start() {
t.Stop()
t.SearchBuff().AddListener(t.app.Cmd())
t.SearchBuff().AddListener(t)
t.CmdBuff().AddListener(t)
t.Styles().AddListener(t.Table)
}
// Stop terminates the component.
func (t *Table) Stop() {
t.SearchBuff().RemoveListener(t.app.Cmd())
t.SearchBuff().RemoveListener(t)
t.CmdBuff().RemoveListener(t)
t.Styles().RemoveListener(t.Table)
}
@ -134,11 +126,16 @@ func (t *Table) SetEnterFn(f EnterFunc) {
func (t *Table) SetExtraActionsFn(BoostActionsFunc) {}
// BufferChanged indicates the buffer was changed.
func (t *Table) BufferChanged(s string) {}
func (t *Table) BufferChanged(s string) {
t.Filter(s)
}
// BufferActive indicates the buff activity changed.
func (t *Table) BufferActive(state bool, k model.BufferKind) {
t.app.BufferActive(state, k)
if !state {
t.app.SetFocus(t)
}
}
func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
@ -153,18 +150,14 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
func (t *Table) bindKeys() {
t.Actions().Add(ui.KeyActions{
ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false),
tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),
tcell.KeyCtrlS: ui.NewSharedKeyAction("Save", t.saveCmd, false),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", t.activateCmd, false),
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", t.clearCmd, false),
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", t.eraseCmd, false),
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", t.eraseCmd, false),
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", t.eraseCmd, false),
tcell.KeyCtrlZ: ui.NewKeyAction("Toggle Faults", t.toggleFaultCmd, false),
tcell.KeyCtrlW: ui.NewKeyAction("Show Wide", t.toggleWideCmd, false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false),
ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false),
ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false),
tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),
tcell.KeyCtrlS: ui.NewSharedKeyAction("Save", t.saveCmd, false),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", t.activateCmd, false),
tcell.KeyCtrlZ: ui.NewKeyAction("Toggle Faults", t.toggleFaultCmd, false),
tcell.KeyCtrlW: ui.NewKeyAction("Show Wide", t.toggleWideCmd, false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false),
ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false),
})
}
@ -217,28 +210,11 @@ func (t *Table) clearMarksCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
func (t *Table) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
if !t.SearchBuff().IsActive() {
return evt
}
t.SearchBuff().Clear()
return nil
}
func (t *Table) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if t.SearchBuff().IsActive() {
t.SearchBuff().Delete()
}
return nil
}
func (t *Table) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if t.app.InCmdMode() {
return evt
}
t.SearchBuff().SetActive(true)
t.App().ResetPrompt(t.CmdBuff())
return nil
}

View File

@ -65,8 +65,8 @@ func TestTableViewFilter(t *testing.T) {
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
v.SetModel(&testTableModel{})
v.SearchBuff().SetActive(true)
v.SearchBuff().Set("blee")
v.CmdBuff().SetActive(true)
v.CmdBuff().SetText("blee")
v.Refresh()
assert.Equal(t, 1, v.GetRowCount())
}

View File

@ -105,14 +105,9 @@ func (x *Xray) SetInstance(string) {}
func (x *Xray) bindKeys() {
x.Actions().Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Goto", x.gotoCmd, true),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", x.activateCmd, false),
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", x.clearCmd, false),
tcell.KeyCtrlW: ui.NewSharedKeyAction("Clear Filter", x.clearCmd, false),
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", x.resetCmd, false),
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", x.activateCmd, false),
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", x.resetCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Goto", x.gotoCmd, true),
})
}
@ -214,7 +209,7 @@ func (x *Xray) k9sEnv() Env {
return env
}
env["FILTER"] = x.CmdBuff().String()
env["FILTER"] = x.CmdBuff().GetText()
if env["FILTER"] == "" {
ns, n := client.Namespaced(spec.Path())
env["NAMESPACE"], env["FILTER"] = ns, n
@ -418,27 +413,7 @@ func (x *Xray) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if x.app.InCmdMode() {
return evt
}
x.CmdBuff().SetActive(true)
return nil
}
func (x *Xray) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
if !x.CmdBuff().IsActive() {
return evt
}
x.CmdBuff().Clear()
x.model.ClearFilter()
x.Start()
return nil
}
func (x *Xray) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if x.CmdBuff().IsActive() {
x.CmdBuff().Delete()
}
x.UpdateTitle()
x.app.ResetPrompt(x.CmdBuff())
return nil
}
@ -457,7 +432,7 @@ func (x *Xray) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if x.CmdBuff().IsActive() {
if ui.IsLabelSelector(x.CmdBuff().String()) {
if ui.IsLabelSelector(x.CmdBuff().GetText()) {
x.Start()
}
x.CmdBuff().SetActive(false)
@ -481,7 +456,7 @@ func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (x *Xray) filter(root *xray.TreeNode) *xray.TreeNode {
q := x.CmdBuff().String()
q := x.CmdBuff().GetText()
if x.CmdBuff().Empty() || ui.IsLabelSelector(q) {
return root
}
@ -568,11 +543,12 @@ func (x *Xray) hydrate(parent *tview.TreeNode, n *xray.TreeNode) {
func (x *Xray) SetEnvFn(EnvFunc) {}
// Refresh updates the view
func (x *Xray) Refresh() {
}
func (x *Xray) Refresh() {}
// BufferChanged indicates the buffer was changed.
func (x *Xray) BufferChanged(s string) {}
func (x *Xray) BufferChanged(s string) {
x.update(x.filter(x.model.Peek()))
}
// BufferActive indicates the buff activity changed.
func (x *Xray) BufferActive(state bool, k model.BufferKind) {
@ -585,7 +561,7 @@ func (x *Xray) defaultContext() context.Context {
if x.CmdBuff().Empty() {
ctx = context.WithValue(ctx, internal.KeyLabels, "")
} else {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(x.CmdBuff().String()))
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(x.CmdBuff().GetText()))
}
return ctx
@ -594,8 +570,6 @@ func (x *Xray) defaultContext() context.Context {
// Start initializes resource watch loop.
func (x *Xray) Start() {
x.Stop()
x.CmdBuff().AddListener(x.app.Cmd())
x.CmdBuff().AddListener(x)
ctx := x.defaultContext()
@ -611,8 +585,6 @@ func (x *Xray) Stop() {
}
x.cancelFn()
x.cancelFn = nil
x.CmdBuff().RemoveListener(x.app.Cmd())
x.CmdBuff().RemoveListener(x)
}
@ -651,17 +623,17 @@ func (x *Xray) styleTitle() string {
ns = client.NamespaceAll
}
buff := x.CmdBuff().String()
var title string
if ns == client.ClusterScope {
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, x.Count), x.app.Styles.Frame())
} else {
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, x.Count), x.app.Styles.Frame())
}
buff := x.CmdBuff().GetText()
if buff == "" {
return title
}
if ui.IsLabelSelector(buff) {
buff = ui.TrimLabelSelector(buff)
}