diff --git a/complete.go b/complete.go index 8776504..883983b 100644 --- a/complete.go +++ b/complete.go @@ -12,16 +12,16 @@ type AutoCompleter interface { // 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: + // [go, git, git-shell, grep] // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 - // Do("gi", 2) => ["t", "t-shell"], 1 - // Do("git", 3) => ["", "-shell"], 0 + // Do("gi", 2) => ["t", "t-shell"], 2 + // Do("git", 3) => ["", "-shell"], 3 Do(line []rune, pos int) (newLine [][]rune, length int) } type opCompleter struct { w io.Writer op *Operation - ac AutoCompleter width int inCompleteMode bool @@ -37,7 +37,6 @@ func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { return &opCompleter{ w: w, op: op, - ac: op.cfg.AutoComplete, width: width, } } @@ -77,7 +76,7 @@ func (o *opCompleter) OnComplete() { o.ExitCompleteSelectMode() o.candidateSource = rs - newLines, offset := o.ac.Do(rs, buf.idx) + newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) if len(newLines) == 0 { o.ExitCompleteMode(false) return diff --git a/complete_segment.go b/complete_segment.go new file mode 100644 index 0000000..2a0c865 --- /dev/null +++ b/complete_segment.go @@ -0,0 +1,66 @@ +package readline + +type SegmentCompleter interface { + // a + // |- a1 + // |--- a11 + // |- a2 + // b + // input: + // DoTree([], 0) [a, b] + // DoTree([a], 1) [a] + // DoTree([a, ], 0) [a1, a2] + // DoTree([a, a], 1) [a1, a2] + // DoTree([a, a1], 2) [a1] + // DoTree([a, a1, ], 0) [a11] + // DoTree([a, a1, a], 1) [a11] + DoSegment([][]rune, int) [][]rune +} + +type dumpSegmentCompleter struct { + f func([][]rune, int) [][]rune +} + +func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { + return d.f(segment, n) +} + +func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { + return &SegmentComplete{&dumpSegmentCompleter{f}} +} + +type SegmentComplete struct { + SegmentCompleter +} + +func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { + ret := make([][]rune, 0, len(cands)) + lastSegment := segments[len(segments)-1] + for _, cand := range cands { + ret = append(ret, cand[len(lastSegment):]) + } + return ret, idx +} + +func SplitSegment(line []rune, pos int) ([][]rune, int) { + segs := [][]rune{} + lastIdx := -1 + line = line[:pos] + pos = 0 + for idx, l := range line { + if l == ' ' { + pos = 0 + segs = append(segs, line[lastIdx+1:idx]) + lastIdx = idx + } else { + pos++ + } + } + segs = append(segs, line[lastIdx+1:]) + return segs, pos +} + +func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { + segment, idx := SplitSegment(line, pos) + return RetSegment(segment, c.DoSegment(segment, idx), idx) +} diff --git a/complete_segment_test.go b/complete_segment_test.go new file mode 100644 index 0000000..dfdf075 --- /dev/null +++ b/complete_segment_test.go @@ -0,0 +1,160 @@ +package readline + +import ( + "fmt" + "testing" + + "gopkg.in/logex.v1" + + "github.com/chzyer/test" +) + +func rs(s [][]rune) []string { + ret := make([]string, len(s)) + for idx, ss := range s { + ret[idx] = string(ss) + } + return ret +} + +func sr(s ...string) [][]rune { + ret := make([][]rune, len(s)) + for idx, ss := range s { + ret[idx] = []rune(ss) + } + return ret +} + +func TestRetSegment(t *testing.T) { + defer test.New(t) + // a + // |- a1 + // |--- a11 + // |--- a12 + // |- a2 + // |--- a21 + // b + ret := []struct { + Segments [][]rune + Cands [][]rune + idx int + Ret [][]rune + pos int + }{ + {sr(""), sr("a", "b"), 0, sr("a", "b"), 0}, + {sr("a"), sr("a"), 1, sr(""), 1}, + {sr("a", ""), sr("a1", "a2"), 0, sr("a1", "a2"), 0}, + {sr("a", "a"), sr("a1", "a2"), 1, sr("1", "2"), 1}, + {sr("a", "a1"), sr("a1"), 2, sr(""), 2}, + } + for idx, r := range ret { + ret, pos := RetSegment(r.Segments, r.Cands, r.idx) + test.Equal(ret, r.Ret, fmt.Errorf("%v", idx)) + test.Equal(pos, r.pos, fmt.Errorf("%v", idx)) + } +} + +func TestSplitSegment(t *testing.T) { + defer test.New(t) + // a + // |- a1 + // |--- a11 + // |--- a12 + // |- a2 + // |--- a21 + // b + ret := []struct { + Line string + Pos int + Segments [][]rune + Idx int + }{ + {"", 0, sr(""), 0}, + {"a", 1, sr("a"), 1}, + {"a ", 2, sr("a", ""), 0}, + {"a a", 3, sr("a", "a"), 1}, + {"a a1", 4, sr("a", "a1"), 2}, + {"a a1 ", 5, sr("a", "a1", ""), 0}, + } + + for i, r := range ret { + ret, idx := SplitSegment([]rune(r.Line), r.Pos) + test.Equal(rs(ret), rs(r.Segments), fmt.Errorf("%v", i)) + test.Equal(idx, r.Idx, fmt.Errorf("%v", i)) + } +} + +type Tree struct { + Name string + Children []Tree +} + +func TestSegmentCompleter(t *testing.T) { + defer test.New(t) + + tree := Tree{"", []Tree{ + {"a", []Tree{ + {"a1", []Tree{ + {"a11", nil}, + {"a12", nil}, + }}, + {"a2", []Tree{ + {"a21", nil}, + }}, + }}, + {"b", nil}, + }} + s := SegmentFunc(func(ret [][]rune, n int) [][]rune { + + tree := tree + main: + for level := 0; level < len(ret); { + name := string(ret[level]) + for _, t := range tree.Children { + if t.Name == name { + tree = t + level++ + continue main + } + } + ret = make([][]rune, len(tree.Children)) + for idx, r := range tree.Children { + ret[idx] = []rune(r.Name) + } + + return ret + } + + logex.Info(rs(ret), n, tree) + return [][]rune{[]rune(tree.Name)} + }) + + // a + // |- a1 + // |--- a11 + // |--- a12 + // |- a2 + // |--- a21 + // b + ret := []struct { + Line string + Pos int + Ret [][]rune + Share int + }{ + {"", 0, sr("a", "b"), 0}, + {"a", 1, sr(""), 1}, + {"a ", 2, sr("a1", "a2"), 0}, + {"a a", 3, sr("1", "2"), 1}, + {"a a1", 4, sr(""), 2}, + {"a a1 ", 5, sr("a11", "a12"), 0}, + {"a a1 a", 6, sr("11", "12"), 1}, + {"a a1 a1", 7, sr("1", "2"), 2}, + {"a a1 a11", 8, sr(""), 3}, + } + for i, r := range ret { + newLine, length := s.Do([]rune(r.Line), r.Pos) + test.Equal(newLine, r.Ret, fmt.Errorf("%v", i)) + test.Equal(length, r.Share, fmt.Errorf("%v", i)) + } +}