diff --git a/tools/tui/readline/actions.go b/tools/tui/readline/actions.go index 68761e05f..f72b79cf4 100644 --- a/tools/tui/readline/actions.go +++ b/tools/tui/readline/actions.go @@ -465,209 +465,175 @@ func (self *Readline) yank(repeat_count uint, pop bool) bool { return true } -func (self *Readline) apply_history_text(text string) { - self.input_state.lines = utils.Splitlines(text) - if len(self.input_state.lines) == 0 { - self.input_state.lines = []string{""} - } -} - 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 + self.create_history_matches() + return self.history_matches.first(self) } 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 + self.create_history_matches() + return self.history_matches.last(self) } 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 + self.create_history_matches() + return self.history_matches.previous(repeat_count, self) } 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 + self.create_history_matches() + return self.history_matches.next(repeat_count, self) } -func (self *Readline) perform_action(ac Action, repeat_count uint) error { - defer func() { self.last_action = ac }() +func (self *Readline) _perform_action(ac Action, repeat_count uint) (err error, dont_set_last_action bool) { switch ac { case ActionBackspace: if self.history_search != nil { if self.remove_text_from_history_search(repeat_count) > 0 { - return nil + return } } else { if self.erase_chars_before_cursor(repeat_count, true) > 0 { - return nil + return } } case ActionDelete: if self.erase_chars_after_cursor(repeat_count, true) > 0 { - return nil + return } case ActionMoveToStartOfLine: if self.move_to_start_of_line() { - return nil + return } case ActionMoveToEndOfLine: if self.move_to_end_of_line() { - return nil + return } case ActionMoveToEndOfWord: if self.move_to_end_of_word(repeat_count, true, has_word_chars) > 0 { - return nil + return } case ActionMoveToStartOfWord: if self.move_to_start_of_word(repeat_count, true, has_word_chars) > 0 { - return nil + return } case ActionMoveToStartOfDocument: if self.move_to_start() { - return nil + return } case ActionMoveToEndOfDocument: if self.move_to_end() { - return nil + return } case ActionCursorLeft: if self.move_cursor_left(repeat_count, true) > 0 { - return nil + return } case ActionCursorRight: if self.move_cursor_right(repeat_count, true) > 0 { - return nil + return } case ActionEndInput: line := self.input_state.lines[self.input_state.cursor.Y] if line == "" { - return io.EOF + err = io.EOF + + } else { + err = self.perform_action(ActionAcceptInput, 1) } - return self.perform_action(ActionAcceptInput, 1) + return case ActionAcceptInput: - return ErrAcceptInput + err = ErrAcceptInput + return case ActionCursorUp: if self.move_cursor_vertically(-int(repeat_count)) != 0 { - return nil + return } case ActionCursorDown: if self.move_cursor_vertically(int(repeat_count)) != 0 { - return nil + return } case ActionHistoryPreviousOrCursorUp: + dont_set_last_action = true if self.perform_action(ActionCursorUp, repeat_count) == ErrCouldNotPerformAction { - return self.perform_action(ActionHistoryPrevious, repeat_count) + err = self.perform_action(ActionHistoryPrevious, repeat_count) } - return nil + return case ActionHistoryNextOrCursorDown: + dont_set_last_action = true if self.perform_action(ActionCursorDown, repeat_count) == ErrCouldNotPerformAction { - return self.perform_action(ActionHistoryNext, repeat_count) + err = self.perform_action(ActionHistoryNext, repeat_count) } - return nil + return case ActionHistoryFirst: if self.history_first() { - return nil + return } case ActionHistoryPrevious: if self.history_prev(repeat_count) { - return nil + return } case ActionHistoryNext: if self.history_next(repeat_count) { - return nil + return } case ActionHistoryLast: if self.history_last() { - return nil + return } case ActionClearScreen: self.loop.StartAtomicUpdate() self.loop.ClearScreen() self.RedrawNonAtomic() self.loop.EndAtomicUpdate() - return nil + return case ActionKillToEndOfLine: if self.kill_to_end_of_line() { - return nil + return } case ActionKillToStartOfLine: if self.kill_to_start_of_line() { - return nil + return } case ActionKillNextWord: if self.kill_next_word(repeat_count, true) > 0 { - return nil + return } case ActionKillPreviousWord: if self.kill_previous_word(repeat_count, true) > 0 { - return nil + return } case ActionKillPreviousSpaceDelimitedWord: if self.kill_previous_space_delimited_word(repeat_count, true) > 0 { - return nil + return } case ActionYank: if self.yank(repeat_count, false) { - return nil + return } case ActionPopYank: if self.yank(repeat_count, true) { - return nil + return } case ActionAbortCurrentLine: self.loop.QueueWriteString("\r\n") self.ResetText() - return nil + return case ActionHistoryIncrementalSearchForwards: if self.history_search == nil { self.create_history_search(false, repeat_count) - return nil + return } if self.next_history_search(false, repeat_count) { - return nil + return } case ActionHistoryIncrementalSearchBackwards: if self.history_search == nil { self.create_history_search(true, repeat_count) - return nil + return } if self.next_history_search(true, repeat_count) { - return nil + return } case ActionAddText: text := strings.Repeat(self.text_to_be_added, int(repeat_count)) @@ -677,17 +643,26 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error { } else { self.add_text(text) } - return nil + return case ActionTerminateHistorySearchAndRestore: if self.history_search != nil { self.end_history_search(false) - return nil + return } case ActionTerminateHistorySearchAndApply: if self.history_search != nil { self.end_history_search(true) - return nil + return } } - return ErrCouldNotPerformAction + err = ErrCouldNotPerformAction + return +} + +func (self *Readline) perform_action(ac Action, repeat_count uint) error { + err, dont_set_last_action := self._perform_action(ac, repeat_count) + if err == nil && !dont_set_last_action { + self.last_action = ac + } + return err } diff --git a/tools/tui/readline/actions_test.go b/tools/tui/readline/actions_test.go index 589143843..c3772a7ad 100644 --- a/tools/tui/readline/actions_test.go +++ b/tools/tui/readline/actions_test.go @@ -450,27 +450,27 @@ func TestHistory(t *testing.T) { } } - test(ActionHistoryPrevious, "", "b four") - test(ActionHistoryPrevious, "", "b three") - test(ActionHistoryPrevious, "", "a two") - test(ActionHistoryPrevious, "", "a one") - test(ActionHistoryPrevious, "", "a one") - test(ActionHistoryNext, "", "a two") - test(ActionHistoryNext, "", "b three") - test(ActionHistoryNext, "", "b four") + test(ActionHistoryPreviousOrCursorUp, "b four", "") + test(ActionHistoryPreviousOrCursorUp, "b three", "") + test(ActionHistoryPrevious, "a two", "") + test(ActionHistoryPrevious, "a one", "") + test(ActionHistoryPrevious, "a one", "") + test(ActionHistoryNext, "a two", "") + test(ActionHistoryNext, "b three", "") + test(ActionHistoryNext, "b four", "") test(ActionHistoryNext, "", "") test(ActionHistoryNext, "", "") - test(ActionHistoryPrevious, "", "b four") - test(ActionHistoryPrevious, "", "b three") - test(ActionHistoryNext, "", "b four") + test(ActionHistoryPrevious, "b four", "") + test(ActionHistoryPrevious, "b three", "") + test(ActionHistoryNext, "b four", "") rl.ResetText() rl.add_text("a") - test(ActionHistoryPrevious, "a", " two") - test(ActionHistoryPrevious, "a", " one") - test(ActionHistoryPrevious, "a", " one") - test(ActionHistoryNext, "a", " two") + test(ActionHistoryPrevious, "a two", "") + test(ActionHistoryPrevious, "a one", "") + test(ActionHistoryPrevious, "a one", "") + test(ActionHistoryNext, "a two", "") test(ActionHistoryNext, "a", "") test(ActionHistoryNext, "a", "") diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 8843de161..a62cfe493 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -43,6 +43,7 @@ type Action uint const ( ActionNil Action = iota + ActionIgnored ActionBackspace ActionDelete ActionMoveToStartOfLine diff --git a/tools/tui/readline/history.go b/tools/tui/readline/history.go index c2b4cfd07..25d8b4ca8 100644 --- a/tools/tui/readline/history.go +++ b/tools/tui/readline/history.go @@ -27,9 +27,10 @@ type HistoryItem struct { } type HistoryMatches struct { - items []HistoryItem - prefix string - current_idx int + items []HistoryItem + prefix string + current_idx int + original_input_state InputState } type HistorySearch struct { @@ -168,8 +169,8 @@ func NewHistory(path string, max_items int) *History { return &ans } -func (self *History) FindPrefixMatches(prefix, current_command string) *HistoryMatches { - ans := HistoryMatches{items: make([]HistoryItem, 0, len(self.items)+1), prefix: prefix} +func (self *History) find_prefix_matches(prefix, current_command string, input_state InputState) *HistoryMatches { + ans := HistoryMatches{items: make([]HistoryItem, 0, len(self.items)+1), prefix: prefix, original_input_state: input_state} if prefix == "" { ans.items = ans.items[:len(self.items)] copy(ans.items, self.items) @@ -185,30 +186,65 @@ func (self *History) FindPrefixMatches(prefix, current_command string) *HistoryM return &ans } -func (self *HistoryMatches) first() (ans *HistoryItem) { +func (self *Readline) create_history_matches() { + if self.last_action_was_history_movement() && self.history_matches != nil { + return + } + prefix := self.text_upto_cursor_pos() + self.history_matches = self.history.find_prefix_matches(prefix, self.AllText(), self.input_state.copy()) +} + +func (self *Readline) last_action_was_history_movement() bool { + switch self.last_action { + case ActionHistoryLast, ActionHistoryFirst, ActionHistoryNext, ActionHistoryPrevious: + return true + default: + return false + } +} + +func (self *HistoryMatches) apply(rl *Readline) bool { + if self.current_idx >= len(self.items) || self.current_idx < 0 { + return false + } + if self.current_idx == len(self.items)-1 { + rl.input_state = self.original_input_state.copy() + } else { + item := self.items[self.current_idx] + rl.input_state.lines = utils.Splitlines(item.Cmd) + if len(rl.input_state.lines) == 0 { + rl.input_state.lines = []string{""} + } + idx := len(rl.input_state.lines) - 1 + rl.input_state.cursor = Position{Y: idx, X: len(rl.input_state.lines[idx])} + } + return true +} + +func (self *HistoryMatches) first(rl *Readline) bool { self.current_idx = 0 - return &self.items[self.current_idx] + return self.apply(rl) } -func (self *HistoryMatches) last() (ans *HistoryItem) { - self.current_idx = len(self.items) - 1 - return &self.items[self.current_idx] +func (self *HistoryMatches) last(rl *Readline) bool { + self.current_idx = utils.Max(0, len(self.items)-1) + return self.apply(rl) } -func (self *HistoryMatches) previous(num uint) (ans *HistoryItem) { +func (self *HistoryMatches) previous(num uint, rl *Readline) bool { if self.current_idx > 0 { self.current_idx = utils.Max(0, self.current_idx-int(num)) - ans = &self.items[self.current_idx] + return self.apply(rl) } - return + return false } -func (self *HistoryMatches) next(num uint) (ans *HistoryItem) { +func (self *HistoryMatches) next(num uint, rl *Readline) bool { 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 self.apply(rl) } - return + return false } func (self *Readline) create_history_search(backwards bool, num uint) {