1
0
mirror of https://github.com/gdamore/tcell.git synced 2025-04-24 13:48:51 +08:00

fixes #37 Improve docs & fix golint

fixes #38 Broke wide characters in last update
fixes #39 Clean up logic for encodings, enhance fallback options
fixes #36 Support more mouse buttons on Windows.
This commit is contained in:
Garrett D'Amore 2015-10-07 20:15:33 -07:00
parent 02eef725e2
commit 7322e40c26
26 changed files with 845 additions and 443 deletions

View File

@ -24,14 +24,23 @@ import (
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"github.com/mattn/go-runewidth"
)
var defStyle tcell.Style
func emitStr(s tcell.Screen, x, y int, style tcell.Style, str string) {
for _, c := range str {
s.SetContent(x, y, c, nil, style)
x++
var comb []rune
w := runewidth.RuneWidth(c)
if w == 0 {
comb = []rune{c}
c = ' '
w = 1
}
s.SetContent(x, y, c, comb, style)
x += w
}
}
@ -81,7 +90,7 @@ func drawSelect(s tcell.Screen, x1, y1, x2, y2 int, sel bool) {
}
style = style.Reverse(sel)
s.SetContent(col, row, mainc, combc, style)
col += width-1
col += width - 1
}
}
}

91
ascii.go Normal file
View File

@ -0,0 +1,91 @@
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tcell
import (
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/transform"
)
type ascii struct{ transform.NopResetter }
type asciiDecoder struct{ transform.NopResetter }
type asciiEncoder struct{ transform.NopResetter }
// ASCII represents an basic 7-bit ASCII scheme. It decodes directly to UTF-8
// without change, as all ASCII values are legal UTF-8. It encodes any UTF-8
// runes outside of ASCII to 0x1A, the ASCII substitution character.
var ASCII encoding.Encoding = ascii{}
func (ascii) NewDecoder() transform.Transformer {
return asciiDecoder{}
}
func (ascii) NewEncoder() transform.Transformer {
return asciiEncoder{}
}
func (asciiDecoder) Transform(dst, src []byte, atEOF bool) (int, int, error) {
var e error
var ndst, nsrc int
for _, c := range src {
if ndst >= len(dst) {
e = transform.ErrShortDst
break
}
dst[ndst] = c
ndst++
nsrc++
}
return ndst, nsrc, e
}
func (asciiEncoder) Transform(dst, src []byte, atEOF bool) (int, int, error) {
var e error
var ndst, nsrc int
var sz int
for nsrc < len(src) {
if ndst >= len(dst) {
e = transform.ErrShortDst
break
}
r := rune(src[nsrc])
if r < utf8.RuneSelf {
dst[ndst] = uint8(r)
nsrc++
ndst++
continue
}
// No valid runes beyond ASCII. However, we need to consume
// the full rune, and report incomplete runes properly.
// Attempt to decode a multibyte rune
r, sz = utf8.DecodeRune(src[nsrc:])
if sz == 1 {
// If its inconclusive due to insufficient data in
// in the source, report it
if !atEOF && !utf8.FullRune(src[nsrc:]) {
e = transform.ErrShortSrc
break
}
}
nsrc += sz
dst[ndst] = encoding.ASCIISub
ndst++
}
return ndst, nsrc, e
}

37
cell.go
View File

