From ea583f60b3cc7ad76abce94f80288205962db9f2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Oct 2022 12:18:25 +0530 Subject: [PATCH] Start work on kill ring --- tools/tui/readline/actions.go | 39 +++++++++++++++++++++++++++++ tools/tui/readline/api.go | 47 ++++++++++++++++++++++++++++++++++- tools/tui/readline/keys.go | 4 +++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/tools/tui/readline/actions.go b/tools/tui/readline/actions.go index 7de119666..ad5306b80 100644 --- a/tools/tui/readline/actions.go +++ b/tools/tui/readline/actions.go @@ -51,6 +51,7 @@ func (self *Readline) all_text() string { } func (self *Readline) add_text(text string) { + defer func() { self.last_action = ActionAddText }() new_lines := make([]string, 0, len(self.lines)+4) new_lines = append(new_lines, self.lines[:self.cursor.Y]...) var lines_after []string @@ -355,7 +356,37 @@ func (self *Readline) move_to_start_of_word(amt uint, traverse_line_breaks bool) return } +func (self *Readline) kill_text(text string) { + if ActionStartKillActions < self.last_action && self.last_action < ActionEndKillActions { + self.kill_ring.append_to_existing_item(text) + } else { + self.kill_ring.add_new_item(text) + } +} + +func (self *Readline) kill_to_end_of_line() bool { + line := self.lines[self.cursor.Y] + if self.cursor.X >= len(line) { + return false + } + self.lines[self.cursor.Y] = line[:self.cursor.X] + self.kill_text(line[self.cursor.X:]) + return true +} + +func (self *Readline) kill_to_start_of_line() bool { + line := self.lines[self.cursor.Y] + if self.cursor.X <= 0 { + return false + } + self.lines[self.cursor.Y] = line[self.cursor.X:] + self.kill_text(line[:self.cursor.X]) + self.cursor.X = 0 + return true +} + func (self *Readline) perform_action(ac Action, repeat_count uint) error { + defer func() { self.last_action = ac }() switch ac { case ActionBackspace: if self.erase_chars_before_cursor(repeat_count, true) > 0 { @@ -435,6 +466,14 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error { self.RedrawNonAtomic() self.loop.EndAtomicUpdate() return nil + case ActionKillToEndOfLine: + if self.kill_to_end_of_line() { + return nil + } + case ActionKillToStartOfLine: + if self.kill_to_start_of_line() { + return nil + } } return ErrCouldNotPerformAction } diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 9419ba715..0a0c1a914 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -3,6 +3,7 @@ package readline import ( + "container/list" "fmt" "strings" @@ -56,8 +57,49 @@ const ( ActionHistoryNext ActionHistoryPrevious ActionClearScreen + ActionAddText + + ActionStartKillActions + ActionKillToEndOfLine + ActionKillToStartOfLine + ActionEndKillActions ) +type kill_ring struct { + items *list.List +} + +func (self *kill_ring) append_to_existing_item(text string) { + e := self.items.Front() + if e == nil { + self.add_new_item(text) + } + e.Value = e.Value.(string) + text +} + +func (self *kill_ring) add_new_item(text string) { + if text != "" { + self.items.PushFront(text) + } +} + +func (self *kill_ring) yank() string { + e := self.items.Front() + if e == nil { + return "" + } + return e.Value.(string) +} + +func (self *kill_ring) pop_yank() string { + e := self.items.Front() + if e == nil { + return "" + } + self.items.MoveToBack(e) + return self.yank() +} + type Readline struct { prompt string prompt_len int @@ -66,6 +108,7 @@ type Readline struct { mark_prompts bool loop *loop.Loop history *History + kill_ring kill_ring // The number of lines after the initial line on the screen cursor_y int @@ -75,6 +118,7 @@ type Readline struct { // The cursor position in the text cursor Position bracketed_paste_buffer strings.Builder + last_action Action } func New(loop *loop.Loop, r RlInit) *Readline { @@ -84,7 +128,7 @@ func New(loop *loop.Loop, r RlInit) *Readline { } ans := &Readline{ prompt: r.Prompt, prompt_len: wcswidth.Stringwidth(r.Prompt), mark_prompts: !r.DontMarkPrompts, - loop: loop, lines: []string{""}, history: NewHistory(r.HistoryPath, hc), + loop: loop, lines: []string{""}, history: NewHistory(r.HistoryPath, hc), kill_ring: kill_ring{items: list.New().Init()}, } if r.ContinuationPrompt != "" || !r.EmptyContinuationPrompt { ans.continuation_prompt = r.ContinuationPrompt @@ -112,6 +156,7 @@ func (self *Readline) ResetText() { self.lines = []string{""} self.cursor = Position{} self.cursor_y = 0 + self.last_action = ActionNil } func (self *Readline) ChangeLoopAndResetText(lp *loop.Loop) { diff --git a/tools/tui/readline/keys.go b/tools/tui/readline/keys.go index 923c685b2..0cca8010d 100644 --- a/tools/tui/readline/keys.go +++ b/tools/tui/readline/keys.go @@ -39,6 +39,10 @@ var default_shortcuts = map[string]Action{ "ctrl+d": ActionEndInput, "enter": ActionAcceptInput, + + "ctrl+k": ActionKillToEndOfLine, + "ctrl+x": ActionKillToStartOfLine, + "ctrl+u": ActionKillToStartOfLine, } func action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Action {