diff --git a/complete.go b/complete.go index 94adb11..8ee823d 100644 --- a/complete.go +++ b/complete.go @@ -9,56 +9,13 @@ import ( ) type AutoCompleter interface { - GetName() []rune - GetChildren() []AutoCompleter -} - -func Do(ac AutoCompleter, line []rune, pos int) (newLine [][]rune, offset int) { - line = line[:pos] - goNext := false - var lineCompleter AutoCompleter - - for _, child := range ac.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 - } - } else { - if runes.HasPrefix(childName, line) { - newLine = append(newLine, childName[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[i] == ' ' { - continue - } - - tmpLine = append(tmpLine, line[i:]...) - return Do(lineCompleter, tmpLine, len(tmpLine)) - } - - if goNext { - return Do(lineCompleter, nil, 0) - } - - return + // Readline will pass the whole line and current offset to it + // Completer need to pass all the candidates, and how long they shared the same characters in line + // Example: + // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 + // Do("gi", 2) => ["t", "t-shell"], 1 + // Do("git", 3) => ["", "-shell"], 0 + Do(line []rune, pos int) (newLine [][]rune, length int) } type opCompleter struct { @@ -118,7 +75,7 @@ func (o *opCompleter) OnComplete() { o.ExitCompleteSelectMode() o.candidateSource = rs - newLines, offset := Do(o.ac, rs, buf.idx) + newLines, offset := o.ac.Do(rs, buf.idx) if len(newLines) == 0 { o.ExitCompleteMode(false) return diff --git a/complete_helper.go b/complete_helper.go index b74e6d5..7e8a807 100644 --- a/complete_helper.go +++ b/complete_helper.go @@ -1,27 +1,92 @@ package readline +import ( + "bytes" + "strings" + + "github.com/chzyer/readline/runes" +) + type PrefixCompleter struct { Name []rune - Children []AutoCompleter + Children []*PrefixCompleter } -func (p PrefixCompleter) GetName() []rune { - return p.Name +func (p *PrefixCompleter) Tree(prefix string) string { + buf := bytes.NewBuffer(nil) + p.Print(prefix, 0, buf) + return buf.String() } -func (p PrefixCompleter) GetChildren() []AutoCompleter { - return p.Children +func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.Name)) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.Name) + "\n") + level++ + } + for _, ch := range p.Children { + ch.Print(prefix, level, buf) + } } -func NewPrefixCompleter(pc ...AutoCompleter) AutoCompleter { +func NewPrefixCompleter(pc ...*PrefixCompleter) *PrefixCompleter { return PcItem("", pc...) } -func PcItem(name string, pc ...AutoCompleter) AutoCompleter { +func PcItem(name string, pc ...*PrefixCompleter) *PrefixCompleter { name += " " - result := AutoCompleter(PrefixCompleter{ + return &PrefixCompleter{ Name: []rune(name), Children: pc, - }) - return result + } +} + +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 runes.HasPrefix(line, child.Name) { + if len(line) == len(child.Name) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, child.Name) + } + offset = len(child.Name) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(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[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return lineCompleter.Do(tmpLine, len(tmpLine)) + } + + if goNext { + return lineCompleter.Do(nil, 0) + } + return } diff --git a/complete_helper_ext.go b/complete_helper_ext.go new file mode 100644 index 0000000..ef86153 --- /dev/null +++ b/complete_helper_ext.go @@ -0,0 +1,110 @@ +package readline + +import ( + "bytes" + "strings" + + "github.com/chzyer/readline/runes" +) + +type PrefixCompleterInterface interface { + Print(prefix string, level int, buf *bytes.Buffer) + Do(line []rune, pos int) (newLine [][]rune, length int) + GetName() []rune + GetChildren() []PrefixCompleterInterface +} + +type PrefixCompleterExt struct { + Name []rune + Children []PrefixCompleterInterface +} + +func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.GetName())) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.GetName()) + "\n") + level++ + } + for _, ch := range p.GetChildren() { + ch.Print(prefix, level, buf) + } +} + +func (p *PrefixCompleterExt) Print(prefix string, level int, buf *bytes.Buffer) { + Print(p, prefix, level, buf) +} + +func (p *PrefixCompleterExt) GetName() []rune { + return p.Name +} + +func (p *PrefixCompleterExt) GetChildren() []PrefixCompleterInterface { + return p.Children +} + +func NewPrefixCompleterExt(pc ...PrefixCompleterInterface) *PrefixCompleterExt { + return PceItem("", pc...) +} + +func PceItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleterExt { + name += " " + return &PrefixCompleterExt{ + Name: []rune(name), + Children: pc, + } +} + +func (p *PrefixCompleterExt) Do(line []rune, pos int) (newLine [][]rune, offset int) { + return Do(p, line, pos) +} + +func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + line = 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 + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[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[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return lineCompleter.Do(tmpLine, len(tmpLine)) + } + + if goNext { + return lineCompleter.Do(nil, 0) + } + return +}