From e1ab2383b31937defdfea1859791d971cbfad97c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 4 Nov 2022 20:13:10 +0530 Subject: [PATCH] Add basic history operations --- tools/cmd/at/shell.go | 2 +- tools/tui/readline/actions.go | 90 ++++++++++++++++++++++++++++++----- tools/tui/readline/api.go | 3 ++ tools/tui/readline/history.go | 49 +++++++++++++++++++ tools/tui/readline/keys.go | 5 ++ 5 files changed, 136 insertions(+), 13 deletions(-) diff --git a/tools/cmd/at/shell.go b/tools/cmd/at/shell.go index 3885c765c..0649faf50 100644 --- a/tools/cmd/at/shell.go +++ b/tools/cmd/at/shell.go @@ -121,7 +121,7 @@ func exec_command(rl *readline.Readline, cmdline string) bool { return true } cwd, _ := os.Getwd() - hi := readline.HistoryItem{Timestamp: time.Now(), Cmd: cmdline, ExitCode: -1, Cwd: cwd} + hi := readline.HistoryItem{Timestamp: time.Now(), Cmd: rl.AllText(), ExitCode: -1, Cwd: cwd} switch parsed_cmdline[0] { case "exit": hi.ExitCode = 0 diff --git a/tools/tui/readline/actions.go b/tools/tui/readline/actions.go index ced58d240..3f9ad8d84 100644 --- a/tools/tui/readline/actions.go +++ b/tools/tui/readline/actions.go @@ -463,6 +463,62 @@ func (self *Readline) yank(repeat_count uint, pop bool) bool { return true } +func (self *Readline) apply_history_text(text string) { + self.lines = utils.Splitlines(text) +} + +func (self *Readline) history_first() bool { + prefix := self.text_upto_cursor_pos() + if self.history_matches == nil || self.history_matches.prefix != prefix { + return false + } + item := self.history_matches.first() + if item == nil { + return false + } + self.apply_history_text(item.Cmd) + return true +} + +func (self *Readline) history_last() bool { + prefix := self.text_upto_cursor_pos() + if self.history_matches == nil || self.history_matches.prefix != prefix { + return false + } + item := self.history_matches.last() + if item == nil { + return false + } + self.apply_history_text(item.Cmd) + return true +} + +func (self *Readline) history_prev(repeat_count uint) bool { + prefix := self.text_upto_cursor_pos() + if self.history_matches == nil || self.history_matches.prefix != prefix { + self.history_matches = self.history.FindPrefixMatches(prefix, self.AllText()) + } + item := self.history_matches.previous(repeat_count) + if item == nil { + return false + } + self.apply_history_text(item.Cmd) + return true +} + +func (self *Readline) history_next(repeat_count uint) bool { + prefix := self.text_upto_cursor_pos() + if self.history_matches == nil || self.history_matches.prefix != prefix { + return false + } + item := self.history_matches.next(repeat_count) + if item == nil { + return false + } + self.apply_history_text(item.Cmd) + return true +} + func (self *Readline) perform_action(ac Action, repeat_count uint) error { defer func() { self.last_action = ac }() switch ac { @@ -523,21 +579,31 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error { return nil } case ActionHistoryPreviousOrCursorUp: - if self.cursor.Y == 0 { - r := self.perform_action(ActionHistoryPrevious, repeat_count) - if r == nil { - return nil - } + if self.perform_action(ActionCursorUp, repeat_count) != nil { + return self.perform_action(ActionHistoryPrevious, repeat_count) } - return self.perform_action(ActionCursorUp, repeat_count) + return nil case ActionHistoryNextOrCursorDown: - if self.cursor.Y == 0 { - r := self.perform_action(ActionHistoryNext, repeat_count) - if r == nil { - return nil - } + if self.perform_action(ActionCursorDown, repeat_count) != nil { + return self.perform_action(ActionHistoryNext, repeat_count) + } + return nil + case ActionHistoryFirst: + if self.history_first() { + return nil + } + case ActionHistoryPrevious: + if self.history_prev(repeat_count) { + return nil + } + case ActionHistoryNext: + if self.history_next(repeat_count) { + return nil + } + case ActionHistoryLast: + if self.history_last() { + return nil } - return self.perform_action(ActionCursorDown, repeat_count) case ActionClearScreen: self.loop.StartAtomicUpdate() self.loop.ClearScreen() diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 6568e35f3..bac784e92 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -56,6 +56,8 @@ const ( ActionHistoryNextOrCursorDown ActionHistoryNext ActionHistoryPrevious + ActionHistoryFirst + ActionHistoryLast ActionClearScreen ActionAddText @@ -132,6 +134,7 @@ type Readline struct { } bracketed_paste_buffer strings.Builder last_action Action + history_matches *HistoryMatches } func New(loop *loop.Loop, r RlInit) *Readline { diff --git a/tools/tui/readline/history.go b/tools/tui/readline/history.go index d341c9586..f7a1db2a1 100644 --- a/tools/tui/readline/history.go +++ b/tools/tui/readline/history.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "strings" "time" "kitty/tools/utils" @@ -22,6 +23,12 @@ type HistoryItem struct { ExitCode int `json:"exit_code"` } +type HistoryMatches struct { + items []HistoryItem + prefix string + current_idx int +} + type History struct { file_path string file *os.File @@ -148,3 +155,45 @@ func NewHistory(path string, max_items int) *History { ans.Read() return &ans } + +func (self *History) FindPrefixMatches(prefix, current_command string) *HistoryMatches { + ans := HistoryMatches{items: make([]HistoryItem, 0, len(self.items)+1), prefix: prefix} + if prefix == "" { + copy(ans.items, self.items) + } else { + for _, x := range self.items { + if strings.HasPrefix(x.Cmd, prefix) { + ans.items = append(ans.items, x) + } + } + } + ans.items = append(ans.items, HistoryItem{Cmd: current_command}) + ans.current_idx = len(ans.items) - 1 + return &ans +} + +func (self *HistoryMatches) first() (ans *HistoryItem) { + self.current_idx = 0 + return &self.items[self.current_idx] +} + +func (self *HistoryMatches) last() (ans *HistoryItem) { + self.current_idx = len(self.items) - 1 + return &self.items[self.current_idx] +} + +func (self *HistoryMatches) previous(num uint) (ans *HistoryItem) { + if self.current_idx > 0 { + self.current_idx = utils.Max(0, self.current_idx-int(num)) + ans = &self.items[self.current_idx] + } + return +} + +func (self *HistoryMatches) next(num uint) (ans *HistoryItem) { + if self.current_idx+1 < len(self.items) { + self.current_idx = utils.Min(len(self.items)-1, self.current_idx+int(num)) + ans = &self.items[self.current_idx] + } + return +} diff --git a/tools/tui/readline/keys.go b/tools/tui/readline/keys.go index af1eaac39..4176c6803 100644 --- a/tools/tui/readline/keys.go +++ b/tools/tui/readline/keys.go @@ -48,6 +48,11 @@ var default_shortcuts = map[string]Action{ "ctrl+w": ActionKillPreviousSpaceDelimitedWord, "ctrl+y": ActionYank, "alt+y": ActionPopYank, + + "ctrl+p": ActionHistoryPrevious, + "ctrl+n": ActionHistoryNext, + "alt+<": ActionHistoryFirst, + "alt+>": ActionHistoryLast, } func action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Action {