diff --git a/tools/cli/completion-main.go b/tools/cli/completion-main.go index bc0948e02..d787c1e55 100644 --- a/tools/cli/completion-main.go +++ b/tools/cli/completion-main.go @@ -51,14 +51,6 @@ 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 68d564d14..dc7f8365d 100644 --- a/tools/cmd/at/shell.go +++ b/tools/cmd/at/shell.go @@ -175,13 +175,19 @@ 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 +func completions(before_cursor, after_cursor string) (ans *cli.Completions) { + const prefix = "kitty @ " + text := prefix + before_cursor + argv, position_of_last_arg := shlex.SplitForCompletion(text) + if len(argv) == 0 || position_of_last_arg < len(prefix) { + return } - return cli.CompletionsForArgv(argv) + root := cli.NewRootCommand() + c := root.AddSubCommand(&cli.Command{Name: "kitty-tool"}) + EntryPoint(c) + ans = root.GetCompletions(argv, nil) + ans.CurrentWordIdx = position_of_last_arg - len(prefix) + return } func shell_main(cmd *cli.Command, args []string) (int, error) { diff --git a/tools/tui/readline/actions_test.go b/tools/tui/readline/actions_test.go index c3772a7ad..378ff3fc4 100644 --- a/tools/tui/readline/actions_test.go +++ b/tools/tui/readline/actions_test.go @@ -5,7 +5,9 @@ package readline import ( "container/list" "fmt" + "kitty/tools/cli" "kitty/tools/tui/loop" + "kitty/tools/utils/shlex" "strconv" "strings" "testing" @@ -481,7 +483,7 @@ func TestHistory(t *testing.T) { t.Fatalf("Text before cursor not as expected:\n%s", diff) } if diff := cmp.Diff(after_cursor, aa); diff != "" { - t.Fatalf("Text before cursor not as expected:\n%s", diff) + t.Fatalf("Text after cursor not as expected:\n%s", diff) } } add_item("xyz1") @@ -512,3 +514,48 @@ func TestHistory(t *testing.T) { rl.perform_action(ActionTerminateHistorySearchAndRestore, 1) ah("a", "") } + +func TestReadlineCompletion(t *testing.T) { + lp, _ := loop.New() + + completer := func(before_cursor, after_cursor string) (ans *cli.Completions) { + root := cli.NewRootCommand() + c := root.AddSubCommand(&cli.Command{Name: "test-completion"}) + c.AddSubCommand(&cli.Command{Name: "a1"}) + c.AddSubCommand(&cli.Command{Name: "a11"}) + c.AddSubCommand(&cli.Command{Name: "a2"}) + prefix := c.Name + " " + text := prefix + before_cursor + argv, position_of_last_arg := shlex.SplitForCompletion(text) + if len(argv) == 0 || position_of_last_arg < len(prefix) { + return + } + ans = root.GetCompletions(argv, nil) + ans.CurrentWordIdx = position_of_last_arg - len(prefix) + return + + } + rl := New(lp, RlInit{Prompt: "$$ ", Completer: completer}) + + ah := func(before_cursor, after_cursor string) { + ab := rl.text_upto_cursor_pos() + aa := rl.text_after_cursor_pos() + if diff := cmp.Diff(before_cursor, ab); diff != "" { + t.Fatalf("Text before cursor not as expected:\n%s", diff) + } + if diff := cmp.Diff(after_cursor, aa); diff != "" { + t.Fatalf("Text after cursor not as expected:\n%s", diff) + } + } + rl.add_text("a") + rl.perform_action(ActionCompleteForward, 1) + ah("a", "") + rl.perform_action(ActionCompleteForward, 1) + ah("a1 ", "") + rl.perform_action(ActionCompleteForward, 1) + ah("a11 ", "") + rl.perform_action(ActionCompleteForward, 1) + ah("a2 ", "") + rl.perform_action(ActionCompleteBackward, 1) + ah("a11 ", "") +} diff --git a/tools/tui/readline/api.go b/tools/tui/readline/api.go index 83c860579..90ee36922 100644 --- a/tools/tui/readline/api.go +++ b/tools/tui/readline/api.go @@ -181,6 +181,7 @@ func (self *Readline) ResetText() { self.last_action = ActionNil self.keyboard_state = KeyboardState{} self.history_search = nil + self.completions.current = completion{} self.cursor_y = 0 } diff --git a/tools/tui/readline/completion.go b/tools/tui/readline/completion.go index acbb968fd..477b784b0 100644 --- a/tools/tui/readline/completion.go +++ b/tools/tui/readline/completion.go @@ -6,6 +6,7 @@ import ( "fmt" "kitty/tools/cli" + "kitty/tools/utils" ) var _ = fmt.Print @@ -39,7 +40,11 @@ func (self *completion) current_match_text() string { for _, g := range self.results.Groups { for _, m := range g.Matches { if i == self.current_match { - return m.Word + t := m.Word + if !g.NoTrailingSpace { + t += " " + } + return t } i++ } @@ -83,6 +88,19 @@ func (self *Readline) complete(forwards bool, repeat_count uint) bool { } ct := c.current.current_match_text() if ct != "" { + before := c.current.before_cursor[:c.current.results.CurrentWordIdx] + ct + after := c.current.after_cursor + self.input_state.lines = utils.Splitlines(before) + if len(self.input_state.lines) == 0 { + self.input_state.lines = []string{""} + } + self.input_state.cursor.Y = len(self.input_state.lines) - 1 + self.input_state.cursor.X = len(self.input_state.lines[self.input_state.cursor.Y]) + al := utils.Splitlines(after) + if len(al) > 0 { + self.input_state.lines[self.input_state.cursor.Y] += al[0] + self.input_state.lines = append(self.input_state.lines, al[1:]...) + } } if repeat_count > 0 { self.complete(forwards, repeat_count)