mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
pkg/util: Move the wcwidth utilities to a new package, and add more tests.
This addresses #944.
This commit is contained in:
parent
f55bc315c9
commit
3037f6c8bf
|
@ -3,7 +3,7 @@ package cli
|
|||
import (
|
||||
"github.com/elves/elvish/pkg/cli/term"
|
||||
"github.com/elves/elvish/pkg/ui"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// View model, calculated from State and used for rendering.
|
||||
|
@ -112,7 +112,7 @@ func truncateToHeight(b *term.Buffer, maxHeight int) {
|
|||
func styledWcswidth(t ui.Text) int {
|
||||
w := 0
|
||||
for _, seg := range t {
|
||||
w += util.Wcswidth(seg.Text)
|
||||
w += wcwidth.Of(seg.Text)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package cli
|
||||
|
||||
import "github.com/elves/elvish/pkg/util"
|
||||
import "github.com/elves/elvish/pkg/wcwidth"
|
||||
|
||||
// The number of lines the listing mode keeps between the current selected item
|
||||
// and the top and bottom edges of the window, unless the available height is
|
||||
|
@ -133,7 +133,7 @@ func maxWidth(items Items, padding, low, high int) int {
|
|||
for i := low; i < high && i < n; i++ {
|
||||
w := 0
|
||||
for _, seg := range items.Show(i) {
|
||||
w += util.Wcswidth(seg.Text)
|
||||
w += wcwidth.Of(seg.Text)
|
||||
}
|
||||
if width < w {
|
||||
width = w
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// Cell is an indivisible unit on the screen. It is not necessarily 1 column
|
||||
|
@ -23,7 +23,7 @@ type Pos struct {
|
|||
func CellsWidth(cs []Cell) int {
|
||||
w := 0
|
||||
for _, c := range cs {
|
||||
w += util.Wcswidth(c.Text)
|
||||
w += wcwidth.Of(c.Text)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ func (b *Buffer) TTYString() string {
|
|||
lastStyle = cell.Style
|
||||
}
|
||||
sb.WriteString(cell.Text)
|
||||
usedWidth += util.Wcswidth(cell.Text)
|
||||
usedWidth += wcwidth.Of(cell.Text)
|
||||
}
|
||||
if lastStyle != "" {
|
||||
sb.WriteString("\033[m")
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/elves/elvish/pkg/ui"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// BufferBuilder supports building of Buffer.
|
||||
|
@ -68,7 +68,7 @@ func (bb *BufferBuilder) appendLine() {
|
|||
func (bb *BufferBuilder) appendCell(c Cell) {
|
||||
n := len(bb.Lines)
|
||||
bb.Lines[n-1] = append(bb.Lines[n-1], c)
|
||||
bb.Col += util.Wcswidth(c.Text)
|
||||
bb.Col += wcwidth.Of(c.Text)
|
||||
}
|
||||
|
||||
// Newline starts a newline.
|
||||
|
@ -104,7 +104,7 @@ func (bb *BufferBuilder) WriteRuneSGR(r rune, style string) *BufferBuilder {
|
|||
c = Cell{"^" + string(r^0x40), style}
|
||||
}
|
||||
|
||||
if bb.Col+util.Wcswidth(c.Text) > bb.Width {
|
||||
if bb.Col+wcwidth.Of(c.Text) > bb.Width {
|
||||
bb.Newline()
|
||||
bb.appendCell(c)
|
||||
} else {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/elves/elvish/pkg/sys"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// Setup sets up the terminal so that it is suitable for the Reader and
|
||||
|
@ -53,7 +53,7 @@ func setupVT(out *os.File) error {
|
|||
LackEOL character. Otherwise, we are now in the next line and this is
|
||||
a no-op. The LackEOL character remains.
|
||||
*/
|
||||
s += fmt.Sprintf("\033[?7h%s%*s\r \r", lackEOL, width-util.Wcwidth(lackEOLRune), "")
|
||||
s += fmt.Sprintf("\033[?7h%s%*s\r \r", lackEOL, width-wcwidth.OfRune(lackEOLRune), "")
|
||||
|
||||
/*
|
||||
Turn off autowrap.
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
"github.com/elves/elvish/pkg/cli/term"
|
||||
"github.com/elves/elvish/pkg/ui"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// TextView is a Widget for displaying text, with support for vertical
|
||||
|
@ -68,7 +68,7 @@ func (w *textView) Render(width, height int) *term.Buffer {
|
|||
if i > first {
|
||||
bb.Newline()
|
||||
}
|
||||
bb.Write(util.TrimWcwidth(lines[i], textWidth))
|
||||
bb.Write(wcwidth.Trim(lines[i], textWidth))
|
||||
}
|
||||
buf := bb.Buffer()
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// Context is a range of text in a source code. It is typically used for
|
||||
|
@ -92,7 +92,7 @@ func (c *Context) ShowCompact(sourceIndent string) string {
|
|||
}
|
||||
desc := c.Name + ", " + c.lineRange() + " "
|
||||
// Extra indent so that following lines line up with the first line.
|
||||
descIndent := strings.Repeat(" ", util.Wcswidth(desc))
|
||||
descIndent := strings.Repeat(" ", wcwidth.Of(desc))
|
||||
return desc + c.relevantSource(sourceIndent+descIndent)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/elves/elvish/pkg/parse/parseutil"
|
||||
"github.com/elves/elvish/pkg/ui"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
//elvdoc:fn binding-table
|
||||
|
@ -333,8 +334,8 @@ func moveDotUp(buffer string, dot int) int {
|
|||
}
|
||||
prevEOL := sol - 1
|
||||
prevSOL := util.FindLastSOL(buffer[:prevEOL])
|
||||
width := util.Wcswidth(buffer[sol:dot])
|
||||
return prevSOL + len(util.TrimWcwidth(buffer[prevSOL:prevEOL], width))
|
||||
width := wcwidth.Of(buffer[sol:dot])
|
||||
return prevSOL + len(wcwidth.Trim(buffer[prevSOL:prevEOL], width))
|
||||
}
|
||||
|
||||
//elvdoc:fn move-dot-down
|
||||
|
@ -351,8 +352,8 @@ func moveDotDown(buffer string, dot int) int {
|
|||
nextSOL := eol + 1
|
||||
nextEOL := util.FindFirstEOL(buffer[nextSOL:]) + nextSOL
|
||||
sol := util.FindLastSOL(buffer[:dot])
|
||||
width := util.Wcswidth(buffer[sol:dot])
|
||||
return nextSOL + len(util.TrimWcwidth(buffer[nextSOL:nextEOL], width))
|
||||
width := wcwidth.Of(buffer[sol:dot])
|
||||
return nextSOL + len(wcwidth.Trim(buffer[nextSOL:nextEOL], width))
|
||||
}
|
||||
|
||||
// TODO(xiaq): Document the concepts of words, small words and alnum words.
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"github.com/elves/elvish/pkg/eval/vals"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// String operations.
|
||||
|
@ -260,8 +260,8 @@ func init() {
|
|||
"chr": chr,
|
||||
"base": base,
|
||||
|
||||
"wcswidth": util.Wcswidth,
|
||||
"-override-wcwidth": util.OverrideWcwidth,
|
||||
"wcswidth": wcwidth.Of,
|
||||
"-override-wcwidth": wcwidth.Override,
|
||||
|
||||
"has-prefix": strings.HasPrefix,
|
||||
"has-suffix": strings.HasSuffix,
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/elves/elvish/pkg/eval/vals"
|
||||
"github.com/elves/elvish/pkg/util"
|
||||
"github.com/elves/elvish/pkg/wcwidth"
|
||||
)
|
||||
|
||||
// Text contains of a list of styled Segments.
|
||||
|
@ -177,10 +177,10 @@ func (t Text) SplitByRune(r rune) []Text {
|
|||
func (t Text) TrimWcwidth(wmax int) Text {
|
||||
var newt Text
|
||||
for _, seg := range t {
|
||||
w := util.Wcswidth(seg.Text)
|
||||
w := wcwidth.Of(seg.Text)
|
||||
if w >= wmax {
|
||||
newt = append(newt,
|
||||
&Segment{seg.Style, util.TrimWcwidth(seg.Text, wmax)})
|
||||
&Segment{seg.Style, wcwidth.Trim(seg.Text, wmax)})
|
||||
break
|
||||
}
|
||||
wmax -= w
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var wcwidthTests = []struct {
|
||||
in rune
|
||||
wanted int
|
||||
}{
|
||||
{'\u0301', 0}, // Combining acute accent
|
||||
{'a', 1},
|
||||
{'Ω', 1},
|
||||
{'好', 2},
|
||||
{'か', 2},
|
||||
}
|
||||
|
||||
func TestWcwidth(t *testing.T) {
|
||||
for _, tt := range wcwidthTests {
|
||||
out := Wcwidth(tt.in)
|
||||
if out != tt.wanted {
|
||||
t.Errorf("wcwidth(%q) => %v, want %v", tt.in, out, tt.wanted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideWcwidth(t *testing.T) {
|
||||
r := '❱'
|
||||
oldw := Wcwidth(r)
|
||||
w := oldw + 1
|
||||
|
||||
OverrideWcwidth(r, w)
|
||||
if Wcwidth(r) != w {
|
||||
t.Errorf("Wcwidth(%q) != %d after OverrideWcwidth", r, w)
|
||||
}
|
||||
UnoverrideWcwidth(r)
|
||||
if Wcwidth(r) != oldw {
|
||||
t.Errorf("Wcwidth(%q) != %d after UnoverrideWcwidth", r, oldw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimWcwidth(t *testing.T) {
|
||||
if TrimWcwidth("abc", 2) != "ab" {
|
||||
t.Errorf("TrimWcwidth #1 fails")
|
||||
}
|
||||
if TrimWcwidth("你好", 3) != "你" {
|
||||
t.Errorf("TrimWcwidth #2 fails")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForceWcwidth(t *testing.T) {
|
||||
for i, c := range []struct {
|
||||
s string
|
||||
w int
|
||||
want string
|
||||
}{
|
||||
// Triming
|
||||
{"abc", 2, "ab"},
|
||||
{"你好", 2, "你"},
|
||||
// Padding
|
||||
{"abc", 4, "abc "},
|
||||
{"你好", 5, "你好 "},
|
||||
// Trimming and Padding
|
||||
{"你好", 3, "你 "},
|
||||
} {
|
||||
if got := ForceWcwidth(c.s, c.w); got != c.want {
|
||||
t.Errorf("ForceWcwidth #%d fails", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimEachLineWcwidth(t *testing.T) {
|
||||
if TrimEachLineWcwidth("abcdefg\n你好", 3) != "abc\n你" {
|
||||
t.Errorf("TestTrimEachLineWcwidth fails")
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
package util
|
||||
// Package wcwidth provides utilities for determining the column width of
|
||||
// characters when displayed on the terminal.
|
||||
package wcwidth
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
@ -65,8 +67,8 @@ func isCombining(r rune) bool {
|
|||
return i < n && r >= combining[i][0]
|
||||
}
|
||||
|
||||
// Wcwidth returns the width of a rune when displayed on the terminal.
|
||||
func Wcwidth(r rune) int {
|
||||
// OfRune returns the column width of a rune.
|
||||
func OfRune(r rune) int {
|
||||
if w, ok := wcwidthOverride[r]; ok {
|
||||
return w
|
||||
}
|
||||
|
@ -95,34 +97,33 @@ func Wcwidth(r rune) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
// OverrideWcwidth overrides the wcwidth of a rune to be a specific non-negative
|
||||
// value. OverrideWcwidth panics if w < 0.
|
||||
func OverrideWcwidth(r rune, w int) {
|
||||
// Override overrides the column width of a rune to be a specific non-negative
|
||||
// value. It panics if w < 0.
|
||||
func Override(r rune, w int) {
|
||||
if w < 0 {
|
||||
panic("negative width")
|
||||
}
|
||||
wcwidthOverride[r] = w
|
||||
}
|
||||
|
||||
// UnoverrideWcwidth removes the override of a rune.
|
||||
func UnoverrideWcwidth(r rune) {
|
||||
// Unoverride removes the column width override of a rune.
|
||||
func Unoverride(r rune) {
|
||||
delete(wcwidthOverride, r)
|
||||
}
|
||||
|
||||
// Wcswidth returns the width of a string when displayed on the terminal,
|
||||
// assuming no soft line breaks.
|
||||
func Wcswidth(s string) (w int) {
|
||||
// Of returns the column width of a string, assuming no soft line breaks.
|
||||
func Of(s string) (w int) {
|
||||
for _, r := range s {
|
||||
w += Wcwidth(r)
|
||||
w += OfRune(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TrimWcwidth trims the string s so that it has a width of at most wmax.
|
||||
func TrimWcwidth(s string, wmax int) string {
|
||||
// Trim trims the string s so that it has a column width of at most wmax.
|
||||
func Trim(s string, wmax int) string {
|
||||
w := 0
|
||||
for i, r := range s {
|
||||
w += Wcwidth(r)
|
||||
w += OfRune(r)
|
||||
if w > wmax {
|
||||
return s[:i]
|
||||
}
|
||||
|
@ -130,12 +131,11 @@ func TrimWcwidth(s string, wmax int) string {
|
|||
return s
|
||||
}
|
||||
|
||||
// ForceWcwidth forces the string s to the given display width by trimming and
|
||||
// padding.
|
||||
func ForceWcwidth(s string, width int) string {
|
||||
// Force forces the string s to the given column width by trimming and padding.
|
||||
func Force(s string, width int) string {
|
||||
w := 0
|
||||
for i, r := range s {
|
||||
w0 := Wcwidth(r)
|
||||
w0 := OfRune(r)
|
||||
w += w0
|
||||
if w > width {
|
||||
w -= w0
|
||||
|
@ -146,12 +146,12 @@ func ForceWcwidth(s string, width int) string {
|
|||
return s + strings.Repeat(" ", width-w)
|
||||
}
|
||||
|
||||
// TrimEachLineWcwidth trims each line of s so that it is no wider than the
|
||||
// specified width.
|
||||
func TrimEachLineWcwidth(s string, width int) string {
|
||||
// TrimEachLine trims each line of s so that it is no wider than the specified
|
||||
// width.
|
||||
func TrimEachLine(s string, width int) string {
|
||||
lines := strings.Split(s, "\n")
|
||||
for i := range lines {
|
||||
lines[i] = TrimWcwidth(lines[i], width)
|
||||
lines[i] = Trim(lines[i], width)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
71
pkg/wcwidth/wcwidth_test.go
Normal file
71
pkg/wcwidth/wcwidth_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package wcwidth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/pkg/tt"
|
||||
)
|
||||
|
||||
var Args = tt.Args
|
||||
|
||||
func TestOf(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("Of", Of), tt.Table{
|
||||
Args("\u0301").Rets(0), // Combining acute accent
|
||||
Args("a").Rets(1),
|
||||
Args("Ω").Rets(1),
|
||||
Args("好").Rets(2),
|
||||
Args("か").Rets(2),
|
||||
|
||||
Args("abc").Rets(3),
|
||||
Args("你好").Rets(4),
|
||||
})
|
||||
}
|
||||
|
||||
func TestOverride(t *testing.T) {
|
||||
r := '❱'
|
||||
oldw := OfRune(r)
|
||||
w := oldw + 1
|
||||
|
||||
Override(r, w)
|
||||
if OfRune(r) != w {
|
||||
t.Errorf("Wcwidth(%q) != %d after OverrideWcwidth", r, w)
|
||||
}
|
||||
Unoverride(r)
|
||||
if OfRune(r) != oldw {
|
||||
t.Errorf("Wcwidth(%q) != %d after UnoverrideWcwidth", r, oldw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrim(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("Trim", Trim), tt.Table{
|
||||
Args("abc", 1).Rets("a"),
|
||||
Args("abc", 2).Rets("ab"),
|
||||
Args("abc", 3).Rets("abc"),
|
||||
Args("abc", 4).Rets("abc"),
|
||||
|
||||
Args("你好", 1).Rets(""),
|
||||
Args("你好", 2).Rets("你"),
|
||||
Args("你好", 3).Rets("你"),
|
||||
Args("你好", 4).Rets("你好"),
|
||||
Args("你好", 5).Rets("你好"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestForce(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("Force", Force), tt.Table{
|
||||
// Triming
|
||||
Args("abc", 2).Rets("ab"),
|
||||
Args("你好", 2).Rets("你"),
|
||||
// Padding
|
||||
Args("abc", 4).Rets("abc "),
|
||||
Args("你好", 5).Rets("你好 "),
|
||||
// Trimming and Padding
|
||||
Args("你好", 3).Rets("你 "),
|
||||
})
|
||||
}
|
||||
|
||||
func TestTrimEachLine(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("TrimEachLine", TrimEachLine), tt.Table{
|
||||
Args("abcdefg\n你好", 3).Rets("abc\n你"),
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user