From cffdf641d1121640d4cc46e360c5732d2440c591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Setni=C4=8Dka?= Date: Tue, 26 Jul 2016 15:39:09 +0200 Subject: [PATCH] Dynamic autocompletion (#60) * Dynamic autocompletion implemented Now there is a new item type "PcItemDynamic" which takes function as parameter. When it comes to autocomplete at this position, the function is called, the whole line is given to it (for example if autocompletion depend on previous autocompleted field) and functio should return possible strings how to continue. Example usage: * listing some dynamic key-value storage * listing files in directory * Dynamic autocompletion: Updated example * Dynamic autocompletion: Internal Do() is passing the original full line To serve it to dynamic autocompletion functions. Previously passed line was only following segment (which doesn't work). * Dynamic autocompletion: New dynamic interface added + type assertion in Do function Do function was split into doInternal with changed declaration and Do with original declaration. --- complete_helper.go | 84 ++++++++++++++++++++------ example/readline-demo/readline-demo.go | 21 ++++++- 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/complete_helper.go b/complete_helper.go index f609d44..58d7248 100644 --- a/complete_helper.go +++ b/complete_helper.go @@ -5,6 +5,9 @@ import ( "strings" ) +// Caller type for dynamic completion +type DynamicCompleteFunc func(string) []string + type PrefixCompleterInterface interface { Print(prefix string, level int, buf *bytes.Buffer) Do(line []rune, pos int) (newLine [][]rune, length int) @@ -13,8 +16,16 @@ type PrefixCompleterInterface interface { SetChildren(children []PrefixCompleterInterface) } +type DynamicPrefixCompleterInterface interface { + PrefixCompleterInterface + IsDynamic() bool + GetDynamicNames(line []rune) [][]rune +} + type PrefixCompleter struct { Name []rune + Dynamic bool + Callback DynamicCompleteFunc Children []PrefixCompleterInterface } @@ -44,10 +55,22 @@ func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { Print(p, prefix, level, buf) } +func (p *PrefixCompleter) IsDynamic() bool { + return p.Dynamic +} + func (p *PrefixCompleter) GetName() []rune { return p.Name } +func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { + var names = [][]rune{} + for _, name := range p.Callback(string(line)) { + names = append(names, []rune(name+" ")) + } + return names +} + func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { return p.Children } @@ -64,36 +87,59 @@ func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { name += " " return &PrefixCompleter{ Name: []rune(name), + Dynamic: false, + Children: pc, + } +} + +func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { + return &PrefixCompleter{ + Callback: callback, + Dynamic: true, Children: pc, } } func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { - return Do(p, line, pos) + return doInternal(p, line, pos, line) } func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { line = runes.TrimSpaceLeft(line[:pos]) goNext := false var lineCompleter PrefixCompleterInterface for _, child := range p.GetChildren() { - childName := child.GetName() - if len(line) >= len(childName) { - if runes.HasPrefix(line, childName) { - if len(line) == len(childName) { - newLine = append(newLine, []rune{' '}) - } else { - newLine = append(newLine, childName) - } - offset = len(childName) - lineCompleter = child - goNext = true - } + childNames := make([][]rune, 1) + + childDynamic, ok := child.(DynamicPrefixCompleterInterface) + if ok && childDynamic.IsDynamic() { + childNames = childDynamic.GetDynamicNames(origLine) } else { - if runes.HasPrefix(childName, line) { - newLine = append(newLine, childName[len(line):]) - offset = len(line) - lineCompleter = child + childNames[0] = child.GetName() + } + + for _, childName := range childNames { + if len(line) >= len(childName) { + if runes.HasPrefix(line, childName) { + if len(line) == len(childName) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, childName) + } + offset = len(childName) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[len(line):]) + offset = len(line) + lineCompleter = child + } } } } @@ -109,11 +155,11 @@ func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, off } tmpLine = append(tmpLine, line[i:]...) - return lineCompleter.Do(tmpLine, len(tmpLine)) + return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) } if goNext { - return lineCompleter.Do(nil, 0) + return doInternal(lineCompleter, nil, 0, origLine) } return } diff --git a/example/readline-demo/readline-demo.go b/example/readline-demo/readline-demo.go index eeb3605..d80e59e 100644 --- a/example/readline-demo/readline-demo.go +++ b/example/readline-demo/readline-demo.go @@ -3,6 +3,7 @@ package main import ( "fmt" "io" + "io/ioutil" "log" "strconv" "strings" @@ -16,6 +17,18 @@ func usage(w io.Writer) { io.WriteString(w, completer.Tree(" ")) } +// Function constructor - constructs new function for listing given directory +func listFiles(path string) func(string) []string { + return func(line string) []string { + names := make([]string, 0) + files, _ := ioutil.ReadDir(path) + for _, f := range files { + names = append(names, f.Name()) + } + return names + } +} + var completer = readline.NewPrefixCompleter( readline.PcItem("mode", readline.PcItem("vi"), @@ -23,6 +36,12 @@ var completer = readline.NewPrefixCompleter( ), readline.PcItem("login"), readline.PcItem("say", + readline.PcItemDynamic(listFiles("./"), + readline.PcItem("with", + readline.PcItem("following"), + readline.PcItem("items"), + ), + ), readline.PcItem("hello"), readline.PcItem("bye"), ), @@ -119,7 +138,7 @@ func main() { break } go func() { - for _ = range time.Tick(time.Second) { + for range time.Tick(time.Second) { log.Println(line) } }()