1package readline
2
3import (
4	"bytes"
5	"strings"
6)
7
8// Caller type for dynamic completion
9type DynamicCompleteFunc func(string) []string
10
11type PrefixCompleterInterface interface {
12	Print(prefix string, level int, buf *bytes.Buffer)
13	Do(line []rune, pos int) (newLine [][]rune, length int)
14	GetName() []rune
15	GetChildren() []PrefixCompleterInterface
16	SetChildren(children []PrefixCompleterInterface)
17}
18
19type DynamicPrefixCompleterInterface interface {
20	PrefixCompleterInterface
21	IsDynamic() bool
22	GetDynamicNames(line []rune) [][]rune
23}
24
25type PrefixCompleter struct {
26	Name     []rune
27	Dynamic  bool
28	Callback DynamicCompleteFunc
29	Children []PrefixCompleterInterface
30}
31
32func (p *PrefixCompleter) Tree(prefix string) string {
33	buf := bytes.NewBuffer(nil)
34	p.Print(prefix, 0, buf)
35	return buf.String()
36}
37
38func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) {
39	if strings.TrimSpace(string(p.GetName())) != "" {
40		buf.WriteString(prefix)
41		if level > 0 {
42			buf.WriteString("├")
43			buf.WriteString(strings.Repeat("─", (level*4)-2))
44			buf.WriteString(" ")
45		}
46		buf.WriteString(string(p.GetName()) + "\n")
47		level++
48	}
49	for _, ch := range p.GetChildren() {
50		ch.Print(prefix, level, buf)
51	}
52}
53
54func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) {
55	Print(p, prefix, level, buf)
56}
57
58func (p *PrefixCompleter) IsDynamic() bool {
59	return p.Dynamic
60}
61
62func (p *PrefixCompleter) GetName() []rune {
63	return p.Name
64}
65
66func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune {
67	var names = [][]rune{}
68	for _, name := range p.Callback(string(line)) {
69		names = append(names, []rune(name+" "))
70	}
71	return names
72}
73
74func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface {
75	return p.Children
76}
77
78func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) {
79	p.Children = children
80}
81
82func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter {
83	return PcItem("", pc...)
84}
85
86func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter {
87	name += " "
88	return &PrefixCompleter{
89		Name:     []rune(name),
90		Dynamic:  false,
91		Children: pc,
92	}
93}
94
95func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter {
96	return &PrefixCompleter{
97		Callback: callback,
98		Dynamic:  true,
99		Children: pc,
100	}
101}
102
103func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
104	return doInternal(p, line, pos, line)
105}
106
107func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) {
108	return doInternal(p, line, pos, line)
109}
110
111func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) {
112	line = runes.TrimSpaceLeft(line[:pos])
113	goNext := false
114	var lineCompleter PrefixCompleterInterface
115	for _, child := range p.GetChildren() {
116		childNames := make([][]rune, 1)
117
118		childDynamic, ok := child.(DynamicPrefixCompleterInterface)
119		if ok && childDynamic.IsDynamic() {
120			childNames = childDynamic.GetDynamicNames(origLine)
121		} else {
122			childNames[0] = child.GetName()
123		}
124
125		for _, childName := range childNames {
126			if len(line) >= len(childName) {
127				if runes.HasPrefix(line, childName) {
128					if len(line) == len(childName) {
129						newLine = append(newLine, []rune{' '})
130					} else {
131						newLine = append(newLine, childName)
132					}
133					offset = len(childName)
134					lineCompleter = child
135					goNext = true
136				}
137			} else {
138				if runes.HasPrefix(childName, line) {
139					newLine = append(newLine, childName[len(line):])
140					offset = len(line)
141					lineCompleter = child
142				}
143			}
144		}
145	}
146
147	if len(newLine) != 1 {
148		return
149	}
150
151	tmpLine := make([]rune, 0, len(line))
152	for i := offset; i < len(line); i++ {
153		if line[i] == ' ' {
154			continue
155		}
156
157		tmpLine = append(tmpLine, line[i:]...)
158		return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)
159	}
160
161	if goNext {
162		return doInternal(lineCompleter, nil, 0, origLine)
163	}
164	return
165}
166