readline/complete.go

284 lines
6.0 KiB
Go
Raw Normal View History

2015-09-25 17:56:00 +03:00
package readline
import (
"bufio"
2015-09-25 17:56:00 +03:00
"bytes"
"fmt"
"io"
)
2015-09-25 19:31:09 +03:00
type AutoCompleter interface {
2015-09-27 05:33:19 +03:00
// 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:
2016-04-01 20:23:53 +03:00
// [go, git, git-shell, grep]
2015-09-27 05:33:19 +03:00
// Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
2016-04-01 20:23:53 +03:00
// Do("gi", 2) => ["t", "t-shell"], 2
// Do("git", 3) => ["", "-shell"], 3
2015-09-27 05:33:19 +03:00
Do(line []rune, pos int) (newLine [][]rune, length int)
2015-09-25 19:31:09 +03:00
}
2016-09-03 06:26:39 +03:00
type TabCompleter struct{}
func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
return [][]rune{[]rune("\t")}, 0
}
2015-09-25 17:56:00 +03:00
type opCompleter struct {
2016-03-13 13:32:48 +03:00
w io.Writer
op *Operation
width int
2015-09-25 17:56:00 +03:00
inCompleteMode bool
inSelectMode bool
2015-09-27 05:33:19 +03:00
candidate [][]rune
candidateSource []rune
candidateOff int
candidateChoise int
candidateColNum int
2015-09-25 17:56:00 +03:00
}
2016-03-13 13:32:48 +03:00
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
2015-09-25 17:56:00 +03:00
return &opCompleter{
2016-03-13 13:32:48 +03:00
w: w,
op: op,
width: width,
2015-09-25 17:56:00 +03:00
}
}
func (o *opCompleter) doSelect() {
2015-09-27 05:33:19 +03:00
if len(o.candidate) == 1 {
o.op.buf.WriteRunes(o.candidate[0])
2015-09-25 17:56:00 +03:00
o.ExitCompleteMode(false)
return
}
2015-09-27 05:33:19 +03:00
o.nextCandidate(1)
2015-09-25 17:56:00 +03:00
o.CompleteRefresh()
}
2015-09-27 05:33:19 +03:00
func (o *opCompleter) nextCandidate(i int) {
o.candidateChoise += i
o.candidateChoise = o.candidateChoise % len(o.candidate)
if o.candidateChoise < 0 {
o.candidateChoise = len(o.candidate) + o.candidateChoise
2015-09-25 17:56:00 +03:00
}
}
func (o *opCompleter) OnComplete() bool {
if o.width == 0 {
return false
}
2015-09-25 17:56:00 +03:00
if o.IsInCompleteSelectMode() {
o.doSelect()
return true
2015-09-25 17:56:00 +03:00
}
buf := o.op.buf
rs := buf.Runes()
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
2015-09-25 17:56:00 +03:00
o.EnterCompleteSelectMode()
o.doSelect()
return true
2015-09-25 17:56:00 +03:00
}
o.ExitCompleteSelectMode()
2015-09-27 05:33:19 +03:00
o.candidateSource = rs
2016-04-01 20:23:53 +03:00
newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
2015-09-25 17:56:00 +03:00
if len(newLines) == 0 {
o.ExitCompleteMode(false)
return true
2015-09-25 17:56:00 +03:00
}
2015-09-27 05:33:19 +03:00
// only Aggregate candidates in non-complete mode
2015-09-25 17:56:00 +03:00
if !o.IsInCompleteMode() {
if len(newLines) == 1 {
buf.WriteRunes(newLines[0])
o.ExitCompleteMode(false)
return true
2015-09-25 17:56:00 +03:00
}
2015-10-04 16:58:34 +03:00
same, size := runes.Aggregate(newLines)
2015-09-25 17:56:00 +03:00
if size > 0 {
buf.WriteRunes(same)
o.ExitCompleteMode(false)
return true
2015-09-25 17:56:00 +03:00
}
}
o.EnterCompleteMode(offset, newLines)
return true
2015-09-25 17:56:00 +03:00
}
func (o *opCompleter) IsInCompleteSelectMode() bool {
return o.inSelectMode
}
func (o *opCompleter) IsInCompleteMode() bool {
return o.inCompleteMode
}
func (o *opCompleter) HandleCompleteSelect(r rune) bool {
next := true
switch r {
case CharEnter, CharCtrlJ:
next = false
2015-09-27 05:33:19 +03:00
o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])
2015-09-25 17:56:00 +03:00
o.ExitCompleteMode(false)
case CharLineStart:
2015-09-27 05:33:19 +03:00
num := o.candidateChoise % o.candidateColNum
o.nextCandidate(-num)
2015-09-25 17:56:00 +03:00
case CharLineEnd:
2015-09-27 05:33:19 +03:00
num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1
o.candidateChoise += num
if o.candidateChoise >= len(o.candidate) {
o.candidateChoise = len(o.candidate) - 1
2015-09-25 17:56:00 +03:00
}
case CharBackspace:
o.ExitCompleteSelectMode()
next = false
case CharTab, CharForward:
o.doSelect()
2015-10-01 17:44:43 +03:00
case CharBell, CharInterrupt:
2015-09-25 17:56:00 +03:00
o.ExitCompleteMode(true)
next = false
case CharNext:
2015-09-27 05:33:19 +03:00
tmpChoise := o.candidateChoise + o.candidateColNum
2015-09-25 17:56:00 +03:00
if tmpChoise >= o.getMatrixSize() {
tmpChoise -= o.getMatrixSize()
2015-09-27 05:33:19 +03:00
} else if tmpChoise >= len(o.candidate) {
tmpChoise += o.candidateColNum
2015-09-25 17:56:00 +03:00
tmpChoise -= o.getMatrixSize()
}
2015-09-27 05:33:19 +03:00
o.candidateChoise = tmpChoise
2015-09-25 17:56:00 +03:00
case CharBackward:
2015-09-27 05:33:19 +03:00
o.nextCandidate(-1)
2015-09-25 17:56:00 +03:00
case CharPrev:
2015-09-27 05:33:19 +03:00
tmpChoise := o.candidateChoise - o.candidateColNum
2015-09-25 17:56:00 +03:00
if tmpChoise < 0 {
tmpChoise += o.getMatrixSize()
2015-09-27 05:33:19 +03:00
if tmpChoise >= len(o.candidate) {
tmpChoise -= o.candidateColNum
2015-09-25 17:56:00 +03:00
}
}
2015-09-27 05:33:19 +03:00
o.candidateChoise = tmpChoise
2015-09-25 17:56:00 +03:00
default:
next = false
2015-09-25 20:01:20 +03:00
o.ExitCompleteSelectMode()
2015-09-25 17:56:00 +03:00
}
if next {
o.CompleteRefresh()
return true
}
return false
}
func (o *opCompleter) getMatrixSize() int {
2015-09-27 05:33:19 +03:00
line := len(o.candidate) / o.candidateColNum
if len(o.candidate)%o.candidateColNum != 0 {
2015-09-25 17:56:00 +03:00
line++
}
2015-09-27 05:33:19 +03:00
return line * o.candidateColNum
2015-09-25 17:56:00 +03:00
}
2016-03-13 13:32:48 +03:00
func (o *opCompleter) OnWidthChange(newWidth int) {
o.width = newWidth
}
2015-09-25 17:56:00 +03:00
func (o *opCompleter) CompleteRefresh() {
if !o.inCompleteMode {
return
}
lineCnt := o.op.buf.CursorLineCount()
colWidth := 0
2015-09-27 05:33:19 +03:00
for _, c := range o.candidate {
2015-10-04 16:56:34 +03:00
w := runes.WidthAll(c)
2015-09-25 17:56:00 +03:00
if w > colWidth {
colWidth = w
}
}
colWidth += o.candidateOff + 1
same := o.op.buf.RuneSlice(-o.candidateOff)
// -1 to avoid reach the end of line
width := o.width - 1
colNum := width / colWidth
colWidth += (width - (colWidth * colNum)) / colNum
2015-09-27 05:33:19 +03:00
o.candidateColNum = colNum
buf := bufio.NewWriter(o.w)
2015-09-25 17:56:00 +03:00
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
2015-09-25 17:56:00 +03:00
colIdx := 0
lines := 1
buf.WriteString("\033[J")
2015-09-27 05:33:19 +03:00
for idx, c := range o.candidate {
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
2015-09-25 17:56:00 +03:00
if inSelect {
buf.WriteString("\033[30;47m")
}
buf.WriteString(string(same))
buf.WriteString(string(c))
buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c)))
2015-09-25 17:56:00 +03:00
if inSelect {
buf.WriteString("\033[0m")
}
colIdx++
if colIdx == colNum {
buf.WriteString("\n")
lines++
colIdx = 0
}
}
// move back
fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines)
fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen())
buf.Flush()
2015-09-25 17:56:00 +03:00
}
2015-09-27 05:33:19 +03:00
func (o *opCompleter) aggCandidate(candidate [][]rune) int {
2015-09-25 17:56:00 +03:00
offset := 0
2015-09-27 05:33:19 +03:00
for i := 0; i < len(candidate[0]); i++ {
for j := 0; j < len(candidate)-1; j++ {
if i > len(candidate[j]) {
2015-09-25 17:56:00 +03:00
goto aggregate
}
2015-09-27 05:33:19 +03:00
if candidate[j][i] != candidate[j+1][i] {
2015-09-25 17:56:00 +03:00
goto aggregate
}
}
offset = i
}
aggregate:
return offset
}
func (o *opCompleter) EnterCompleteSelectMode() {
o.inSelectMode = true
2015-09-27 05:33:19 +03:00
o.candidateChoise = -1
2015-09-25 17:56:00 +03:00
o.CompleteRefresh()
}
2015-09-27 05:33:19 +03:00
func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
2015-09-25 17:56:00 +03:00
o.inCompleteMode = true
2015-09-27 05:33:19 +03:00
o.candidate = candidate
o.candidateOff = offset
2015-09-25 17:56:00 +03:00
o.CompleteRefresh()
}
func (o *opCompleter) ExitCompleteSelectMode() {
o.inSelectMode = false
2015-09-27 05:33:19 +03:00
o.candidate = nil
o.candidateChoise = -1
o.candidateOff = -1
o.candidateSource = nil
2015-09-25 17:56:00 +03:00
}
func (o *opCompleter) ExitCompleteMode(revent bool) {
o.inCompleteMode = false
o.ExitCompleteSelectMode()
}