diff --git a/internal/config/alias.go b/internal/config/alias.go index 875f8a5e..ae6f9582 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -8,7 +8,7 @@ import ( "gopkg.in/yaml.v2" ) -// K9sAlias stores K9s command aliases. +// K9sAlias manages K9s aliases. var K9sAlias = filepath.Join(K9sHome, "alias.yml") type Alias map[string]string @@ -33,7 +33,7 @@ func (a Aliases) loadDefaults() { a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings" a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles" a.Alias["rob"] = "rbac.authorization.k8s.io/v1/rolebindings" - a.Alias["np"] = "networking.k8s.io/v1beta1/rolebindings" + a.Alias["np"] = "networking.k8s.io/v1/networkpolicies" { a.Alias["ctx"] = "contexts" a.Alias["contexts"] = "contexts" @@ -87,7 +87,7 @@ func (a Aliases) Define(args ...string) { } } -// LoadAliases K9s alias from a given file. +// LoadAliases loads alias from a given file. func (a Aliases) LoadAliases(path string) error { f, err := ioutil.ReadFile(path) if err != nil { diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index f1a199be..ccb04c18 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -7,20 +7,42 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAliasesLoad(t *testing.T) { - aa := config.NewAliases() - assert.Nil(t, aa.LoadAliases("test_assets/alias.yml")) +func TestAliasDefine(t *testing.T) { + uu := map[string]struct { + aa []string + }{ + "one": {[]string{"blee", "duh"}}, + "multi": {[]string{"blee", "duh", "fred", "zorg"}}, + } - assert.Equal(t, 27, len(aa.Alias)) + for k, u := range uu { + t.Run(k, func(t *testing.T) { + a := config.NewAliases() + a.Define(u.aa...) + for i := 0; i < len(u.aa); i += 2 { + v, ok := a.Get(u.aa[i]) + + assert.True(t, ok) + assert.Equal(t, u.aa[i+1], v) + } + }) + } +} + +func TestAliasesLoad(t *testing.T) { + a := config.NewAliases() + assert.Nil(t, a.LoadAliases("test_assets/alias.yml")) + + assert.Equal(t, 27, len(a.Alias)) } func TestAliasesSave(t *testing.T) { - aa := config.NewAliases() + a := config.NewAliases() - aa.Alias["test"] = "fred" - aa.Alias["blee"] = "duh" - aa.SaveAliases("/tmp/a.yml") + a.Alias["test"] = "fred" + a.Alias["blee"] = "duh" + a.SaveAliases("/tmp/a.yml") - assert.Nil(t, aa.LoadAliases("/tmp/a.yml")) - assert.Equal(t, 28, len(aa.Alias)) + assert.Nil(t, a.LoadAliases("/tmp/a.yml")) + assert.Equal(t, 28, len(a.Alias)) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b7566343..e5763750 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -100,7 +100,6 @@ func TestConfigLoad(t *testing.T) { assert.Equal(t, "minikube", cfg.K9s.CurrentCluster) assert.NotNil(t, cfg.K9s.Clusters) assert.Equal(t, 2, len(cfg.K9s.Clusters)) - assert.Equal(t, 1, len(cfg.K9s.Plugins)) nn := []string{ "default", @@ -294,19 +293,6 @@ var expectedConfig = `k9s: - kube-system view: active: ctx - plugins: - blah: - shortCut: shift-s - scopes: - - po - - dp - description: blee - command: duh - background: false - args: - - -n - - $NAMESPACE - - -boolean ` var resetConfig = `k9s: @@ -324,17 +310,4 @@ var resetConfig = `k9s: - default view: active: po - plugins: - blah: - shortCut: shift-s - scopes: - - po - - dp - description: blee - command: duh - background: false - args: - - -n - - $NAMESPACE - - -boolean ` diff --git a/internal/config/k9s.go b/internal/config/k9s.go index af85ba86..66cb4f4e 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -19,7 +19,6 @@ type K9s struct { CurrentContext string `yaml:"currentContext"` CurrentCluster string `yaml:"currentCluster"` Clusters map[string]*Cluster `yaml:"clusters,omitempty"` - Plugins map[string]*Plugin `yaml:"plugins,omitempty"` manualRefreshRate int manualHeadless *bool manualCommand *string @@ -32,7 +31,6 @@ func NewK9s() *K9s { LogBufferSize: defaultLogBufferSize, LogRequestSize: defaultLogRequestSize, Clusters: make(map[string]*Cluster), - Plugins: make(map[string]*Plugin), } } diff --git a/internal/config/plugin.go b/internal/config/plugin.go index e761eab7..b0b50d4e 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -1,5 +1,20 @@ package config +import ( + "io/ioutil" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +// K9sPlugins manages K9s plugins. +var K9sPlugins = filepath.Join(K9sHome, "plugin.yml") + +// Plugins represents a collection of plugins. +type Plugins struct { + Plugin map[string]Plugin `yaml:"plugin"` +} + // Plugin describes a K9s plugin type Plugin struct { ShortCut string `yaml:"shortCut"` @@ -9,3 +24,33 @@ type Plugin struct { Background bool `yaml:"background"` Args []string `yaml:"args"` } + +// NewPlugins returns a new plugin. +func NewPlugins() Plugins { + return Plugins{ + Plugin: make(map[string]Plugin), + } +} + +// Load K9s plugins. +func (p Plugins) Load() error { + return p.LoadPlugins(K9sPlugins) +} + +// LoadPlugins loads plugins from a given file. +func (p Plugins) LoadPlugins(path string) error { + f, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + var pp Plugins + if err := yaml.Unmarshal(f, &pp); err != nil { + return err + } + for k, v := range pp.Plugin { + p.Plugin[k] = v + } + + return nil +} diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go new file mode 100644 index 00000000..3b5148e1 --- /dev/null +++ b/internal/config/plugin_test.go @@ -0,0 +1,15 @@ +package config_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/config" + "github.com/stretchr/testify/assert" +) + +func TestPluginLoad(t *testing.T) { + p := config.NewPlugins() + assert.Nil(t, p.LoadPlugins("test_assets/plugin.yml")) + + assert.Equal(t, 1, len(p.Plugin)) +} diff --git a/internal/config/style.go b/internal/config/style.go index d0cb0708..cb276136 100644 --- a/internal/config/style.go +++ b/internal/config/style.go @@ -197,7 +197,7 @@ func newTitle() Title { BgColor: "black", HighlightColor: "fuchsia", CounterColor: "papayawhip", - FilterColor: "orange", + FilterColor: "seagreen", } } diff --git a/internal/config/test_assets/k9s.yml b/internal/config/test_assets/k9s.yml index 64873df2..91f4ea26 100644 --- a/internal/config/test_assets/k9s.yml +++ b/internal/config/test_assets/k9s.yml @@ -27,15 +27,3 @@ k9s: - kube-system view: active: po - plugins: - blah: - shortCut: shift-s - description: blee - scopes: - - po - - dp - command: duh - args: - - -n - - $NAMESPACE - - -boolean \ No newline at end of file diff --git a/internal/config/test_assets/plugin.yml b/internal/config/test_assets/plugin.yml new file mode 100644 index 00000000..783eb72f --- /dev/null +++ b/internal/config/test_assets/plugin.yml @@ -0,0 +1,12 @@ +plugin: + blah: + shortCut: shift-s + description: blee + scopes: + - po + - dp + command: duh + args: + - -n + - $NAMESPACE + - -boolean diff --git a/internal/k8s/mapper.go b/internal/k8s/mapper.go index 28f95533..57629399 100644 --- a/internal/k8s/mapper.go +++ b/internal/k8s/mapper.go @@ -16,7 +16,6 @@ import ( var ( // RestMapping holds k8s resource mapping - // BOZO!! Has to be a better way... RestMapping = &RestMapper{} toFileName = regexp.MustCompile(`[^(\w/\.)]`) ) diff --git a/internal/ui/app.go b/internal/ui/app.go index 3f6541b5..fd19f13f 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -54,7 +54,7 @@ func NewApp() *App { actions: make(KeyActions), pages: tview.NewPages(), content: tview.NewPages(), - cmdBuff: NewCmdBuff(':'), + cmdBuff: NewCmdBuff(':', CommandBuff), } s.RefreshStyles() @@ -62,7 +62,7 @@ func NewApp() *App { s.views = map[string]tview.Primitive{ "menu": NewMenuView(s.Styles), "logo": NewLogoView(s.Styles), - "cmd": NewCmdView(s.Styles, '🐶'), + "cmd": NewCmdView(s.Styles), "crumbs": NewCrumbsView(s.Styles), } @@ -186,7 +186,6 @@ func (a *App) activateCmd(evt *tcell.EventKey) *tcell.EventKey { if a.InCmdMode() { return evt } - a.Flash().Info("Command mode activated.") a.cmdBuff.SetActive(true) a.cmdBuff.Clear() diff --git a/internal/ui/cmd.go b/internal/ui/cmd.go index 8ee3509e..925bad90 100644 --- a/internal/ui/cmd.go +++ b/internal/ui/cmd.go @@ -5,6 +5,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/tview" + "github.com/gdamore/tcell" ) const defaultPrompt = "%c> %s" @@ -20,8 +21,8 @@ type CmdView struct { } // NewCmdView returns a new command view. -func NewCmdView(styles *config.Styles, ic rune) *CmdView { - v := CmdView{styles: styles, icon: ic, TextView: tview.NewTextView()} +func NewCmdView(styles *config.Styles) *CmdView { + v := CmdView{styles: styles, TextView: tview.NewTextView()} { v.SetWordWrap(true) v.SetWrap(true) @@ -29,7 +30,7 @@ func NewCmdView(styles *config.Styles, ic rune) *CmdView { v.SetBorder(true) v.SetBorderPadding(0, 0, 1, 1) v.SetBackgroundColor(styles.BgColor()) - v.SetBorderColor(config.AsColor(styles.Frame().Border.FocusColor)) + // v.SetBorderColor(config.AsColor(styles.Frame().Border.FocusColor)) v.SetTextColor(styles.FgColor()) } return &v @@ -67,11 +68,13 @@ func (v *CmdView) BufferChanged(s string) { } // BufferActive indicates the buff activity changed. -func (v *CmdView) BufferActive(f bool) { +func (v *CmdView) BufferActive(f bool, k BufferKind) { v.activated = f if f { v.SetBorder(true) + v.icon = iconFor(k) v.SetTextColor(v.styles.FgColor()) + v.SetBorderColor(colorFor(k)) v.activate() } else { v.SetBorder(false) @@ -79,3 +82,20 @@ func (v *CmdView) BufferActive(f bool) { v.Clear() } } + +func colorFor(k BufferKind) tcell.Color { + switch k { + case CommandBuff: + return tcell.ColorAqua + default: + return tcell.ColorSeaGreen + } +} +func iconFor(k BufferKind) rune { + switch k { + case CommandBuff: + return '🐶' + default: + return '🤓' + } +} diff --git a/internal/ui/cmd_buff.go b/internal/ui/cmd_buff.go index 7be4b3de..4adf83bb 100644 --- a/internal/ui/cmd_buff.go +++ b/internal/ui/cmd_buff.go @@ -2,19 +2,30 @@ package ui const maxBuff = 10 +const ( + // CommandBuff indicates a command buffer. + CommandBuff BufferKind = 1 << iota + // FilterBuff indicates a search buffer. + FilterBuff +) + type ( + // BufferKind indicates a buffer type + BufferKind int8 + // BuffWatcher represents a command buffer listener. BuffWatcher interface { // Changed indicates the buffer was changed. BufferChanged(s string) // Active indicates the buff activity changed. - BufferActive(state bool) + BufferActive(state bool, kind BufferKind) } // CmdBuff represents user command input. CmdBuff struct { buff []rune + kind BufferKind hotKey rune active bool listeners []BuffWatcher @@ -22,9 +33,10 @@ type ( ) // NewCmdBuff returns a new command buffer. -func NewCmdBuff(key rune) *CmdBuff { +func NewCmdBuff(key rune, kind BufferKind) *CmdBuff { return &CmdBuff{ hotKey: key, + kind: kind, buff: make([]rune, 0, maxBuff), listeners: []BuffWatcher{}, } @@ -47,8 +59,9 @@ func (c *CmdBuff) String() string { } // Set initializes the buffer with a command. -func (c *CmdBuff) Set(rr []rune) { - c.buff = rr +func (c *CmdBuff) Set(cmd string) { + c.buff = []rune(cmd) + c.fireChanged() } // Add adds a new charater to the buffer. @@ -104,6 +117,6 @@ func (c *CmdBuff) fireChanged() { func (c *CmdBuff) fireActive(b bool) { for _, l := range c.listeners { - l.BufferActive(b) + l.BufferActive(b, c.kind) } } diff --git a/internal/ui/cmd_buff_test.go b/internal/ui/cmd_buff_test.go index 347865da..e370c4a1 100644 --- a/internal/ui/cmd_buff_test.go +++ b/internal/ui/cmd_buff_test.go @@ -16,7 +16,7 @@ func (l *testListener) BufferChanged(s string) { l.text = s } -func (l *testListener) BufferActive(s bool) { +func (l *testListener) BufferActive(s bool, _ BufferKind) { if s { l.act++ return @@ -25,7 +25,7 @@ func (l *testListener) BufferActive(s bool) { } func TestCmdBuffActivate(t *testing.T) { - b, l := NewCmdBuff('>'), testListener{} + b, l := NewCmdBuff('>', CommandBuff), testListener{} b.AddListener(&l) b.SetActive(true) @@ -35,7 +35,7 @@ func TestCmdBuffActivate(t *testing.T) { } func TestCmdBuffDeactivate(t *testing.T) { - b, l := NewCmdBuff('>'), testListener{} + b, l := NewCmdBuff('>', CommandBuff), testListener{} b.AddListener(&l) b.SetActive(false) @@ -45,7 +45,7 @@ func TestCmdBuffDeactivate(t *testing.T) { } func TestCmdBuffChanged(t *testing.T) { - b, l := NewCmdBuff('>'), testListener{} + b, l := NewCmdBuff('>', CommandBuff), testListener{} b.AddListener(&l) b.Add('b') @@ -77,7 +77,7 @@ func TestCmdBuffChanged(t *testing.T) { } func TestCmdBuffAdd(t *testing.T) { - b := NewCmdBuff('>') + b := NewCmdBuff('>', CommandBuff) uu := []struct { runes []rune @@ -98,7 +98,7 @@ func TestCmdBuffAdd(t *testing.T) { } func TestCmdBuffDel(t *testing.T) { - b := NewCmdBuff('>') + b := NewCmdBuff('>', CommandBuff) uu := []struct { runes []rune @@ -120,7 +120,7 @@ func TestCmdBuffDel(t *testing.T) { } func TestCmdBuffEmpty(t *testing.T) { - b := NewCmdBuff('>') + b := NewCmdBuff('>', CommandBuff) uu := []struct { runes []rune diff --git a/internal/ui/cmd_test.go b/internal/ui/cmd_test.go index 8425945c..ad33e552 100644 --- a/internal/ui/cmd_test.go +++ b/internal/ui/cmd_test.go @@ -9,20 +9,20 @@ import ( func TestNewCmdUpdate(t *testing.T) { defaults, _ := config.NewStyles("") - v := NewCmdView(defaults, 'T') + v := NewCmdView(defaults) v.update("blee") - assert.Equal(t, "T> blee\n", v.GetText(false)) + assert.Equal(t, "\x00> blee\n", v.GetText(false)) } func TestCmdInCmdMode(t *testing.T) { defaults, _ := config.NewStyles("") - v := NewCmdView(defaults, 'T') + v := NewCmdView(defaults) v.update("blee") v.append('!') - assert.Equal(t, "T> blee!\n", v.GetText(false)) + assert.Equal(t, "\x00> blee!\n", v.GetText(false)) assert.False(t, v.InCmdMode()) - v.BufferActive(true) + v.BufferActive(true, CommandBuff) assert.True(t, v.InCmdMode()) } diff --git a/internal/ui/menu.go b/internal/ui/menu.go index 809d0925..e212e09b 100644 --- a/internal/ui/menu.go +++ b/internal/ui/menu.go @@ -47,7 +47,7 @@ func (v *MenuView) HydrateMenu(hh Hints) { t := v.buildMenuTable(hh) for row := 0; row < len(t); row++ { for col := 0; col < len(t[row]); col++ { - if len(t[row][col]) == 0 { + if t[row][col] == "" { continue } c := tview.NewTableCell(t[row][col]) @@ -57,33 +57,42 @@ func (v *MenuView) HydrateMenu(hh Hints) { } } +func isDigit(s string) bool { + return menuRX.MatchString(s) +} + func (v *MenuView) buildMenuTable(hh Hints) [][]string { - table := make([][]Hint, maxRows+1) - - colCount := (len(hh) / maxRows) + 1 - for row := 0; row < maxRows; row++ { - table[row] = make([]Hint, colCount+1) + table := make([][]Hint, maxRows) + colCount := len(hh) / maxRows + if colCount == 0 { + colCount = 1 } - - var row, col int + if isDigit(hh[0].Mnemonic) { + colCount++ + } + for row := 0; row < maxRows; row++ { + table[row] = make([]Hint, colCount) + } + var row, col, added int firstCmd := true maxKeys := make([]int, colCount+1) for _, h := range hh { if !h.Visible { continue } - isDigit := menuRX.MatchString(h.Mnemonic) - if !isDigit && firstCmd { + if !isDigit(h.Mnemonic) && firstCmd { row, col, firstCmd = 0, col+1, false + if added == 0 { + col = 0 + } } if maxKeys[col] < len(h.Mnemonic) { maxKeys[col] = len(h.Mnemonic) } table[row][col] = h - row++ + added, row = added+1, row+1 if row >= maxRows { - col++ - row = 0 + row, col = 0, col+1 } } @@ -248,6 +257,7 @@ func initKeys() { initNumbKeys() initStdKeys() initShiftKeys() + initCtrlKeys() } func initNumbKeys() { @@ -292,6 +302,12 @@ func initStdKeys() { tcell.KeyNames[tcell.Key(KeyZ)] = "z" } +// BOZO!! No sure why these aren't mapped?? +func initCtrlKeys() { + tcell.KeyNames[tcell.KeyCtrlI] = "Ctrl-I" + tcell.KeyNames[tcell.KeyCtrlM] = "Ctrl-M" +} + func initShiftKeys() { tcell.KeyNames[tcell.Key(KeyShiftA)] = "Shift-A" tcell.KeyNames[tcell.Key(KeyShiftB)] = "Shift-B" diff --git a/internal/ui/menu_test.go b/internal/ui/menu_test.go index 47fbf577..1be4b70f 100644 --- a/internal/ui/menu_test.go +++ b/internal/ui/menu_test.go @@ -12,9 +12,9 @@ func TestNewMenuView(t *testing.T) { defaults, _ := config.NewStyles("") v := NewMenuView(defaults) v.HydrateMenu(Hints{ + {"0", "zero", true}, {"a", "bleeA", true}, {"b", "bleeB", true}, - {"0", "zero", true}, }) assert.Equal(t, " [fuchsia:black:b]<0> [white:black:d]zero ", v.GetCell(0, 0).Text) diff --git a/internal/ui/table.go b/internal/ui/table.go index 373b42af..f17e1c2a 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -12,8 +12,6 @@ import ( "github.com/derailed/k9s/internal/resource" "github.com/derailed/tview" "github.com/gdamore/tcell" - - // "github.com/ktr0731/go-fuzzyfinder/matching" "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" "k8s.io/apimachinery/pkg/util/duration" @@ -52,7 +50,7 @@ func NewTable(title string, styles *config.Styles) *Table { Table: tview.NewTable(), styles: styles, actions: make(KeyActions), - cmdBuff: NewCmdBuff('/'), + cmdBuff: NewCmdBuff('/', FilterBuff), baseTitle: title, sortCol: SortColumn{0, 0, true}, } @@ -178,6 +176,7 @@ func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey { v.SearchBuff().Add(evt.Rune()) v.ClearSelection() v.doUpdate(v.filtered()) + v.UpdateTitle() v.SelectFirstRow() return nil } @@ -383,7 +382,7 @@ func (v *Table) filtered() resource.TableData { q := v.cmdBuff.String() if isFuzzySelector(q) { - return v.fuzzFilter(q[2:]) + return v.fuzzyFilter(q[2:]) } return v.rxFilter(q) @@ -412,7 +411,7 @@ func (v *Table) rxFilter(q string) resource.TableData { return filtered } -func (v *Table) fuzzFilter(q string) resource.TableData { +func (v *Table) fuzzyFilter(q string) resource.TableData { var ss, kk []string for k, row := range v.data.Rows { ss = append(ss, row.Fields[v.NameColIndex()]) @@ -506,7 +505,7 @@ func (v *Table) UpdateTitle() { title = skinTitle(fmt.Sprintf(nsTitleFmt, v.baseTitle, ns, rc), v.styles.Frame()) } - if !v.cmdBuff.IsActive() && !v.cmdBuff.Empty() { + if !v.cmdBuff.Empty() { cmd := v.cmdBuff.String() if isLabelSelector(cmd) { cmd = trimLabelSelector(cmd) diff --git a/internal/views/alias.go b/internal/views/alias.go index f8c2eed2..584ef7be 100644 --- a/internal/views/alias.go +++ b/internal/views/alias.go @@ -49,6 +49,7 @@ func (v *aliasView) Init(context.Context, string) { func (v *aliasView) registerActions() { v.RmAction(ui.KeyShiftA) + v.RmAction(tcell.KeyCtrlS) v.SetActions(ui.KeyActions{ tcell.KeyEnter: ui.NewKeyAction("Goto", v.gotoCmd, true), diff --git a/internal/views/app.go b/internal/views/app.go index 97a6ff0c..fb97d284 100644 --- a/internal/views/app.go +++ b/internal/views/app.go @@ -20,6 +20,7 @@ const ( splashTime = 1 devMode = "dev" clusterRefresh = time.Duration(5 * time.Second) + indicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%" ) type ( @@ -55,6 +56,7 @@ type ( forwarders map[string]forwarder version string showHeader bool + filter string } ) @@ -105,7 +107,6 @@ func (a *appView) Init(version string, rate int) { a.Main().AddPage("splash", ui.NewSplash(a.Styles, version), true, true) main.AddItem(a.indicator(), 1, 1, false) - // main.AddItem(a.Cmd(), 3, 1, false) main.AddItem(a.Frame(), 0, 10, true) main.AddItem(a.Crumbs(), 2, 1, false) main.AddItem(a.Flash(), 1, 1, false) @@ -116,7 +117,7 @@ func (a *appView) Init(version string, rate int) { func (a *appView) BufferChanged(s string) {} // Active indicates the buff activity changed. -func (a *appView) BufferActive(state bool) { +func (a *appView) BufferActive(state bool, _ ui.BufferKind) { flex, ok := a.Main().GetPrimitive("main").(*tview.Flex) if !ok { return @@ -144,6 +145,7 @@ func (a *appView) toggleHeader(flag bool) { func (a *appView) buildHeader() tview.Primitive { header := tview.NewFlex() + header.SetBorderPadding(0, 0, 1, 1) header.SetDirection(tview.FlexColumn) if !a.showHeader { return header @@ -192,7 +194,7 @@ func (a *appView) refreshIndicator() { } info := fmt.Sprintf( - "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%", + indicatorFmt, a.version, cluster.ClusterName(), cluster.UserName(), diff --git a/internal/views/command.go b/internal/views/command.go index 5f3e3888..2f78b824 100644 --- a/internal/views/command.go +++ b/internal/views/command.go @@ -54,7 +54,7 @@ func (c *command) defaultCmd() { // Helpers... -var policyMatcher = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`) +var authRX = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`) func (c *command) isK9sCmd(cmd string) bool { cmds := strings.Split(cmd, " ") @@ -69,10 +69,10 @@ func (c *command) isK9sCmd(cmd string) bool { c.app.aliasCmd(nil) return true default: - if !policyMatcher.MatchString(cmd) { + if !authRX.MatchString(cmd) { return false } - tokens := policyMatcher.FindAllStringSubmatch(cmd, -1) + tokens := authRX.FindAllStringSubmatch(cmd, -1) if len(tokens) == 1 && len(tokens[0]) == 3 { c.app.inject(newPolicyView(c.app, tokens[0][1], tokens[0][2])) return true @@ -100,8 +100,8 @@ func (c *command) viewMetaFor(cmd string) (string, *viewer) { } v, ok := vv[gvr] if !ok { - log.Error().Err(fmt.Errorf("Huh? `%s` viewer not found", cmd)).Msg("Viewer Failed") - c.app.Flash().Warnf("Huh? `%s` viewer not found", gvr) + log.Error().Err(fmt.Errorf("Huh? `%s` viewer not found", gvr)).Msg("Viewer Failed") + c.app.Flash().Warnf("Huh? viewer for %s not found", cmd) return "", nil } diff --git a/internal/views/container.go b/internal/views/container.go index 11346219..d1af6de3 100644 --- a/internal/views/container.go +++ b/internal/views/container.go @@ -60,7 +60,6 @@ func (v *containerView) k9sEnv() K9sEnv { ns, n := namespaced(*v.path) env["POD"] = n env["NAMESPACE"] = ns - log.Debug().Msgf("OVER ENV %#v", env) return env } diff --git a/internal/views/details.go b/internal/views/details.go index d8820aa3..b39732c8 100644 --- a/internal/views/details.go +++ b/internal/views/details.go @@ -55,7 +55,7 @@ func newDetailsView(app *appView, backFn ui.ActionHandler) *detailsView { v.SetTitleColor(tcell.ColorAqua) v.SetInputCapture(v.keyboard) - v.cmdBuff = ui.NewCmdBuff('/') + v.cmdBuff = ui.NewCmdBuff('/', ui.FilterBuff) v.cmdBuff.AddListener(app.Cmd()) v.cmdBuff.Reset() diff --git a/internal/views/env.go b/internal/views/env.go index 0e35b8cd..71ed834a 100644 --- a/internal/views/env.go +++ b/internal/views/env.go @@ -10,7 +10,7 @@ import ( type K9sEnv map[string]string // EnvRX match $XXX custom arg. -var envRX = regexp.MustCompile(`\A\$([\w|-]+)`) +var envRX = regexp.MustCompile(`\A\$([\w]+)`) func (e K9sEnv) envFor(n string) (string, error) { envs := envRX.FindStringSubmatch(n) @@ -22,5 +22,5 @@ func (e K9sEnv) envFor(n string) (string, error) { return "", fmt.Errorf("No matching for %s", n) } - return env, nil + return envRX.ReplaceAllString(n, env), nil } diff --git a/internal/views/env_test.go b/internal/views/env_test.go index efdb07cb..10aadd76 100644 --- a/internal/views/env_test.go +++ b/internal/views/env_test.go @@ -14,16 +14,17 @@ func TestK9sEnv(t *testing.T) { err error e string }{ - "match": {q: "$A", err: nil, e: "10"}, + "match": {q: "$A", e: "10"}, "noMatch": {q: "$BLEE", err: errors.New("No matching for $BLEE"), e: ""}, - "lower": {q: "$b", err: nil, e: "blee"}, - "dash": {q: "$col-0", err: nil, e: "fred"}, + "lower": {q: "$b", e: "blee"}, + "dash": {q: "$col0", e: "fred"}, + "mix": {q: "$col0-blee", e: "fred-blee"}, } e := K9sEnv{ - "A": "10", - "B": "blee", - "COL-0": "fred", + "A": "10", + "B": "blee", + "COL0": "fred", } for k, u := range uu { diff --git a/internal/views/log.go b/internal/views/log.go index 6a5835eb..77df56ca 100644 --- a/internal/views/log.go +++ b/internal/views/log.go @@ -130,16 +130,16 @@ func (v *logView) flush(index int, buff []string) { return } - v.log(strings.Join(buff[:index], "\n")) if atomic.LoadInt32(&v.autoScroll) == 1 { + v.log(strings.Join(buff[:index], "\n")) v.app.QueueUpdateDraw(func() { - v.update() + v.updateIndicator() v.logs.ScrollToEnd() }) } } -func (v *logView) update() { +func (v *logView) updateIndicator() { status := "Off" if v.autoScroll == 1 { status = "On" @@ -205,7 +205,7 @@ func (v *logView) toggleScrollCmd(evt *tcell.EventKey) *tcell.EventKey { v.logs.LineUp() v.app.Flash().Info("Autoscroll is off.") } - v.update() + v.updateIndicator() return nil } diff --git a/internal/views/resource.go b/internal/views/resource.go index 10e69509..3417e6b3 100644 --- a/internal/views/resource.go +++ b/internal/views/resource.go @@ -334,7 +334,6 @@ func (v *resourceView) refresh() { if v.list.Namespaced() { v.list.SetNamespace(v.currentNS) } - log.Debug().Msgf("Reconcile with NS %q", v.currentNS) if err := v.list.Reconcile(v.app.informer, v.path); err != nil { v.app.Flash().Err(err) } @@ -347,7 +346,6 @@ func (v *resourceView) refresh() { func (v *resourceView) namespaceActions(aa ui.KeyActions) { ns, err := v.app.Conn().Config().CurrentNamespaceName() - log.Debug().Msgf("NAMESPACE %q -- %v", ns, err) if err == nil && ns != resource.AllNamespace { return } @@ -401,7 +399,13 @@ func (v *resourceView) refreshActions() { } func (v *resourceView) customActions(aa ui.KeyActions) { - for k, plugin := range v.app.Config.K9s.Plugins { + pp := config.NewPlugins() + if err := pp.Load(); err != nil { + log.Warn().Msgf("No plugin configuration found") + return + } + + for k, plugin := range pp.Plugin { if !in(plugin.Scopes, v.list.GetName()) { continue } @@ -428,10 +432,12 @@ func (v *resourceView) execCmd(bin string, bg bool, args ...string) ui.ActionHan return evt } - env := v.envFn() - aa := make([]string, len(args)) + var ( + env = v.envFn() + aa = make([]string, len(args)) + err error + ) for i, a := range args { - var err error aa[i], err = env.envFor(a) if err != nil { log.Error().Err(err).Msg("Args match failed") @@ -454,12 +460,10 @@ func (v *resourceView) defaultK9sEnv() K9sEnv { "NAMESPACE": ns, "NAME": n, } - row := v.masterPage().GetRow() for i, r := range row { - env["COL-"+strconv.Itoa(i)] = r + env["COL"+strconv.Itoa(i)] = r } - log.Debug().Msgf("ENVs %#v", env) return env } diff --git a/internal/views/table.go b/internal/views/table.go index 27dc3582..d103a900 100644 --- a/internal/views/table.go +++ b/internal/views/table.go @@ -19,8 +19,7 @@ func newTableView(app *appView, title string) *tableView { } v.SearchBuff().AddListener(app.Cmd()) v.SearchBuff().AddListener(&v) - v.SearchBuff().Reset() - + v.SearchBuff().Set(app.filter) v.bindKeys() return &v @@ -30,8 +29,8 @@ func newTableView(app *appView, title string) *tableView { func (v *tableView) BufferChanged(s string) {} // BufferActive indicates the buff activity changed. -func (v *tableView) BufferActive(state bool) { - v.app.BufferActive(state) +func (v *tableView) BufferActive(state bool, k ui.BufferKind) { + v.app.BufferActive(state, k) } func (v *tableView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -46,6 +45,11 @@ func (v *tableView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *tableView) setFilterFn(fn func(string)) { v.filterFn = fn + + cmd := v.SearchBuff().String() + if isLabelSelector(cmd) && v.filterFn != nil { + v.filterFn(trimLabelSelector(cmd)) + } } func (v *tableView) bindKeys() { @@ -70,6 +74,7 @@ func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey { v.SearchBuff().SetActive(false) cmd := v.SearchBuff().String() + v.app.filter = cmd if isLabelSelector(cmd) && v.filterFn != nil { v.filterFn(trimLabelSelector(cmd)) return nil @@ -91,6 +96,7 @@ func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { if !v.SearchBuff().Empty() { v.app.Flash().Info("Clearing filter...") } + v.app.filter = "" if isLabelSelector(v.SearchBuff().String()) { v.filterFn("") } @@ -106,11 +112,12 @@ func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey { } v.app.Flash().Info("Filter mode activated.") - if isLabelSelector(v.SearchBuff().String()) { - return nil - } - v.SearchBuff().Reset() + // if isLabelSelector(v.SearchBuff().String()) { + // return nil + // } + // v.SearchBuff().Reset() v.SearchBuff().SetActive(true) + v.SearchBuff().Set(v.app.filter) return nil } diff --git a/internal/views/table_test.go b/internal/views/table_test.go index ade5cb9c..d9bddf72 100644 --- a/internal/views/table_test.go +++ b/internal/views/table_test.go @@ -73,7 +73,7 @@ func TestTableViewFilter(t *testing.T) { } v.Update(data) v.SearchBuff().SetActive(true) - v.SearchBuff().Set([]rune("blee")) + v.SearchBuff().Set("blee") v.filterCmd(nil) assert.Equal(t, 2, v.GetRowCount()) v.resetCmd(nil)