Skip to content

Commit

Permalink
Dynamic autocompletion (chzyer#60)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
setnicka authored and lunixbochs committed May 4, 2017
1 parent d91f50f commit c7bb751
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 20 deletions.
84 changes: 65 additions & 19 deletions complete_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
}
}
}
Expand All @@ -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
}
21 changes: 20 additions & 1 deletion example/readline-demo/readline-demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"strconv"
"strings"
Expand All @@ -16,13 +17,31 @@ 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"),
readline.PcItem("emacs"),
),
readline.PcItem("login"),
readline.PcItem("say",
readline.PcItemDynamic(listFiles("./"),
readline.PcItem("with",
readline.PcItem("following"),
readline.PcItem("items"),
),
),
readline.PcItem("hello"),
readline.PcItem("bye"),
),
Expand Down Expand Up @@ -119,7 +138,7 @@ func main() {
break
}
go func() {
for _ = range time.Tick(time.Second) {
for range time.Tick(time.Second) {
log.Println(line)
}
}()
Expand Down

0 comments on commit c7bb751

Please sign in to comment.