From d07044cdb65e83e992fe3844883c78777ca2c54e Mon Sep 17 00:00:00 2001 From: Cheney Date: Sat, 26 Sep 2015 00:31:09 +0800 Subject: [PATCH] add prefix completer --- complete.go | 4 +++ complete_helper.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++ example/main.go | 52 +++++++++++++++++++-------------------- readline.go | 8 ------ utils.go | 7 ++++++ 5 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 complete_helper.go diff --git a/complete.go b/complete.go index 4655946..262722b 100644 --- a/complete.go +++ b/complete.go @@ -6,6 +6,10 @@ import ( "io" ) +type AutoCompleter interface { + Do(line []rune, pos int) (newLine [][]rune, offset int) +} + type opCompleter struct { w io.Writer op *Operation diff --git a/complete_helper.go b/complete_helper.go new file mode 100644 index 0000000..82926dd --- /dev/null +++ b/complete_helper.go @@ -0,0 +1,61 @@ +package readline + +type PrefixCompleter struct { + Name []rune + Children []*PrefixCompleter +} + +func NewPrefixCompleter(pc ...*PrefixCompleter) *PrefixCompleter { + return PcItem("", pc...) +} + +func PcItem(name string, pc ...*PrefixCompleter) *PrefixCompleter { + if len(pc) != 0 { + name += " " + } + return &PrefixCompleter{ + Name: []rune(name), + Children: pc, + } +} + +func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { + line = line[:pos] + goNext := false + var lineCompleter *PrefixCompleter + for _, child := range p.Children { + if len(line) >= len(child.Name) { + if RunesHasPrefix(line, child.Name) { + newLine = append(newLine, child.Name) + offset = len(child.Name) + lineCompleter = child + goNext = true + } + } else { + if RunesHasPrefix(child.Name, line) { + newLine = append(newLine, child.Name[len(line):]) + offset = len(line) + lineCompleter = child + } + } + } + + if len(newLine) != 1 { + return + } + + tmpLine := make([]rune, 0, len(line)) + for i := offset; i < len(line); i++ { + if line[offset] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return lineCompleter.Do(tmpLine, len(tmpLine)) + } + + if goNext { + return lineCompleter.Do(nil, 0) + } + return +} diff --git a/example/main.go b/example/main.go index c9199e6..8875e14 100644 --- a/example/main.go +++ b/example/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "io" "log" "strconv" @@ -18,30 +17,25 @@ bye: quit `[1:]) } -type Completer struct { -} - -func (c *Completer) Do(line []rune, pos int) (newLine [][]rune, off int) { - list := [][]rune{ - []rune("sayhello"), []rune("help"), []rune("bye"), - } - for i := 0; i <= 100; i++ { - list = append(list, []rune(fmt.Sprintf("com%d", i))) - } - line = line[:pos] - for _, r := range list { - if strings.HasPrefix(string(r), string(line)) { - newLine = append(newLine, r[len(line):]) - } - } - return newLine, len(line) -} +var completer = readline.NewPrefixCompleter( + readline.PcItem("say", + readline.PcItem("hello"), + readline.PcItem("bye"), + ), + readline.PcItem("bye"), + readline.PcItem("help"), + readline.PcItem("go", + readline.PcItem("build"), + readline.PcItem("install"), + readline.PcItem("test"), + ), +) func main() { l, err := readline.NewEx(&readline.Config{ Prompt: "\033[31m»\033[0m ", HistoryFile: "/tmp/readline.tmp", - AutoComplete: new(Completer), + AutoComplete: completer, }) if err != nil { panic(err) @@ -54,18 +48,24 @@ func main() { if err != nil { break } - switch line { - case "help": + + switch { + case line == "help": usage(l.Stderr()) - case "sayhello": + case strings.HasPrefix(line, "say"): + line := strings.TrimSpace(line[3:]) + if len(line) == 0 { + log.Println("say what?") + break + } go func() { for _ = range time.Tick(time.Second) { - log.Println("hello") + log.Println(line) } }() - case "bye": + case line == "bye": goto exit - case "": + case line == "": default: log.Println("you said:", strconv.Quote(line)) } diff --git a/readline.go b/readline.go index 6da9267..864cfab 100644 --- a/readline.go +++ b/readline.go @@ -7,14 +7,6 @@ type Instance struct { o *Operation } -type AutoCompleter interface { - Do(line []rune, pos int) (newLine [][]rune, offset int) -} - -type AutoCompleteHinter interface { - Hint(line []rune, pos int) ([]rune, bool) -} - type Config struct { Prompt string HistoryFile string diff --git a/utils.go b/utils.go index 2870d59..7b77634 100644 --- a/utils.go +++ b/utils.go @@ -270,3 +270,10 @@ func EqualRunes(r, r2 []rune) bool { } return true } + +func RunesHasPrefix(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return EqualRunes(r[:len(prefix)], prefix) +}