/
runewidth.go
142 lines (121 loc) · 3.58 KB
/
runewidth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package runewidth is a wrapper over github.com/mattn/go-runewidth which
// gives different treatment to certain runes with ambiguous width.
package runewidth
import runewidth "github.com/mattn/go-runewidth"
// Option is used to provide options.
type Option interface {
// set sets the provided option.
set(*options)
}
// options stores the provided options.
type options struct {
runeWidths map[rune]int
}
// newOptions create a new instance of options.
func newOptions() *options {
return &options{
runeWidths: map[rune]int{},
}
}
// option implements Option.
type option func(*options)
// set implements Option.set.
func (o option) set(opts *options) {
o(opts)
}
// CountAsWidth overrides the default behavior, counting the specified runes as
// the specified width. Can be provided multiple times to specify an override
// for multiple runes.
func CountAsWidth(r rune, width int) Option {
return option(func(opts *options) {
opts.runeWidths[r] = width
})
}
// RuneWidth returns the number of cells needed to draw r.
// Background in http://www.unicode.org/reports/tr11/.
//
// Treats runes used internally by termdash as single-cell (half-width) runes
// regardless of the locale. I.e. runes that are used to draw lines, boxes,
// indicate resize or text trimming was needed and runes used by the braille
// canvas.
//
// This should be safe, since even in locales where these runes have ambiguous
// width, we still place all the character content around them so they should
// have be half-width.
func RuneWidth(r rune, opts ...Option) int {
o := newOptions()
for _, opt := range opts {
opt.set(o)
}
if w, ok := o.runeWidths[r]; ok {
return w
}
if inTable(r, exceptions) {
return 1
}
return runewidth.RuneWidth(r)
}
// StringWidth is like RuneWidth, but returns the number of cells occupied by
// all the runes in the string.
func StringWidth(s string, opts ...Option) int {
var width int
for _, r := range []rune(s) {
width += RuneWidth(r, opts...)
}
return width
}
// inTable determines if the rune falls within the table.
// Copied from github.com/mattn/go-runewidth/blob/master/runewidth.go.
func inTable(r rune, t table) bool {
// func (t table) IncludesRune(r rune) bool {
if r < t[0].first {
return false
}
bot := 0
top := len(t) - 1
for top >= bot {
mid := (bot + top) >> 1
switch {
case t[mid].last < r:
bot = mid + 1
case t[mid].first > r:
top = mid - 1
default:
return true
}
}
return false
}
type interval struct {
first rune
last rune
}
type table []interval
// exceptions runes defined here are always considered to be half-width even if
// they might be ambiguous in some contexts.
var exceptions = table{
// Characters used by termdash to indicate text trim or scroll.
{0x2026, 0x2026},
{0x21c4, 0x21c4},
{0x21e7, 0x21e7},
{0x21e9, 0x21e9},
// Box drawing, used as line-styles.
// https://en.wikipedia.org/wiki/Box-drawing_character
{0x2500, 0x257F},
// Block elements used as sparks.
// https://en.wikipedia.org/wiki/Box-drawing_character
{0x2580, 0x258F},
}