diff --git a/internal/view/app.go b/internal/view/app.go index 89a4e147e8..4d1d2fc4ce 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -151,6 +151,16 @@ func (a *App) initSignals() { } func (a *App) suggestCommand() model.SuggestionFunc { + namespaceNames, err := a.namespaceNames() + if err != nil { + log.Error().Err(err).Msg("failed to list namespaces") + } + + contextNames, err := a.contextNames() + if err != nil { + log.Error().Err(err).Msg("failed to list contexts") + } + return func(s string) (entries sort.StringSlice) { if s == "" { if a.cmdHistory.Empty() { @@ -161,13 +171,12 @@ func (a *App) suggestCommand() model.SuggestionFunc { s = strings.ToLower(s) for _, k := range a.command.alias.Aliases.Keys() { - if k == s { - continue - } - if strings.HasPrefix(k, s) { - entries = append(entries, strings.Replace(k, s, "", 1)) + if suggest, ok := shouldAddSuggest(s, k); ok { + entries = append(entries, suggest) } } + + entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...) if len(entries) == 0 { return nil } @@ -176,6 +185,32 @@ func (a *App) suggestCommand() model.SuggestionFunc { } } +func (a *App) namespaceNames() ([]string, error) { + namespaces, err := a.factory.Client().ValidNamespaces() + if err != nil { + return nil, err + } + + namespaceNames := make([]string, 0, len(namespaces)) + for _, namespace := range namespaces { + namespaceNames = append(namespaceNames, namespace.Name) + } + return namespaceNames, nil +} + +func (a *App) contextNames() ([]string, error) { + contexts, err := a.factory.Client().Config().Contexts() + if err != nil { + return nil, err + } + + contextNames := make([]string, 0, len(contexts)) + for ctxName := range contexts { + contextNames = append(contextNames, ctxName) + } + return contextNames, nil +} + func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey { if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() { return k.Action(evt) @@ -671,3 +706,45 @@ func (a *App) clusterInfo() *ClusterInfo { func (a *App) statusIndicator() *ui.StatusIndicator { return a.Views()["statusIndicator"].(*ui.StatusIndicator) } + +// ---------------------------------------------------------------------------- +// Helpers + +func suggestSubCommand(command string, namespaces, contexts []string) []string { + cmds := strings.Fields(command) + if len(cmds[0]) == 0 || len(cmds) != 2 { + return nil + } + + var suggests []string + switch strings.ToLower(cmds[0]) { + case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir": + return nil // ignore special commands + case "ctx", "context", "contexts": + for _, ctxName := range contexts { + if suggest, ok := shouldAddSuggest(cmds[1], ctxName); ok { + suggests = append(suggests, suggest) + } + } + default: + if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok { + suggests = append(suggests, suggest) + } + + for _, ns := range namespaces { + if suggest, ok := shouldAddSuggest(cmds[1], ns); ok { + suggests = append(suggests, suggest) + } + } + } + + return suggests +} + +func shouldAddSuggest(command, suggest string) (string, bool) { + if command != suggest && strings.HasPrefix(suggest, command) { + return strings.TrimPrefix(suggest, command), true + } + + return "", false +} diff --git a/internal/view/app_test.go b/internal/view/app_test.go index e214d7c4db..42555251d9 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -1,19 +1,46 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package view_test +package view import ( "testing" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/view" "github.com/stretchr/testify/assert" ) func TestAppNew(t *testing.T) { - a := view.NewApp(config.NewConfig(ks{})) + a := NewApp(config.NewConfig(ks{})) _ = a.Init("blee", 10) assert.Equal(t, 11, len(a.GetActions())) } + +func Test_suggestSubCommand(t *testing.T) { + namespaceNames := []string{"kube-system", "kube-public", "default", "nginx-ingress"} + contextNames := []string{"develop", "test", "pre", "prod"} + + tests := []struct { + Command string + Suggestions []string + }{ + {Command: "q", Suggestions: nil}, + {Command: "xray dp", Suggestions: nil}, + {Command: "help k", Suggestions: nil}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx pr", Suggestions: []string{"e", "od"}}, + {Command: "context d", Suggestions: []string{"evelop"}}, + {Command: "contexts t", Suggestions: []string{"est"}}, + {Command: "po ", Suggestions: nil}, + {Command: "po x", Suggestions: nil}, + {Command: "po k", Suggestions: []string{"ube-system", "ube-public"}}, + {Command: "po kube-", Suggestions: []string{"system", "public"}}, + } + + for _, tt := range tests { + got := suggestSubCommand(tt.Command, namespaceNames, contextNames) + assert.Equal(t, tt.Suggestions, got) + } +}