parent
fe830a3d5a
commit
9f47b066db
@ -0,0 +1,26 @@ |
||||
go-runewidth |
||||
============ |
||||
|
||||
[](https://travis-ci.org/mattn/go-runewidth) |
||||
[](https://coveralls.io/r/mattn/go-runewidth?branch=HEAD) |
||||
[](http://godoc.org/github.com/mattn/go-runewidth) |
||||
|
||||
Provides functions to get fixed width of the character or string. |
||||
|
||||
Usage |
||||
----- |
||||
|
||||
```go |
||||
runewidth.StringWidth("ใคใฎใ โHIRO") == 12 |
||||
``` |
||||
|
||||
|
||||
Author |
||||
------ |
||||
|
||||
Yasuhiro Matsumoto |
||||
|
||||
License |
||||
------- |
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2013 |
@ -0,0 +1,464 @@ |
||||
package runewidth |
||||
|
||||
var EastAsianWidth = IsEastAsian() |
||||
var DefaultCondition = &Condition{EastAsianWidth} |
||||
|
||||
type interval struct { |
||||
first rune |
||||
last rune |
||||
} |
||||
|
||||
var combining = []interval{ |
||||
{0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, |
||||
{0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, |
||||
{0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, |
||||
{0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, |
||||
{0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, |
||||
{0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, |
||||
{0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, |
||||
{0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, |
||||
{0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, |
||||
{0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, |
||||
{0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, |
||||
{0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, |
||||
{0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, |
||||
{0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, |
||||
{0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, |
||||
{0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, |
||||
{0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, |
||||
{0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, |
||||
{0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, |
||||
{0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, |
||||
{0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, |
||||
{0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, |
||||
{0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, |
||||
{0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, |
||||
{0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, |
||||
{0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, |
||||
{0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, |
||||
{0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, |
||||
{0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, |
||||
{0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, |
||||
{0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, |
||||
{0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, |
||||
{0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, |
||||
{0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, |
||||
{0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, |
||||
{0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, |
||||
{0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, |
||||
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, |
||||
{0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, |
||||
{0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, |
||||
{0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, |
||||
{0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, |
||||
{0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, |
||||
{0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, |
||||
{0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, |
||||
{0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, |
||||
{0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, |
||||
{0xE0100, 0xE01EF}, |
||||
} |
||||
|
||||
type ctype int |
||||
|
||||
const ( |
||||
narrow ctype = iota |
||||
ambiguous |
||||
wide |
||||
halfwidth |
||||
fullwidth |
||||
neutral |
||||
) |
||||
|
||||
type intervalType struct { |
||||
first rune |
||||
last rune |
||||
ctype ctype |
||||
} |
||||
|
||||
var ctypes = []intervalType{ |
||||
{0x0020, 0x007E, narrow}, |
||||
{0x00A1, 0x00A1, ambiguous}, |
||||
{0x00A2, 0x00A3, narrow}, |
||||
{0x00A4, 0x00A4, ambiguous}, |
||||
{0x00A5, 0x00A6, narrow}, |
||||
{0x00A7, 0x00A8, ambiguous}, |
||||
{0x00AA, 0x00AA, ambiguous}, |
||||
{0x00AC, 0x00AC, narrow}, |
||||
{0x00AD, 0x00AE, ambiguous}, |
||||
{0x00AF, 0x00AF, narrow}, |
||||
{0x00B0, 0x00B4, ambiguous}, |
||||
{0x00B6, 0x00BA, ambiguous}, |
||||
{0x00BC, 0x00BF, ambiguous}, |
||||
{0x00C6, 0x00C6, ambiguous}, |
||||
{0x00D0, 0x00D0, ambiguous}, |
||||
{0x00D7, 0x00D8, ambiguous}, |
||||
{0x00DE, 0x00E1, ambiguous}, |
||||
{0x00E6, 0x00E6, ambiguous}, |
||||
{0x00E8, 0x00EA, ambiguous}, |
||||
{0x00EC, 0x00ED, ambiguous}, |
||||
{0x00F0, 0x00F0, ambiguous}, |
||||
{0x00F2, 0x00F3, ambiguous}, |
||||
{0x00F7, 0x00FA, ambiguous}, |
||||
{0x00FC, 0x00FC, ambiguous}, |
||||
{0x00FE, 0x00FE, ambiguous}, |
||||
{0x0101, 0x0101, ambiguous}, |
||||
{0x0111, 0x0111, ambiguous}, |
||||
{0x0113, 0x0113, ambiguous}, |
||||
{0x011B, 0x011B, ambiguous}, |
||||
{0x0126, 0x0127, ambiguous}, |
||||
{0x012B, 0x012B, ambiguous}, |
||||
{0x0131, 0x0133, ambiguous}, |
||||
{0x0138, 0x0138, ambiguous}, |
||||
{0x013F, 0x0142, ambiguous}, |
||||
{0x0144, 0x0144, ambiguous}, |
||||
{0x0148, 0x014B, ambiguous}, |
||||
{0x014D, 0x014D, ambiguous}, |
||||
{0x0152, 0x0153, ambiguous}, |
||||
{0x0166, 0x0167, ambiguous}, |
||||
{0x016B, 0x016B, ambiguous}, |
||||
{0x01CE, 0x01CE, ambiguous}, |
||||
{0x01D0, 0x01D0, ambiguous}, |
||||
{0x01D2, 0x01D2, ambiguous}, |
||||
{0x01D4, 0x01D4, ambiguous}, |
||||
{0x01D6, 0x01D6, ambiguous}, |
||||
{0x01D8, 0x01D8, ambiguous}, |
||||
{0x01DA, 0x01DA, ambiguous}, |
||||
{0x01DC, 0x01DC, ambiguous}, |
||||
{0x0251, 0x0251, ambiguous}, |
||||
{0x0261, 0x0261, ambiguous}, |
||||
{0x02C4, 0x02C4, ambiguous}, |
||||
{0x02C7, 0x02C7, ambiguous}, |
||||
{0x02C9, 0x02CB, ambiguous}, |
||||
{0x02CD, 0x02CD, ambiguous}, |
||||
{0x02D0, 0x02D0, ambiguous}, |
||||
{0x02D8, 0x02DB, ambiguous}, |
||||
{0x02DD, 0x02DD, ambiguous}, |
||||
{0x02DF, 0x02DF, ambiguous}, |
||||
{0x0300, 0x036F, ambiguous}, |
||||
{0x0391, 0x03A2, ambiguous}, |
||||
{0x03A3, 0x03A9, ambiguous}, |
||||
{0x03B1, 0x03C1, ambiguous}, |
||||
{0x03C3, 0x03C9, ambiguous}, |
||||
{0x0401, 0x0401, ambiguous}, |
||||
{0x0410, 0x044F, ambiguous}, |
||||
{0x0451, 0x0451, ambiguous}, |
||||
{0x1100, 0x115F, wide}, |
||||
{0x2010, 0x2010, ambiguous}, |
||||
{0x2013, 0x2016, ambiguous}, |
||||
{0x2018, 0x2019, ambiguous}, |
||||
{0x201C, 0x201D, ambiguous}, |
||||
{0x2020, 0x2022, ambiguous}, |
||||
{0x2024, 0x2027, ambiguous}, |
||||
{0x2030, 0x2030, ambiguous}, |
||||
{0x2032, 0x2033, ambiguous}, |
||||
{0x2035, 0x2035, ambiguous}, |
||||
{0x203B, 0x203B, ambiguous}, |
||||
{0x203E, 0x203E, ambiguous}, |
||||
{0x2074, 0x2074, ambiguous}, |
||||
{0x207F, 0x207F, ambiguous}, |
||||
{0x2081, 0x2084, ambiguous}, |
||||
{0x20A9, 0x20A9, halfwidth}, |
||||
{0x20AC, 0x20AC, ambiguous}, |
||||
{0x2103, 0x2103, ambiguous}, |
||||
{0x2105, 0x2105, ambiguous}, |
||||
{0x2109, 0x2109, ambiguous}, |
||||
{0x2113, 0x2113, ambiguous}, |
||||
{0x2116, 0x2116, ambiguous}, |
||||
{0x2121, 0x2122, ambiguous}, |
||||
{0x2126, 0x2126, ambiguous}, |
||||
{0x212B, 0x212B, ambiguous}, |
||||
{0x2153, 0x2154, ambiguous}, |
||||
{0x215B, 0x215E, ambiguous}, |
||||
{0x2160, 0x216B, ambiguous}, |
||||
{0x2170, 0x2179, ambiguous}, |
||||
{0x2189, 0x218A, ambiguous}, |
||||
{0x2190, 0x2199, ambiguous}, |
||||
{0x21B8, 0x21B9, ambiguous}, |
||||
{0x21D2, 0x21D2, ambiguous}, |
||||
{0x21D4, 0x21D4, ambiguous}, |
||||
{0x21E7, 0x21E7, ambiguous}, |
||||
{0x2200, 0x2200, ambiguous}, |
||||
{0x2202, 0x2203, ambiguous}, |
||||
{0x2207, 0x2208, ambiguous}, |
||||
{0x220B, 0x220B, ambiguous}, |
||||
{0x220F, 0x220F, ambiguous}, |
||||
{0x2211, 0x2211, ambiguous}, |
||||
{0x2215, 0x2215, ambiguous}, |
||||
{0x221A, 0x221A, ambiguous}, |
||||
{0x221D, 0x2220, ambiguous}, |
||||
{0x2223, 0x2223, ambiguous}, |
||||
{0x2225, 0x2225, ambiguous}, |
||||
{0x2227, 0x222C, ambiguous}, |
||||
{0x222E, 0x222E, ambiguous}, |
||||
{0x2234, 0x2237, ambiguous}, |
||||
{0x223C, 0x223D, ambiguous}, |
||||
{0x2248, 0x2248, ambiguous}, |
||||
{0x224C, 0x224C, ambiguous}, |
||||
{0x2252, 0x2252, ambiguous}, |
||||
{0x2260, 0x2261, ambiguous}, |
||||
{0x2264, 0x2267, ambiguous}, |
||||
{0x226A, 0x226B, ambiguous}, |
||||
{0x226E, 0x226F, ambiguous}, |
||||
{0x2282, 0x2283, ambiguous}, |
||||
{0x2286, 0x2287, ambiguous}, |
||||
{0x2295, 0x2295, ambiguous}, |
||||
{0x2299, 0x2299, ambiguous}, |
||||
{0x22A5, 0x22A5, ambiguous}, |
||||
{0x22BF, 0x22BF, ambiguous}, |
||||
{0x2312, 0x2312, ambiguous}, |
||||
{0x2329, 0x232A, wide}, |
||||
{0x2460, 0x24E9, ambiguous}, |
||||
{0x24EB, 0x254B, ambiguous}, |
||||
{0x2550, 0x2573, ambiguous}, |
||||
{0x2580, 0x258F, ambiguous}, |
||||
{0x2592, 0x2595, ambiguous}, |
||||
{0x25A0, 0x25A1, ambiguous}, |
||||
{0x25A3, 0x25A9, ambiguous}, |
||||
{0x25B2, 0x25B3, ambiguous}, |
||||
{0x25B6, 0x25B7, ambiguous}, |
||||
{0x25BC, 0x25BD, ambiguous}, |
||||
{0x25C0, 0x25C1, ambiguous}, |
||||
{0x25C6, 0x25C8, ambiguous}, |
||||
{0x25CB, 0x25CB, ambiguous}, |
||||
{0x25CE, 0x25D1, ambiguous}, |
||||
{0x25E2, 0x25E5, ambiguous}, |
||||
{0x25EF, 0x25EF, ambiguous}, |
||||
{0x2605, 0x2606, ambiguous}, |
||||
{0x2609, 0x2609, ambiguous}, |
||||
{0x260E, 0x260F, ambiguous}, |
||||
{0x2614, 0x2615, ambiguous}, |
||||
{0x261C, 0x261C, ambiguous}, |
||||
{0x261E, 0x261E, ambiguous}, |
||||
{0x2640, 0x2640, ambiguous}, |
||||
{0x2642, 0x2642, ambiguous}, |
||||
{0x2660, 0x2661, ambiguous}, |
||||
{0x2663, 0x2665, ambiguous}, |
||||
{0x2667, 0x266A, ambiguous}, |
||||
{0x266C, 0x266D, ambiguous}, |
||||
{0x266F, 0x266F, ambiguous}, |
||||
{0x269E, 0x269F, ambiguous}, |
||||
{0x26BE, 0x26BF, ambiguous}, |
||||
{0x26C4, 0x26CD, ambiguous}, |
||||
{0x26CF, 0x26E1, ambiguous}, |
||||
{0x26E3, 0x26E3, ambiguous}, |
||||
{0x26E8, 0x26FF, ambiguous}, |
||||
{0x273D, 0x273D, ambiguous}, |
||||
{0x2757, 0x2757, ambiguous}, |
||||
{0x2776, 0x277F, ambiguous}, |
||||
{0x27E6, 0x27ED, narrow}, |
||||
{0x2985, 0x2986, narrow}, |
||||
{0x2B55, 0x2B59, ambiguous}, |
||||
{0x2E80, 0x2E9A, wide}, |
||||
{0x2E9B, 0x2EF4, wide}, |
||||
{0x2F00, 0x2FD6, wide}, |
||||
{0x2FF0, 0x2FFC, wide}, |
||||
{0x3000, 0x3000, fullwidth}, |
||||
{0x3001, 0x303E, wide}, |
||||
{0x3041, 0x3097, wide}, |
||||
{0x3099, 0x3100, wide}, |
||||
{0x3105, 0x312E, wide}, |
||||
{0x3131, 0x318F, wide}, |
||||
{0x3190, 0x31BB, wide}, |
||||
{0x31C0, 0x31E4, wide}, |
||||
{0x31F0, 0x321F, wide}, |
||||
{0x3220, 0x3247, wide}, |
||||
{0x3248, 0x324F, ambiguous}, |
||||
{0x3250, 0x32FF, wide}, |
||||
{0x3300, 0x4DBF, wide}, |
||||
{0x4E00, 0xA48D, wide}, |
||||
{0xA490, 0xA4C7, wide}, |
||||
{0xA960, 0xA97D, wide}, |
||||
{0xAC00, 0xD7A4, wide}, |
||||
{0xE000, 0xF8FF, ambiguous}, |
||||
{0xF900, 0xFAFF, wide}, |
||||
{0xFE00, 0xFE0F, ambiguous}, |
||||
{0xFE10, 0xFE1A, wide}, |
||||
{0xFE30, 0xFE53, wide}, |
||||
{0xFE54, 0xFE67, wide}, |
||||
{0xFE68, 0xFE6C, wide}, |
||||
{0xFF01, 0xFF60, fullwidth}, |
||||
{0xFF61, 0xFFBF, halfwidth}, |
||||
{0xFFC2, 0xFFC8, halfwidth}, |
||||
{0xFFCA, 0xFFD0, halfwidth}, |
||||
{0xFFD2, 0xFFD8, halfwidth}, |
||||
{0xFFDA, 0xFFDD, halfwidth}, |
||||
{0xFFE0, 0xFFE7, fullwidth}, |
||||
{0xFFE8, 0xFFEF, halfwidth}, |
||||
{0xFFFD, 0xFFFE, ambiguous}, |
||||
{0x1B000, 0x1B002, wide}, |
||||
{0x1F100, 0x1F10A, ambiguous}, |
||||
{0x1F110, 0x1F12D, ambiguous}, |
||||
{0x1F130, 0x1F169, ambiguous}, |
||||
{0x1F170, 0x1F19B, ambiguous}, |
||||
{0x1F200, 0x1F203, wide}, |
||||
{0x1F210, 0x1F23B, wide}, |
||||
{0x1F240, 0x1F249, wide}, |
||||
{0x1F250, 0x1F252, wide}, |
||||
{0x20000, 0x2FFFE, wide}, |
||||
{0x30000, 0x3FFFE, wide}, |
||||
{0xE0100, 0xE01F0, ambiguous}, |
||||
{0xF0000, 0xFFFFD, ambiguous}, |
||||
{0x100000, 0x10FFFE, ambiguous}, |
||||
} |
||||
|
||||
type Condition struct { |
||||
EastAsianWidth bool |
||||
} |
||||
|
||||
func NewCondition() *Condition { |
||||
return &Condition{EastAsianWidth} |
||||
} |
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func (c *Condition) RuneWidth(r rune) int { |
||||
if r == 0 { |
||||
return 0 |
||||
} |
||||
if r < 32 || (r >= 0x7f && r < 0xa0) { |
||||
return 1 |
||||
} |
||||
for _, iv := range combining { |
||||
if iv.first <= r && r <= iv.last { |
||||
return 0 |
||||
} |
||||
} |
||||
|
||||
if c.EastAsianWidth && IsAmbiguousWidth(r) { |
||||
return 2 |
||||
} |
||||
|
||||
if r >= 0x1100 && |
||||
(r <= 0x115f || r == 0x2329 || r == 0x232a || |
||||
(r >= 0x2e80 && r <= 0xa4cf && r != 0x303f) || |
||||
(r >= 0xac00 && r <= 0xd7a3) || |
||||
(r >= 0xf900 && r <= 0xfaff) || |
||||
(r >= 0xfe30 && r <= 0xfe6f) || |
||||
(r >= 0xff00 && r <= 0xff60) || |
||||
(r >= 0xffe0 && r <= 0xffe6) || |
||||
(r >= 0x20000 && r <= 0x2fffd) || |
||||
(r >= 0x30000 && r <= 0x3fffd)) { |
||||
return 2 |
||||
} |
||||
return 1 |
||||
} |
||||
|
||||
func (c *Condition) StringWidth(s string) (width int) { |
||||
for _, r := range []rune(s) { |
||||
width += c.RuneWidth(r) |
||||
} |
||||
return width |
||||
} |
||||
|
||||
func (c *Condition) Truncate(s string, w int, tail string) string { |
||||
if c.StringWidth(s) <= w { |
||||
return s |
||||
} |
||||
r := []rune(s) |
||||
tw := c.StringWidth(tail) |
||||
w -= tw |
||||
width := 0 |
||||
i := 0 |
||||
for ; i < len(r); i++ { |
||||
cw := c.RuneWidth(r[i]) |
||||
if width+cw > w { |
||||
break |
||||
} |
||||
width += cw |
||||
} |
||||
return string(r[0:i]) + tail |
||||
} |
||||
|
||||
func (c *Condition) Wrap(s string, w int) string { |
||||
width := 0 |
||||
out := "" |
||||
for _, r := range []rune(s) { |
||||
cw := RuneWidth(r) |
||||
if r == '\n' { |
||||
out += string(r) |
||||
width = 0 |
||||
continue |
||||
} else if width+cw > w { |
||||
out += "\n" |
||||
width = 0 |
||||
out += string(r) |
||||
width += cw |
||||
continue |
||||
} |
||||
out += string(r) |
||||
width += cw |
||||
} |
||||
return out |
||||
} |
||||
|
||||
func (c *Condition) FillLeft(s string, w int) string { |
||||
width := c.StringWidth(s) |
||||
count := w - width |
||||
if count > 0 { |
||||
b := make([]byte, count) |
||||
for i := range b { |
||||
b[i] = ' ' |
||||
} |
||||
return string(b) + s |
||||
} |
||||
return s |
||||
} |
||||
|
||||
func (c *Condition) FillRight(s string, w int) string { |
||||
width := c.StringWidth(s) |
||||
count := w - width |
||||
if count > 0 { |
||||
b := make([]byte, count) |
||||
for i := range b { |
||||
b[i] = ' ' |
||||
} |
||||
return s + string(b) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func RuneWidth(r rune) int { |
||||
return DefaultCondition.RuneWidth(r) |
||||
} |
||||
|
||||
func ct(r rune) ctype { |
||||
for _, iv := range ctypes { |
||||
if iv.first <= r && r <= iv.last { |
||||
return iv.ctype |
||||
} |
||||
} |
||||
return neutral |
||||
} |
||||
|
||||
// IsAmbiguousWidth returns whether is ambiguous width or not.
|
||||
func IsAmbiguousWidth(r rune) bool { |
||||
return ct(r) == ambiguous |
||||
} |
||||
|
||||
// IsAmbiguousWidth returns whether is ambiguous width or not.
|
||||
func IsNeutralWidth(r rune) bool { |
||||
return ct(r) == neutral |
||||
} |
||||
|
||||
func StringWidth(s string) (width int) { |
||||
return DefaultCondition.StringWidth(s) |
||||
} |
||||
|
||||
func Truncate(s string, w int, tail string) string { |
||||
return DefaultCondition.Truncate(s, w, tail) |
||||
} |
||||
|
||||
func Wrap(s string, w int) string { |
||||
return DefaultCondition.Wrap(s, w) |
||||
} |
||||
|
||||
func FillLeft(s string, w int) string { |
||||
return DefaultCondition.FillLeft(s, w) |
||||
} |
||||
|
||||
func FillRight(s string, w int) string { |
||||
return DefaultCondition.FillRight(s, w) |
||||
} |
@ -0,0 +1,8 @@ |
||||
// +build js
|
||||
|
||||
package runewidth |
||||
|
||||
func IsEastAsian() bool { |
||||
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
|
||||
return false |
||||
} |
@ -0,0 +1,69 @@ |
||||
// +build !windows,!js
|
||||
|
||||
package runewidth |
||||
|
||||
import ( |
||||
"os" |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`) |
||||
|
||||
func IsEastAsian() bool { |
||||
locale := os.Getenv("LC_CTYPE") |
||||
if locale == "" { |
||||
locale = os.Getenv("LANG") |
||||
} |
||||
|
||||
// ignore C locale
|
||||
if locale == "POSIX" || locale == "C" { |
||||
return false |
||||
} |
||||
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') { |
||||
return false |
||||
} |
||||
|
||||
charset := strings.ToLower(locale) |
||||
r := reLoc.FindStringSubmatch(locale) |
||||
if len(r) == 2 { |
||||
charset = strings.ToLower(r[1]) |
||||
} |
||||
|
||||
if strings.HasSuffix(charset, "@cjk_narrow") { |
||||
return false |
||||
} |
||||
|
||||
for pos, b := range []byte(charset) { |
||||
if b == '@' { |
||||
charset = charset[:pos] |
||||
break |
||||
} |
||||
} |
||||
|
||||
mbc_max := 1 |
||||
switch charset { |
||||
case "utf-8", "utf8": |
||||
mbc_max = 6 |
||||
case "jis": |
||||
mbc_max = 8 |
||||
case "eucjp": |
||||
mbc_max = 3 |
||||
case "euckr", "euccn": |
||||
mbc_max = 2 |
||||
case "sjis", "cp932", "cp51932", "cp936", "cp949", "cp950": |
||||
mbc_max = 2 |
||||
case "big5": |
||||
mbc_max = 2 |
||||
case "gbk", "gb2312": |
||||
mbc_max = 2 |
||||
} |
||||
|
||||
if mbc_max > 1 && (charset[0] != 'u' || |
||||
strings.HasPrefix(locale, "ja") || |
||||
strings.HasPrefix(locale, "ko") || |
||||
strings.HasPrefix(locale, "zh")) { |
||||
return true |
||||
} |
||||
return false |
||||
} |
@ -0,0 +1,229 @@ |
||||
package runewidth |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
var runewidthtests = []struct { |
||||
in rune |
||||
out int |
||||
}{ |
||||
{'ไธ', 2}, |
||||
{'็', 2}, |
||||
{'๏ฝพ', 1}, |
||||
{'๏ฝถ', 1}, |
||||
{'๏ฝฒ', 1}, |
||||
{'โ', 2}, // double width in ambiguous
|
||||
{'\x00', 0}, |
||||
{'\x01', 1}, |
||||
{'\u0300', 0}, |
||||
} |
||||
|
||||
func TestRuneWidth(t *testing.T) { |
||||
c := NewCondition() |
||||
c.EastAsianWidth = true |
||||
for _, tt := range runewidthtests { |
||||
if out := c.RuneWidth(tt.in); out != tt.out { |
||||
t.Errorf("Width(%q) = %q, want %q", tt.in, out, tt.out) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var isambiguouswidthtests = []struct { |
||||
in rune |
||||
out bool |
||||
}{ |
||||
{'ไธ', false}, |
||||
{'โ ', true}, |
||||
{'็', false}, |
||||
{'โ', true}, |
||||
{'ใฑ', false}, |
||||
{'โ ', true}, |
||||
{'โก', true}, |
||||
{'โข', true}, |
||||
{'โฃ', true}, |
||||
{'โค', true}, |
||||
{'โฅ', true}, |
||||
{'โฆ', true}, |
||||
{'โง', true}, |
||||
{'โจ', true}, |
||||
{'โฉ', true}, |
||||
{'โช', true}, |
||||
{'โซ', true}, |
||||
{'โฌ', true}, |
||||
{'โญ', true}, |
||||
{'โฎ', true}, |
||||
{'โฏ', true}, |
||||
{'โฐ', true}, |
||||
{'โฑ', true}, |
||||
{'โฒ', true}, |
||||
{'โณ', true}, |
||||
{'โ', true}, |
||||
} |
||||
|
||||
func TestIsAmbiguousWidth(t *testing.T) { |
||||
for _, tt := range isambiguouswidthtests { |
||||
if out := IsAmbiguousWidth(tt.in); out != tt.out { |
||||
t.Errorf("IsAmbiguousWidth(%q) = %q, want %q", tt.in, out, tt.out) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var stringwidthtests = []struct { |
||||
in string |
||||
out int |
||||
}{ |
||||
{"โ ใฑใฎไธ็โ ", 12}, |
||||
{"ในใฟใผโ", 8}, |
||||
} |
||||
|
||||
func TestStringWidth(t *testing.T) { |
||||
c := NewCondition() |
||||
c.EastAsianWidth = true |
||||
for _, tt := range stringwidthtests { |
||||
if out := c.StringWidth(tt.in); out != tt.out { |
||||
t.Errorf("StringWidth(%q) = %q, want %q", tt.in, out, tt.out) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestStringWidthInvalid(t *testing.T) { |
||||
s := "ใใใซใกใ\x00ไธ็" |
||||
if out := StringWidth(s); out != 14 { |
||||
t.Errorf("StringWidth(%q) = %q, want %q", s, out, 14) |
||||
} |
||||
} |
||||
|
||||
func TestTruncateSmaller(t *testing.T) { |
||||
s := "ใใใใใ" |
||||
expected := "ใใใใใ" |
||||
|
||||
if out := Truncate(s, 10, "..."); out != expected { |
||||
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
||||
|
||||
func TestTruncate(t *testing.T) { |
||||
s := "ใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใ" |
||||
expected := "ใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใ..." |
||||
|
||||
out := Truncate(s, 80, "...") |
||||
if out != expected { |
||||
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
width := StringWidth(out) |
||||
if width != 79 { |
||||
t.Errorf("width of Truncate(%q) should be %d, but %d", s, 79, width) |
||||
} |
||||
} |
||||
|
||||
func TestTruncateFit(t *testing.T) { |
||||
s := "aใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใ" |
||||
expected := "aใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใ..." |
||||
|
||||
out := Truncate(s, 80, "...") |
||||
if out != expected { |
||||
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
width := StringWidth(out) |
||||
if width != 80 { |
||||
t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width) |
||||
} |
||||
} |
||||
|
||||
func TestTruncateJustFit(t *testing.T) { |
||||
s := "ใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใ" |
||||
expected := "ใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใใ" |
||||
|
||||
out := Truncate(s, 80, "...") |
||||
if out != expected { |
||||
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
width := StringWidth(out) |
||||
if width != 80 { |
||||
t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width) |
||||
} |
||||
} |
||||
|
||||
func TestWrap(t *testing.T) { |
||||
s := `ๆฑไบฌ็น่จฑ่จฑๅฏๅฑๅฑ้ทใฏใใๆฟๅฐใๅฎขใ /ๆฑไบฌ็น่จฑ่จฑๅฏๅฑๅฑ้ทใฏใใๆฟๅฐใๅฎขใ |
||||
123456789012345678901234567890 |
||||
|
||||
END` |
||||
expected := `ๆฑไบฌ็น่จฑ่จฑๅฏๅฑๅฑ้ทใฏใใๆฟๅฐใ |
||||
ๅฎขใ /ๆฑไบฌ็น่จฑ่จฑๅฏๅฑๅฑ้ทใฏใใ |
||||
ๆฟๅฐใๅฎขใ |
||||
123456789012345678901234567890 |
||||
|
||||
END` |
||||
|
||||
if out := Wrap(s, 30); out != expected { |
||||
t.Errorf("Wrap(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
||||
|
||||
func TestTruncateNoNeeded(t *testing.T) { |
||||
s := "ใใใใใใใ" |
||||
expected := "ใใใใใใใ" |
||||
|
||||
if out := Truncate(s, 80, "..."); out != expected { |
||||
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
||||
|
||||
var isneutralwidthtests = []struct { |
||||
in rune |
||||
out bool |
||||
}{ |
||||
{'โ', false}, |
||||
{'โ', false}, |
||||
{'โ', false}, |
||||
{'๏ฝ', false}, |
||||
{'โ', false}, |
||||
{'โฃ', true}, |
||||
{'โฃ', true}, |
||||
} |
||||
|
||||
func TestIsNeutralWidth(t *testing.T) { |
||||
for _, tt := range isneutralwidthtests { |
||||
if out := IsNeutralWidth(tt.in); out != tt.out { |
||||
t.Errorf("IsNeutralWidth(%q) = %q, want %q", tt.in, out, tt.out) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFillLeft(t *testing.T) { |
||||
s := "ใxใใใใ" |
||||
expected := " ใxใใใใ" |
||||
|
||||
if out := FillLeft(s, 15); out != expected { |
||||
t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
||||
|
||||
func TestFillLeftFit(t *testing.T) { |
||||
s := "ใใใใใ" |
||||
expected := "ใใใใใ" |
||||
|
||||
if out := FillLeft(s, 10); out != expected { |
||||
t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
||||
|
||||
func TestFillRight(t *testing.T) { |
||||
s := "ใxใใใใ" |
||||
expected := "ใxใใใใ " |
||||
|
||||
if out := FillRight(s, 15); out != expected { |
||||
t.Errorf("FillRight(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
||||
|
||||
func TestFillRightFit(t *testing.T) { |
||||
s := "ใใใใใ" |
||||
expected := "ใใใใใ" |
||||
|
||||
if out := FillRight(s, 10); out != expected { |
||||
t.Errorf("FillRight(%q) = %q, want %q", s, out, expected) |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package runewidth |
||||
|
||||
import ( |
||||
"syscall" |
||||
) |
||||
|
||||
var ( |
||||
kernel32 = syscall.NewLazyDLL("kernel32") |
||||
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP") |
||||
) |
||||
|
||||
func IsEastAsian() bool { |
||||
r1, _, _ := procGetConsoleOutputCP.Call() |
||||
if r1 == 0 { |
||||
return false |
||||
} |
||||
|
||||
switch int(r1) { |
||||
case 932, 51932, 936, 949, 950: |
||||
return true |
||||
} |
||||
|
||||
return false |
||||
} |
@ -0,0 +1,4 @@ |
||||
# Please keep this file sorted. |
||||
|
||||
Georg Reinke <guelfey@googlemail.com> |
||||
nsf <no.smile.face@gmail.com> |
@ -0,0 +1,19 @@ |
||||
Copyright (C) 2012 termbox-go authors |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
@ -0,0 +1,25 @@ |
||||
## Termbox |
||||
Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area. |
||||
|
||||
### Installation |
||||
Install and update this go package with `go get -u github.com/nsf/termbox-go` |
||||
|
||||
### Examples |
||||
For examples of what can be done take a look at demos in the _demos directory. You can try them with go run: `go run _demos/keyboard.go` |
||||
|
||||
There are also some interesting projects using termbox-go: |
||||
- [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox. |
||||
- [gomatrix](https://github.com/GeertJohan/gomatrix) connects to The Matrix and displays its data streams in your terminal. |
||||
- [gotetris](https://github.com/jjinux/gotetris) is an implementation of Tetris. |
||||
- [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game. |
||||
- [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan. |
||||
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs. |
||||
- [mop](https://github.com/michaeldv/mop) is stock market tracker for hackers. |
||||
- [termui](https://github.com/gizak/termui) is a terminal dashboard. |
||||
- [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine. |
||||
- [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart. |
||||
- [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces. |
||||
- [dry](https://github.com/moncho/dry) is an interactive cli to manage Docker containers. |
||||
|
||||
### API reference |
||||
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go) |
@ -0,0 +1,459 @@ |
||||
// +build !windows
|
||||
|
||||
package termbox |
||||
|
||||
import "github.com/mattn/go-runewidth" |
||||
import "fmt" |
||||
import "os" |
||||
import "os/signal" |
||||
import "syscall" |
||||
import "runtime" |
||||
|
||||
// public API
|
||||
|
||||
// Initializes termbox library. This function should be called before any other functions.
|
||||
// After successful initialization, the library must be finalized using 'Close' function.
|
||||
//
|
||||
// Example usage:
|
||||
// err := termbox.Init()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer termbox.Close()
|
||||
func Init() error { |
||||
var err error |
||||
|
||||
out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = setup_term() |
||||
if err != nil { |
||||
return fmt.Errorf("termbox: error while reading terminfo data: %v", err) |
||||
} |
||||
|
||||
signal.Notify(sigwinch, syscall.SIGWINCH) |
||||
signal.Notify(sigio, syscall.SIGIO) |
||||
|
||||
_, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid()) |
||||
if runtime.GOOS != "darwin" && err != nil { |
||||
return err |
||||
} |
||||
err = tcgetattr(out.Fd(), &orig_tios) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
tios := orig_tios |
||||
tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK | |
||||
syscall_ISTRIP | syscall_INLCR | syscall_IGNCR | |
||||
syscall_ICRNL | syscall_IXON |
||||
tios.Oflag &^= syscall_OPOST |
||||
tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON | |
||||
syscall_ISIG | syscall_IEXTEN |
||||
tios.Cflag &^= syscall_CSIZE | syscall_PARENB |
||||
tios.Cflag |= syscall_CS8 |
||||
tios.Cc[syscall_VMIN] = 1 |
||||
tios.Cc[syscall_VTIME] = 0 |
||||
|
||||
err = tcsetattr(out.Fd(), &tios) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
out.WriteString(funcs[t_enter_ca]) |
||||
out.WriteString(funcs[t_enter_keypad]) |
||||
out.WriteString(funcs[t_hide_cursor]) |
||||
out.WriteString(funcs[t_clear_screen]) |
||||
|
||||
termw, termh = get_term_size(out.Fd()) |
||||
back_buffer.init(termw, termh) |
||||
front_buffer.init(termw, termh) |
||||
back_buffer.clear() |
||||
front_buffer.clear() |
||||
|
||||
go func() { |
||||
buf := make([]byte, 128) |
||||
for { |
||||
select { |
||||
case <-sigio: |
||||
for { |
||||
n, err := syscall.Read(in, buf) |
||||
if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK { |
||||
break |
||||
} |
||||
select { |
||||
case input_comm <- input_event{buf[:n], err}: |
||||
ie := <-input_comm |
||||
buf = ie.data[:128] |
||||
case <-quit: |
||||
return |
||||
} |
||||
} |
||||
case <-quit: |
||||
return |
||||
} |
||||
} |
||||
}() |
||||
|
||||
IsInit = true |
||||
return nil |
||||
} |
||||
|
||||
// Interrupt an in-progress call to PollEvent by causing it to return
|
||||
// EventInterrupt. Note that this function will block until the PollEvent
|
||||
// function has successfully been interrupted.
|
||||
func Interrupt() { |
||||
interrupt_comm <- struct{}{} |
||||
} |
||||
|
||||
// Finalizes termbox library, should be called after successful initialization
|
||||
// when termbox's functionality isn't required anymore.
|
||||
func Close() { |
||||
quit <- 1 |
||||
out.WriteString(funcs[t_show_cursor]) |
||||
out.WriteString(funcs[t_sgr0]) |
||||
out.WriteString(funcs[t_clear_screen]) |
||||
out.WriteString(funcs[t_exit_ca]) |
||||
out.WriteString(funcs[t_exit_keypad]) |
||||
out.WriteString(funcs[t_exit_mouse]) |
||||
tcsetattr(out.Fd(), &orig_tios) |
||||
|
||||
out.Close() |
||||
syscall.Close(in) |
||||
|
||||
// reset the state, so that on next Init() it will work again
|
||||
termw = 0 |
||||
termh = 0 |
||||
input_mode = InputEsc |
||||
out = nil |
||||
in = 0 |
||||
lastfg = attr_invalid |
||||
lastbg = attr_invalid |
||||
lastx = coord_invalid |
||||
lasty = coord_invalid |
||||
cursor_x = cursor_hidden |
||||
cursor_y = cursor_hidden |
||||
foreground = ColorDefault |
||||
background = ColorDefault |
||||
IsInit = false |
||||
} |
||||
|
||||
// Synchronizes the internal back buffer with the terminal.
|
||||
func Flush() error { |
||||
// invalidate cursor position
|
||||
lastx = coord_invalid |
||||
lasty = coord_invalid |
||||
|
||||
update_size_maybe() |
||||
|
||||
for y := 0; y < front_buffer.height; y++ { |
||||
line_offset := y * front_buffer.width |
||||
for x := 0; x < front_buffer.width; { |
||||
cell_offset := line_offset + x |
||||
back := &back_buffer.cells[cell_offset] |
||||
front := &front_buffer.cells[cell_offset] |
||||
if back.Ch < ' ' { |
||||
back.Ch = ' ' |
||||
} |
||||
w := runewidth.RuneWidth(back.Ch) |
||||
if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) { |
||||
w = 1 |
||||
} |
||||
if *back == *front { |
||||
x += w |
||||
continue |
||||
} |
||||
*front = *back |
||||
send_attr(back.Fg, back.Bg) |
||||
|
||||
if w == 2 && x == front_buffer.width-1 { |
||||
// there's not enough space for 2-cells rune,
|
||||
// let's just put a space in there
|
||||
send_char(x, y, ' ') |
||||
} else { |
||||
send_char(x, y, back.Ch) |
||||
if w == 2 { |
||||
next := cell_offset + 1 |
||||
front_buffer.cells[next] = Cell{ |
||||
Ch: 0, |
||||
Fg: back.Fg, |
||||
Bg: back.Bg, |
||||
} |
||||
} |
||||
} |
||||
x += w |
||||
} |
||||
} |
||||
if !is_cursor_hidden(cursor_x, cursor_y) { |
||||
write_cursor(cursor_x, cursor_y) |
||||
} |
||||
return flush() |
||||
} |
||||
|
||||
// Sets the position of the cursor. See also HideCursor().
|
||||
func SetCursor(x, y int) { |
||||
if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) { |
||||
outbuf.WriteString(funcs[t_show_cursor]) |
||||
} |
||||
|
||||
if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) { |
||||
outbuf.WriteString(funcs[t_hide_cursor]) |
||||
} |
||||
|
||||
cursor_x, cursor_y = x, y |
||||
if !is_cursor_hidden(cursor_x, cursor_y) { |
||||
write_cursor(cursor_x, cursor_y) |
||||
} |
||||
} |
||||
|
||||
// The shortcut for SetCursor(-1, -1).
|
||||
func HideCursor() { |
||||
SetCursor(cursor_hidden, cursor_hidden) |
||||
} |
||||
|
||||
// Changes cell's parameters in the internal back buffer at the specified
|
||||
// position.
|
||||
func SetCell(x, y int, ch rune, fg, bg Attribute) { |
||||
if x < 0 || x >= back_buffer.width { |
||||
return |
||||
} |
||||
if y < 0 || y >= back_buffer.height { |
||||
return |
||||
} |
||||
|
||||
back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg} |
||||
} |
||||
|
||||
// Returns a slice into the termbox's back buffer. You can get its dimensions
|
||||
// using 'Size' function. The slice remains valid as long as no 'Clear' or
|
||||
// 'Flush' function calls were made after call to this function.
|
||||
func CellBuffer() []Cell { |
||||
return back_buffer.cells |
||||
} |
||||
|
||||
// After getting a raw event from PollRawEvent function call, you can parse it
|
||||
// again into an ordinary one using termbox logic. That is parse an event as
|
||||
// termbox would do it. Returned event in addition to usual Event struct fields
|
||||
// sets N field to the amount of bytes used within 'data' slice. If the length
|
||||
// of 'data' slice is zero or event cannot be parsed for some other reason, the
|
||||
// function will return a special event type: EventNone.
|
||||
//
|
||||
// IMPORTANT: EventNone may contain a non-zero N, which means you should skip
|
||||
// these bytes, because termbox cannot recognize them.
|
||||
//
|
||||
// NOTE: This API is experimental and may change in future.
|
||||
func ParseEvent(data []byte) Event { |
||||
event := Event{Type: EventKey} |
||||
ok := extract_event(data, &event) |
||||
if !ok { |
||||
return Event{Type: EventNone, N: event.N} |
||||
} |
||||
return event |
||||
} |
||||
|
||||
// Wait for an event and return it. This is a blocking function call. Instead
|
||||
// of EventKey and EventMouse it returns EventRaw events. Raw event is written
|
||||
// into `data` slice and Event's N field is set to the amount of bytes written.
|
||||
// The minimum required length of the 'data' slice is 1. This requirement may
|
||||
// vary on different platforms.
|
||||
//
|
||||
// NOTE: This API is experimental and may change in future.
|
||||
func PollRawEvent(data []byte) Event { |
||||
if len(data) == 0 { |
||||
panic("len(data) >= 1 is a requirement") |
||||
} |
||||
|
||||
var event Event |
||||
if extract_raw_event(data, &event) { |
||||
return event |
||||
} |
||||
|
||||
for { |
||||
select { |
||||
case ev := <-input_comm: |
||||
if ev.err != nil { |
||||
return Event{Type: EventError, Err: ev.err} |
||||
} |
||||
|
||||
inbuf = append(inbuf, ev.data...) |
||||
input_comm <- ev |
||||
if extract_raw_event(data, &event) { |
||||
return event |
||||
} |
||||
case <-interrupt_comm: |
||||
event.Type = EventInterrupt |
||||
return event |
||||
|
||||
case <-sigwinch: |
||||
event.Type = EventResize |
||||
event.Width, event.Height = get_term_size(out.Fd()) |
||||
return event |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Wait for an event and return it. This is a blocking function call.
|
||||
func PollEvent() Event { |
||||
var event Event |
||||
|
||||
// try to extract event from input buffer, return on success
|
||||
event.Type = EventKey |
||||
ok := extract_event(inbuf, &event) |
||||
if event.N != 0 { |
||||
copy(inbuf, inbuf[event.N:]) |
||||
inbuf = inbuf[:len(inbuf)-event.N] |
||||
} |
||||
if ok { |
||||
return event |
||||
} |
||||
|
||||
for { |
||||
select { |
||||
case ev := <-input_comm: |
||||
if ev.err != nil { |
||||
return Event{Type: EventError, Err: ev.err} |
||||
} |
||||
|
||||
inbuf = append(inbuf, ev.data...) |
||||
input_comm <- ev |
||||
ok := extract_event(inbuf, &event) |
||||
if event.N != 0 { |
||||
copy(inbuf, inbuf[event.N:]) |
||||
inbuf = inbuf[:len(inbuf)-event.N] |
||||
} |
||||
if ok { |
||||
return event |
||||
} |
||||
case <-interrupt_comm: |
||||
event.Type = EventInterrupt |
||||
return event |
||||
|
||||
case <-sigwinch: |
||||
event.Type = EventResize |
||||
event.Width, event.Height = get_term_size(out.Fd()) |
||||
return event |
||||
} |
||||
} |
||||
panic("unreachable") |
||||
} |
||||
|
||||
// Returns the size of the internal back buffer (which is mostly the same as
|
||||
// terminal's window size in characters). But it doesn't always match the size
|
||||
// of the terminal window, after the terminal size has changed, the internal
|
||||
// back buffer will get in sync only after Clear or Flush function calls.
|
||||
func Size() (width int, height int) { |
||||
return termw, termh |
||||
} |
||||
|
||||
// Clears the internal back buffer.
|
||||
func Clear(fg, bg Attribute) error { |
||||
foreground, background = fg, bg |
||||
err := update_size_maybe() |
||||
back_buffer.clear() |
||||
return err |
||||
} |
||||
|
||||
// Sets termbox input mode. Termbox has two input modes:
|
||||
//
|
||||
// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
|
||||
// any known sequence. ESC means KeyEsc. This is the default input mode.
|
||||
//
|
||||
// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
|
||||
// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
|
||||
//
|
||||
// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
|
||||
// enable mouse button press/release and drag events.
|
||||
//
|
||||
// If 'mode' is InputCurrent, returns the current input mode. See also Input*
|
||||
// constants.
|
||||
func SetInputMode(mode InputMode) InputMode { |
||||
if mode == InputCurrent { |
||||
return input_mode |
||||
} |
||||
if mode&(InputEsc|InputAlt) == 0 { |
||||
mode |= InputEsc |
||||
} |
||||
if mode&(InputEsc|InputAlt) == InputEsc|InputAlt { |
||||
mode &^= InputAlt |
||||
} |
||||
if mode&InputMouse != 0 { |
||||
out.WriteString(funcs[t_enter_mouse]) |
||||
} else { |
||||
out.WriteString(funcs[t_exit_mouse]) |
||||
} |
||||
|
||||
input_mode = mode |
||||
return input_mode |
||||
} |
||||
|
||||
// Sets the termbox output mode. Termbox has four output options:
|
||||
//
|
||||
// 1. OutputNormal => [1..8]
|
||||
// This mode provides 8 different colors:
|
||||
// black, red, green, yellow, blue, magenta, cyan, white
|
||||
// Shortcut: ColorBlack, ColorRed, ...
|
||||
// Attributes: AttrBold, AttrUnderline, AttrReverse
|
||||
//
|
||||
// Example usage:
|
||||
// SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
|
||||
//
|
||||
// 2. Output256 => [1..256]
|
||||
// In this mode you can leverage the 256 terminal mode:
|
||||
// 0x01 - 0x08: the 8 colors as in OutputNormal
|
||||
// 0x09 - 0x10: Color* | AttrBold
|
||||
// 0x11 - 0xe8: 216 different colors
|
||||
// 0xe9 - 0x1ff: 24 different shades of grey
|
||||
//
|
||||
// Example usage:
|
||||
// SetCell(x, y, '@', 184, 240);
|
||||
// SetCell(x, y, '@', 0xb8, 0xf0);
|
||||
//
|
||||
// 3. Output216 => [1..216]
|
||||
// This mode supports the 3rd range of the 256 mode only.
|
||||
// But you dont need to provide an offset.
|
||||
//
|
||||
// 4. OutputGrayscale => [1..26]
|
||||
// This mode supports the 4th range of the 256 mode
|
||||
// and black and white colors from 3th range of the 256 mode
|
||||
// But you dont need to provide an offset.
|
||||
//
|
||||
// In all modes, 0x00 represents the default color.
|
||||
//
|
||||
// `go run _demos/output.go` to see its impact on your terminal.
|
||||
//
|
||||
// If 'mode' is OutputCurrent, it returns the current output mode.
|
||||
//
|
||||
// Note that this may return a different OutputMode than the one requested,
|
||||
// as the requested mode may not be available on the target platform.
|
||||
func SetOutputMode(mode OutputMode) OutputMode { |
||||
if mode == OutputCurrent { |
||||
return output_mode |
||||
} |
||||
|
||||
output_mode = mode |
||||
return output_mode |
||||
} |
||||
|
||||
// Sync comes handy when something causes desync between termbox's understanding
|
||||
// of a terminal buffer and the reality. Such as a third party process. Sync
|
||||
// forces a complete resync between the termbox and a terminal, it may not be
|
||||
// visually pretty though.
|
||||
func Sync() error { |
||||
front_buffer.clear() |
||||
err := send_clear() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return Flush() |
||||
} |
@ -0,0 +1,187 @@ |
||||
// termbox is a library for creating cross-platform text-based interfaces
|
||||
package termbox |
||||
|
||||
// public API, common OS agnostic part
|
||||
|
||||
type ( |
||||
InputMode int |
||||
OutputMode int |
||||
EventType uint8 |
||||
Modifier uint8 |
||||
Key uint16 |
||||
Attribute uint16 |
||||
) |
||||
|
||||
// This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are
|
||||
// valid if 'Type' is EventKey. The 'Width' and 'Height' fields are valid if
|
||||
// 'Type' is EventResize. The 'Err' field is valid if 'Type' is EventError.
|
||||
type Event struct { |
||||
Type EventType // one of Event* constants
|
||||
Mod Modifier // one of Mod* constants or 0
|
||||
Key Key // one of Key* constants, invalid if 'Ch' is not 0
|
||||
Ch rune // a unicode character
|
||||
Width int // width of the screen
|
||||
Height int // height of the screen
|
||||
Err error // error in case if input failed
|
||||
MouseX int // x coord of mouse
|
||||
MouseY int // y coord of mouse
|
||||
N int // number of bytes written when getting a raw event
|
||||
} |
||||
|
||||
// A cell, single conceptual entity on the screen. The screen is basically a 2d
|
||||
// array of cells. 'Ch' is a unicode character, 'Fg' and 'Bg' are foreground
|
||||
|