mirror of https://github.com/chzyer/readline.git
support double-width-char & colorful prompt
This commit is contained in:
parent
af66dc48f7
commit
f179b24304
|
@ -18,7 +18,7 @@ bye: quit
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
l, err := readline.NewEx(&readline.Config{
|
l, err := readline.NewEx(&readline.Config{
|
||||||
Prompt: "home » ",
|
Prompt: "home \033[31m»\033[0m ",
|
||||||
HistoryFile: "/tmp/readline.tmp",
|
HistoryFile: "/tmp/readline.tmp",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
28
runebuf.go
28
runebuf.go
|
@ -20,8 +20,12 @@ func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
|
||||||
return rb
|
return rb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) CurrentWidth(x int) int {
|
||||||
|
return RunesWidth(r.buf[:x])
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) PromptLen() int {
|
func (r *RuneBuffer) PromptLen() int {
|
||||||
return RunesWidth(r.prompt)
|
return RunesWidth(RunesColorFilter(r.prompt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Runes() []rune {
|
func (r *RuneBuffer) Runes() []rune {
|
||||||
|
@ -221,7 +225,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'}, len(r.buf)-r.idx))
|
buf.Write(bytes.Repeat([]byte{'\b'}, RunesWidth(r.buf[r.idx:])))
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
@ -248,29 +252,29 @@ func (r *RuneBuffer) Reset() []rune {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) calWidth(m int) int {
|
||||||
|
if m > 0 {
|
||||||
|
return RunesWidth(r.buf[r.idx : r.idx+m])
|
||||||
|
}
|
||||||
|
return RunesWidth(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) {
|
||||||
idx := r.idx
|
|
||||||
if end < start {
|
if end < start {
|
||||||
panic("end < start")
|
panic("end < start")
|
||||||
}
|
}
|
||||||
|
|
||||||
// goto start
|
// goto start
|
||||||
move := start - idx
|
move := start - r.idx
|
||||||
if move > 0 {
|
if move > 0 {
|
||||||
r.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))
|
r.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))
|
||||||
} else {
|
} else {
|
||||||
r.w.Write(bytes.Repeat([]byte("\b"), -move))
|
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
|
||||||
}
|
}
|
||||||
r.w.Write([]byte("\033[" + style))
|
r.w.Write([]byte("\033[" + style))
|
||||||
r.w.Write([]byte(string(r.buf[start:end])))
|
r.w.Write([]byte(string(r.buf[start:end])))
|
||||||
r.w.Write([]byte("\033[0m"))
|
r.w.Write([]byte("\033[0m"))
|
||||||
if move > 0 {
|
// TODO: move back
|
||||||
r.w.Write(bytes.Repeat([]byte("\b"), -move+(end-start)))
|
|
||||||
} else if -move < end-start {
|
|
||||||
r.w.Write(bytes.Repeat([]byte("\b"), -move))
|
|
||||||
} else {
|
|
||||||
r.w.Write([]byte(string(r.buf[end:r.idx])))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
|
func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
|
||||||
|
|
|
@ -121,6 +121,7 @@ func (o *opSearch) SearchRefresh(x int) {
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
x = o.buf.idx
|
x = o.buf.idx
|
||||||
}
|
}
|
||||||
|
x = o.buf.CurrentWidth(x)
|
||||||
x += o.buf.PromptLen()
|
x += o.buf.PromptLen()
|
||||||
x = x % getWidth()
|
x = x % getWidth()
|
||||||
|
|
||||||
|
|
69
utils.go
69
utils.go
|
@ -6,7 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
@ -128,17 +128,6 @@ func LineCount(w int) int {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunesWidth(r []rune) (length int) {
|
|
||||||
for i := 0; i < len(r); i++ {
|
|
||||||
if utf8.RuneLen(r[i]) > 3 {
|
|
||||||
length += 2
|
|
||||||
} else {
|
|
||||||
length += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunesIndexBck(r, sub []rune) int {
|
func RunesIndexBck(r, sub []rune) int {
|
||||||
for i := len(r) - len(sub); i >= 0; i-- {
|
for i := len(r) - len(sub); i >= 0; i-- {
|
||||||
found := true
|
found := true
|
||||||
|
@ -183,3 +172,59 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import "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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue