Skip to content

Commit

Permalink
feat(history): add keyboard shortcuts to navigate history
Browse files Browse the repository at this point in the history
  • Loading branch information
tyzbit committed Sep 24, 2024
1 parent 8652c78 commit daa903e
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 101 deletions.
98 changes: 68 additions & 30 deletions internal/model/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ const MaxHistory = 20

// History represents a command history.
type History struct {
commands []string
limit int
commands []string
limit int
activeCommandIndex int
previousCommandIndex int
}

// NewHistory returns a new instance.
Expand All @@ -23,31 +25,76 @@ func NewHistory(limit int) *History {
}
}

// Last returns the most recent history item
func (h *History) Last() string {
// Last switches the current and previous history index positions so the
// new command referenced by the index is the previous command
func (h *History) Last() bool {
if h.Empty() {
return ""
return false
}

return h.commands[0]
h.activeCommandIndex, h.previousCommandIndex = h.previousCommandIndex, h.activeCommandIndex
return true
}

// 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 {
// Back moves the history position index back by one
func (h *History) Back() bool {
if h.Empty() {
return false
}

// Return if there are no more commands left in the backward history
if h.activeCommandIndex == 0 {
return false
}

h.previousCommandIndex = h.activeCommandIndex
h.activeCommandIndex = h.activeCommandIndex - 1
return true
}

// Forward moves the history position index forward by one
func (h *History) Forward() bool {
if h.Empty() {
return false
}

// Return if there are no more commands left in the forward history
if h.activeCommandIndex >= len(h.commands)-1 {
return false
}

count := 1
if len(n) > 1 {
// only one argument is expected
h.previousCommandIndex = h.activeCommandIndex
h.activeCommandIndex = h.activeCommandIndex + 1
return true
}

// CurrentIndex returns the current index of the active command in the history
func (h *History) CurrentIndex() int {
return h.activeCommandIndex
}

// PreviousIndex returns the index of the command that was the most recent
// active command in the history
func (h *History) PreviousIndex() int {
return h.previousCommandIndex
}

// Pop removes the single most recent history item
// and returns a bool if the list changed.
func (h *History) Pop() bool {
return h.PopN(1)
}

// PopN removes the N most recent history item
// and returns a bool if the list changed.
// Argument specifies how many to remove from the history
func (h *History) PopN(n int) bool {
cmdLength := len(h.commands)
if cmdLength == 0 {
return false
} else if len(n) == 1 {
count = n[0]
}

h.commands = h.commands[count:]
h.commands = h.commands[:cmdLength-n]
return true
}

Expand All @@ -63,31 +110,22 @@ func (h *History) Push(c string) {
}

c = strings.ToLower(c)
if i := h.indexOf(c); i != -1 {
return
}
if len(h.commands) < h.limit {
h.commands = append([]string{c}, h.commands...)
h.commands = append(h.commands, c)
h.previousCommandIndex = h.activeCommandIndex
h.activeCommandIndex = len(h.commands) - 1
return
}
h.commands = append([]string{c}, h.commands[:len(h.commands)-1]...)
}

// Clear clears out the stack.
func (h *History) Clear() {
h.commands = nil
h.activeCommandIndex = 0
h.previousCommandIndex = 0
}

// Empty returns true if no history.
func (h *History) Empty() bool {
return len(h.commands) == 0
}

func (h *History) indexOf(s string) int {
for i, c := range h.commands {
if c == s {
return i
}
}
return -1
}
12 changes: 7 additions & 5 deletions internal/ui/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ const (
KeyX
KeyY
KeyZ
KeyHelp = 63
KeySlash = 47
KeyColon = 58
KeySpace = 32
KeyDash = 45 //or minus for those searching in the code
KeyHelp = 63
KeySlash = 47
KeyColon = 58
KeySpace = 32
KeyDash = 45 //or minus for those searching in the code
KeyLeftBracket = 91
KeyRightBracket = 93
)

// Define Shift Keys.
Expand Down
2 changes: 1 addition & 1 deletion internal/view/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func hotKeyActions(r Runner, aa *ui.KeyActions) error {

func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler {
return func(evt *tcell.EventKey) *tcell.EventKey {
r.App().gotoResource(cmd, path, clearStack)
r.App().gotoResource(cmd, path, clearStack, true)
return nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/view/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if r != 0 {
s := ui.TrimCell(a.GetTable().SelectTable, r, 1)
tokens := strings.Split(s, ",")
a.App().gotoResource(tokens[0], "", true)
a.App().gotoResource(tokens[0], "", true, true)
return nil
}

Expand Down
83 changes: 41 additions & 42 deletions internal/view/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,16 @@ func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {

func (a *App) bindKeys() {
a.AddActions(ui.NewKeyActionsFromMap(ui.KeyMap{
ui.KeyShift9: ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false),
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),
ui.KeyShift9: ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false),
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.KeyLeftBracket: ui.NewSharedKeyAction("Go Back", a.previousCommand, false),
ui.KeyRightBracket: ui.NewSharedKeyAction("Go Forward", a.nextCommand, false),
ui.KeyDash: ui.NewSharedKeyAction("Last View", a.lastCommand, 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),
}))
}

Expand Down Expand Up @@ -484,7 +485,7 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error {
log.Debug().Msgf("--> Switching Context %q -- %q -- %q", name, ns, a.Config.ActiveView())
a.Flash().Infof("Switching context to %q::%q", name, ns)
a.ReloadStyles()
a.gotoResource(a.Config.ActiveView(), "", true)
a.gotoResource(a.Config.ActiveView(), "", true, true)
a.clusterModel.Reset(a.factory)
}

Expand Down Expand Up @@ -632,7 +633,7 @@ func (a *App) toggleCrumbsCmd(evt *tcell.EventKey) *tcell.EventKey {

func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() {
a.gotoResource(a.GetCmd(), "", true)
a.gotoResource(a.GetCmd(), "", true, true)
a.ResetCmd()
return nil
}
Expand Down Expand Up @@ -693,51 +694,49 @@ 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() {
// previousCommand returns to the command prior to the current one in the history
func (a *App) previousCommand(evt *tcell.EventKey) *tcell.EventKey {
if evt != nil && evt.Rune() == rune(ui.KeyLeftBracket) && 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")
if !a.cmdHistory.Back() {
a.App.Flash().Warn("Can't go back any further")
return evt
} else {
previousCmd := cmds[1]
a.cmdHistory.Pop()
a.gotoResource(previousCmd, "", true)
a.ResetCmd()
}
a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
return nil
}

// nextCommand returns to the command subsequent to the current one in the history
func (a *App) nextCommand(evt *tcell.EventKey) *tcell.EventKey {
if evt != nil && evt.Rune() == rune(ui.KeyRightBracket) && a.Prompt().InCmdMode() {
return evt
}
cmds := a.cmdHistory.List()
if !a.cmdHistory.Forward() {
a.App.Flash().Warn("Can't go forward any further")
return evt
}
// We go to the resource before updating the history so that
// gotoResource doesn't add this command to the history
a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
return nil
}

// lastView switches between the last view and this one a la `cd -`
func (a *App) lastView(evt *tcell.EventKey) *tcell.EventKey {
// lastCommand switches between the last command and the current one a la `cd -`
func (a *App) lastCommand(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")
if len(cmds) < 1 {
a.App.Flash().Warn("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)
a.cmdHistory.Last()
a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
}

return nil
}

Expand All @@ -754,8 +753,8 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}

func (a *App) gotoResource(c, path string, clearStack bool) {
err := a.command.run(cmd.NewInterpreter(c), path, clearStack)
func (a *App) gotoResource(c, path string, clearStack bool, pushCmd bool) {
err := a.command.run(cmd.NewInterpreter(c), path, clearStack, pushCmd)
if err != nil {
dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error())
}
Expand Down
Loading

0 comments on commit daa903e

Please sign in to comment.