diff --git a/tools/cli/command.go b/tools/cli/command.go index 07e62ba64..b0d243db1 100644 --- a/tools/cli/command.go +++ b/tools/cli/command.go @@ -560,18 +560,18 @@ func (self *Command) Exec(args ...string) { } func (self *Command) GetCompletions(argv []string, init_completions func(*Completions)) *Completions { - ans := Completions{Groups: make([]*MatchGroup, 0, 4)} + ans := NewCompletions() if init_completions != nil { - init_completions(&ans) + init_completions(ans) } if len(argv) > 0 { exe := argv[0] cmd := self.FindSubCommand(exe) if cmd != nil { if cmd.ParseArgsForCompletion != nil { - cmd.ParseArgsForCompletion(cmd, argv[1:], &ans) + cmd.ParseArgsForCompletion(cmd, argv[1:], ans) } else { - completion_parse_args(cmd, argv[1:], &ans) + completion_parse_args(cmd, argv[1:], ans) } } } @@ -582,5 +582,5 @@ func (self *Command) GetCompletions(argv []string, init_completions func(*Comple } } ans.Groups = non_empty_groups - return &ans + return ans } diff --git a/tools/cli/completion.go b/tools/cli/completion.go index 68c9dd2da..e9deee51c 100644 --- a/tools/cli/completion.go +++ b/tools/cli/completion.go @@ -115,12 +115,46 @@ type Completions struct { split_on_equals bool // true if the cmdline is split on = (BASH does this because readline does this) } +func NewCompletions() *Completions { + return &Completions{Groups: make([]*MatchGroup, 0, 4)} +} + func (self *Completions) AddPrefixToAllMatches(prefix string) { for _, mg := range self.Groups { mg.AddPrefixToAllMatches(prefix) } } +func (self *Completions) MergeMatchGroup(mg *MatchGroup) { + if len(mg.Matches) == 0 { + return + } + var dest *MatchGroup + for _, q := range self.Groups { + if q.Title == mg.Title { + dest = q + break + } + } + if dest == nil { + dest = self.AddMatchGroup(mg.Title) + dest.NoTrailingSpace = mg.NoTrailingSpace + dest.IsFiles = mg.IsFiles + } + seen := utils.NewSet[string](64) + for _, q := range self.Groups { + for _, m := range q.Matches { + seen.Add(m.Word) + } + } + for _, m := range mg.Matches { + if !seen.Has(m.Word) { + seen.Add(m.Word) + dest.Matches = append(dest.Matches, m) + } + } +} + func (self *Completions) AddMatchGroup(title string) *MatchGroup { for _, q := range self.Groups { if q.Title == title { diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index cd60b78f8..45a99b8d8 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -156,6 +156,9 @@ func New(loop *loop.Loop, r RlInit) *Readline { completions: completions{completer: r.Completer}, kill_ring: kill_ring{items: list.New().Init()}, } + if ans.completions.completer == nil && r.HistoryPath != "" { + ans.completions.completer = ans.HistoryCompleter + } ans.prompt = ans.make_prompt(r.Prompt, false) t := "" if r.ContinuationPrompt != "" || !r.EmptyContinuationPrompt { @@ -168,6 +171,10 @@ func New(loop *loop.Loop, r RlInit) *Readline { return ans } +func (self *Readline) HistoryCompleter(before_cursor, after_cursor string) *cli.Completions { + return self.history_completer(before_cursor, after_cursor) +} + func (self *Readline) SetPrompt(prompt string) { self.prompt = self.make_prompt(prompt, false) } diff --git a/tools/tui/readline/completion.go b/tools/tui/readline/completion.go index 9a71114dc..eb7a8e926 100644 --- a/tools/tui/readline/completion.go +++ b/tools/tui/readline/completion.go @@ -7,6 +7,7 @@ import ( "strings" "kitty/tools/cli" + "kitty/tools/tty" "kitty/tools/utils" "kitty/tools/wcswidth" ) @@ -63,6 +64,8 @@ type completions struct { current completion } +var _ = tty.DebugPrintln + func (self *Readline) complete(forwards bool, repeat_count uint) bool { c := &self.completions if c.completer == nil { diff --git a/tools/tui/readline/history.go b/tools/tui/readline/history.go index 6529713b4..abfcf7fa0 100644 --- a/tools/tui/readline/history.go +++ b/tools/tui/readline/history.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "kitty/tools/cli" + "kitty/tools/tty" "kitty/tools/utils" "kitty/tools/utils/shlex" "kitty/tools/wcswidth" @@ -396,3 +398,37 @@ func (self *Readline) history_search_prompt() string { } return fmt.Sprintf("history %s: ", ans) } + +var _ = tty.DebugPrintln + +func (self *Readline) history_completer(before_cursor, after_cursor string) (ans *cli.Completions) { + ans = cli.NewCompletions() + if before_cursor != "" { + var words_before_cursor []string + words_before_cursor, ans.CurrentWordIdx = shlex.SplitForCompletion(before_cursor) + idx := len(words_before_cursor) + if idx > 0 { + idx-- + } + seen := utils.NewSet[string](16) + mg := ans.AddMatchGroup("History") + for _, x := range self.history.items { + if strings.HasPrefix(x.Cmd, before_cursor) { + words, _ := shlex.SplitForCompletion(x.Cmd) + if idx < len(words) { + word := words[idx] + desc := "" + if !seen.Has(word) { + if word != x.Cmd { + desc = x.Cmd + } + mg.AddMatch(word, desc) + seen.Add(word) + } + } + } + } + } + + return +}