mirror of
https://github.com/gdamore/tcell.git
synced 2025-04-24 13:48:51 +08:00
fixes #27 Add a test framework & test code
This commit is contained in:
parent
b19d7067f2
commit
69be119a27
50
README.md
50
README.md
@ -7,7 +7,7 @@
|
||||
[](https://gitter.im/gdamore/tcell)
|
||||
[](https://godoc.org/github.com/gdamore/tcell)
|
||||
|
||||
> _Tcell is a work in progress (Beta).
|
||||
> _Tcell is a work in progress (Gamma).
|
||||
> Please use with caution; interfaces may change in before final release.
|
||||
> That said, our confidence in Tcell's stability is increasing. If you
|
||||
> would like to use it in your own application, it is recommended that
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
Package tcell provides a cell based view for text terminals, like xterm.
|
||||
It was inspired by termbox, but differs from termbox in some important
|
||||
ways. It also adds signficant functionality beyond termbox.
|
||||
ways. It also adds substantial functionality beyond termbox.
|
||||
|
||||
## Pure Go Terminfo Database
|
||||
|
||||
@ -50,12 +50,18 @@ use with programs that use exec, or otherwise need to manipulate the
|
||||
tty streams. This model is also much closer to idiomatic Go, leading
|
||||
to fewer surprises.
|
||||
|
||||
## Richer Unicode support
|
||||
## Richer Unicode & non-Unicode support
|
||||
|
||||
Tcell includes enhanced support for Unicode, include wide characters and
|
||||
combining characters, provided your terminal can support them. Note that
|
||||
Windows terminals generally don't support the full Unicode repertoire.
|
||||
|
||||
It will also convert to and from Unicode locales, so that the program
|
||||
can work with UTF-8 internally, and get reasonable output in other locales.
|
||||
We try hard to convert to native characters on both input and output, and
|
||||
on output we even make use of the alternate character set to facilitate
|
||||
drawing certain characters.
|
||||
|
||||
## More Function Keys
|
||||
|
||||
It also has richer support for a larger number of
|
||||
@ -63,7 +69,7 @@ special keys that some terminals can send.
|
||||
|
||||
## Better color handling
|
||||
|
||||
Tcell will respect your terminal's color space as specified within your terminfo
|
||||
Tcell will respect your terminal's color space as specified within your terminfo
|
||||
entries, so that for example attempts to emit color sequences on VT100 terminals
|
||||
won't result in unintended consequences.
|
||||
|
||||
@ -71,6 +77,9 @@ In Windows mode, we support 16 colors, underline, bold, dim, and reverse,
|
||||
instead of just termbox's 8 colors with reverse. (Note that there is some
|
||||
conflation with bold/dim and colors.)
|
||||
|
||||
Tcell maps 16 colors down to 8, for Terminals that need it. (The upper
|
||||
8 colors are just brighter versions of the lower 8.)
|
||||
|
||||
## Better mouse support
|
||||
|
||||
It supports enhanced mouse tracking mode, so your application can receive
|
||||
@ -80,7 +89,8 @@ regular mouse motion events, and wheel events, if your terminal supports it.
|
||||
|
||||
I started this project originally by submitting patches to the author of
|
||||
go-termbox, but due to some fundamental differences of opinion, I thought
|
||||
it might be simpler just to start from scratch.
|
||||
it might be simpler just to start from scratch. At this point, Tcell has
|
||||
far exceeded the capabilities of termbox.
|
||||
|
||||
## Termbox compatibility
|
||||
|
||||
@ -89,13 +99,14 @@ directory. To use it, try importing "github.com/gdamore/tcell/termbox"
|
||||
instead. Most termbox-go programs will probably work without further
|
||||
modification.
|
||||
|
||||
## Working with Unicode
|
||||
## Working With Unicode
|
||||
|
||||
This version of the tcells expects that your terminal can support Unicode
|
||||
on output. That is, if you submit Unicode sequences to it, it will attempt
|
||||
send Unicode to the terminal. This works for modern xterm and other emulators,
|
||||
but legacy systems may have poor results. I'm interested to hear reports from
|
||||
folks who need support for other character sets.
|
||||
Internally Tcell uses UTF-8, just like Go. However, it understands how to
|
||||
convert to and from other character sets, using the capabilities of
|
||||
the golang.org/x/text/encoding packages. Your application must supply
|
||||
them, as the full set of the most common ones bloats the program by about
|
||||
2MB. If you're lazy, and want them all anyway, see the tcell/encoding
|
||||
sub package.
|
||||
|
||||
## Wide & Combining Characters
|
||||
|
||||
@ -108,8 +119,7 @@ results are undefined. (Normally the wide character will not be displayed.)
|
||||
|
||||
Experience has shown that the vanilla Windows 8 console application does not
|
||||
support any of these characters properly, but at least some options like
|
||||
ConEmu do support Wide characters at least. Combining characters are
|
||||
disabled for Windows in the release.
|
||||
ConEmu do support Wide characters at least.
|
||||
|
||||
## Colors
|
||||
|
||||
@ -125,9 +135,6 @@ know; it wouldn't be hard to add that if there is need.
|
||||
Reasonable attempts have been made to minimize sending data to terminals,
|
||||
avoiding repeated sequences or drawing the same cell on refresh updates.
|
||||
|
||||
Windows still needs some work here, as it can make numerous system calls
|
||||
a bit less efficiently than it could.
|
||||
|
||||
## Terminfo
|
||||
|
||||
(Not relevent for Windows users.)
|
||||
@ -177,6 +184,13 @@ consoles, all support this quite nicely. On other platforms you might
|
||||
find that only mouse click and release events are reported, with
|
||||
no intervening motion events. It really depends on your terminal.
|
||||
|
||||
## Testablity
|
||||
|
||||
There is a SimulationScreen, that can be used to simulate a real screen
|
||||
for automated testing. The supplied tests do this. The simulation contains
|
||||
event delivery, screen resizing support, and capabilities to inject events
|
||||
and examine "physical" screen contents.
|
||||
|
||||
## Platforms
|
||||
|
||||
On POSIX systems, a POSIX termios implementation with /dev/tty is required.
|
||||
@ -197,3 +211,7 @@ fully compatible consule implementation. We expect that Windows 10
|
||||
ships with a less crippled implementation than prior releases -- we haven't
|
||||
tested that, lacking Windows 10 ourselves.
|
||||
|
||||
The nacl and plan9 platforms won't work, but compilation stubs are supplied
|
||||
for folks that want to include parts of this in software targetting those
|
||||
platforms. The test screens will work, but as we don't know how to allocate
|
||||
a real screen object on those platforms, NewScreen() will fail.
|
||||
|
77
event_test.go
Normal file
77
event_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func eventLoop(s SimulationScreen, evch chan Event) {
|
||||
for {
|
||||
ev := s.PollEvent()
|
||||
if ev == nil {
|
||||
close(evch)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case evch <- ev:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouseEvents(t *testing.T) {
|
||||
|
||||
Convey("Mouse events", t, WithScreen(t, "", func(s SimulationScreen) {
|
||||
|
||||
Convey("Size should be valid", func() {
|
||||
x, y := s.Size()
|
||||
So(x, ShouldEqual, 80)
|
||||
So(y, ShouldEqual, 25)
|
||||
})
|
||||
|
||||
s.EnableMouse()
|
||||
s.InjectMouse(4, 9, Button1, ModCtrl)
|
||||
evch := make(chan Event)
|
||||
em := &EventMouse{}
|
||||
done := false
|
||||
go eventLoop(s, evch)
|
||||
|
||||
for !done {
|
||||
select {
|
||||
case ev := <-evch:
|
||||
if evm, ok := ev.(*EventMouse); ok {
|
||||
em = evm
|
||||
done = true
|
||||
}
|
||||
continue
|
||||
case <-time.After(time.Second):
|
||||
done = true
|
||||
}
|
||||
}
|
||||
Convey("We got our mouse event", func() {
|
||||
So(em, ShouldNotBeNil)
|
||||
x, y := em.Position()
|
||||
So(x, ShouldEqual, 4)
|
||||
So(y, ShouldEqual, 9)
|
||||
So(em.Buttons(), ShouldEqual, Button1)
|
||||
So(em.Modifiers(), ShouldEqual, ModCtrl)
|
||||
})
|
||||
}))
|
||||
|
||||
}
|
151
sim_test.go
Normal file
151
sim_test.go
Normal file
@ -0,0 +1,151 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func WithScreen(t *testing.T, charset string, fn func(s SimulationScreen)) func() {
|
||||
return func() {
|
||||
s := NewSimulationScreen(charset)
|
||||
So(s, ShouldNotBeNil)
|
||||
e := s.Init()
|
||||
So(e, ShouldBeNil)
|
||||
Reset(func() {
|
||||
s.Fini()
|
||||
})
|
||||
fn(s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitScreen(t *testing.T) {
|
||||
|
||||
Convey("Init a screen", t, WithScreen(t, "", func(s SimulationScreen) {
|
||||
|
||||
Convey("Size should be valid", func() {
|
||||
x, y := s.Size()
|
||||
So(x, ShouldEqual, 80)
|
||||
So(y, ShouldEqual, 25)
|
||||
})
|
||||
|
||||
Convey("Default charset is UTF-8", func() {
|
||||
So(s.CharacterSet(), ShouldEqual, "UTF-8")
|
||||
})
|
||||
|
||||
Convey("Backing size is correct", func() {
|
||||
b, x, y := s.GetContents()
|
||||
So(b, ShouldNotBeNil)
|
||||
So(x, ShouldEqual, 80)
|
||||
So(y, ShouldEqual, 25)
|
||||
So(len(b), ShouldEqual, x*y)
|
||||
})
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
func TestClearScreen(t *testing.T) {
|
||||
Convey("Clear screen", t, WithScreen(t, "", func(s SimulationScreen) {
|
||||
|
||||
s.Clear()
|
||||
b, x, y := s.GetContents()
|
||||
So(b, ShouldNotBeNil)
|
||||
So(x, ShouldEqual, 80)
|
||||
So(y, ShouldEqual, 25)
|
||||
So(len(b), ShouldEqual, x*y)
|
||||
s.Sync()
|
||||
|
||||
nmatch := 0
|
||||
for i := 0; i < x*y; i++ {
|
||||
if len(b[i].Runes) == 0 {
|
||||
nmatch++
|
||||
}
|
||||
}
|
||||
So(nmatch, ShouldEqual, x*y)
|
||||
|
||||
nmatch = 0
|
||||
for i := 0; i < x*y; i++ {
|
||||
if len(b[i].Bytes) == 1 {
|
||||
nmatch++
|
||||
}
|
||||
}
|
||||
So(nmatch, ShouldEqual, x*y)
|
||||
|
||||
nmatch = 0
|
||||
for i := 0; i < x*y; i++ {
|
||||
if b[i].Style == StyleDefault {
|
||||
nmatch++
|
||||
}
|
||||
}
|
||||
So(nmatch, ShouldEqual, x*y)
|
||||
So(b[0].Bytes[0], ShouldEqual, ' ')
|
||||
}))
|
||||
}
|
||||
|
||||
func TestSetCell(t *testing.T) {
|
||||
st := StyleDefault.Background(ColorRed).Blink(true)
|
||||
Convey("Set contents", t, WithScreen(t, "", func(s SimulationScreen) {
|
||||
s.SetCell(2, 5, st, '@')
|
||||
b, x, y := s.GetContents()
|
||||
So(len(b), ShouldEqual, x*y)
|
||||
So(x, ShouldEqual, 80)
|
||||
So(y, ShouldEqual, 25)
|
||||
s.Show()
|
||||
|
||||
sc := &b[5*80+2]
|
||||
So(len(sc.Runes), ShouldEqual, 1)
|
||||
So(len(sc.Bytes), ShouldEqual, 1)
|
||||
So(sc.Bytes[0], ShouldEqual, '@')
|
||||
So(sc.Runes[0], ShouldEqual, '@')
|
||||
So(sc.Style, ShouldEqual, st)
|
||||
}))
|
||||
}
|
||||
|
||||
func TestResize(t *testing.T) {
|
||||
st := StyleDefault.Background(ColorYellow).Underline(true)
|
||||
Convey("Resize", t, WithScreen(t, "", func(s SimulationScreen) {
|
||||
s.SetCell(2, 5, st, '&')
|
||||
b, x, y := s.GetContents()
|
||||
So(len(b), ShouldEqual, x*y)
|
||||
So(x, ShouldEqual, 80)
|
||||
So(y, ShouldEqual, 25)
|
||||
s.Show()
|
||||
|
||||
sc := &b[5*80+2]
|
||||
So(len(sc.Runes), ShouldEqual, 1)
|
||||
So(len(sc.Bytes), ShouldEqual, 1)
|
||||
So(sc.Bytes[0], ShouldEqual, '&')
|
||||
So(sc.Runes[0], ShouldEqual, '&')
|
||||
So(sc.Style, ShouldEqual, st)
|
||||
|
||||
Convey("Do resize", func() {
|
||||
s.Resize(30, 10)
|
||||
s.Show()
|
||||
b2, x2, y2 := s.GetContents()
|
||||
So(b2, ShouldNotEqual, b)
|
||||
So(x2, ShouldEqual, 30)
|
||||
So(y2, ShouldEqual, 10)
|
||||
|
||||
sc2 := &b[5*80+2]
|
||||
So(len(sc2.Runes), ShouldEqual, 1)
|
||||
So(len(sc2.Bytes), ShouldEqual, 1)
|
||||
So(sc2.Bytes[0], ShouldEqual, '&')
|
||||
So(sc2.Runes[0], ShouldEqual, '&')
|
||||
So(sc2.Style, ShouldEqual, st)
|
||||
})
|
||||
}))
|
||||
}
|
502
simulation.go
Normal file
502
simulation.go
Normal file
@ -0,0 +1,502 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// NewSimulationScreen returns a SimulationScreen. Note that
|
||||
// SimulationScreen is also a Screen.
|
||||
func NewSimulationScreen(charset string) SimulationScreen {
|
||||
if charset == "" {
|
||||
charset = "UTF-8"
|
||||
}
|
||||
s := &simscreen{charset: charset}
|
||||
return s
|
||||
}
|
||||
|
||||
// SimulationScreen represents a screen simulation. This is intended to
|
||||
// be a superset of normal Screens, but also adds some important interfaces
|
||||
// for testing.
|
||||
type SimulationScreen interface {
|
||||
// InjectKeyBytes injects a stream of bytes corresponding to
|
||||
// the native encoding (see charset). It turns true if the entire
|
||||
// set of bytes were processed and delivered as KeyEvents, false
|
||||
// if any bytes were not fully understood. Any bytes that are not
|
||||
// fully converted are discarded.
|
||||
InjectKeyBytes(buf []byte) bool
|
||||
|
||||
// InjectKey injects a key event. The rune is a UTF-8 rune, post
|
||||
// any translation.
|
||||
InjectKey(key Key, r rune, mod ModMask)
|
||||
|
||||
// InjectMouse injects a mouse event.
|
||||
InjectMouse(x, y int, buttons ButtonMask, mod ModMask)
|
||||
|
||||
// Resize resizes the underlying physical screen. It also causes
|
||||
// a resize event to be injected during the next Show() or Sync().
|
||||
// A new physical contents array will be allocated (with data from
|
||||
// the old copied), so any prior value obtained with GetContents
|
||||
// won't be used anymore
|
||||
Resize(width, height int)
|
||||
|
||||
// GetContents returns screen contents as an array of
|
||||
// cells, along with the physical width & height. Note that the
|
||||
// physical contents will be used until the next time Resize()
|
||||
// is called.
|
||||
GetContents() (cells []SimCell, width int, height int)
|
||||
|
||||
// GetCursor returns the cursor details.
|
||||
GetCursor() (x int, y int, visible bool)
|
||||
|
||||
Screen
|
||||
}
|
||||
|
||||
// SimCell represents a simulated screen cell. The purpose of this
|
||||
// is to track on screen content.
|
||||
type SimCell struct {
|
||||
// Bytes is the actual character bytes. Normally this is
|
||||
// rune data, but it could be be data in another encoding system.
|
||||
Bytes []byte
|
||||
|
||||
// Style is the style used to display the data.
|
||||
Style Style
|
||||
|
||||
// Runes is the list of runes, unadulterated, in UTF-8.
|
||||
Runes []rune
|
||||
}
|
||||
|
||||
type simscreen struct {
|
||||
logw int
|
||||
logh int
|
||||
physw int
|
||||
physh int
|
||||
style Style
|
||||
evch chan Event
|
||||
quit chan struct{}
|
||||
|
||||
front []SimCell
|
||||
back []Cell
|
||||
clear bool
|
||||
cursorx int
|
||||
cursory int
|
||||
cursorvis bool
|
||||
mouse bool
|
||||
charset string
|
||||
encoder transform.Transformer
|
||||
decoder transform.Transformer
|
||||
fillchar rune
|
||||
fillstyle Style
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *simscreen) Init() error {
|
||||
s.evch = make(chan Event, 10)
|
||||
s.fillchar = 'X'
|
||||
s.fillstyle = StyleDefault
|
||||
s.mouse = false
|
||||
s.logw = 80
|
||||
s.logh = 25
|
||||
s.physw = 80
|
||||
s.physh = 25
|
||||
s.cursorx = -1
|
||||
s.cursory = -1
|
||||
s.style = StyleDefault
|
||||
|
||||
switch s.charset {
|
||||
case "UTF-8", "US-ASCII":
|
||||
s.encoder = nil
|
||||
s.decoder = nil
|
||||
default:
|
||||
if enc := GetEncoding(s.charset); enc != nil {
|
||||
s.encoder = enc.NewEncoder()
|
||||
s.decoder = enc.NewDecoder()
|
||||
} else {
|
||||
return errors.New("no support for charset " + s.charset)
|
||||
}
|
||||
}
|
||||
|
||||
s.front = make([]SimCell, s.physw*s.physh)
|
||||
s.back = ResizeCells(nil, 0, 0, s.logw, s.logh)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *simscreen) Fini() {
|
||||
if s.quit != nil {
|
||||
close(s.quit)
|
||||
}
|
||||
s.logw = 0
|
||||
s.logh = 0
|
||||
s.physw = 0
|
||||
s.physh = 0
|
||||
s.front = nil
|
||||
s.back = nil
|
||||
}
|
||||
|
||||
func (s *simscreen) SetStyle(style Style) {
|
||||
s.Lock()
|
||||
s.style = style
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) Clear() {
|
||||
|
||||
s.Lock()
|
||||
ClearCells(s.back, s.style)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) SetCell(x, y int, style Style, ch ...rune) {
|
||||
|
||||
s.Lock()
|
||||
if x < 0 || y < 0 || x >= s.logw || y >= s.logh {
|
||||
s.Unlock()
|
||||
return
|
||||
}
|
||||
cell := &s.back[(y*s.logw)+x]
|
||||
cell.SetCell(ch, style)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) PutCell(x, y int, cell *Cell) {
|
||||
s.Lock()
|
||||
if x < 0 || y < 0 || x >= s.logw || y >= s.logh {
|
||||
s.Unlock()
|
||||
return
|
||||
}
|
||||
cp := &s.back[(y*s.logw)+x]
|
||||
cp.PutStyle(cell.Style)
|
||||
cp.PutChars(cell.Ch)
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) GetCell(x, y int) *Cell {
|
||||
s.Lock()
|
||||
if x < 0 || y < 0 || x >= s.logw || y >= s.logh {
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
cell := s.back[(y*s.logw)+x]
|
||||
s.Unlock()
|
||||
return &cell
|
||||
}
|
||||
|
||||
func (s *simscreen) drawCell(x, y int, cell *Cell) {
|
||||
if x >= s.physw || y >= s.physh || x < 0 || y < 0 {
|
||||
return
|
||||
}
|
||||
simc := &s.front[(y*s.physw)+x]
|
||||
if cell.Style == StyleDefault {
|
||||
simc.Style = s.style
|
||||
} else {
|
||||
simc.Style = cell.Style
|
||||
}
|
||||
simc.Runes = nil
|
||||
simc.Runes = append(simc.Runes, cell.Ch...)
|
||||
|
||||
// now emit runes - taking care to not overrun width with a
|
||||
// wide character, and to ensure that we emit exactly one regular
|
||||
// character followed up by any residual combing characters
|
||||
|
||||
width := int(cell.Width)
|
||||
simc.Bytes = nil
|
||||
|
||||
if len(cell.Ch) == 0 {
|
||||
simc.Bytes = []byte{' '}
|
||||
return
|
||||
}
|
||||
if width > 1 && x >= s.physw-1 {
|
||||
simc.Runes = []rune{' '}
|
||||
simc.Bytes = []byte{' '}
|
||||
return
|
||||
}
|
||||
|
||||
enc := s.encoder
|
||||
ubuf := make([]byte, 12)
|
||||
lbuf := make([]byte, 12)
|
||||
|
||||
for _, r := range simc.Runes {
|
||||
l := utf8.EncodeRune(ubuf, r)
|
||||
if enc == nil {
|
||||
simc.Bytes = append(simc.Bytes, ubuf[:l]...)
|
||||
if s.charset == "US-ASCII" {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
nout, _, _ := enc.Transform(lbuf, ubuf[:l], true)
|
||||
if nout == 1 && lbuf[0] == '\x1a' {
|
||||
// replacement character
|
||||
if simc.Bytes == nil {
|
||||
simc.Bytes = append(simc.Bytes, '?')
|
||||
}
|
||||
} else if nout > 0 {
|
||||
simc.Bytes = append(simc.Bytes, lbuf[:nout]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *simscreen) ShowCursor(x, y int) {
|
||||
s.Lock()
|
||||
s.cursorx, s.cursory = x, y
|
||||
s.showCursor()
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) HideCursor() {
|
||||
s.ShowCursor(-1, -1)
|
||||
}
|
||||
|
||||
func (s *simscreen) showCursor() {
|
||||
|
||||
x, y := s.cursorx, s.cursory
|
||||
if x < 0 || y < 0 || x >= s.physw || y >= s.physh {
|
||||
s.cursorvis = false
|
||||
} else {
|
||||
s.cursorvis = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *simscreen) hideCursor() {
|
||||
// does not update cursor position
|
||||
s.cursorvis = false
|
||||
}
|
||||
|
||||
func (s *simscreen) Show() {
|
||||
s.Lock()
|
||||
s.resize()
|
||||
s.draw()
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) clearScreen() {
|
||||
// We emulate a hardware clear by filling with a specific pattern
|
||||
for i := range s.front {
|
||||
s.front[i].Style = s.fillstyle
|
||||
s.front[i].Runes = []rune{s.fillchar}
|
||||
s.front[i].Bytes = []byte{byte(s.fillchar)}
|
||||
}
|
||||
s.clear = false
|
||||
}
|
||||
|
||||
func (s *simscreen) draw() {
|
||||
// hide the cursor while we move stuff around
|
||||
s.hideCursor()
|
||||
|
||||
if s.clear {
|
||||
s.clearScreen()
|
||||
}
|
||||
|
||||
for row := 0; row < s.logh; row++ {
|
||||
for col := 0; col < s.logw; col++ {
|
||||
cell := &s.back[(row*s.logw)+col]
|
||||
if !cell.Dirty {
|
||||
continue
|
||||
}
|
||||
s.drawCell(col, row, cell)
|
||||
if cell.Width > 1 {
|
||||
col++
|
||||
}
|
||||
cell.Dirty = false
|
||||
}
|
||||
}
|
||||
|
||||
// restore the cursor
|
||||
s.showCursor()
|
||||
}
|
||||
|
||||
func (s *simscreen) EnableMouse() {
|
||||
s.mouse = true
|
||||
}
|
||||
|
||||
func (s *simscreen) DisableMouse() {
|
||||
s.mouse = false
|
||||
}
|
||||
|
||||
func (s *simscreen) Size() (int, int) {
|
||||
s.Lock()
|
||||
w, h := s.logw, s.logh
|
||||
s.Unlock()
|
||||
return w, h
|
||||
}
|
||||
|
||||
func (s *simscreen) resize() {
|
||||
var ev Event
|
||||
w, h := s.physw, s.physh
|
||||
if w != s.logw || h != s.logh {
|
||||
ev = NewEventResize(w, h)
|
||||
s.back = ResizeCells(s.back, s.logw, s.logh, w, h)
|
||||
s.logw = w
|
||||
s.logh = h
|
||||
}
|
||||
if ev != nil {
|
||||
s.PostEvent(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *simscreen) Colors() int {
|
||||
return 256
|
||||
}
|
||||
|
||||
func (s *simscreen) PollEvent() Event {
|
||||
select {
|
||||
case <-s.quit:
|
||||
return nil
|
||||
case ev := <-s.evch:
|
||||
return ev
|
||||
}
|
||||
}
|
||||
|
||||
func (s *simscreen) PostEvent(ev Event) {
|
||||
select {
|
||||
case s.evch <- ev:
|
||||
default:
|
||||
// drop the event on the floor
|
||||
}
|
||||
}
|
||||
|
||||
func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) {
|
||||
ev := NewEventMouse(x, y, buttons, mod)
|
||||
s.PostEvent(ev)
|
||||
}
|
||||
|
||||
func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) {
|
||||
ev := NewEventKey(KeyRune, r, ModNone)
|
||||
s.PostEvent(ev)
|
||||
}
|
||||
|
||||
func (s *simscreen) InjectKeyBytes(b []byte) bool {
|
||||
failed := false
|
||||
|
||||
outer:
|
||||
for len(b) > 0 {
|
||||
if b[0] >= ' ' && b[0] <= 0x7F {
|
||||
// printable ASCII easy to deal with -- no encodings
|
||||
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
|
||||
s.PostEvent(ev)
|
||||
b = b[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if b[0] < 0x80 {
|
||||
mod := ModNone
|
||||
// No encodings start with low numbered values
|
||||
if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ {
|
||||
mod = ModCtrl
|
||||
}
|
||||
ev := NewEventKey(Key(b[0]), 0, mod)
|
||||
s.PostEvent(ev)
|
||||
continue
|
||||
}
|
||||
|
||||
switch s.charset {
|
||||
case "UTF-8":
|
||||
r, l := utf8.DecodeRune(b)
|
||||
if r == utf8.RuneError && (l == 0 || l == 1) {
|
||||
failed = true
|
||||
// yank off one byte
|
||||
b = b[1:]
|
||||
} else {
|
||||
b = b[l:]
|
||||
ev := NewEventKey(KeyRune, r, ModNone)
|
||||
s.PostEvent(ev)
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
s.PostEvent(ev)
|
||||
b = b[1:]
|
||||
continue
|
||||
|
||||
default:
|
||||
utfb := make([]byte, len(b)*4) // worst case
|
||||
dec := s.decoder
|
||||
if dec == nil {
|
||||
failed = true
|
||||
b = b[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
// take care to consume at *most* a single rune
|
||||
for l := 1; l < len(b); l++ {
|
||||
dec.Reset()
|
||||
nout, nin, _ := dec.Transform(utfb, b[:l], true)
|
||||
|
||||
if nout != 0 {
|
||||
r, _ := utf8.DecodeRune(utfb[:nout])
|
||||
ev := NewEventKey(KeyRune, r, ModNone)
|
||||
s.PostEvent(ev)
|
||||
b = b[nin:]
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
failed = true
|
||||
b = b[1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return failed == false
|
||||
}
|
||||
|
||||
func (s *simscreen) Sync() {
|
||||
s.Lock()
|
||||
s.clear = true
|
||||
s.resize()
|
||||
InvalidateCells(s.back)
|
||||
s.draw()
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) CharacterSet() string {
|
||||
return s.charset
|
||||
}
|
||||
|
||||
func (s *simscreen) Resize(w, h int) {
|
||||
s.Lock()
|
||||
newc := make([]SimCell, w*h)
|
||||
for row := 0; row < h && row < s.physh; row++ {
|
||||
for col := 0; col < w && col < s.physw; col++ {
|
||||
newc[(row*w)+col] = s.front[(row*s.physw)+col]
|
||||
}
|
||||
}
|
||||
s.physw = w
|
||||
s.physh = h
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *simscreen) GetContents() ([]SimCell, int, int) {
|
||||
s.Lock()
|
||||
cells, w, h := s.front, s.physw, s.physh
|
||||
s.Unlock()
|
||||
return cells, w, h
|
||||
}
|
||||
|
||||
func (s *simscreen) GetCursor() (int, int, bool) {
|
||||
s.Lock()
|
||||
x, y, vis := s.cursorx, s.cursory, s.cursorvis
|
||||
s.Unlock()
|
||||
return x, y, vis
|
||||
}
|
43
style_test.go
Normal file
43
style_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestStyle(t *testing.T) {
|
||||
Convey("Style checks", t, WithScreen(t, "", func(s SimulationScreen) {
|
||||
|
||||
style := StyleDefault
|
||||
fg, bg, attr := style.Decompose()
|
||||
|
||||
So(fg, ShouldEqual, ColorDefault)
|
||||
So(bg, ShouldEqual, ColorDefault)
|
||||
So(attr, ShouldEqual, AttrNone)
|
||||
|
||||
s2 := style.
|
||||
Background(ColorRed).
|
||||
Foreground(ColorBlue).
|
||||
Blink(true)
|
||||
|
||||
fg, bg, attr = s2.Decompose()
|
||||
So(fg, ShouldEqual, ColorBlue)
|
||||
So(bg, ShouldEqual, ColorRed)
|
||||
So(attr, ShouldEqual, AttrBlink)
|
||||
}))
|
||||
}
|
90
terminfo_test.go
Normal file
90
terminfo_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestTerminfo(t *testing.T) {
|
||||
|
||||
// This terminfo entry is a stripped down version from
|
||||
// xterm-256color, but I've added some of my own entries.
|
||||
ti := &Terminfo{
|
||||
Name: "simulation_test",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Blink: "\x1b2ms$<2>",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
Mouse: "\x1b[M",
|
||||
MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
PadChar: "\x00",
|
||||
}
|
||||
|
||||
Convey("Terminfo parameter processing", t, func() {
|
||||
// This tests %i, and basic parameter strings too
|
||||
Convey("TGoto works", func() {
|
||||
s := ti.TGoto(7, 9)
|
||||
So(s, ShouldEqual, "\x1b[10;8H")
|
||||
})
|
||||
|
||||
// This tests some conditionals
|
||||
Convey("TParm colors work", func() {
|
||||
s := ti.TParm(ti.SetFg, 7)
|
||||
So(s, ShouldEqual, "\x1b[37m")
|
||||
|
||||
s = ti.TParm(ti.SetFg, 15)
|
||||
So(s, ShouldEqual, "\x1b[97m")
|
||||
|
||||
s = ti.TParm(ti.SetFg, 200)
|
||||
So(s, ShouldEqual, "\x1b[38;5;200m")
|
||||
})
|
||||
|
||||
// This tests variables
|
||||
Convey("TParm mouse mode works", func() {
|
||||
s := ti.TParm(ti.MouseMode, 1)
|
||||
So(s, ShouldEqual, "\x1b[?1000h\x1b[?1003h\x1b[?1006h")
|
||||
s = ti.TParm(ti.MouseMode, 0)
|
||||
So(s, ShouldEqual, "\x1b[?1000l\x1b[?1003l\x1b[?1006l")
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Convey("Terminfo delay handling", t, func() {
|
||||
|
||||
Convey("19200 baud", func() {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
ti.TPuts(buf, ti.Blink, 19200)
|
||||
s := string(buf.Bytes())
|
||||
So(s, ShouldEqual, "\x1b2ms\x00\x00\x00\x00")
|
||||
})
|
||||
|
||||
Convey("50 baud", func() {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
ti.TPuts(buf, ti.Blink, 50)
|
||||
s := string(buf.Bytes())
|
||||
So(s, ShouldEqual, "\x1b2ms")
|
||||
})
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user