From 7322e40c2645a6f82d5d40c537dc4cc4b52370a1 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Wed, 7 Oct 2015 20:15:33 -0700 Subject: [PATCH] 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. --- _demos/mouse.go | 15 +- ascii.go | 91 +++++++ cell.go | 37 +-- console_stub.go | 6 +- console_win.go | 28 +- database.go | 436 ++++++++++++++++---------------- encoding.go | 71 +++++- encoding/all.go | 36 ++- encoding/charmap.go | 144 +++++++++++ encoding/latin1.go | 34 +++ encoding/{stub.go => latin5.go} | 24 +- errors.go | 27 +- interrupt.go | 4 + key.go | 6 +- mkdatabase.sh | 1 + mkinfo.go | 4 +- mouse.go | 46 ++-- resize.go | 4 + screen.go | 42 ++- style.go | 13 +- terminfo.go | 47 ++-- tscreen.go | 114 ++++----- tscreen_posix.go | 5 +- tscreen_stub.go | 8 +- tscreen_win.go | 11 +- utf8.go | 34 +++ 26 files changed, 845 insertions(+), 443 deletions(-) create mode 100644 ascii.go create mode 100644 encoding/charmap.go create mode 100644 encoding/latin1.go rename encoding/{stub.go => latin5.go} (61%) create mode 100644 utf8.go diff --git a/_demos/mouse.go b/_demos/mouse.go index 2397945..e102df7 100644 --- a/_demos/mouse.go +++ b/_demos/mouse.go @@ -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 } } } diff --git a/ascii.go b/ascii.go new file mode 100644 index 0000000..4971f5b --- /dev/null +++ b/ascii.go @@ -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 +} diff --git a/cell.go b/cell.go index 6ca8c6a..b54abcb 100644 --- a/cell.go +++ b/cell.go @@ -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] diff --git a/console_stub.go b/console_stub.go index af5a2f6..fda2f09 100644 --- a/console_stub.go +++ b/console_stub.go @@ -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 } diff --git a/console_win.go b/console_win.go index 8666a05..a462d08 100644 --- a/console_win.go +++ b/console_win.go @@ -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 diff --git a/database.go b/database.go index 73fa394..a1c57dd 100644 --- a/database.go +++ b/database.go @@ -1,23 +1,23 @@ -// 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 func init() { AddTerminfo(&Terminfo{ - Name: "adm3a", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1a$<1/>", - PadChar: "\x00", - SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", - CursorBack1: "\b", - CursorUp1: "\v", - KeyUp: "\v", - KeyDown: "\n", - KeyRight: "\f", - KeyLeft: "\b", + Name: "adm3a", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1a$<1/>", + PadChar: "\x00", + SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", + CursorBack1: "\b", + CursorUp1: "\v", + KeyUp: "\v", + KeyDown: "\n", + KeyRight: "\f", + KeyLeft: "\b", }) AddTerminfo(&Terminfo{ Name: "aixterm", @@ -332,173 +332,173 @@ func init() { KeyF20: "\x1b[34~", }) AddTerminfo(&Terminfo{ - Name: "d200", - Aliases: []string{ "d200-dg" }, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\f", - AttrOff: "\x0f\x15\x1d\x1eE", - Underline: "\x14", - Bold: "\x1eD\x14", - Dim: "\x1c", - Blink: "\x0e", - Reverse: "\x1eD", - PadChar: "\x00", - SetCursor: "\x10%p2%c%p1%c", - CursorBack1: "\x19", - CursorUp1: "\x17", - KeyUp: "\x17", - KeyDown: "\x1a", - KeyRight: "\x18", - KeyLeft: "\x19", - KeyHome: "\b", - KeyF1: "\x1eq", - KeyF2: "\x1er", - KeyF3: "\x1es", - KeyF4: "\x1et", - KeyF5: "\x1eu", - KeyF6: "\x1ev", - KeyF7: "\x1ew", - KeyF8: "\x1ex", - KeyF9: "\x1ey", - KeyF10: "\x1ez", - KeyF11: "\x1e{", - KeyF12: "\x1e|", - KeyF13: "\x1e}", - KeyF14: "\x1e~", - KeyF15: "\x1ep", - KeyF16: "\x1ea", - KeyF17: "\x1eb", - KeyF18: "\x1ec", - KeyF19: "\x1ed", - KeyF20: "\x1ee", - KeyF21: "\x1ef", - KeyF22: "\x1eg", - KeyF23: "\x1eh", - KeyF24: "\x1ei", - KeyF25: "\x1ej", - KeyF26: "\x1ek", - KeyF27: "\x1el", - KeyF28: "\x1em", - KeyF29: "\x1en", - KeyF30: "\x1e`", - KeyF31: "\x1e1", - KeyF32: "\x1e2", - KeyF33: "\x1e3", - KeyF34: "\x1e4", - KeyF35: "\x1e5", - KeyF36: "\x1e6", - KeyF37: "\x1e7", - KeyF38: "\x1e8", - KeyF39: "\x1e9", - KeyF40: "\x1e:", - KeyF41: "\x1e;", - KeyF42: "\x1e<", - KeyF43: "\x1e=", - KeyF44: "\x1e>", - KeyF45: "\x1e0", - KeyF46: "\x1e!", - KeyF47: "\x1e\"", - KeyF48: "\x1e#", - KeyF49: "\x1e$", - KeyF50: "\x1e%%", - KeyF51: "\x1e&", - KeyF52: "\x1e'", - KeyF53: "\x1e(", - KeyF54: "\x1e)", - KeyF55: "\x1e*", - KeyF56: "\x1e+", - KeyF57: "\x1e,", - KeyF58: "\x1e-", - KeyF59: "\x1e.", - KeyF60: "\x1e ", - KeyClear: "\f", + Name: "d200", + Aliases: []string{"d200-dg"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\f", + AttrOff: "\x0f\x15\x1d\x1eE", + Underline: "\x14", + Bold: "\x1eD\x14", + Dim: "\x1c", + Blink: "\x0e", + Reverse: "\x1eD", + PadChar: "\x00", + SetCursor: "\x10%p2%c%p1%c", + CursorBack1: "\x19", + CursorUp1: "\x17", + KeyUp: "\x17", + KeyDown: "\x1a", + KeyRight: "\x18", + KeyLeft: "\x19", + KeyHome: "\b", + KeyF1: "\x1eq", + KeyF2: "\x1er", + KeyF3: "\x1es", + KeyF4: "\x1et", + KeyF5: "\x1eu", + KeyF6: "\x1ev", + KeyF7: "\x1ew", + KeyF8: "\x1ex", + KeyF9: "\x1ey", + KeyF10: "\x1ez", + KeyF11: "\x1e{", + KeyF12: "\x1e|", + KeyF13: "\x1e}", + KeyF14: "\x1e~", + KeyF15: "\x1ep", + KeyF16: "\x1ea", + KeyF17: "\x1eb", + KeyF18: "\x1ec", + KeyF19: "\x1ed", + KeyF20: "\x1ee", + KeyF21: "\x1ef", + KeyF22: "\x1eg", + KeyF23: "\x1eh", + KeyF24: "\x1ei", + KeyF25: "\x1ej", + KeyF26: "\x1ek", + KeyF27: "\x1el", + KeyF28: "\x1em", + KeyF29: "\x1en", + KeyF30: "\x1e`", + KeyF31: "\x1e1", + KeyF32: "\x1e2", + KeyF33: "\x1e3", + KeyF34: "\x1e4", + KeyF35: "\x1e5", + KeyF36: "\x1e6", + KeyF37: "\x1e7", + KeyF38: "\x1e8", + KeyF39: "\x1e9", + KeyF40: "\x1e:", + KeyF41: "\x1e;", + KeyF42: "\x1e<", + KeyF43: "\x1e=", + KeyF44: "\x1e>", + KeyF45: "\x1e0", + KeyF46: "\x1e!", + KeyF47: "\x1e\"", + KeyF48: "\x1e#", + KeyF49: "\x1e$", + KeyF50: "\x1e%%", + KeyF51: "\x1e&", + KeyF52: "\x1e'", + KeyF53: "\x1e(", + KeyF54: "\x1e)", + KeyF55: "\x1e*", + KeyF56: "\x1e+", + KeyF57: "\x1e,", + KeyF58: "\x1e-", + KeyF59: "\x1e.", + KeyF60: "\x1e ", + KeyClear: "\f", }) AddTerminfo(&Terminfo{ - Name: "d210", - Aliases: []string{ "d214" }, - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[2J", - AttrOff: "\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[4;7m", - Dim: "\x1b[2m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyHome: "\x1b[H", - KeyF1: "\x1b[001z", - KeyF2: "\x1b[002z", - KeyF3: "\x1b[003z", - KeyF4: "\x1b[004z", - KeyF5: "\x1b[005z", - KeyF6: "\x1b[006z", - KeyF7: "\x1b[007z", - KeyF8: "\x1b[008z", - KeyF9: "\x1b[009z", - KeyF10: "\x1b[010z", - KeyF11: "\x1b[011z", - KeyF12: "\x1b[012z", - KeyF13: "\x1b[013z", - KeyF14: "\x1b[014z", - KeyF15: "\x1b[000z", - KeyF16: "\x1b[101z", - KeyF17: "\x1b[102z", - KeyF18: "\x1b[103z", - KeyF19: "\x1b[104z", - KeyF20: "\x1b[105z", - KeyF21: "\x1b[106z", - KeyF22: "\x1b[107z", - KeyF23: "\x1b[108z", - KeyF24: "\x1b[109z", - KeyF25: "\x1b[110z", - KeyF26: "\x1b[111z", - KeyF27: "\x1b[112z", - KeyF28: "\x1b[113z", - KeyF29: "\x1b[114z", - KeyF30: "\x1b[100z", - KeyF31: "\x1b[201z", - KeyF32: "\x1b[202z", - KeyF33: "\x1b[203z", - KeyF34: "\x1b[204z", - KeyF35: "\x1b[205z", - KeyF36: "\x1b[206z", - KeyF37: "\x1b[207z", - KeyF38: "\x1b[208z", - KeyF39: "\x1b[209z", - KeyF40: "\x1b[210z", - KeyF41: "\x1b[211z", - KeyF42: "\x1b[212z", - KeyF43: "\x1b[213z", - KeyF44: "\x1b[214z", - KeyF45: "\x1b[200z", - KeyF46: "\x1b[301z", - KeyF47: "\x1b[302z", - KeyF48: "\x1b[303z", - KeyF49: "\x1b[304z", - KeyF50: "\x1b[305z", - KeyF51: "\x1b[306z", - KeyF52: "\x1b[307z", - KeyF53: "\x1b[308z", - KeyF54: "\x1b[309z", - KeyF55: "\x1b[310z", - KeyF56: "\x1b[311z", - KeyF57: "\x1b[312z", - KeyF58: "\x1b[313z", - KeyF59: "\x1b[314z", - KeyF60: "\x1b[300z", - KeyPrint: "\x1b[i", - KeyClear: "\x1b[2J", + Name: "d210", + Aliases: []string{"d214"}, + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[2J", + AttrOff: "\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[4;7m", + Dim: "\x1b[2m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1b[A", + KeyDown: "\x1b[B", + KeyRight: "\x1b[C", + KeyLeft: "\x1b[D", + KeyHome: "\x1b[H", + KeyF1: "\x1b[001z", + KeyF2: "\x1b[002z", + KeyF3: "\x1b[003z", + KeyF4: "\x1b[004z", + KeyF5: "\x1b[005z", + KeyF6: "\x1b[006z", + KeyF7: "\x1b[007z", + KeyF8: "\x1b[008z", + KeyF9: "\x1b[009z", + KeyF10: "\x1b[010z", + KeyF11: "\x1b[011z", + KeyF12: "\x1b[012z", + KeyF13: "\x1b[013z", + KeyF14: "\x1b[014z", + KeyF15: "\x1b[000z", + KeyF16: "\x1b[101z", + KeyF17: "\x1b[102z", + KeyF18: "\x1b[103z", + KeyF19: "\x1b[104z", + KeyF20: "\x1b[105z", + KeyF21: "\x1b[106z", + KeyF22: "\x1b[107z", + KeyF23: "\x1b[108z", + KeyF24: "\x1b[109z", + KeyF25: "\x1b[110z", + KeyF26: "\x1b[111z", + KeyF27: "\x1b[112z", + KeyF28: "\x1b[113z", + KeyF29: "\x1b[114z", + KeyF30: "\x1b[100z", + KeyF31: "\x1b[201z", + KeyF32: "\x1b[202z", + KeyF33: "\x1b[203z", + KeyF34: "\x1b[204z", + KeyF35: "\x1b[205z", + KeyF36: "\x1b[206z", + KeyF37: "\x1b[207z", + KeyF38: "\x1b[208z", + KeyF39: "\x1b[209z", + KeyF40: "\x1b[210z", + KeyF41: "\x1b[211z", + KeyF42: "\x1b[212z", + KeyF43: "\x1b[213z", + KeyF44: "\x1b[214z", + KeyF45: "\x1b[200z", + KeyF46: "\x1b[301z", + KeyF47: "\x1b[302z", + KeyF48: "\x1b[303z", + KeyF49: "\x1b[304z", + KeyF50: "\x1b[305z", + KeyF51: "\x1b[306z", + KeyF52: "\x1b[307z", + KeyF53: "\x1b[308z", + KeyF54: "\x1b[309z", + KeyF55: "\x1b[310z", + KeyF56: "\x1b[311z", + KeyF57: "\x1b[312z", + KeyF58: "\x1b[313z", + KeyF59: "\x1b[314z", + KeyF60: "\x1b[300z", + KeyPrint: "\x1b[i", + KeyClear: "\x1b[2J", }) AddTerminfo(&Terminfo{ Name: "dtterm", @@ -557,7 +557,7 @@ func init() { }) AddTerminfo(&Terminfo{ Name: "Eterm", - Aliases: []string{ "Eterm-color" }, + Aliases: []string{"Eterm-color"}, Columns: 80, Lines: 24, Colors: 8, @@ -725,21 +725,21 @@ func init() { KeyHelp: "\x1b[28~", }) AddTerminfo(&Terminfo{ - Name: "eterm", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - AttrOff: "\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", + Name: "eterm", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "\x1b[H\x1b[J", + EnterCA: "\x1b7\x1b[?47h", + ExitCA: "\x1b[2J\x1b[?47l\x1b8", + AttrOff: "\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Reverse: "\x1b[7m", + PadChar: "\x00", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", }) AddTerminfo(&Terminfo{ Name: "gnome", @@ -951,7 +951,7 @@ func init() { }) AddTerminfo(&Terminfo{ Name: "hpterm", - Aliases: []string{ "X-hpterm" }, + Aliases: []string{"X-hpterm"}, Columns: 80, Lines: 24, Bell: "\a", @@ -990,20 +990,20 @@ func init() { KeyClear: "\x1bJ", }) AddTerminfo(&Terminfo{ - Name: "hz1500", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "~\x1c", - PadChar: "\x00", - SetCursor: "~\x11%p2%p2%?%{30}%>%t%' '%+%;%'`'%+%c%p1%'`'%+%c", - CursorBack1: "\b", - CursorUp1: "~\f", - KeyUp: "~\f", - KeyDown: "\n", - KeyRight: "\x10", - KeyLeft: "\b", - KeyHome: "~\x12", + Name: "hz1500", + Columns: 80, + Lines: 24, + Bell: "\a", + Clear: "~\x1c", + PadChar: "\x00", + SetCursor: "~\x11%p2%p2%?%{30}%>%t%' '%+%;%'`'%+%c%p1%'`'%+%c", + CursorBack1: "\b", + CursorUp1: "~\f", + KeyUp: "~\f", + KeyDown: "\n", + KeyRight: "\x10", + KeyLeft: "\b", + KeyHome: "~\x12", }) AddTerminfo(&Terminfo{ Name: "konsole", @@ -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", diff --git a/encoding.go b/encoding.go index 37b3002..0fe3f86 100644 --- a/encoding.go +++ b/encoding.go @@ -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 +} diff --git a/encoding/all.go b/encoding/all.go index b2a38cb..f09fb43 100644 --- a/encoding/all.go +++ b/encoding/all.go @@ -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) + } } - } diff --git a/encoding/charmap.go b/encoding/charmap.go new file mode 100644 index 0000000..f899f2b --- /dev/null +++ b/encoding/charmap.go @@ -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 +} diff --git a/encoding/latin1.go b/encoding/latin1.go new file mode 100644 index 0000000..44373ae --- /dev/null +++ b/encoding/latin1.go @@ -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 +} diff --git a/encoding/stub.go b/encoding/latin5.go similarity index 61% rename from encoding/stub.go rename to encoding/latin5.go index f9312cb..65765da 100644 --- a/encoding/stub.go +++ b/encoding/latin5.go @@ -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 } diff --git a/errors.go b/errors.go index c4a9ad7..182d65c 100644 --- a/errors.go +++ b/errors.go @@ -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} } diff --git a/interrupt.go b/interrupt.go index 7e02b17..70dddfc 100644 --- a/interrupt.go +++ b/interrupt.go @@ -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} } diff --git a/key.go b/key.go index b9d91d1..8685339 100644 --- a/key.go +++ b/key.go @@ -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 ) diff --git a/mkdatabase.sh b/mkdatabase.sh index 76c28ae..892d783 100755 --- a/mkdatabase.sh +++ b/mkdatabase.sh @@ -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" diff --git a/mkinfo.go b/mkinfo.go index 379b421..19e8cba 100644 --- a/mkinfo.go +++ b/mkinfo.go @@ -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) { diff --git a/mouse.go b/mouse.go index 151a1d8..8221bd2 100644 --- a/mouse.go +++ b/mouse.go @@ -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 diff --git a/resize.go b/resize.go index 77ee3d1..0385673 100644 --- a/resize.go +++ b/resize.go @@ -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 } diff --git a/screen.go b/screen.go index 5262237..0fa16f5 100644 --- a/screen.go +++ b/screen.go @@ -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. diff --git a/style.go b/style.go index 001a6d3..e9f9efd 100644 --- a/style.go +++ b/style.go @@ -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) diff --git a/terminfo.go b/terminfo.go index 910252b..0f61326 100644 --- a/terminfo.go +++ b/terminfo.go @@ -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,21 +572,21 @@ func AddTerminfo(t *Terminfo) { } func loadFromFile(fname string, term string) (*Terminfo, error) { - if f, e := os.Open(fname); e != nil { - return nil, ErrNoDatabase - } else { - d := json.NewDecoder(f) - for { - t := &Terminfo{} - if e := d.Decode(t); e != nil { - if e == io.EOF { - return nil, ErrTermNotFound - } - return nil, e - } - if t.Name == term { - return t, nil + f, e := os.Open(fname) + if e != nil { + return nil, e + } + d := json.NewDecoder(f) + for { + t := &Terminfo{} + if e := d.Decode(t); e != nil { + if e == io.EOF { + return nil, ErrTermNotFound } + return nil, e + } + if t.Name == term { + return t, nil } } } @@ -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 diff --git a/tscreen.go b/tscreen.go index babc9ad..f00d831 100644 --- a/tscreen.go +++ b/tscreen.go @@ -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) - } + if enc := GetEncoding(t.charset); enc != nil { + t.encoder = enc.NewEncoder() + t.decoder = enc.NewDecoder() + } else { + 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,26 +380,18 @@ 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 := make([]byte, 0, 6) - buf = t.encodeRune(mainc, buf) - for _, r := range combc { - buf = t.encodeRune(r, buf) - } + buf = t.encodeRune(mainc, buf) + for _, r := range combc { + buf = t.encodeRune(r, buf) + } - str = string(buf) - if width > 1 && str == "?" { - // No FullWidth character support - str = "? " - } + str = string(buf) + 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,43 +886,28 @@ 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 { + utfb := make([]byte, 12) + for l := 1; l <= len(b); l++ { + t.decoder.Reset() + nout, nin, e := t.decoder.Transform(utfb, b[:l], true) + if e == transform.ErrShortSrc { + continue + } + if nout != 0 { + r, _ := utf8.DecodeRune(utfb[:nout]) + if r != utf8.RuneError { 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) - if nout != 0 { - if r, _ := utf8.DecodeRune(utfb[:nout]); r != utf8.RuneError { - ev := NewEventKey(KeyRune, r, ModNone) - t.PostEvent(ev) - } - for eat := 0; eat < nin; eat++ { - buf.ReadByte() - } - return true, true + for nin > 0 { + buf.ReadByte() + nin-- } + return true, true } } // Looks like potential escape @@ -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) diff --git a/tscreen_posix.go b/tscreen_posix.go index c6ff989..55b6f25 100644 --- a/tscreen_posix.go +++ b/tscreen_posix.go @@ -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 } diff --git a/tscreen_stub.go b/tscreen_stub.go index 116d74c..de18d0f 100644 --- a/tscreen_stub.go +++ b/tscreen_stub.go @@ -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 } diff --git a/tscreen_win.go b/tscreen_win.go index f285017..daac097 100644 --- a/tscreen_win.go +++ b/tscreen_win.go @@ -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 { diff --git a/utf8.go b/utf8.go new file mode 100644 index 0000000..8a3e36b --- /dev/null +++ b/utf8.go @@ -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 +}