Start work on incremental history search
This commit is contained in:
parent
6b48624b81
commit
ffea66357a
@ -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 = ""
|
||||
if self.history_search != nil {
|
||||
self.add_text_to_history_search(text)
|
||||
} else {
|
||||
self.add_text(text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrCouldNotPerformAction
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user