add runes package

This commit is contained in:
Cheney 2015-10-04 21:56:34 +08:00
parent bfa8c1dfdb
commit 55809b401d
8 changed files with 242 additions and 224 deletions

View File

@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"github.com/chzyer/readline/runes"
) )
type AutoCompleter interface { type AutoCompleter interface {
@ -65,7 +67,7 @@ func (o *opCompleter) OnComplete() {
buf := o.op.buf buf := o.op.buf
rs := buf.Runes() rs := buf.Runes()
if o.IsInCompleteMode() && RunesEqual(rs, o.candidateSource) { if o.IsInCompleteMode() && runes.Equal(rs, o.candidateSource) {
o.EnterCompleteSelectMode() o.EnterCompleteSelectMode()
o.doSelect() o.doSelect()
return return
@ -176,7 +178,7 @@ func (o *opCompleter) CompleteRefresh() {
lineCnt := o.op.buf.CursorLineCount() lineCnt := o.op.buf.CursorLineCount()
colWidth := 0 colWidth := 0
for _, c := range o.candidate { for _, c := range o.candidate {
w := RunesWidth(c) w := runes.WidthAll(c)
if w > colWidth { if w > colWidth {
colWidth = w colWidth = w
} }

View File

@ -1,5 +1,7 @@
package readline package readline
import "github.com/chzyer/readline/runes"
type PrefixCompleter struct { type PrefixCompleter struct {
Name []rune Name []rune
Children []*PrefixCompleter Children []*PrefixCompleter
@ -25,14 +27,14 @@ func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int
var lineCompleter *PrefixCompleter var lineCompleter *PrefixCompleter
for _, child := range p.Children { for _, child := range p.Children {
if len(line) >= len(child.Name) { if len(line) >= len(child.Name) {
if RunesHasPrefix(line, child.Name) { if runes.HasPrefix(line, child.Name) {
newLine = append(newLine, child.Name) newLine = append(newLine, child.Name)
offset = len(child.Name) offset = len(child.Name)
lineCompleter = child lineCompleter = child
goNext = true goNext = true
} }
} else { } else {
if RunesHasPrefix(child.Name, line) { if runes.HasPrefix(child.Name, line) {
newLine = append(newLine, child.Name[len(line):]) newLine = append(newLine, child.Name[len(line):])
offset = len(line) offset = len(line)
lineCompleter = child lineCompleter = child

View File

@ -5,6 +5,8 @@ import (
"container/list" "container/list"
"os" "os"
"strings" "strings"
"github.com/chzyer/readline/runes"
) )
type hisItem struct { type hisItem struct {
@ -110,7 +112,7 @@ func (o *opHistory) FindHistoryBck(isNewSearch bool, rs []rune, start int) (int,
item = item[:start] item = item[:start]
} }
} }
idx := RunesIndexBck(item, rs) idx := runes.IndexAllBck(item, rs)
if idx < 0 { if idx < 0 {
continue continue
} }
@ -135,7 +137,7 @@ func (o *opHistory) FindHistoryFwd(isNewSearch bool, rs []rune, start int) (int,
continue continue
} }
} }
idx := RunesIndex(item, rs) idx := runes.IndexAll(item, rs)
if idx < 0 { if idx < 0 {
continue continue
} }
@ -187,7 +189,7 @@ func (o *opHistory) NewHistory(current []rune) {
prev := back.Prev() prev := back.Prev()
if prev != nil { if prev != nil {
use := o.showItem(o.current.Value.(*hisItem)) use := o.showItem(o.current.Value.(*hisItem))
if RunesEqual(use, prev.Value.(*hisItem).Source) { if runes.Equal(use, prev.Value.(*hisItem).Source) {
o.current = o.history.Back() o.current = o.history.Back()
o.current.Value.(*hisItem).Clean() o.current.Value.(*hisItem).Clean()
o.historyVer++ o.historyVer++

View File

@ -3,6 +3,8 @@ package readline
import ( import (
"bytes" "bytes"
"io" "io"
"github.com/chzyer/readline/runes"
) )
type runeBufferBck struct { type runeBufferBck struct {
@ -44,11 +46,11 @@ func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
} }
func (r *RuneBuffer) CurrentWidth(x int) int { func (r *RuneBuffer) CurrentWidth(x int) int {
return RunesWidth(r.buf[:x]) return runes.WidthAll(r.buf[:x])
} }
func (r *RuneBuffer) PromptLen() int { func (r *RuneBuffer) PromptLen() int {
return RunesWidth(RunesColorFilter(r.prompt)) return runes.WidthAll(runes.ColorFilter(r.prompt))
} }
func (r *RuneBuffer) RuneSlice(i int) []rune { func (r *RuneBuffer) RuneSlice(i int) []rune {
@ -267,7 +269,7 @@ func (r *RuneBuffer) MoveToLineEnd() {
} }
func (r *RuneBuffer) LineCount() int { func (r *RuneBuffer) LineCount() int {
return LineCount(RunesWidth(r.buf) + r.PromptLen()) return LineCount(runes.WidthAll(r.buf) + r.PromptLen())
} }
func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
@ -300,7 +302,7 @@ func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
} }
func (r *RuneBuffer) IdxLine() int { func (r *RuneBuffer) IdxLine() int {
totalWidth := RunesWidth(r.buf[:r.idx]) + r.PromptLen() totalWidth := runes.WidthAll(r.buf[:r.idx]) + r.PromptLen()
w := getWidth() w := getWidth()
if w == 0 { if w == 0 {
return 0 return 0
@ -336,7 +338,7 @@ func (r *RuneBuffer) output() []byte {
buf.WriteString(string(r.prompt)) buf.WriteString(string(r.prompt))
buf.Write([]byte(string(r.buf))) buf.Write([]byte(string(r.buf)))
if len(r.buf) > r.idx { if len(r.buf) > r.idx {
buf.Write(bytes.Repeat([]byte{'\b'}, RunesWidth(r.buf[r.idx:]))) buf.Write(runes.Backspace(r.buf[r.idx:]))
} }
return buf.Bytes() return buf.Bytes()
} }
@ -350,9 +352,9 @@ func (r *RuneBuffer) Reset() []rune {
func (r *RuneBuffer) calWidth(m int) int { func (r *RuneBuffer) calWidth(m int) int {
if m > 0 { if m > 0 {
return RunesWidth(r.buf[r.idx : r.idx+m]) return runes.WidthAll(r.buf[r.idx : r.idx+m])
} }
return RunesWidth(r.buf[r.idx+m : r.idx]) return runes.WidthAll(r.buf[r.idx+m : r.idx])
} }
func (r *RuneBuffer) SetStyle(start, end int, style string) { func (r *RuneBuffer) SetStyle(start, end int, style string) {

152
runes/runes.go Normal file
View File

@ -0,0 +1,152 @@
package runes
import (
"bytes"
"unicode"
)
func Equal(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
// Search in runes from end to front
func IndexAllBck(r, sub []rune) int {
for i := len(r) - len(sub); i >= 0; i-- {
found := true
for j := 0; j < len(sub); j++ {
if r[i+j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
return -1
}
// Search in runes from front to end
func IndexAll(r, sub []rune) int {
for i := 0; i < len(r); i++ {
found := true
if len(r[i:]) < len(sub) {
return -1
}
for j := 0; j < len(sub); j++ {
if r[i+j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
return -1
}
func Index(r rune, rs []rune) int {
for i := 0; i < len(rs); i++ {
if rs[i] == r {
return i
}
}
return -1
}
func ColorFilter(r []rune) []rune {
newr := make([]rune, 0, len(r))
for pos := 0; pos < len(r); pos++ {
if r[pos] == '\033' && r[pos+1] == '[' {
idx := Index('m', r[pos+2:])
if idx == -1 {
continue
}
pos += idx + 2
continue
}
newr = append(newr, r[pos])
}
return newr
}
var zeroWidth = []*unicode.RangeTable{
unicode.Mn,
unicode.Me,
unicode.Cc,
unicode.Cf,
}
var doubleWidth = []*unicode.RangeTable{
unicode.Han,
unicode.Hangul,
unicode.Hiragana,
unicode.Katakana,
}
func Width(r rune) int {
if unicode.IsOneOf(zeroWidth, r) {
return 0
}
if unicode.IsOneOf(doubleWidth, r) {
return 2
}
return 1
}
func WidthAll(r []rune) (length int) {
for i := 0; i < len(r); i++ {
length += Width(r[i])
}
return
}
func Backspace(r []rune) []byte {
return bytes.Repeat([]byte{'\b'}, WidthAll(r))
}
func Copy(r []rune) []rune {
n := make([]rune, len(r))
copy(n, r)
return n
}
func HasPrefix(r, prefix []rune) bool {
if len(r) < len(prefix) {
return false
}
return Equal(r[:len(prefix)], prefix)
}
func Aggregate(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 = Copy(candicate[0][:size])
for i := 0; i < len(candicate); i++ {
n := Copy(candicate[i])
copy(n, n[size:])
candicate[i] = n[:len(n)-size]
}
}
return
}

68
runes/runes_test.go Normal file
View File

@ -0,0 +1,68 @@
package runes
import (
"reflect"
"testing"
)
type twidth struct {
r []rune
length int
}
func TestRuneWidth(t *testing.T) {
runes := []twidth{
{[]rune("☭"), 1},
{[]rune("a"), 1},
{[]rune("你"), 2},
{ColorFilter([]rune("☭\033[13;1m你")), 3},
}
for _, r := range runes {
if w := WidthAll(r.r); w != r.length {
t.Fatal("result not expect", r.r, r.length, w)
}
}
}
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 := Aggregate(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")
}
}
}

143
utils.go
View File

@ -3,7 +3,6 @@ package readline
import ( import (
"strconv" "strconv"
"syscall" "syscall"
"unicode"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -72,18 +71,6 @@ func escapeKey(r rune) rune {
return r return r
} }
func RunesEqual(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
// calculate how many lines for N character // calculate how many lines for N character
func LineCount(w int) int { func LineCount(w int) int {
screenWidth := getWidth() screenWidth := getWidth()
@ -94,43 +81,6 @@ func LineCount(w int) int {
return r return r
} }
// Search in runes from end to front
func RunesIndexBck(r, sub []rune) int {
for i := len(r) - len(sub); i >= 0; i-- {
found := true
for j := 0; j < len(sub); j++ {
if r[i+j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
return -1
}
// Search in runes from front to end
func RunesIndex(r, sub []rune) int {
for i := 0; i < len(r); i++ {
found := true
if len(r[i:]) < len(sub) {
return -1
}
for j := 0; j < len(sub); j++ {
if r[i+j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
return -1
}
func IsWordBreak(i rune) bool { func IsWordBreak(i rune) bool {
if i >= 'a' && i <= 'z' { if i >= 'a' && i <= 'z' {
return false return false
@ -141,99 +91,6 @@ func IsWordBreak(i rune) bool {
return true return true
} }
var zeroWidth = []*unicode.RangeTable{
unicode.Mn,
unicode.Me,
unicode.Cc,
unicode.Cf,
}
var doubleWidth = []*unicode.RangeTable{
unicode.Han,
unicode.Hangul,
unicode.Hiragana,
unicode.Katakana,
}
func RuneIndex(r rune, rs []rune) int {
for i := 0; i < len(rs); i++ {
if rs[i] == r {
return i
}
}
return -1
}
func RunesColorFilter(r []rune) []rune {
newr := make([]rune, 0, len(r))
for pos := 0; pos < len(r); pos++ {
if r[pos] == '\033' && r[pos+1] == '[' {
idx := RuneIndex('m', r[pos+2:])
if idx == -1 {
continue
}
pos += idx + 2
continue
}
newr = append(newr, r[pos])
}
return newr
}
func RuneWidth(r rune) int {
if unicode.IsOneOf(zeroWidth, r) {
return 0
}
if unicode.IsOneOf(doubleWidth, r) {
return 2
}
return 1
}
func RunesWidth(r []rune) (length int) {
for i := 0; i < len(r); i++ {
length += RuneWidth(r[i])
}
return
}
func RunesAggregate(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 = RunesCopy(candicate[0][:size])
for i := 0; i < len(candicate); i++ {
n := RunesCopy(candicate[i])
copy(n, n[size:])
candicate[i] = n[:len(n)-size]
}
}
return
}
func RunesCopy(r []rune) []rune {
n := make([]rune, len(r))
copy(n, r)
return n
}
func RunesHasPrefix(r, prefix []rune) bool {
if len(r) < len(prefix) {
return false
}
return RunesEqual(r[:len(prefix)], prefix)
}
func GetInt(s []string, def int) int { func GetInt(s []string, def int) int {
if len(s) == 0 { if len(s) == 0 {
return def return def

View File

@ -1,68 +1 @@
package readline package readline
import (
"reflect"
"testing"
)
type twidth struct {
r []rune
length int
}
func TestRuneWidth(t *testing.T) {
runes := []twidth{
{[]rune("☭"), 1},
{[]rune("a"), 1},
{[]rune("你"), 2},
{RunesColorFilter([]rune("☭\033[13;1m你")), 3},
}
for _, r := range runes {
if w := RunesWidth(r.r); w != r.length {
t.Fatal("result not expect", r.r, r.length, w)
}
}
}
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 := RunesAggregate(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")
}
}
}