@ -18,18 +18,6 @@ import (
"github.com/mattn/go-runewidth"
)
// Cell represents a single character cell. This is primarily intended for
// use by Screen implementors.
type Cell struct {
currMain rune
currComb []rune
currStyle Style
lastMain rune
lastStyle Style
lastComb []rune
width int
}
type cell struct {
currMain rune
currComb []rune
@ -40,12 +28,20 @@ type cell struct {
width int
}
// CellBuffer represents a two dimensional array of character cells.
// This is primarily intended for use by Screen implementors; it
// contains much of the common code they need. To create one, just
// declare a variable of its type; no explicit initialization is necessary.
//
// CellBuffer is not thread safe.
type CellBuffer struct {
w int
h int
cells []cell
}
// SetContent sets the contents (primary rune, combining runes,
// and style) for a cell at a given location.
func (cb *CellBuffer) SetContent(x int, y int,
mainc rune, combc []rune, style Style) {
@ -65,7 +61,6 @@ func (cb *CellBuffer) SetContent(x int, y int,
if c.currMain != mainc {
c.width = runewidth.RuneWidth(mainc)
c.width = 1
}
c.currMain = mainc
c.currComb = combc
@ -73,6 +68,10 @@ func (cb *CellBuffer) SetContent(x int, y int,
}
}
// GetContent returns the contents of a character cell, including the
// primary rune, any combining character runes (which will usually be
// nil), the style, and the display width in cells. (The width can be
// either 1, normally, or 2 for East Asian full-width characters.)
func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
var mainc rune
var combc []rune
@ -89,16 +88,22 @@ func (cb *CellBuffer) GetContent(x, y int) (rune, []rune, Style, int) {
return mainc, combc, style, width
}
// Size returns the (width, height) in cells of the buffer.
func (cb *CellBuffer) Size() (int, int) {
return cb.w, cb.h
}
// Invalidate marks all characters within the buffer as dirty.
func (cb *CellBuffer) Invalidate() {
for i := range cb.cells {
cb.cells[i].lastMain = rune(0)
}
}
// Dirty checks if a character at the given location needs an
// to be refreshed on the physical display. This returns true
// if the cell content is different since the last time it was
// marked clean.
func (cb *CellBuffer) Dirty(x, y int) bool {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
@ -123,6 +128,9 @@ func (cb *CellBuffer) Dirty(x, y int) bool {
return false
}
// SetDirty is normally used to indicate that a cell has
// been displayed (in which case dirty is false), or to manually
// force a cell to be marked dirty.
func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
@ -167,8 +175,7 @@ func (cb *CellBuffer) Resize(w, h int) {
// Fill fills the entire cell buffer array with the specified character
// and style. Normally choose ' ' to clear the screen. This API doesn't
// support combining characters. (Why would you want to fill with combining
// characters?!?)
// support combining characters.
func (cb *CellBuffer) Fill(r rune, style Style) {
for i := range cb.cells {
c := &cb.cells[i]

View File

@ -16,8 +16,8 @@
package tcell
import "errors"
// NewConsoleScreen returns a console based screen. This platform
// doesn't have support for any, so it returns nil and a suitable error.
func NewConsoleScreen() (Screen, error) {
return nil, errors.New("no platform specific console support")
return nil, ErrNoScreen
}

View File

@ -47,12 +47,17 @@ type cScreen struct {
sync.Mutex
}
// all Windows systems are little endian
var k32 = syscall.NewLazyDLL("kernel32.dll")
// We have to bring in the kernel32.dll directly, so we can get access to some
// system calls that the core Go API lacks.
//
// Note that Windows appends some functions with W to indicate that wide
// characters (Unicode) are in use. The documentation refers to them
// without this suffix, as the resolution is made via preprocessor.
// We have to bring in the kernel32.dll directly, so we can get access to some
// system calls that the core Go API lacks.
var (
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
@ -68,9 +73,9 @@ var (
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
)
// We have to bring in the kernel32.dll directly, so we can get access to some
// system calls that the core Go API lacks.
// NewConsoleScreen returns a Screen for the Windows console associated
// with the current process. The Screen makes use of the Windows Console
// API to display content and read events.
func NewConsoleScreen() (Screen, error) {
return &cScreen{}, nil
}
@ -218,8 +223,8 @@ func (s *cScreen) doCursor() {
}
}
func (c *cScreen) HideCursor() {
c.ShowCursor(-1, -1)
func (s *cScreen) HideCursor() {
s.ShowCursor(-1, -1)
}
type charInfo struct {
@ -511,6 +516,15 @@ func (s *cScreen) getConsoleInput() error {
if mrec.btns&0x10 != 0 {
btns |= Button5
}
if mrec.btns&0x20 != 0 {
btns |= Button6
}
if mrec.btns&0x40 != 0 {
btns |= Button7
}
if mrec.btns&0x80 != 0 {
btns |= Button8
}
if mrec.flags&mouseVWheeled != 0 {
if mrec.btns&0x80000000 == 0 {
@ -701,7 +715,7 @@ func (s *cScreen) draw() {
continue
}
}
if x > s.w - width {
if x > s.w-width {
mainc = ' '
combc = nil
width = 1

View File

@ -1,4 +1,4 @@
// Generated by ./mkinfo (darwin/amd64) on Tue Oct 6 00:00:22 PDT 2015.
// Generated by ./mkinfo (darwin/amd64) on Thu Oct 8 09:34:28 PDT 2015.
// DO NOT HAND-EDIT
package tcell
@ -333,7 +333,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "d200",
Aliases: []string{ "d200-dg" },
Aliases: []string{"d200-dg"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -417,7 +417,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "d210",
Aliases: []string{ "d214" },
Aliases: []string{"d214"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -557,7 +557,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "Eterm",
Aliases: []string{ "Eterm-color" },
Aliases: []string{"Eterm-color"},
Columns: 80,
Lines: 24,
Colors: 8,
@ -951,7 +951,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "hpterm",
Aliases: []string{ "X-hpterm" },
Aliases: []string{"X-hpterm"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1565,7 +1565,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "sun",
Aliases: []string{ "sun1", "sun2" },
Aliases: []string{"sun1", "sun2"},
Columns: 80,
Lines: 34,
Bell: "\a",
@ -1668,7 +1668,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "tvi912",
Aliases: []string{ "tvi914", "tvi920" },
Aliases: []string{"tvi914", "tvi920"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1838,7 +1838,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt100",
Aliases: []string{ "vt100-am" },
Aliases: []string{"vt100-am"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1911,7 +1911,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt220",
Aliases: []string{ "vt200" },
Aliases: []string{"vt200"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1957,7 +1957,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt320",
Aliases: []string{ "vt300" },
Aliases: []string{"vt300"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -2010,7 +2010,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt400",
Aliases: []string{ "dec-vt400", "vt400-24" },
Aliases: []string{"dec-vt400", "vt400-24"},
Columns: 80,
Lines: 24,
Clear: "\x1b[H\x1b[J$<10/>",
@ -2086,7 +2086,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "wy50",
Aliases: []string{ "wyse50" },
Aliases: []string{"wyse50"},
Columns: 80,
Lines: 24,
Bell: "\a",
@ -2134,7 +2134,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "wy60",
Aliases: []string{ "wyse60" },
Aliases: []string{"wyse60"},
Columns: 80,
Lines: 24,
Bell: "\a",

View File

@ -15,6 +15,7 @@
package tcell
import (
"strings"
"sync"
"golang.org/x/text/encoding"
@ -22,10 +23,15 @@ import (
var encodings map[string]encoding.Encoding
var encodingLk sync.Mutex
var encodingFallback EncodingFallback = EncodingFallbackFail
// RegisterEncoding may be called by the application to register an encoding.
// The presence of additional encodings will facilitate application usage with
// terminal environments where the I/O subsystem does not support Unicode.
//
// Windows systems use Unicode natively, and do not need any of the encoding
// subsystem when using Windows Console screens.
//
// Please see the Go documentation for golang.org/x/text/encoding -- most of
// the common ones exist already as stock variables. For example, ISO8859-15
// can be registered using the following code:
@ -43,16 +49,12 @@ var encodingLk sync.Mutex
// These are expected to have the following pattern:
//
// $language[.$codeset[@$variant]
//
// We extract only the $codeset part, which will usually be something like
// UTF-8 or ISO8859-15 or KOI8-R. Note that if the locale is either "POSIX"
// or "C", then we assume US-ASCII (the POSIX 'portable character set'
// and assume all other characters are somehow invalid.)
//
// On Windows systems, the Console is assumed to be UTF-16LE. As we
// communicate with the console subsystem using UTF-16LE, no conversions are
// necessary. So none of this is required for Windows systems.
//
// Modern POSIX systems and terminal emulators may use UTF-8, and for those
// systems, this API is also unnecessary. For example, Darwin (MacOS X) and
// modern Linux running modern xterm generally will out of the box without
@ -64,12 +66,43 @@ var encodingLk sync.Mutex
// increase quite a bit as each encoding is added. The East Asian encodings
// have been seen to add 100-200K per encoding to the application size.
//
func RegisterEncoding(name string, enc encoding.Encoding) {
func RegisterEncoding(charset string, enc encoding.Encoding) {
encodingLk.Lock()
if encodings == nil {
encodings = make(map[string]encoding.Encoding)
}
encodings[name] = enc
charset = strings.ToLower(charset)
encodings[charset] = enc
encodingLk.Unlock()
}
// EncodingFallback describes how the system behavees when the locale
// requires a character set that we do not support. The system always
// supports UTF-8 and US-ASCII. On Windows consoles, UTF-16LE is also
// supported automatically. Other character sets must be added using the
// RegisterEncoding API. (A large group of nearly all of them can be
// added using the RegisterAll function in the encoding sub package.)
type EncodingFallback int
const (
// EncodingFallbackFail behavior causes GetEncoding to fail
// when it cannot find an encoding.
EncodingFallbackFail = iota
// EncodingFallbackASCII behaviore causes GetEncoding to fall back
// to a 7-bit ASCII encoding, if no other encoding can be found.
EncodingFallbackASCII
// EncodingFallbackUTF8 behavior causes GetEncoding to assume
// UTF8 can pass unmodified upon failure. Note that this behavior
// is not recommended, unless you are sure your terminal can cope
// with real UTF8 sequences.
EncodingFallbackUTF8
)
// SetEncodingFallback changes the behavior of GetEncoding when a suitable
// encoding is not found. The default is EncodingFallbackFail, which
// causes GetEncoding to simply return nil.
func SetEncodingFallback(fb EncodingFallback) {
encodingLk.Lock()
encodingFallback = fb
encodingLk.Unlock()
}
@ -77,11 +110,25 @@ func RegisterEncoding(name string, enc encoding.Encoding) {
// for the given character set name. Note that this will return nil for
// either the Unicode (UTF-8) or ASCII encodings, since we don't use
// encodings for them but instead have our own native methods.
func GetEncoding(name string) encoding.Encoding {
func GetEncoding(charset string) encoding.Encoding {
charset = strings.ToLower(charset)
encodingLk.Lock()
defer encodingLk.Unlock()
if enc, ok := encodings[name]; ok {
if enc, ok := encodings[charset]; ok {
return enc
}
switch encodingFallback {
case EncodingFallbackASCII:
return ASCII
case EncodingFallbackUTF8:
return encoding.Nop
}
return nil
}
func init() {
// We always support UTF-8 and ASCII.
encodings = make(map[string]encoding.Encoding)
encodings["utf-8"] = UTF8
encodings["us-ascii"] = ASCII
}

View File

@ -1,5 +1,3 @@
// +build !windows,!nacl,!plan9
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -26,8 +24,16 @@ import (
"golang.org/x/text/encoding/traditionalchinese"
)
// Register registers all known encodings. This is a short-cut to
// add full character set support to your program. Note that this can
// add several megabytes to your program's size, because some of the encoodings
// are rather large (particularly those from East Asia.)
func Register() {
tcell.RegisterEncoding("ISO8859-1", charmap.ISO8859_15) // alias for now
// We supply latin1 and latin5, because Go doesn't
tcell.RegisterEncoding("ISO8859-1", ISO8859_1)
tcell.RegisterEncoding("ISO8859-9", ISO8859_9)
tcell.RegisterEncoding("ISO8859-10", charmap.ISO8859_10)
tcell.RegisterEncoding("ISO8859-13", charmap.ISO8859_13)
tcell.RegisterEncoding("ISO8859-14", charmap.ISO8859_14)
tcell.RegisterEncoding("ISO8859-15", charmap.ISO8859_15)
@ -39,14 +45,12 @@ func Register() {
tcell.RegisterEncoding("ISO8859-6", charmap.ISO8859_6)
tcell.RegisterEncoding("ISO8859-7", charmap.ISO8859_7)
tcell.RegisterEncoding("ISO8859-8", charmap.ISO8859_8)
// ISO8859-9 is missing -- not present in GO, which is a shame since its basically
// almost 8859-1/-15.
tcell.RegisterEncoding("KOI8-R", charmap.KOI8R)
tcell.RegisterEncoding("KOI8-U", charmap.KOI8U)
// Asian stuff
tcell.RegisterEncoding("EUC-JP", japanese.EUCJP)
tcell.RegisterEncoding("Shift_JIS", japanese.ShiftJIS)
tcell.RegisterEncoding("SHIFT_JIS", japanese.ShiftJIS)
tcell.RegisterEncoding("ISO2022JP", japanese.ISO2022JP)
tcell.RegisterEncoding("EUC-KR", korean.EUCKR)
@ -83,16 +87,28 @@ func Register() {
"ISO-8859-7": "ISO8859-7",
"8859-8": "ISO8859-8",
"ISO-8859-8": "ISO8859-8",
"8859-9": "ISO8859-9",
"ISO-8859-9": "ISO8859-9",
"SJIS": "Shift_JIS",
"eucJP": "EUC-JP",
"EUCJP": "EUC-JP",
"2022-JP": "ISO2022JP",
"ISO-2022-JP": "ISO2022JP",
"eucKR": "EUC-KR",
"EUCKR": "EUC-KR",
// ISO646 isn't quite exactly ASCII, but the 1991 IRV
// (international reference version) is so. This helps
// some older systems that may use "646" for POSIX locales.
"646": "US-ASCII",
"ISO646": "US-ASCII",
// Other names for UTF-8
"UTF8": "UTF-8",
}
for n, v := range aliases {
tcell.RegisterEncoding(n, tcell.GetEncoding(v))
if enc := tcell.GetEncoding(v); enc != nil {
tcell.RegisterEncoding(n, enc)
}
}
}

144
encoding/charmap.go Normal file
View File

@ -0,0 +1,144 @@
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 encoding
import (
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/transform"
)
// suitable for 8-bit encodings
type cmap struct {
transform.NopResetter
bytes map[rune]byte
runes [256]rune // offset by 128, as all values are identical
ascii bool
}
type cmapDecoder struct {
transform.NopResetter
cmap *cmap
}
type cmapEncoder struct {
transform.NopResetter
cmap *cmap
}
func (c *cmap) Init() {
c.bytes = make(map[rune]byte)
for i := 0; i < 256; i++ {
c.bytes[rune(i)] = byte(i)
c.runes[i] = rune(i)
c.ascii = true
}
}
func (c *cmap) Map(b byte, r rune) {
if b < 128 {
c.ascii = false
}
// delete the old self-mapping
delete(c.bytes, rune(b))
// and add the new one
c.bytes[r] = b
c.runes[int(b)] = r
}
func (c *cmap) NewDecoder() transform.Transformer {
return cmapDecoder{cmap: c}
}
func (c *cmap) NewEncoder() transform.Transformer {
return cmapEncoder{cmap: c}
}
func (d cmapDecoder) Transform(dst, src []byte, atEOF bool) (int, int, error) {
var e error
var ndst, nsrc int
for _, c := range src {
if d.cmap.ascii && c < utf8.RuneSelf {
if ndst >= len(dst) {
e = transform.ErrShortDst
break
}
dst[ndst] = c
ndst++
nsrc++
continue
}
r := d.cmap.runes[c]
l := utf8.RuneLen(r)
// l will be a positive number, because we never inject invalid
// runes into the rune map.
if ndst+l > len(dst) {
e = transform.ErrShortDst
break
}
utf8.EncodeRune(dst[ndst:], r)
ndst += l
nsrc++
}
return ndst, nsrc, e
}
func (d cmapEncoder) Transform(dst, src []byte, atEOF bool) (int, int, error) {
var e error
var ndst, nsrc int
for nsrc < len(src) {
if ndst >= len(dst) {
e = transform.ErrShortDst
break
}
ch := src[nsrc]
if d.cmap.ascii && ch < utf8.RuneSelf {
dst[ndst] = ch
nsrc++
ndst++
continue
}
// No valid runes beyond 0xFF. However, we need to consume
// the full rune, and report incomplete runes properly.
// Attempt to decode a multibyte rune
r, sz := utf8.DecodeRune(src[nsrc:])
if r == utf8.RuneError && sz == 1 {
// If its inconclusive due to insufficient data in
// in the source, report it
if !atEOF && !utf8.FullRune(src[nsrc:]) {
e = transform.ErrShortSrc
break
}
}
if c, ok := d.cmap.bytes[r]; ok {
dst[ndst] = c
} else {
dst[ndst] = encoding.ASCIISub
}
nsrc += sz
ndst++
}
return ndst, nsrc, e
}

34
encoding/latin1.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 encoding
import (
"golang.org/x/text/encoding"
)
// ISO8859_1 represents the 8-bit ISO8859-1 scheme. It decodes directly to
// UTF-8 without change, as all ISO8859-1 values are legal UTF-8.
// Unicode values less than 256 (i.e. 8 bits) map 1:1 with 8859-1.
// It encodes runes outside of that to 0x1A, the ASCII substitution character.
var ISO8859_1 encoding.Encoding
func init() {
cm := &cmap{}
cm.Init()
// No further mapping needed for ISO8859-1, as there is exactly a 1:1
// mapping between the Unicode and 8859-1 namespaces.
ISO8859_1 = cm
}

View File

@ -1,5 +1,3 @@
// +build windows nacl plan9
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -16,10 +14,22 @@
package encoding
func Register() {
// So Windows is only UTF-16LE (yay!)
import (
"golang.org/x/text/encoding"
)
// Other platforms that don't use termios/terminfo are pretty much unsupported.
// Therefore, we shouldn't bring in all this stuff because it creates a lot of
// bloat for those platforms. So, just punt.
// ISO8859_9 represents the 8-bit ISO8859-1 scheme. It decodes to UTF-8
// unchanged for all 256 positions except for six positions.
var ISO8859_9 encoding.Encoding
func init() {
cm := &cmap{}
cm.Init()
cm.Map(0xD0, 'Ğ')
cm.Map(0xDD, 'İ')
cm.Map(0xDE, 'Ş')
cm.Map(0xF0, 'ğ')
cm.Map(0xFD, 'ı')
cm.Map(0xFE, 'ş')
ISO8859_9 = cm
}

View File

@ -20,23 +20,48 @@ import (
)
var (
ErrNoDatabase = errors.New("terminal database not found")
// ErrTermNotFound indicates that a suitable terminal entry could
// not be found. This can result from either not having TERM set,
// or from the TERM failing to support certain minimal functionality,
// in particular absolute cursor addressability (the cup capability)
// is required. For example, legacy "adm3" lacks this capability,
// whereas the slightly newer "adm3a" supports it. This failure
// occurs most often with "dumb".
ErrTermNotFound = errors.New("terminal entry not found")
// ErrNoScreen indicates that no suitable screen could be found.
// This may result from attempting to run on a platform where there
// is no support for either termios or console I/O (such as nacl),
// or from running in an environment where there is no access to
// a suitable console/terminal device. (For example, running on
// without a controlling TTY or with no /dev/tty on POSIX platforms.)
ErrNoScreen = errors.New("no suitable screen available")
// ErrNoCharset indicates that the locale environment the
// program is not supported by the program, because no suitable
// encoding was found for it. This problem never occurs if
// the environment is UTF-8 or UTF-16.
ErrNoCharset = errors.New("character set not supported")
)
// An EventError is an event representing some sort of error, and carries
// an error payload.
type EventError struct {
t time.Time
err error
}
// When returns the time when the event was created.
func (ev *EventError) When() time.Time {
return ev.t
}
// Error implements the error.
func (ev *EventError) Error() string {
return ev.err.Error()
}
// NewEventError creates an ErrorEvent with the given error payload.
func NewEventError(err error) *EventError {
return &EventError{t: time.Now(), err: err}
}

View File

@ -25,13 +25,17 @@ type EventInterrupt struct {
v interface{}
}
// When returns the time when this event was created.
func (ev *EventInterrupt) When() time.Time {
return ev.t
}
// Data is used to obtain the opaque event payload.
func (ev *EventInterrupt) Data() interface{} {
return ev.v
}
// NewEventInterrupt creates an EventInterrupt with the given payload.
func NewEventInterrupt(data interface{}) *EventInterrupt {
return &EventInterrupt{t: time.Now(), v: data}
}

6
key.go
View File

@ -70,11 +70,11 @@ func (ev *EventKey) Key() Key {
return ev.key
}
// ModMask returns the modifiers that were present with the key press. Note
// Modifiers returns the modifiers that were present with the key press. Note
// that not all platforms and terminals support this equally well, and some
// cases we will not not know for sure. Hence, applications should avoid
// using this in most circumstances.
func (ev *EventKey) Mod() ModMask {
func (ev *EventKey) Modifiers() ModMask {
return ev.mod
}
@ -397,5 +397,5 @@ const (
KeyEscape = KeyESC
KeyEnter = KeyCR
KeySpace = KeySP
KeyBackspace2 = KeyDEL // This is delete back, not forward
KeyBackspace2 = KeyDEL
)

View File

@ -43,6 +43,7 @@ go build mkinfo.go
# first make the database.go file
echo "Building Go database"
./mkinfo -go database.go `cat models.txt aliases.txt`
go fmt database.go
echo "Building JSON database"

View File

@ -269,7 +269,7 @@ func dotGoAddArr(w io.Writer, n string, a []string) {
if len(a) == 0 {
return
}
fmt.Fprintf(w, " %-13s []string{ ", n+":")
fmt.Fprintf(w, " %-13s []string{", n+":")
did := false
for _, b := range a {
if did {
@ -278,7 +278,7 @@ func dotGoAddArr(w io.Writer, n string, a []string) {
did = true
fmt.Fprintf(w, "%q", b)
}
fmt.Fprintln(w, " },")
fmt.Fprintln(w, "},")
}
func dotGoHeader(w io.Writer) {

View File

@ -22,18 +22,18 @@ import (
// events. It is also sent on mouse motion events - if the terminal supports
// it. We make every effort to ensure that mouse release events are delivered.
// Hence, click drag can be identified by a motion event with the mouse down,
// without any intervening button release.
// without any intervening button release. On some terminals only the initiating
// press and terminating release event will be delivered.
//
// Mouse wheel events, when reported, may appear on their own as individual
// impulses; that is, there will normally not be a release event delivered
// for mouse wheel movements.
//
// Most terminals cannot report the state of more than one button at a time --
// and many cannot report motion events. (Windows consoles, modern XTerm, and
// modern emulators like iTerm2, are known to support this well, though.)
// and some cannot report motion events unless a button is pressed.
//
// Applications can inspect the time between events to figure out double clicks
// and such.
// Applications can inspect the time between events to resolve double or
// triple clicks.
type EventMouse struct {
t time.Time
btn ButtonMask
@ -42,11 +42,12 @@ type EventMouse struct {
y int
}
// When returns the time when this EventMouse was created.
func (ev *EventMouse) When() time.Time {
return ev.t
}
// ButtonMask returns the list of buttons that were pressed.
// Buttons returns the list of buttons that were pressed or wheel motions.
func (ev *EventMouse) Buttons() ButtonMask {
return ev.btn
}
@ -69,26 +70,27 @@ func NewEventMouse(x, y int, btn ButtonMask, mod ModMask) *EventMouse {
return &EventMouse{t: time.Now(), x: x, y: y, btn: btn, mod: mod}
}
// BtnMask is a mask of mouse buttons.
// ButtonMask is a mask of mouse buttons and wheel events. Mouse button presses
// are normally delivered as both press and release events. Mouse wheel events
// are normally just single impulse events. Windows supports up to eight
// separate buttons plus all four wheel directions, but XTerm can only support
// mouse buttons 1-3 and wheel up/down. Its not unheard of for terminals
// to support only one or two buttons (think Macs). Old terminals, and true
// emulations (such as vt100) won't support mice at all, of course.
type ButtonMask int16
const (
// Button1 is usually the left mouse button.
Button1 ButtonMask = 1 << iota
// Button2 is usually the middle mouse button, for three button mice.
Button2
// Button3 is usually the right mouse button on 2 or 3 button mice.
Button3
Button4
Button5
Button1 ButtonMask = 1 << iota // Usually left mouse button.
Button2 // Usually the middle mouse button.
Button3 // Usually the right mouse button.
Button4 // Often a side button (thumb/next).
Button5 // Often a side button (thumb/prev).
Button6
Button7
Button8
// WheelUp indicates the wheel being moved up, away from the user.
WheelUp
// WheelDown indicates the wheel being moved down, towards the user.
WheelDown
WheelLeft
WheelRight
WheelUp // Wheel motion up/away from user.
WheelDown // Wheel motion down/towards user.
WheelLeft // Wheel motion to left.
WheelRight // Wheel motion to right.
ButtonNone ButtonMask = 0 // No button or wheel events.
)
const ButtonNone ButtonMask = 0

View File

@ -25,14 +25,18 @@ type EventResize struct {
h int
}
// NewEventResize creates an EventResize with the new updated window size,
// which is given in character cells.
func NewEventResize(width, height int) *EventResize {
return &EventResize{t: time.Now(), w: width, h: height}
}
// When returns the time when the Event was created.
func (ev *EventResize) When() time.Time {
return ev.t
}
// Size returns the new window size as width, height in character cells.
func (ev *EventResize) Size() (int, int) {
return ev.w, ev.h
}

View File

@ -29,24 +29,8 @@ type Screen interface {
// filling the screen with spaces, using the global default style.
Clear()
// SetCell sets the cell at the given location.
// The ch list contains at most one rune of width > 0, and the
// runes with zero width (combining marks) must follow the first
// non-zero width character. (If only combining marks are present,
// a space character will be filled in.)
//
// Note that double wide runes occupy two cells, and attempts to
// place a character at the immediately adjacent cell will have
// undefined effects. Double wide runes that are printed in the
// last column will be replaced with a single width space on output.
//
// SetCell may change the cursor location. Callers should explictly
// save and restore cursor state if neccesary. The cursor visibility
// is not affected, so callers probably should hide the cursor when
// calling this.
//
// Note that the results will not be visible until either Show() or
// Sync() are called.
// SetCell is an older API, and will be removed. Please use
// SetContent instead; SetCell is implemented in terms of SetContent.
SetCell(x int, y int, style Style, ch ...rune)
// GetContent returns the contents at the given location. If the
@ -54,14 +38,23 @@ type Screen interface {
// StyleDefault. Note that the contents returned are logical contents
// and may not actually be what is displayed, but rather are what will
// be displayed if Show() or Sync() is called. The width is the width
// in screen cells - this should either be 1 or 2.
// in screen cells; most often this will be 1, but some East Asian
// characters require two cells.
GetContent(x, y int) (mainc rune, combc []rune, style Style, width int)
// SetContent sets the contents of the given cell location. If
// the coordinates are out of range, then the operation is ignored.
//
// The first rune is the primary non-zero width rune. The array
// that follows is a possible list of combining characters to append.
// that follows is a possible list of combining characters to append,
// and will usually be nil (no combining characters.)
//
// The results are not displayd until Show() or Sync() is called.
//
// Note that wide (East Asian full width) runes occupy two cells,
// and attempts to place character at next cell to the right will have
// undefined effects. Wide runes that are printed in the
// last column will be replaced with a single width space on output.
SetContent(x int, y int, mainc rune, combc []rune, style Style)
// SetStyle sets the default style to use when clearing the screen
@ -101,15 +94,18 @@ type Screen interface {
// return 0.
Colors() int
// Show takes any output that was deferred due to buffering, and
// flushes it to the physical display. It does so in the most
// efficient and least visually disruptive manner possible.
// Show makes all the content changes made using SetContent() visible
// on the display.
//
// It does so in the most efficient and least visually disruptive
// manner possible.
Show()
// Sync works like Show(), but it updates every visible cell on the
// physical display, assuming that it is not synchronized with any
// internal model. This may be both expensive and visually jarring,
// so it should only be used when believed to actually be necessary.
//
// Typically this is called as a result of a user-requested redraw
// (e.g. to clear up on screen corruption caused by some other program),
// or during a resize event.

View File

@ -23,12 +23,12 @@ package tcell
// Note that not all terminals can display all colors or attributes, and
// many might have specific incompatibilities between specific attributes
// and color combinations.
//
// To use Style, just declare a variable of its type.
type Style int64
func NewStyle() Style {
return Style(0)
}
// StyleDefault represents a default style, based upon the context.
// It is the zero value.
const StyleDefault Style = 0
// Foreground returns a new style based on s, with the foreground color set
@ -54,9 +54,8 @@ func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) {
func (s Style) setAttrs(attrs Style, on bool) Style {
if on {
return s | (attrs << 32)
} else {
return s &^ (attrs << 32)
}
return s &^ (attrs << 32)
}
// Normal returns the style with all attributes disabled.
@ -89,7 +88,7 @@ func (s Style) Reverse(on bool) Style {
return s.setAttrs(Style(AttrReverse), on)
}
// Reverse returns a new style based on s, with the underline attribute set
// Underline returns a new style based on s, with the underline attribute set
// as requested.
func (s Style) Underline(on bool) Style {
return s.setAttrs(Style(AttrUnderline), on)

View File

@ -188,9 +188,8 @@ func (st stack) PushBool(i bool) stack {
func nextch(s string, index int) (byte, int) {
if index < len(s) {
return s[index], index + 1
} else {
return 0, index
}
return 0, index
}
// static vars
@ -461,6 +460,12 @@ func (t *Terminfo) TParm(s string, p ...int) string {
return out.String()
}
// TPuts emits the string to the writer, but expands inline padding
// indications (of the form $<[delay]> where [delay] is msec) to
// a suitable number of padding characters (usually null bytes) based
// upon the supplied baud. At high baud rates, more padding characters
// will be inserted. All Terminfo based strings should be emitted using
// this function.
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
for {
beg := strings.Index(s, "$<")
@ -515,7 +520,7 @@ func (t *Terminfo) TGoto(col, row int) string {
return t.TParm(t.SetCursor, row, col)
}
// Color returns a string corresponding to the given foreground and background
// TColor returns a string corresponding to the given foreground and background
// colors. Either fg or bg can be set to -1 to elide.
func (t *Terminfo) TColor(fg, bg Color) string {
fi := int(fg - 1)
@ -567,9 +572,10 @@ func AddTerminfo(t *Terminfo) {
}
func loadFromFile(fname string, term string) (*Terminfo, error) {
if f, e := os.Open(fname); e != nil {
return nil, ErrNoDatabase
} else {
f, e := os.Open(fname)
if e != nil {
return nil, e
}
d := json.NewDecoder(f)
for {
t := &Terminfo{}
@ -583,7 +589,6 @@ func loadFromFile(fname string, term string) (*Terminfo, error) {
return t, nil
}
}
}
}
// LookupTerminfo attemps to find a definition for the named $TERM.
@ -599,22 +604,18 @@ func LookupTerminfo(name string) (*Terminfo, error) {
dblock.Unlock()
if t == nil {
var e error
// Load the database located here. Its expected that TCELLSDB
// points either to a single JSON file, or to a directory of
// of files all of which should be loaded.
if pth := os.Getenv("TCELLDB"); pth != "" {
t, e = loadFromFile(pth, name)
t, _ = loadFromFile(pth, name)
} else {
pth = path.Join(os.Getenv("GOPATH"), "src",
"github.com", "gdamore", "tcell",
"database.json")
t, e = loadFromFile(pth, name)
t, _ = loadFromFile(pth, name)
}
if t == nil {
return nil, e
}
if t != nil {
dblock.Lock()
terminfos[name] = t

View File

@ -16,7 +16,6 @@ package tcell
import (
"bytes"
"errors"
"io"
"os"
"strconv"
@ -91,17 +90,11 @@ func (t *tScreen) Init() error {
t.charset = "UTF-8"
t.charset = t.getCharset()
switch t.charset {
case "UTF-8", "US-ASCII":
t.encoder = nil
t.decoder = nil
default:
if enc := GetEncoding(t.charset); enc != nil {
t.encoder = enc.NewEncoder()
t.decoder = enc.NewDecoder()
} else {
return errors.New("no support for charset " + t.charset)
}
return ErrNoCharset
}
ti := t.ti
@ -292,12 +285,6 @@ func (t *tScreen) SetCell(x, y int, style Style, ch ...rune) {
func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
// all the character sets we care about are ASCII supersets
if r < 0x80 {
buf = append(buf, byte(r))
return buf
}
enc := t.encoder
if enc == nil {
// This is probably ASCII. Only append a filler character
@ -341,7 +328,6 @@ func (t *tScreen) drawCell(x, y int) int {
ti := t.ti
mainc, combc, style, width := t.cells.GetContent(x, y)
if !t.cells.Dirty(x, y) {
return width
}
@ -394,14 +380,6 @@ func (t *tScreen) drawCell(x, y int) int {
var str string
switch t.charset {
case "UTF-8":
str = string(mainc)
if combc != nil {
str += string(combc)
}
default:
// Non-Unicode systems. Make do.
buf := make([]byte, 0, 6)
buf = t.encodeRune(mainc, buf)
@ -413,7 +391,7 @@ func (t *tScreen) drawCell(x, y int) int {
if width > 1 && str == "?" {
// No FullWidth character support
str = "? "
}
t.cx = -1
}
// XXX: check for hazeltine not being able to display ~
@ -426,6 +404,9 @@ func (t *tScreen) drawCell(x, y int) int {
io.WriteString(t.out, str)
t.cx += width
t.cells.SetDirty(x, y, false)
if width > 1 {
t.cx = -1
}
return width
}
@ -502,6 +483,14 @@ func (t *tScreen) draw() {
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
if width > 1 {
if x+1 < t.w {
// this is necessary so that if we ever
// go back to drawing that cell, we
// actually will *draw* it.
t.cells.SetDirty(x+1, y, true)
}
}
x += width - 1
}
}
@ -750,7 +739,7 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer) (bool, bool) {
state = 3
case '-':
if state != 3 || state != 4 || state != 5 {
if state != 3 && state != 4 && state != 5 {
return false, false
}
if dig || neg {
@ -897,45 +886,30 @@ func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) {
}
if b[0] < 0x80 {
// No encodings start with low numbered values
// Low numbered values are control keys, not runes.
return false, false
}
switch t.charset {
case "UTF-8":
if utf8.FullRune(b) {
r, _, e := buf.ReadRune()
if e == nil {
ev := NewEventKey(KeyRune, r, ModNone)
t.PostEvent(ev)
return true, true
}
}
case "US-ASCII":
// ASCII cannot generate this, so most likely it was
// entered as an Alt sequence
ev := NewEventKey(KeyRune, rune(b[0]-128), ModAlt)
t.PostEvent(ev)
buf.ReadByte()
return true, true
default:
utfb := make([]byte, 12)
for l := 1; l <= len(b); l++ {
t.decoder.Reset()
nout, nin, _ := t.decoder.Transform(utfb, b[:l], true)
nout, nin, e := t.decoder.Transform(utfb, b[:l], true)
if e == transform.ErrShortSrc {
continue
}
if nout != 0 {
if r, _ := utf8.DecodeRune(utfb[:nout]); r != utf8.RuneError {
r, _ := utf8.DecodeRune(utfb[:nout])
if r != utf8.RuneError {
ev := NewEventKey(KeyRune, r, ModNone)
t.PostEvent(ev)
}
for eat := 0; eat < nin; eat++ {
for nin > 0 {
buf.ReadByte()
nin--
}
return true, true
}
}
}
// Looks like potential escape
return true, false
}
@ -983,8 +957,8 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
if partials == 0 || expire {
// Nothing was going to match, or we timed out
// waiting for more data -- just deliver the characters
// to the app & let them sort it out. Possibly we should only
// do this for control characters such like ESC.
// to the app & let them sort it out. Possibly we
// should only do this for control characters like ESC.
by, _ := buf.ReadByte()
ev := NewEventKey(KeyRune, rune(by), ModNone)
t.PostEvent(ev)

View File

@ -227,9 +227,8 @@ func (t *tScreen) getCharset() string {
func (t *tScreen) getWinSize() (int, int, error) {
var cx, cy C.int
if r, e := C.getwinsize(C.int(t.out.Fd()), &cx, &cy); r == 0 {
return int(cx), int(cy), nil
} else {
if r, e := C.getwinsize(C.int(t.out.Fd()), &cx, &cy); r != 0 {
return 0, 0, e
}
return int(cx), int(cy), nil
}

View File

@ -16,16 +16,12 @@
package tcell
import (
"errors"
)
// This stub file is for systems that have no termios.
type termiosPrivate struct{}
func (t *tScreen) termioInit() error {
return errors.New("no termios support on this platform")
return ErrNoScreen
}
func (t *tScreen) termioFini() {
@ -36,5 +32,5 @@ func (t *tScreen) getCharset() string {
}
func (t *tScreen) getWinSize() (int, int, error) {
return 0, 0, errors.New("no termios support on this platform")
return 0, 0, ErrNoScreen
}

View File

@ -16,26 +16,21 @@
package tcell
// On win32 we don't have support for termios. We probably could, and
// On Windows we don't have support for termios. We probably could, and
// may should, in a cygwin type environment. Its not clear how to make
// this all work nicely with both cygwin and Windows console, so we
// decline to do so here.
import (
"errors"
)
func (t *tScreen) termioInit() error {
return errors.New("no termios on Windows")
return ErrNoScreen
}
func (t *tScreen) termioFini() {
return
}
func (t *tScreen) getWinSize() (int, int, error) {
return 0, 0, errors.New("no temrios on Windows")
return 0, 0, ErrNoScreen
}
func (t *tScreen) getCharset() string {

34
utf8.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2015 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tcell
import (
"golang.org/x/text/encoding"
"golang.org/x/text/transform"
)
type validUtf8 struct{}
// UTF8 is an encoding for UTF-8. All it does is verify that the UTF-8
// in is valid.
var UTF8 encoding.Encoding = validUtf8{}
func (validUtf8) NewDecoder() transform.Transformer {
return encoding.UTF8Validator
}
func (validUtf8) NewEncoder() transform.Transformer {
return encoding.UTF8Validator
}