Start work on incremental history search

This commit is contained in:
Kovid Goyal 2022-11-06 20:52:26 +05:30
parent 6b48624b81
commit ffea66357a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 176 additions and 19 deletions

View File

@ -647,10 +647,30 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error {
self.loop.QueueWriteString("\r\n")
self.ResetText()
return nil
case ActionHistoryIncrementalSearchForwards:
if self.history_search == nil {
self.create_history_search(false, repeat_count)
return nil
}
if self.next_history_search(false, repeat_count) {
return nil
}
case ActionHistoryIncrementalSearchBackwards:
if self.history_search == nil {
self.create_history_search(true, repeat_count)
return nil
}
if self.next_history_search(true, repeat_count) {
return nil
}
case ActionAddText:
text := strings.Repeat(self.text_to_be_added, int(repeat_count))
self.text_to_be_added = ""
self.add_text(text)
if self.history_search != nil {
self.add_text_to_history_search(text)
} else {
self.add_text(text)
}
return nil
}
return ErrCouldNotPerformAction

View File

@ -59,6 +59,8 @@ const (
ActionHistoryPrevious
ActionHistoryFirst
ActionHistoryLast
ActionHistoryIncrementalSearchBackwards
ActionHistoryIncrementalSearchForwards
ActionClearScreen
ActionAddText
ActionAbortCurrentLine
@ -132,7 +134,7 @@ type Prompt struct {
}
type Readline struct {
prompt, continuation_prompt, reverse_search_prompt, forward_search_prompt Prompt
prompt, continuation_prompt Prompt
mark_prompts bool
loop *loop.Loop
@ -152,11 +154,23 @@ type Readline struct {
bracketed_paste_buffer strings.Builder
last_action Action
history_matches *HistoryMatches
history_search *HistorySearch
keyboard_state KeyboardState
fmt_ctx *markup.Context
text_to_be_added string
}
func (self *Readline) make_prompt(text string, is_secondary bool) Prompt {
if self.mark_prompts {
m := PROMPT_MARK + "A"
if is_secondary {
m += ";k=s"
}
text = m + ST + text
}
return Prompt{Text: text, Length: wcswidth.Stringwidth(text)}
}
func New(loop *loop.Loop, r RlInit) *Readline {
hc := r.HistoryCount
if hc == 0 {
@ -166,17 +180,7 @@ func New(loop *loop.Loop, r RlInit) *Readline {
mark_prompts: !r.DontMarkPrompts, fmt_ctx: markup.New(true),
loop: loop, lines: []string{""}, history: NewHistory(r.HistoryPath, hc), kill_ring: kill_ring{items: list.New().Init()},
}
make_prompt := func(text string, is_secondary bool) Prompt {
if ans.mark_prompts {
m := PROMPT_MARK + "A"
if is_secondary {
m += ";k=s"
}
text = m + ST + text
}
return Prompt{Text: text, Length: wcswidth.Stringwidth(text)}
}
ans.prompt = make_prompt(r.Prompt, false)
ans.prompt = ans.make_prompt(r.Prompt, false)
t := ""
if r.ContinuationPrompt != "" || !r.EmptyContinuationPrompt {
t = r.ContinuationPrompt
@ -184,9 +188,7 @@ func New(loop *loop.Loop, r RlInit) *Readline {
t = ans.fmt_ctx.Yellow(">") + " "
}
}
ans.continuation_prompt = make_prompt(t, true)
ans.reverse_search_prompt = make_prompt(ans.fmt_ctx.Blue("?")+": ", false)
ans.forward_search_prompt = make_prompt(ans.fmt_ctx.Cyan("/")+": ", false)
ans.continuation_prompt = ans.make_prompt(t, true)
return ans
}

View File

@ -38,10 +38,12 @@ func (self *Readline) format_arg_prompt(cna string) string {
func (self *Readline) prompt_for_line_number(i int) Prompt {
is_line_with_cursor := i == self.cursor.Y
if is_line_with_cursor && self.keyboard_state.current_numeric_argument != "" {
text := self.format_arg_prompt(self.keyboard_state.current_numeric_argument)
return Prompt{Text: text, Length: wcswidth.Stringwidth(text)}
return self.make_prompt(self.format_arg_prompt(self.keyboard_state.current_numeric_argument), i > 0)
}
if i == 0 {
if self.history_search != nil {
return self.make_prompt(self.history_search_prompt(), i > 0)
}
return self.prompt
}
return self.continuation_prompt

View File

@ -11,6 +11,7 @@ import (
"time"
"kitty/tools/utils"
"kitty/tools/wcswidth"
)
var _ = fmt.Print
@ -29,6 +30,16 @@ type HistoryMatches struct {
current_idx int
}
type HistorySearch struct {
query string
tokens []string
items []*HistoryItem
current_idx int
backwards bool
original_lines []string
original_cursor Position
}
type History struct {
file_path string
file *os.File
@ -124,7 +135,7 @@ func (self *History) Read() {
}
var items []HistoryItem
err = json.Unmarshal(data, &items)
if err != nil {
if err == nil {
self.merge_items(items...)
}
}
@ -198,3 +209,117 @@ func (self *HistoryMatches) next(num uint) (ans *HistoryItem) {
}
return
}
func (self *Readline) create_history_search(backwards bool, num uint) {
self.history_search = &HistorySearch{backwards: backwards, original_lines: self.lines, original_cursor: self.cursor}
self.markup_history_search()
}
func (self *Readline) end_history_search(accept bool) {
self.cursor = Position{}
if accept && self.history_search.current_idx < len(self.history_search.items) {
self.lines = utils.Splitlines(self.history_search.items[self.history_search.current_idx].Cmd)
self.cursor.Y = len(self.lines) - 1
self.cursor.X = len(self.lines[self.cursor.Y])
} else {
self.lines = self.history_search.original_lines
self.cursor = self.history_search.original_cursor
}
self.cursor = *self.ensure_position_in_bounds(&self.cursor)
}
func (self *Readline) markup_history_search() {
if len(self.history_search.items) == 0 {
if len(self.history_search.tokens) == 0 {
self.lines = []string{""}
} else {
self.lines = []string{"No matches for: " + self.fmt_ctx.BrightRed(self.history_search.query)}
}
self.cursor = Position{X: wcswidth.Stringwidth(self.lines[0])}
return
}
lines := utils.Splitlines(self.history_search.items[self.history_search.current_idx].Cmd)
cursor := Position{Y: len(lines)}
for _, tok := range self.history_search.tokens {
for i, line := range lines {
if idx := strings.Index(line, tok); idx > -1 {
lines[i] = line[:idx] + self.fmt_ctx.Green(tok) + line[idx+len(tok):]
q := Position{Y: i, X: idx}
if q.Less(cursor) {
cursor = q
}
break
}
}
}
self.lines = lines
self.cursor = *self.ensure_position_in_bounds(&cursor)
}
func (self *Readline) add_text_to_history_search(text string) {
self.history_search.query += text
self.history_search.tokens = strings.Split(self.history_search.query, " ")
var current_item *HistoryItem
if len(self.history_search.items) > 0 {
current_item = self.history_search.items[self.history_search.current_idx]
}
items := make([]*HistoryItem, len(self.history.items))
for i, x := range self.history.items {
items[i] = &x
}
for _, token := range self.history_search.tokens {
matches := make([]*HistoryItem, 0, len(items))
for _, item := range items {
if strings.Contains(item.Cmd, token) {
matches = append(matches, item)
}
}
items = matches
}
self.history_search.items = items
idx := -1
for i, item := range self.history_search.items {
if item == current_item {
idx = i
break
}
}
if idx == -1 {
idx = len(self.history_search.items) - 1
}
self.history_search.current_idx = utils.Max(0, idx)
self.markup_history_search()
}
func (self *Readline) next_history_search(backwards bool, num uint) bool {
ni := self.history_search.current_idx
self.history_search.backwards = backwards
if len(self.history_search.items) == 0 {
return false
}
if backwards {
ni = utils.Max(0, ni-int(num))
} else {
ni = utils.Min(ni+int(num), len(self.history_search.items)-1)
}
if ni == self.history_search.current_idx {
return false
}
self.history_search.current_idx = ni
self.markup_history_search()
return true
}
func (self *Readline) history_search_prompt() string {
ans := "↑"
if !self.history_search.backwards {
ans = "↓"
}
failed := len(self.history_search.tokens) > 0 && len(self.history_search.items) == 0
if failed {
ans = self.fmt_ctx.BrightRed(ans)
} else {
ans = self.fmt_ctx.Green(ans)
}
return fmt.Sprintf("history %s: ", ans)
}

View File

@ -79,6 +79,7 @@ func default_shortcuts() *ShortcutMap {
sm.add(ActionClearScreen, "ctrl+l")
sm.add(ActionAbortCurrentLine, "ctrl+c")
sm.add(ActionAbortCurrentLine, "ctrl+g")
sm.add(ActionEndInput, "ctrl+d")
sm.add(ActionAcceptInput, "enter")
@ -98,6 +99,10 @@ func default_shortcuts() *ShortcutMap {
sm.add(ActionHistoryNext, "ctrl+n")
sm.add(ActionHistoryFirst, "alt+<")
sm.add(ActionHistoryLast, "alt+>")
sm.add(ActionHistoryIncrementalSearchBackwards, "ctrl+r")
sm.add(ActionHistoryIncrementalSearchBackwards, "ctrl+?")
sm.add(ActionHistoryIncrementalSearchForwards, "ctrl+s")
sm.add(ActionHistoryIncrementalSearchForwards, "ctrl+/")
sm.add(ActionNumericArgumentDigit0, "alt+0")
sm.add(ActionNumericArgumentDigit1, "alt+1")
@ -148,6 +153,9 @@ func (self *Readline) handle_numeric_arg(ac Action) {
func (self *Readline) dispatch_key_action(ac Action) error {
self.keyboard_state.current_pending_keys = nil
if ActionNumericArgumentDigit0 <= ac && ac <= ActionNumericArgumentDigitMinus {
if self.history_search != nil {
return ErrCouldNotPerformAction
}
self.handle_numeric_arg(ac)
return nil
}