forked from mirror/readline
add auto complete
This commit is contained in:
parent
28ad744d6b
commit
04f86e9c53
2
char.go
2
char.go
|
@ -7,7 +7,7 @@ const (
|
||||||
CharDelete = 4
|
CharDelete = 4
|
||||||
CharLineEnd = 5
|
CharLineEnd = 5
|
||||||
CharForward = 6
|
CharForward = 6
|
||||||
CharCannel = 7
|
CharCancel = 7
|
||||||
CharCtrlH = 8
|
CharCtrlH = 8
|
||||||
CharTab = 9
|
CharTab = 9
|
||||||
CharCtrlJ = 10
|
CharCtrlJ = 10
|
||||||
|
|
|
@ -0,0 +1,249 @@
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type opCompleter struct {
|
||||||
|
w io.Writer
|
||||||
|
op *Operation
|
||||||
|
ac AutoCompleter
|
||||||
|
|
||||||
|
inCompleteMode bool
|
||||||
|
inSelectMode bool
|
||||||
|
candicate [][]rune
|
||||||
|
candicateSource []rune
|
||||||
|
candicateOff int
|
||||||
|
candicateChoise int
|
||||||
|
candicateColNum int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOpCompleter(w io.Writer, op *Operation) *opCompleter {
|
||||||
|
return &opCompleter{
|
||||||
|
w: w,
|
||||||
|
op: op,
|
||||||
|
ac: op.cfg.AutoComplete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) doSelect() {
|
||||||
|
if len(o.candicate) == 1 {
|
||||||
|
o.op.buf.WriteRunes(o.candicate[0])
|
||||||
|
o.ExitCompleteMode(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.nextCandicate(1)
|
||||||
|
o.CompleteRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) nextCandicate(i int) {
|
||||||
|
o.candicateChoise += i
|
||||||
|
o.candicateChoise = o.candicateChoise % len(o.candicate)
|
||||||
|
if o.candicateChoise < 0 {
|
||||||
|
o.candicateChoise = len(o.candicate) + o.candicateChoise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) OnComplete() {
|
||||||
|
if o.IsInCompleteSelectMode() {
|
||||||
|
o.doSelect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := o.op.buf
|
||||||
|
rs := buf.Runes()
|
||||||
|
|
||||||
|
if o.IsInCompleteMode() && EqualRunes(rs, o.candicateSource) {
|
||||||
|
o.EnterCompleteSelectMode()
|
||||||
|
o.doSelect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.ExitCompleteSelectMode()
|
||||||
|
o.candicateSource = rs
|
||||||
|
newLines, offset := o.ac.Do(rs, buf.idx)
|
||||||
|
if len(newLines) == 0 {
|
||||||
|
o.ExitCompleteMode(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// only Aggregate candicates in non-complete mode
|
||||||
|
if !o.IsInCompleteMode() {
|
||||||
|
if len(newLines) == 1 {
|
||||||
|
buf.WriteRunes(newLines[0])
|
||||||
|
o.ExitCompleteMode(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
same, size := AggRunes(newLines)
|
||||||
|
if size > 0 {
|
||||||
|
buf.WriteRunes(same)
|
||||||
|
o.ExitCompleteMode(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.EnterCompleteMode(offset, newLines)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
o.op.buf.WriteRunes(o.op.candicate[o.op.candicateChoise])
|
||||||
|
o.ExitCompleteMode(false)
|
||||||
|
case CharLineStart:
|
||||||
|
num := o.candicateChoise % o.candicateColNum
|
||||||
|
o.nextCandicate(-num)
|
||||||
|
case CharLineEnd:
|
||||||
|
num := o.candicateColNum - o.candicateChoise%o.candicateColNum - 1
|
||||||
|
o.candicateChoise += num
|
||||||
|
if o.candicateChoise >= len(o.candicate) {
|
||||||
|
o.candicateChoise = len(o.candicate) - 1
|
||||||
|
}
|
||||||
|
case CharBackspace:
|
||||||
|
o.ExitCompleteSelectMode()
|
||||||
|
next = false
|
||||||
|
case CharTab, CharForward:
|
||||||
|
o.doSelect()
|
||||||
|
case CharCancel, CharInterrupt:
|
||||||
|
o.ExitCompleteMode(true)
|
||||||
|
next = false
|
||||||
|
case CharNext:
|
||||||
|
tmpChoise := o.candicateChoise + o.candicateColNum
|
||||||
|
if tmpChoise >= o.getMatrixSize() {
|
||||||
|
tmpChoise -= o.getMatrixSize()
|
||||||
|
} else if tmpChoise >= len(o.candicate) {
|
||||||
|
tmpChoise += o.candicateColNum
|
||||||
|
tmpChoise -= o.getMatrixSize()
|
||||||
|
}
|
||||||
|
o.candicateChoise = tmpChoise
|
||||||
|
case CharBackward:
|
||||||
|
o.nextCandicate(-1)
|
||||||
|
case CharPrev:
|
||||||
|
tmpChoise := o.candicateChoise - o.candicateColNum
|
||||||
|
if tmpChoise < 0 {
|
||||||
|
tmpChoise += o.getMatrixSize()
|
||||||
|
if tmpChoise >= len(o.candicate) {
|
||||||
|
tmpChoise -= o.candicateColNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.candicateChoise = tmpChoise
|
||||||
|
default:
|
||||||
|
next = false
|
||||||
|
}
|
||||||
|
if next {
|
||||||
|
o.CompleteRefresh()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) getMatrixSize() int {
|
||||||
|
line := len(o.candicate) / o.candicateColNum
|
||||||
|
if len(o.candicate)%o.candicateColNum != 0 {
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
return line * o.candicateColNum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) CompleteRefresh() {
|
||||||
|
if !o.inCompleteMode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lineCnt := o.op.buf.CursorLineCount()
|
||||||
|
colWidth := 0
|
||||||
|
for _, c := range o.candicate {
|
||||||
|
w := RunesWidth(c)
|
||||||
|
if w > colWidth {
|
||||||
|
colWidth = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colNum := getWidth() / (colWidth + o.candicateOff + 2)
|
||||||
|
o.candicateColNum = colNum
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||||
|
same := o.op.buf.RuneSlice(-o.candicateOff)
|
||||||
|
colIdx := 0
|
||||||
|
lines := 1
|
||||||
|
buf.WriteString("\033[J")
|
||||||
|
for idx, c := range o.candicate {
|
||||||
|
inSelect := idx == o.candicateChoise && o.IsInCompleteSelectMode()
|
||||||
|
if inSelect {
|
||||||
|
buf.WriteString("\033[30;47m")
|
||||||
|
}
|
||||||
|
buf.WriteString(string(same))
|
||||||
|
buf.WriteString(string(c))
|
||||||
|
buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c)))
|
||||||
|
if inSelect {
|
||||||
|
buf.WriteString("\033[0m")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(" ")
|
||||||
|
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())
|
||||||
|
o.w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) aggCandicate(candicate [][]rune) int {
|
||||||
|
offset := 0
|
||||||
|
for i := 0; i < len(candicate[0]); i++ {
|
||||||
|
for j := 0; j < len(candicate)-1; j++ {
|
||||||
|
if i > len(candicate[j]) {
|
||||||
|
goto aggregate
|
||||||
|
}
|
||||||
|
if candicate[j][i] != candicate[j+1][i] {
|
||||||
|
goto aggregate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = i
|
||||||
|
}
|
||||||
|
aggregate:
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) EnterCompleteSelectMode() {
|
||||||
|
o.inSelectMode = true
|
||||||
|
o.candicateChoise = -1
|
||||||
|
o.CompleteRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) EnterCompleteMode(offset int, candicate [][]rune) {
|
||||||
|
o.inCompleteMode = true
|
||||||
|
o.candicate = candicate
|
||||||
|
o.candicateOff = offset
|
||||||
|
o.CompleteRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) ExitCompleteSelectMode() {
|
||||||
|
o.inSelectMode = false
|
||||||
|
o.candicate = nil
|
||||||
|
o.candicateChoise = -1
|
||||||
|
o.candicateOff = -1
|
||||||
|
o.candicateSource = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opCompleter) ExitCompleteMode(revent bool) {
|
||||||
|
o.inCompleteMode = false
|
||||||
|
o.ExitCompleteSelectMode()
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
|
@ -16,10 +18,30 @@ bye: quit
|
||||||
`[1:])
|
`[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Completer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Completer) Do(line []rune, pos int) (newLine [][]rune, off int) {
|
||||||
|
list := [][]rune{
|
||||||
|
[]rune("sayhello"), []rune("help"), []rune("bye"),
|
||||||
|
}
|
||||||
|
for i := 0; i <= 100; i++ {
|
||||||
|
list = append(list, []rune(fmt.Sprintf("com%d", i)))
|
||||||
|
}
|
||||||
|
line = line[:pos]
|
||||||
|
for _, r := range list {
|
||||||
|
if strings.HasPrefix(string(r), string(line)) {
|
||||||
|
newLine = append(newLine, r[len(line):])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newLine, len(line)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
l, err := readline.NewEx(&readline.Config{
|
l, err := readline.NewEx(&readline.Config{
|
||||||
Prompt: "\033[31m»\033[0m ",
|
Prompt: "\033[31m»\033[0m ",
|
||||||
HistoryFile: "/tmp/readline.tmp",
|
HistoryFile: "/tmp/readline.tmp",
|
||||||
|
AutoComplete: new(Completer),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
60
operation.go
60
operation.go
|
@ -13,6 +13,7 @@ type Operation struct {
|
||||||
|
|
||||||
*opHistory
|
*opHistory
|
||||||
*opSearch
|
*opSearch
|
||||||
|
*opCompleter
|
||||||
}
|
}
|
||||||
|
|
||||||
type wrapWriter struct {
|
type wrapWriter struct {
|
||||||
|
@ -31,6 +32,9 @@ func (w *wrapWriter) Write(b []byte) (int, error) {
|
||||||
if w.r.IsSearchMode() {
|
if w.r.IsSearchMode() {
|
||||||
w.r.SearchRefresh(-1)
|
w.r.SearchRefresh(-1)
|
||||||
}
|
}
|
||||||
|
if w.r.IsInCompleteMode() {
|
||||||
|
w.r.CompleteRefresh()
|
||||||
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +47,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
opHistory: newOpHistory(cfg.HistoryFile),
|
opHistory: newOpHistory(cfg.HistoryFile),
|
||||||
}
|
}
|
||||||
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
||||||
|
op.opCompleter = newOpCompleter(op.buf.w, op)
|
||||||
go op.ioloop()
|
go op.ioloop()
|
||||||
return op
|
return op
|
||||||
}
|
}
|
||||||
|
@ -50,17 +55,41 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
func (o *Operation) ioloop() {
|
func (o *Operation) ioloop() {
|
||||||
for {
|
for {
|
||||||
keepInSearchMode := false
|
keepInSearchMode := false
|
||||||
|
keepInCompleteMode := false
|
||||||
r := o.t.ReadRune()
|
r := o.t.ReadRune()
|
||||||
|
|
||||||
|
if o.IsInCompleteSelectMode() {
|
||||||
|
keepInCompleteMode = o.HandleCompleteSelect(r)
|
||||||
|
if keepInCompleteMode {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
o.buf.Refresh()
|
||||||
switch r {
|
switch r {
|
||||||
case CharCannel:
|
case CharInterrupt, CharEnter, CharCtrlJ:
|
||||||
|
o.t.KickRead()
|
||||||
|
fallthrough
|
||||||
|
case CharCancel:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case CharCancel:
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
o.ExitSearchMode(true)
|
o.ExitSearchMode(true)
|
||||||
o.buf.Refresh()
|
o.buf.Refresh()
|
||||||
}
|
}
|
||||||
|
if o.IsInCompleteMode() {
|
||||||
|
o.ExitCompleteMode(true)
|
||||||
|
o.buf.Refresh()
|
||||||
|
}
|
||||||
case CharTab:
|
case CharTab:
|
||||||
if o.cfg.AutoComplete == nil {
|
if o.opCompleter == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
o.OnComplete()
|
||||||
|
keepInCompleteMode = true
|
||||||
case CharBckSearch:
|
case CharBckSearch:
|
||||||
o.SearchMode(S_DIR_BCK)
|
o.SearchMode(S_DIR_BCK)
|
||||||
keepInSearchMode = true
|
keepInSearchMode = true
|
||||||
|
@ -69,6 +98,7 @@ func (o *Operation) ioloop() {
|
||||||
keepInSearchMode = true
|
keepInSearchMode = true
|
||||||
case CharKill:
|
case CharKill:
|
||||||
o.buf.Kill()
|
o.buf.Kill()
|
||||||
|
keepInCompleteMode = true
|
||||||
case MetaNext:
|
case MetaNext:
|
||||||
o.buf.MoveToNextWord()
|
o.buf.MoveToNextWord()
|
||||||
case CharTranspose:
|
case CharTranspose:
|
||||||
|
@ -87,8 +117,12 @@ func (o *Operation) ioloop() {
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
o.SearchBackspace()
|
o.SearchBackspace()
|
||||||
keepInSearchMode = true
|
keepInSearchMode = true
|
||||||
} else {
|
break
|
||||||
|
}
|
||||||
|
|
||||||
o.buf.Backspace()
|
o.buf.Backspace()
|
||||||
|
if o.IsInCompleteMode() {
|
||||||
|
o.OnComplete()
|
||||||
}
|
}
|
||||||
case MetaBackspace, CharCtrlW:
|
case MetaBackspace, CharCtrlW:
|
||||||
o.buf.BackEscapeWord()
|
o.buf.BackEscapeWord()
|
||||||
|
@ -122,6 +156,12 @@ func (o *Operation) ioloop() {
|
||||||
o.ExitSearchMode(true)
|
o.ExitSearchMode(true)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if o.IsInCompleteMode() {
|
||||||
|
o.t.KickRead()
|
||||||
|
o.ExitCompleteMode(true)
|
||||||
|
o.buf.Refresh()
|
||||||
|
break
|
||||||
|
}
|
||||||
o.buf.MoveToLineEnd()
|
o.buf.MoveToLineEnd()
|
||||||
o.buf.Refresh()
|
o.buf.Refresh()
|
||||||
o.buf.WriteString("^C\n")
|
o.buf.WriteString("^C\n")
|
||||||
|
@ -130,13 +170,25 @@ func (o *Operation) ioloop() {
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
o.SearchChar(r)
|
o.SearchChar(r)
|
||||||
keepInSearchMode = true
|
keepInSearchMode = true
|
||||||
} else {
|
break
|
||||||
|
}
|
||||||
o.buf.WriteRune(r)
|
o.buf.WriteRune(r)
|
||||||
|
if o.IsInCompleteMode() {
|
||||||
|
o.OnComplete()
|
||||||
|
keepInCompleteMode = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !keepInSearchMode && o.IsSearchMode() {
|
if !keepInSearchMode && o.IsSearchMode() {
|
||||||
o.ExitSearchMode(false)
|
o.ExitSearchMode(false)
|
||||||
o.buf.Refresh()
|
o.buf.Refresh()
|
||||||
|
} else if o.IsInCompleteMode() {
|
||||||
|
if !keepInCompleteMode {
|
||||||
|
o.ExitCompleteMode(false)
|
||||||
|
o.buf.Refresh()
|
||||||
|
} else {
|
||||||
|
o.buf.Refresh()
|
||||||
|
o.CompleteRefresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !o.IsSearchMode() {
|
if !o.IsSearchMode() {
|
||||||
o.UpdateHistory(o.buf.Runes(), false)
|
o.UpdateHistory(o.buf.Runes(), false)
|
||||||
|
|
|
@ -8,7 +8,11 @@ type Instance struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutoCompleter interface {
|
type AutoCompleter interface {
|
||||||
Do(line []rune, pos int) (newLine []rune, newPos int, ok bool)
|
Do(line []rune, pos int) (newLine [][]rune, offset int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutoCompleteHinter interface {
|
||||||
|
Hint(line []rune, pos int) ([]rune, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
17
runebuf.go
17
runebuf.go
|
@ -28,8 +28,21 @@ func (r *RuneBuffer) PromptLen() int {
|
||||||
return RunesWidth(RunesColorFilter(r.prompt))
|
return RunesWidth(RunesColorFilter(r.prompt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) RuneSlice(i int) []rune {
|
||||||
|
if i > 0 {
|
||||||
|
rs := make([]rune, i)
|
||||||
|
copy(rs, r.buf[r.idx:r.idx+i])
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
rs := make([]rune, -i)
|
||||||
|
copy(rs, r.buf[r.idx+i:r.idx])
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Runes() []rune {
|
func (r *RuneBuffer) Runes() []rune {
|
||||||
return r.buf
|
newr := make([]rune, len(r.buf))
|
||||||
|
copy(newr, r.buf)
|
||||||
|
return newr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Pos() int {
|
func (r *RuneBuffer) Pos() int {
|
||||||
|
@ -67,7 +80,7 @@ func (r *RuneBuffer) WriteRune(s rune) {
|
||||||
func (r *RuneBuffer) WriteRunes(s []rune) {
|
func (r *RuneBuffer) WriteRunes(s []rune) {
|
||||||
tail := append(s, r.buf[r.idx:]...)
|
tail := append(s, r.buf[r.idx:]...)
|
||||||
r.buf = append(r.buf[:r.idx], tail...)
|
r.buf = append(r.buf[:r.idx], tail...)
|
||||||
r.idx++
|
r.idx += len(s)
|
||||||
r.Refresh()
|
r.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
utils.go
42
utils.go
|
@ -228,3 +228,45 @@ func RunesWidth(r []rune) (length int) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AggRunes(candicate [][]rune) (same []rune, size int) {
|
||||||
|
for i := 0; i < len(candicate[0]); i++ {
|
||||||
|
for j := 0; j < len(candicate)-1; j++ {
|
||||||
|
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
|
||||||
|
goto aggregate
|
||||||
|
}
|
||||||
|
if candicate[j][i] != candicate[j+1][i] {
|
||||||
|
goto aggregate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size = i + 1
|
||||||
|
}
|
||||||
|
aggregate:
|
||||||
|
if size > 0 {
|
||||||
|
same = CopyRunes(candicate[0][:size])
|
||||||
|
for i := 0; i < len(candicate); i++ {
|
||||||
|
n := CopyRunes(candicate[i])
|
||||||
|
copy(n, n[size:])
|
||||||
|
candicate[i] = n[:len(n)-size]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyRunes(r []rune) []rune {
|
||||||
|
n := make([]rune, len(r))
|
||||||
|
copy(n, r)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func EqualRunes(r, r2 []rune) bool {
|
||||||
|
if len(r) != len(r2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for idx := range r {
|
||||||
|
if r[idx] != r2[idx] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
type Twidth struct {
|
type twidth struct {
|
||||||
r []rune
|
r []rune
|
||||||
length int
|
length int
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuneWidth(t *testing.T) {
|
func TestRuneWidth(t *testing.T) {
|
||||||
runes := []Twidth{
|
runes := []twidth{
|
||||||
{[]rune("☭"), 1},
|
{[]rune("☭"), 1},
|
||||||
{[]rune("a"), 1},
|
{[]rune("a"), 1},
|
||||||
{[]rune("你"), 2},
|
{[]rune("你"), 2},
|
||||||
|
@ -20,3 +23,46 @@ func TestRuneWidth(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tagg struct {
|
||||||
|
r [][]rune
|
||||||
|
e [][]rune
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAggRunes(t *testing.T) {
|
||||||
|
runes := []tagg{
|
||||||
|
{
|
||||||
|
[][]rune{[]rune("ab"), []rune("a"), []rune("abc")},
|
||||||
|
[][]rune{[]rune("b"), []rune(""), []rune("bc")},
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")},
|
||||||
|
[][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")},
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||||
|
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")},
|
||||||
|
[][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, r := range runes {
|
||||||
|
same, off := AggRunes(r.r)
|
||||||
|
if off != r.length {
|
||||||
|
t.Fatal("result not expect", off)
|
||||||
|
}
|
||||||
|
if len(same) != off {
|
||||||
|
t.Fatal("result not expect", same)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(r.r, r.e) {
|
||||||
|
t.Fatal("result not expect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue