More work on readline completion

This commit is contained in:
Kovid Goyal 2022-11-12 11:27:27 +05:30
parent 4974219e0f
commit 9e2c96653f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 80 additions and 16 deletions

View File

@ -51,14 +51,6 @@ func RegisterExeForCompletion(x func(root *Command)) {
registered_exes = append(registered_exes, x) 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 { func GenerateCompletions(args []string) error {
output_type := "json" output_type := "json"
if len(args) > 0 { if len(args) > 0 {

View File

@ -175,13 +175,19 @@ func exec_command(rl *readline.Readline, cmdline string) bool {
return true return true
} }
func completions(before_cursor, after_cursor string) *cli.Completions { func completions(before_cursor, after_cursor string) (ans *cli.Completions) {
text := "kitty @ " + before_cursor const prefix = "kitty @ "
argv, err := shlex.Split(text) text := prefix + before_cursor
if err != nil { argv, position_of_last_arg := shlex.SplitForCompletion(text)
return nil 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) { func shell_main(cmd *cli.Command, args []string) (int, error) {

View File

@ -5,7 +5,9 @@ package readline
import ( import (
"container/list" "container/list"
"fmt" "fmt"
"kitty/tools/cli"
"kitty/tools/tui/loop" "kitty/tools/tui/loop"
"kitty/tools/utils/shlex"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -481,7 +483,7 @@ func TestHistory(t *testing.T) {
t.Fatalf("Text before cursor not as expected:\n%s", diff) t.Fatalf("Text before cursor not as expected:\n%s", diff)
} }
if diff := cmp.Diff(after_cursor, aa); 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") add_item("xyz1")
@ -512,3 +514,48 @@ func TestHistory(t *testing.T) {
rl.perform_action(ActionTerminateHistorySearchAndRestore, 1) rl.perform_action(ActionTerminateHistorySearchAndRestore, 1)
ah("a", "") 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 ", "")
}

View File

@ -181,6 +181,7 @@ func (self *Readline) ResetText() {
self.last_action = ActionNil self.last_action = ActionNil
self.keyboard_state = KeyboardState{} self.keyboard_state = KeyboardState{}
self.history_search = nil self.history_search = nil
self.completions.current = completion{}
self.cursor_y = 0 self.cursor_y = 0
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"kitty/tools/cli" "kitty/tools/cli"
"kitty/tools/utils"
) )
var _ = fmt.Print var _ = fmt.Print
@ -39,7 +40,11 @@ func (self *completion) current_match_text() string {
for _, g := range self.results.Groups { for _, g := range self.results.Groups {
for _, m := range g.Matches { for _, m := range g.Matches {
if i == self.current_match { if i == self.current_match {
return m.Word t := m.Word
if !g.NoTrailingSpace {
t += " "
}
return t
} }
i++ i++
} }
@ -83,6 +88,19 @@ func (self *Readline) complete(forwards bool, repeat_count uint) bool {
} }
ct := c.current.current_match_text() ct := c.current.current_match_text()
if ct != "" { 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 { if repeat_count > 0 {
self.complete(forwards, repeat_count) self.complete(forwards, repeat_count)