Move cursor to end of input when traversing history list

This commit is contained in:
Kovid Goyal 2022-11-08 18:00:18 +05:30
parent 1523fef000
commit cfb6d93dc0
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 133 additions and 121 deletions

View File

@ -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
}

View File

@ -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", "")

View File

@ -43,6 +43,7 @@ type Action uint
const (
ActionNil Action = iota
ActionIgnored
ActionBackspace
ActionDelete
ActionMoveToStartOfLine

View File

@ -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) {