diff --git a/gen-go-code.py b/gen-go-code.py index 98da11002..f2309c8c5 100755 --- a/gen-go-code.py +++ b/gen-go-code.py @@ -434,6 +434,9 @@ def generate_readline_actions() -> str: ActionNumericArgumentDigit8 ActionNumericArgumentDigit9 ActionNumericArgumentDigitMinus + + ActionCompleteForward + ActionCompleteBackward ''') diff --git a/tools/cli/completion-main.go b/tools/cli/completion-main.go index d787c1e55..bc0948e02 100644 --- a/tools/cli/completion-main.go +++ b/tools/cli/completion-main.go @@ -51,6 +51,14 @@ func RegisterExeForCompletion(x func(root *Command)) { registered_exes = append(registered_exes, x) } +func CompletionsForArgv(argv []string) *Completions { + var root = NewRootCommand() + for _, re := range registered_exes { + re(root) + } + return root.GetCompletions(argv, init_completions["json"]) +} + func GenerateCompletions(args []string) error { output_type := "json" if len(args) > 0 { diff --git a/tools/cmd/at/shell.go b/tools/cmd/at/shell.go index 0649faf50..d83f6ccde 100644 --- a/tools/cmd/at/shell.go +++ b/tools/cmd/at/shell.go @@ -176,6 +176,15 @@ func exec_command(rl *readline.Readline, cmdline string) bool { return true } +func completions(before_cursor, after_cursor string) *cli.Completions { + text := "kitty @ " + before_cursor + argv, err := shlex.Split(text) + if err != nil { + return nil + } + return cli.CompletionsForArgv(argv) +} + func shell_main(cmd *cli.Command, args []string) (int, error) { formatter = markup.New(true) fmt.Println("Welcome to the kitty shell!") @@ -189,7 +198,7 @@ func shell_main(cmd *cli.Command, args []string) (int, error) { } fmt.Println(amsg) } - rl := readline.New(nil, readline.RlInit{Prompt: prompt, HistoryPath: filepath.Join(utils.CacheDir(), "shell.history.json")}) + rl := readline.New(nil, readline.RlInit{Prompt: prompt, Completer: completions, HistoryPath: filepath.Join(utils.CacheDir(), "shell.history.json")}) defer func() { rl.Shutdown() }() diff --git a/tools/tui/readline/actions.go b/tools/tui/readline/actions.go index f72b79cf4..b06cc54f7 100644 --- a/tools/tui/readline/actions.go +++ b/tools/tui/readline/actions.go @@ -654,6 +654,14 @@ func (self *Readline) _perform_action(ac Action, repeat_count uint) (err error, self.end_history_search(true) return } + case ActionCompleteForward: + if self.complete(true, repeat_count) { + return + } + case ActionCompleteBackward: + if self.complete(false, repeat_count) { + return + } } err = ErrCouldNotPerformAction return diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 5b6df226e..83c860579 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "kitty/tools/cli" "kitty/tools/cli/markup" "kitty/tools/tui/loop" "kitty/tools/wcswidth" @@ -18,6 +19,7 @@ const ST = "\x1b\\" const PROMPT_MARK = "\x1b]133;" type SyntaxHighlightFunction = func(text string, x, y int) string +type CompleterFunction = func(before_cursor, after_cursor string) *cli.Completions type RlInit struct { Prompt string @@ -27,6 +29,7 @@ type RlInit struct { EmptyContinuationPrompt bool DontMarkPrompts bool SyntaxHighlighter SyntaxHighlightFunction + Completer CompleterFunction } type Position struct { @@ -127,6 +130,7 @@ type Readline struct { fmt_ctx *markup.Context text_to_be_added string syntax_highlighted syntax_highlighted + completions completions } func (self *Readline) make_prompt(text string, is_secondary bool) Prompt { @@ -149,6 +153,7 @@ func New(loop *loop.Loop, r RlInit) *Readline { mark_prompts: !r.DontMarkPrompts, fmt_ctx: markup.New(true), loop: loop, input_state: InputState{lines: []string{""}}, history: NewHistory(r.HistoryPath, hc), syntax_highlighted: syntax_highlighted{highlighter: r.SyntaxHighlighter}, + completions: completions{completer: r.Completer}, kill_ring: kill_ring{items: list.New().Init()}, } ans.prompt = ans.make_prompt(r.Prompt, false) diff --git a/tools/tui/readline/completion.go b/tools/tui/readline/completion.go new file mode 100644 index 000000000..acbb968fd --- /dev/null +++ b/tools/tui/readline/completion.go @@ -0,0 +1,91 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package readline + +import ( + "fmt" + + "kitty/tools/cli" +) + +var _ = fmt.Print + +type completion struct { + before_cursor, after_cursor string + results *cli.Completions + results_displayed, forwards bool + num_of_matches, current_match int +} + +func (self *completion) initialize() { + self.num_of_matches = 0 + if self.results != nil { + for _, g := range self.results.Groups { + self.num_of_matches += len(g.Matches) + } + } + self.current_match = -1 + if !self.forwards { + self.current_match = self.num_of_matches + } + if self.num_of_matches == 1 { + self.current_match = 0 + } +} + +func (self *completion) current_match_text() string { + if self.results != nil { + i := 0 + for _, g := range self.results.Groups { + for _, m := range g.Matches { + if i == self.current_match { + return m.Word + } + i++ + } + } + } + return "" +} + +type completions struct { + completer CompleterFunction + current completion +} + +func (self *Readline) complete(forwards bool, repeat_count uint) bool { + c := &self.completions + if c.completer == nil { + return false + } + if self.last_action == ActionCompleteForward || self.last_action == ActionCompleteBackward { + delta := -1 + if forwards { + delta = 1 + } + delta *= int(repeat_count) + c.current.current_match = (c.current.current_match + delta + c.current.num_of_matches) % c.current.num_of_matches + repeat_count = 0 + } else { + before, after := self.text_upto_cursor_pos(), self.text_after_cursor_pos() + if before == "" { + return false + } + c.current = completion{before_cursor: before, after_cursor: after, forwards: forwards, results: c.completer(before, after)} + c.current.initialize() + if repeat_count > 0 { + repeat_count-- + } + } + c.current.forwards = forwards + if c.current.results == nil { + return false + } + ct := c.current.current_match_text() + if ct != "" { + } + if repeat_count > 0 { + self.complete(forwards, repeat_count) + } + return true +}