diff --git a/README.md b/README.md index 5ff67c5ca4..f8f7f61c0e 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,8 @@ K9s uses aliases to navigate most K8s resources. | To view and switch to another Kubernetes context (Pod view) | `:`ctx⏎ | | | To view and switch directly to another Kubernetes context (Last used view) | `:`ctx context-name⏎ | | | To view and switch to another Kubernetes namespace | `:`ns⏎ | | +| To switch between the last active view/command (like how "cd -" works) | `-` | Navigation that adds breadcrumbs to the bottom are not commands | +| To go back through previous views/commands | `b` | Same as above | | To view all saved resources | `:`screendump or sd⏎ | | | To delete a resource (TAB and ENTER to confirm) | `ctrl-d` | | | To kill a resource (no confirmation dialog, equivalent to kubectl delete --now) | `ctrl-k` | | diff --git a/internal/model/history.go b/internal/model/history.go index 7469e32af6..adb4df6c5a 100644 --- a/internal/model/history.go +++ b/internal/model/history.go @@ -23,7 +23,8 @@ func NewHistory(limit int) *History { } } -func (h *History) Pop() string { +// Last returns the most recent history item +func (h *History) Last() string { if h.Empty() { return "" } @@ -31,6 +32,25 @@ func (h *History) Pop() string { return h.commands[0] } +// Pop removes the most recent history item and returns a bool if the list changed. +// Optional argument specifies how many to remove from the history +func (h *History) Pop(n ...int) bool { + if len(h.commands) == 0 { + return false + } + + count := 1 + if len(n) > 1 { + // only one argument is expected + return false + } else if len(n) == 1 { + count = n[0] + } + + h.commands = h.commands[count:] + return true +} + // List returns the current command history. func (h *History) List() []string { return h.commands diff --git a/internal/ui/key.go b/internal/ui/key.go index ff90b310ba..1f36b9d45a 100644 --- a/internal/ui/key.go +++ b/internal/ui/key.go @@ -80,6 +80,7 @@ const ( KeySlash = 47 KeyColon = 58 KeySpace = 32 + KeyDash = 45 //or minus for those searching in the code ) // Define Shift Keys. diff --git a/internal/view/app.go b/internal/view/app.go index 4ac7e7c2b1..23d6248b45 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -244,6 +244,8 @@ func (a *App) bindKeys() { tcell.KeyCtrlE: ui.NewSharedKeyAction("ToggleHeader", a.toggleHeaderCmd, false), tcell.KeyCtrlG: ui.NewSharedKeyAction("toggleCrumbs", a.toggleCrumbsCmd, false), ui.KeyHelp: ui.NewSharedKeyAction("Help", a.helpCmd, false), + ui.KeyB: ui.NewSharedKeyAction("Go Back", a.previousView, false), + ui.KeyDash: ui.NewSharedKeyAction("Last View", a.lastView, false), tcell.KeyCtrlA: ui.NewSharedKeyAction("Aliases", a.aliasCmd, false), tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, false), tcell.KeyCtrlC: ui.NewKeyAction("Quit", a.quitCmd, false), @@ -691,6 +693,54 @@ func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } +// previousView returns to the view prior to the current one in the history +func (a *App) previousView(evt *tcell.EventKey) *tcell.EventKey { + if evt != nil && evt.Rune() == rune(ui.KeyB) && a.Prompt().InCmdMode() { + return evt + } + cmds := a.cmdHistory.List() + if !(len(cmds) > 1) { + dialog.ShowError( + a.Styles.Dialog(), + a.Content.Pages, + "Can't go back any further") + return evt + } else { + previousCmd := cmds[1] + a.cmdHistory.Pop() + a.gotoResource(previousCmd, "", true) + a.ResetCmd() + } + + return nil +} + +// lastView switches between the last view and this one a la `cd -` +func (a *App) lastView(evt *tcell.EventKey) *tcell.EventKey { + if evt != nil && evt.Rune() == ui.KeyDash && a.Prompt().InCmdMode() { + return evt + } + cmds := a.cmdHistory.List() + if !(len(cmds) > 1) { + dialog.ShowError( + a.Styles.Dialog(), + a.Content.Pages, + "No previous view to switch to") + return evt + } else { + current := cmds[0] + last := cmds[1] + a.gotoResource(last, "", true) + // remove current page and last page + a.cmdHistory.Pop(2) + // re add in opposite order + a.cmdHistory.Push(current) + a.cmdHistory.Push(last) + } + + return nil +} + func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { if a.Content.Top() != nil && a.Content.Top().Name() == aliasTitle { a.Content.Pop() diff --git a/internal/view/app_test.go b/internal/view/app_test.go index e1e932f0e6..d4d709d952 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -15,5 +15,5 @@ func TestAppNew(t *testing.T) { a := view.NewApp(mock.NewMockConfig()) _ = a.Init("blee", 10) - assert.Equal(t, 12, a.GetActions().Len()) + assert.Equal(t, 14, a.GetActions().Len()) } diff --git a/internal/view/command.go b/internal/view/command.go index befa0b8dca..22415a38bb 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -319,7 +319,7 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component, log.Error().Msg(string(debug.Stack())) p := cmd.NewInterpreter("pod") - if cmd := c.app.cmdHistory.Pop(); cmd != "" { + if cmd := c.app.cmdHistory.Last(); cmd != "" { p = p.Reset(cmd) } err = c.run(p, "", true)