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.
This commit is contained in:
Jiří Setnička 2016-07-26 15:39:09 +02:00 committed by chzyer
parent dc578a10ae
commit cffdf641d1
2 changed files with 85 additions and 20 deletions

View File

@ -5,6 +5,9 @@ import (
"strings" "strings"
) )
// Caller type for dynamic completion
type DynamicCompleteFunc func(string) []string
type PrefixCompleterInterface interface { type PrefixCompleterInterface interface {
Print(prefix string, level int, buf *bytes.Buffer) Print(prefix string, level int, buf *bytes.Buffer)
Do(line []rune, pos int) (newLine [][]rune, length int) Do(line []rune, pos int) (newLine [][]rune, length int)
@ -13,8 +16,16 @@ type PrefixCompleterInterface interface {
SetChildren(children []PrefixCompleterInterface) SetChildren(children []PrefixCompleterInterface)
} }
type DynamicPrefixCompleterInterface interface {
PrefixCompleterInterface
IsDynamic() bool
GetDynamicNames(line []rune) [][]rune
}
type PrefixCompleter struct { type PrefixCompleter struct {
Name []rune Name []rune
Dynamic bool
Callback DynamicCompleteFunc
Children []PrefixCompleterInterface Children []PrefixCompleterInterface
} }
@ -44,10 +55,22 @@ func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) {
Print(p, prefix, level, buf) Print(p, prefix, level, buf)
} }
func (p *PrefixCompleter) IsDynamic() bool {
return p.Dynamic
}
func (p *PrefixCompleter) GetName() []rune { func (p *PrefixCompleter) GetName() []rune {
return p.Name 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 { func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface {
return p.Children return p.Children
} }
@ -64,36 +87,59 @@ func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter {
name += " " name += " "
return &PrefixCompleter{ return &PrefixCompleter{
Name: []rune(name), Name: []rune(name),
Dynamic: false,
Children: pc,
}
}
func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter {
return &PrefixCompleter{
Callback: callback,
Dynamic: true,
Children: pc, Children: pc,
} }
} }
func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { 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) { 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]) line = runes.TrimSpaceLeft(line[:pos])
goNext := false goNext := false
var lineCompleter PrefixCompleterInterface var lineCompleter PrefixCompleterInterface
for _, child := range p.GetChildren() { for _, child := range p.GetChildren() {
childName := child.GetName() childNames := make([][]rune, 1)
if len(line) >= len(childName) {
if runes.HasPrefix(line, childName) { childDynamic, ok := child.(DynamicPrefixCompleterInterface)
if len(line) == len(childName) { if ok && childDynamic.IsDynamic() {
newLine = append(newLine, []rune{' '}) childNames = childDynamic.GetDynamicNames(origLine)
} else {
newLine = append(newLine, childName)
}
offset = len(childName)
lineCompleter = child
goNext = true
}
} else { } else {
if runes.HasPrefix(childName, line) { childNames[0] = child.GetName()
newLine = append(newLine, childName[len(line):]) }
offset = len(line)
lineCompleter = child 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:]...) tmpLine = append(tmpLine, line[i:]...)
return lineCompleter.Do(tmpLine, len(tmpLine)) return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)
} }
if goNext { if goNext {
return lineCompleter.Do(nil, 0) return doInternal(lineCompleter, nil, 0, origLine)
} }
return return
} }

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"strconv" "strconv"
"strings" "strings"
@ -16,6 +17,18 @@ func usage(w io.Writer) {
io.WriteString(w, completer.Tree(" ")) 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( var completer = readline.NewPrefixCompleter(
readline.PcItem("mode", readline.PcItem("mode",
readline.PcItem("vi"), readline.PcItem("vi"),
@ -23,6 +36,12 @@ var completer = readline.NewPrefixCompleter(
), ),
readline.PcItem("login"), readline.PcItem("login"),
readline.PcItem("say", readline.PcItem("say",
readline.PcItemDynamic(listFiles("./"),
readline.PcItem("with",
readline.PcItem("following"),
readline.PcItem("items"),
),
),
readline.PcItem("hello"), readline.PcItem("hello"),
readline.PcItem("bye"), readline.PcItem("bye"),
), ),
@ -119,7 +138,7 @@ func main() {
break break
} }
go func() { go func() {
for _ = range time.Tick(time.Second) { for range time.Tick(time.Second) {
log.Println(line) log.Println(line)
} }
}() }()