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

fixes #10 Eliminate separate buffered implementation

fixes #9 Various API enhancements
fixes #8 Mouse demo improvements
fixes #7 Add support for handling $<delay> delays
fixes #6 mkinfo doesn't honor terminfo LINES and COLUMNS
fixes #5 Add better mouse tracking
fixes #3 Windows performance improvements
This commit is contained in:
Garrett D'Amore 2015-10-02 21:49:13 -07:00
parent 385072b4a8
commit 434fc57169
16 changed files with 3046 additions and 2264 deletions

View File

@ -8,28 +8,62 @@
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/gdamore/tcell)
> _Tcell is a work in progress (Beta).
> Please use with caution; interfaces may change in before final release._
> 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
> you drop a message to garrett@damore.org before commitment._
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.
ways. It also adds signficant functionality beyond termbox.
## Pure Go Terminfo Database
First, it includes a full parser and expander for terminfo capability strings,
so that it can avoid hard coding escape strings for formatting. It also favors
portability, and includes support for all POSIX systems, at the slight expense
of needing cgo support for terminal initializations. (This will be corrected
of needing cgo support for terminal initializations. (This may be corrected
when Go provides standard support for terminal handling via termio ioctls on
all POSIX platforms.)
all POSIX platforms.) The database itself, while built using CGO, as well
as the parser for it, is implemented in Pure Go.
Also, this code is able to operate without requiring
The database is also flexible & extensibel, and can modified by either running a
program to build the database, or hand editing of simple JSON files.
## More Portable
Tcell is portable to a wider variety of systems. It relies on standard
POSIX supported function calls (on POSIX platforms) for setting terminal
modes, which leads to improved support for a broader array of platforms.
This does come at the cost of requiring your code to be able to use CGO, but
we believe that the vastly improved portability justifies this
requirement. Note that the functions called are part of the standard C
library, so there shouldn't be any additional external requirements beyond
that required for every POSIX program.
## No async IO
Termbox code is able to operate without requiring
SIGIO signals, or asynchronous I/O, and can instead use standard Go file
objects and Go routines.
objects and Go routines. This means it should be safe, especially for
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.
It also includes enhanced support for Unicode, include wide characters and
combining characters. It also has richer support for a larger number of
## Richer 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.
## More Function Keys
It also has richer support for a larger number of
special keys that some terminals can send.
It will respect your terminal's color space as specified within your terminfo
## Better color handling
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.
@ -37,6 +71,13 @@ 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.)
## Better mouse support
It supports enhanced mouse tracking mode, so your application can receive
regular mouse motion events, and wheel events, if your terminal supports it.
## Why not just patch termbox-go?
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.
@ -124,12 +165,17 @@ is not possible as terminfo sequences are not defined.)
On Windows, the mouse works normally.
Mouse wheels are unlikely to work, and I have made no effort to support them,
given the lack of portability across emulation packages.
Mouse wheel buttons on various terminals are known to work, but the support
in terminal emulators, as well as support for various buttons and
live mouse tracking, varies widely. As a particular datum, MacOS X Terminal
does not support Mouse events at all (as of MacOS 10.10, aka Yosemite.) The
excellent iTerm application is fully supported, as is vanilla XTerm.
Only button press and release events are reported at this time. In the
future we can easily add full tracking for Windows, and for "genuine" XTerm,
but most alternatives don't have support for the full mouse motion events.
Mouse tracking with live tracking also varies widely. Current XTerm
implementations, as well as current Screen and iTerm2, and Windows
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.
## Platforms
@ -138,12 +184,16 @@ It also requires functional cgo to run. As of this writing, Cgo is available
on all POSIX Go 1.5 platforms.
Windows console mode applications are supported. Unfortunately mintty
and other cygwin style applications are not supported; modern console
applications like ConEmu support all the good features (resize, mouse, etc.)
The Windows version is a bit inefficient in its drawing, as it performs
a single WriteConsoleOutput call for each on screen cell that is changed,
but experimentally I wasn't able to notice the inefficiency.
and other cygwin style applications are not supported.
Modern console applications like ConEmu support all the good features
(resize, mouse tracking, etc.)
I haven't figured out how to cleanly resolve the dichotomy between cygwin
style termios and the Windows Console API; it seems that perhaps nobody else
has either. If anyone has suggestions, let me know!
has either. If anyone has suggestions, let me know! Really, if you're
using a Windows application, you should use the native Windows console or a
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.

View File

@ -17,6 +17,7 @@
package main
import (
"fmt"
"math/rand"
"os"
"time"
@ -24,44 +25,62 @@ import (
"github.com/gdamore/tcell"
)
func makebox(s tcell.BufferedScreen) {
func makebox(s tcell.Screen) {
w, h := s.Size()
if w == 0 || h == 0 {
return
}
glyphs := []rune { '@', '#', '&', '*', '=', '%', 'Z', 'A' }
lx := rand.Int() % w
ly := rand.Int() % h
lw := rand.Int() % (w-lx)
lh := rand.Int() % (h-ly)
st := tcell.StyleDefault.Background(tcell.Color(rand.Int() % s.Colors()))
lw := rand.Int() % (w - lx)
lh := rand.Int() % (h - ly)
st := tcell.StyleDefault
gl := ' '
if s.Colors() > 1 {
st = st.Background(tcell.Color(rand.Int() % s.Colors()))
} else {
st = st.Reverse(rand.Int() % 2 == 0)
gl = glyphs[rand.Int() % len(glyphs)]
}
for row := 0; row < lh; row++ {
for col := 0; col < lw; col++ {
s.SetCell(lx + col, ly + row, st, ' ')
s.SetCell(lx+col, ly+row, st, gl)
}
}
s.Show()
}
func main() {
s, e := tcell.NewBufferedScreen()
s, e := tcell.NewScreen()
if e != nil {
panic(e.Error())
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}
if e = s.Init(); e != nil {
panic(e.Error())
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}
s.SetStyle(tcell.StyleDefault.
Foreground(tcell.ColorBlack).
Background(tcell.ColorWhite))
s.Clear()
quit := make(chan struct{})
go func() {
for {
ev := s.PollEvent()
switch ev := ev.(type) {
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyEscape:
s.Fini()
os.Exit(0)
case tcell.KeyEscape, tcell.KeyEnter:
close(quit)
return
case tcell.KeyCtrlL:
s.Sync()
}
@ -71,8 +90,22 @@ func main() {
}
}()
cnt := 0
dur := time.Duration(0)
loop:
for {
select {
case <-quit:
break loop
case <-time.After(time.Millisecond * 50):
}
start := time.Now()
makebox(s)
time.Sleep(time.Millisecond*10)
cnt++
dur += time.Now().Sub(start)
}
s.Fini()
fmt.Printf("Finished %d boxes in %s\n", cnt, dur)
fmt.Printf("Average is %0.3f ms / box\n", (float64(dur)/float64(cnt))/1000000.0)
}

View File

@ -25,60 +25,186 @@ import (
"github.com/gdamore/tcell"
)
// This program just shows simple mouse and keyboard events. Press ESC to
// exit.
func main() {
s, e := tcell.NewBufferedScreen()
if e != nil {
fmt.Printf("oops: %v", e)
}
s.Init()
s.EnableMouse()
s.Clear()
var defStyle tcell.Style
i := 1
for _, c := range "Press ESC to exit." {
s.SetCell(i, 1, tcell.StyleDefault, c)
i++
func emitStr(s tcell.Screen, x, y int, style tcell.Style, str string) {
for _, c := range str {
s.SetCell(x, y, style, c)
x++
}
}
func drawBox(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, r rune) {
if y2 < y1 {
y1, y2 = y2, y1
}
if x2 < x1 {
x1, x2 = x2, x1
}
for {
s.Show()
ev := s.PollEvent()
st := tcell.StyleDefault.Background(tcell.ColorBrightRed)
up := tcell.StyleDefault.Background(tcell.ColorBlue)
switch ev := ev.(type) {
case *tcell.EventResize:
s.Sync()
x, y := ev.Size()
s.SetCell(x-1, y-1, st, 'R')
case *tcell.EventKey:
x, y := s.Size()
s.SetCell(x-2, y-2, st, ev.Rune())
s.SetCell(x-1, y-1, st, 'K')
if ev.Key() == tcell.KeyEscape {
s.Fini()
os.Exit(0)
}
case *tcell.EventMouse:
x, y := ev.Position()
switch ev.Buttons() {
case tcell.ButtonNone:
s.SetCell(x, y, up, '-')
case tcell.Button1:
s.SetCell(x, y, st, '1')
case tcell.Button2:
s.SetCell(x, y, st, '2')
case tcell.Button3:
s.SetCell(x, y, st, '3')
default:
s.SetCell(x, y, st, '*')
}
x, y = s.Size()
s.SetCell(x-1, y-1, st, 'M')
default:
x, y := s.Size()
s.SetCell(x-1, y-1, st, 'X')
for row := y1; row <= y2; row++ {
for col := x1; col <= x2; col++ {
s.SetCell(col, row, style, r)
}
}
}
func drawSelect(s tcell.Screen, x1, y1, x2, y2 int, sel bool) {
if y2 < y1 {
y1, y2 = y2, y1
}
if x2 < x1 {
x1, x2 = x2, x1
}
for row := y1; row <= y2; row++ {
for col := x1; col <= x2; col++ {
if cp := s.GetCell(col, row); cp != nil {
st := cp.Style
if st == tcell.StyleDefault {
st = defStyle
}
st = st.Reverse(sel)
cp.Style = st
s.PutCell(col, row, cp)
}
}
}
}
// This program just shows simple mouse and keyboard events. Press ESC to
// exit.
func main() {
s, e := tcell.NewScreen()
if e != nil {
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}
if e := s.Init(); e != nil {
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}
defStyle = tcell.StyleDefault.
Background(tcell.ColorBlack).
Foreground(tcell.ColorWhite)
s.SetStyle(defStyle)
s.EnableMouse()
s.Clear()
posfmt := "Mouse: %d, %d "
btnfmt := "Buttons: %-20s"
white := tcell.StyleDefault.
Foreground(tcell.ColorBrightWhite).Background(tcell.ColorRed)
mx, my := -1, -1
ox, oy := -1, -1
bx, by := -1, -1
w, h := s.Size()
lchar := '*'
bstr := ""
for {
drawBox(s, 1, 1, 31, 3, white, ' ')
emitStr(s, 1, 1, white, "Press ESC to exit, C to clear.")
emitStr(s, 1, 2, white, fmt.Sprintf(posfmt, mx, my))
emitStr(s, 1, 3, white, fmt.Sprintf(btnfmt, bstr))
s.Show()
bstr = ""
ev := s.PollEvent()
st := tcell.StyleDefault.Background(tcell.ColorBrightRed)
up := tcell.StyleDefault.
Background(tcell.ColorBrightBlue).
Foreground(tcell.ColorBrightGreen)
w, h = s.Size()
// always clear any old selection box
if ox >= 0 && oy >= 0 && bx >= 0 {
drawSelect(s, ox, oy, bx, by, false)
}
switch ev := ev.(type) {
case *tcell.EventResize:
s.Sync()
s.SetCell(w-1, h-1, st, 'R')
case *tcell.EventKey:
s.SetCell(w-2, h-2, st, ev.Rune())
s.SetCell(w-1, h-1, st, 'K')
if ev.Key() == tcell.KeyEscape {
s.Fini()
os.Exit(0)
} else if ev.Rune() == 'C' || ev.Rune() == 'c' {
s.Clear()
}
case *tcell.EventMouse:
x, y := ev.Position()
button := ev.Buttons()
for i := uint(0); i < 8; i++ {
if int(button) & (1 << i) != 0 {
bstr += fmt.Sprintf(" Button%d", i+1)
}
}
if button & tcell.WheelUp != 0 {
bstr += " WheelUp"
}
if button & tcell.WheelDown != 0 {
bstr += " WheelDown"
}
if button & tcell.WheelLeft != 0 {
bstr += " WheelLeft"
}
if button & tcell.WheelRight != 0 {
bstr += " WheelRight"
}
// Only buttons, not wheel events
button &= tcell.ButtonMask(0xff)
ch := '*'
if button != tcell.ButtonNone && ox < 0 {
ox, oy = x, y
}
switch ev.Buttons() {
case tcell.ButtonNone:
if ox >= 0 {
bg := tcell.Color((lchar - '0')*2+1)
drawBox(s, ox, oy, x, y,
up.Background(bg),
lchar)
ox, oy = -1, -1
bx, by = -1, -1
}
case tcell.Button1:
ch = '1'
case tcell.Button2:
ch = '2'
case tcell.Button3:
ch = '3'
case tcell.Button4:
ch = '4'
case tcell.Button5:
ch = '5'
case tcell.Button6:
ch = '6'
case tcell.Button7:
ch = '7'
case tcell.Button8:
ch = '8'
default:
ch = '*'
}
if button != tcell.ButtonNone {
bx, by = x, y
}
lchar = ch
s.SetCell(w-1, h-1, st, 'M')
mx, my = x, y
default:
s.SetCell(w-1, h-1, st, 'X')
}
if ox >= 0 && bx >= 0 {
drawSelect(s, ox, oy, bx, by, true)
}
}
}

View File

@ -1,316 +0,0 @@
// 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 (
"sync"
"github.com/mattn/go-runewidth"
)
// BufferedScreen is like the base screen, but is buffered. Each screen
// represents the root window that applications interface with.
type BufferedScreen interface {
// Init initializes the screen for use.
Init() error
// Fini finazlizes the screen also releasing resources.
Fini()
// Clear erases the screen.
Clear()
// SetCell sets the cell at the given location.
// The ch array 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(x int, y int, style Style, ch ...rune)
// ShowCursor is used to display the cursor at a given location.
ShowCursor(x int, y int)
// HideCursor is used to hide the cursor.
HideCursor()
// Size returns the screen size as width, height. This changes in
// response to a call to Clear or Flush.
Size() (int, int)
// PollEvent waits for events to arrive. Main application loops
// can generally spin on this.
PollEvent() Event
// PostEvent posts an event into the event stream.
PostEvent(Event)
// Colors returns the number of colors. All colors are assumed to
// use the ANSI color map.
Colors() int
// EnableMouse enables mouse events, if your terminal has support
// for them.
EnableMouse()
// DisableMouse disables mouse events.
DisableMouse()
// Sync synchronizes the buffered content with the screen, without
// making any assumptions about the content that is displayed.
// This is most often useful when some other program has altered the
// screen state. Because this is a full redraw, it can be visually
// jarring & expensive, and should only be done when truly needed.
Sync()
// Show writes the contents of the buffer to the physical screen.
// Only the contents that have changed will be written. This is what
// applications call to redraw the screen.
Show()
}
type cell struct {
ch []rune
dirty bool
width uint8
style Style
}
type bScreen struct {
s Screen
cells []cell
w int
h int
cursorx int
cursory int
clear bool
sync.Mutex
}
func (b *bScreen) Init() error {
if e := b.s.Init(); e != nil {
return e
}
// Allocate cells
b.w, b.h = b.s.Size()
b.cells = make([]cell, b.w*b.h)
b.cursorx = -1
b.cursory = -1
return nil
}
func (b *bScreen) Fini() {
b.Lock()
b.cells = nil
b.w = 0
b.h = 0
b.clear = false
b.cursorx = -1
b.cursory = -1
b.Unlock()
b.s.Fini()
}
func (b *bScreen) Colors() int {
return b.s.Colors()
}
func (b *bScreen) Size() (int, int) {
b.Lock()
w, h := b.w, b.h
b.Unlock()
return w, h
}
func (b *bScreen) PollEvent() Event {
ev := b.s.PollEvent()
// We need to capture resize events from the bottom screen, so that
// we can readjust our buffer. This is important since the we need to
// do any adjustment before the application starts drawing in response,
// or coordinates may be erroneously believed out of range and results
// discarded.
if _, ok := ev.(*EventResize); ok {
b.Lock()
b.checkResize()
b.Unlock()
}
return ev
}
func (b *bScreen) PostEvent(ev Event) {
// See comment in PollEvent for why we do this. Note that normally
// events are posted directly into the screen below, so these are only
// application supplied events.
if _, ok := ev.(*EventResize); ok {
b.Lock()
b.checkResize()
b.Unlock()
}
b.s.PostEvent(ev)
}
func (b *bScreen) Clear() {
b.Lock()
for i := range b.cells {
b.cells[i].dirty = true
b.cells[i].style = StyleDefault
b.cells[i].ch = nil
}
b.Unlock()
}
func (b *bScreen) HideCursor() {
b.Lock()
b.cursorx = -1
b.cursory = -1
b.Unlock()
}
func (b *bScreen) ShowCursor(x, y int) {
b.Lock()
b.cursorx = x
b.cursory = y
b.Unlock()
}
func (b *bScreen) checkResize() {
// Must be called with lock held!
w, h := b.s.Size()
if w == b.w && h == b.h {
return
}
// We could reuse the cells, if we knew that both the row size did not
// increase, and the total size did not increase. For now we just
// take the lazy approach and grow a new cells structure. It would be
// bad if the window size changes very frequently, but that shouldn't
// happen.
newc := make([]cell, w*h)
for row := 0; row < h && row < b.h; row++ {
for col := 0; col < w && col < b.w; col++ {
newc[(row*w)+col] = b.cells[(row*b.w)+col]
newc[(row*w)+col].dirty = true
}
}
b.w = w
b.h = h
b.cells = newc
// force a full screen redraw - just to be sure
}
func (b *bScreen) SetCell(x int, y int, style Style, ch ...rune) {
// compare ch, compare style
b.Lock()
if x < 0 || y < 0 || x >= b.w || y >= b.h {
b.Unlock()
return
}
cell := &b.cells[(y*b.w)+x]
// check to see if its the same value, if it is, don't mark it dirty
match := false
if len(ch) == len(cell.ch) && style == cell.style {
match = true
for i, r := range cell.ch {
if ch[i] != r {
match = false
break
}
}
}
if !match {
cell.dirty = true
cell.ch = ch
cell.style = style
cell.width = 1
for i := range cell.ch {
if runewidth.RuneWidth(ch[i]) == 2 {
cell.width = 2
}
}
}
b.Unlock()
}
func (b *bScreen) Show() {
b.Lock()
b.checkResize()
b.s.HideCursor()
if b.clear {
b.s.Clear()
b.clear = false
}
for row := 0; row < b.h; row++ {
for col := 0; col < b.w; col++ {
c := &b.cells[(row*b.w)+col]
if !c.dirty {
continue
}
b.s.SetCell(col, row, c.style, c.ch...)
c.dirty = false
if c.width == 2 {
col++
}
}
}
b.s.ShowCursor(b.cursorx, b.cursory)
b.Unlock()
}
func (b *bScreen) Sync() {
b.Lock()
// do a clear screen and also mark everything dirty
b.clear = true
for i := range b.cells {
b.cells[i].dirty = true
}
b.Unlock()
b.Show()
}
func (b *bScreen) EnableMouse() {
b.Lock()
b.s.EnableMouse()
b.Unlock()
}
func (b *bScreen) DisableMouse() {
b.Lock()
b.s.DisableMouse()
b.Unlock()
}
func NewBufferedScreen() (BufferedScreen, error) {
s, e := NewScreen()
if e != nil {
return nil, e
}
return MakeBufferedScreen(s), nil
}
func MakeBufferedScreen(s Screen) BufferedScreen {
return &bScreen{s: s}
}

170
cell.go Normal file
View File

@ -0,0 +1,170 @@
// 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 (
"github.com/mattn/go-runewidth"
)
// Cell represents a single character cell. This is primarily intended for
// use by Screen implementors.
type Cell struct {
Ch []rune
Dirty bool
Width uint8
Style Style
}
// ClearCells clears the entire set of cells, making them all whitespace with
// the provided attribute.
func ClearCells(c []Cell, style Style) {
for i := range c {
c[i].Ch = nil
c[i].Style = style
c[i].Width = 1
c[i].Dirty = true
}
}
// InvalidateCells marks all cells in the array dirty.
func InvalidateCells(c []Cell) {
for i := range c {
c[i].Dirty = true
}
}
// ResizeCells is used to create a new cells array, with different dimensions,
// while preserving the original contents. The returned array may be the same
// as the original, if we can reuse it. Hence, the old array should no longer
// be used by the caller after this call. The cells will be marked dirty so
// that they can be redrawn.
func ResizeCells(oldc []Cell, oldw, oldh, neww, newh int) []Cell {
if oldh == newh && oldw == neww {
return oldc
}
newc := oldc
// Probably are other conditions where we could reuse, but if there is
// any doubt at all, its easier & safest to just realloc the window.
if newh > oldh || neww > oldw {
newc = make([]Cell, neww*newh)
}
for row := 0; row < newh && row < oldh; row++ {
for col := 0; col < oldw && col < neww; col++ {
newc[(row*neww)+col] = oldc[(row*oldw)+col]
newc[(row*neww)+col].Dirty = true
}
}
return newc
}
// SetCell writes the contents into the cell. It ensures that at most one
// nonzero width rune is present in the Ch array (and if any zero width runes
// are present without a non-zero one, then a space is inserted), and updates
// the Dirty bit if the contents are different than they were.
func (c *Cell) SetCell(ch []rune, style Style) {
c.PutChars(ch)
c.PutStyle(style)
/*
var mainc rune
var width uint8
var compc []rune
width = 1
mainc = ' '
for _, r := range ch {
if r < ' ' {
// skip over non-printable control characters
continue
}
switch runewidth.RuneWidth(r) {
case 1:
mainc = r
width = 1
case 2:
mainc = r
width = 2
case 0:
compc = append(compc, r)
}
}
newch := append([]rune{mainc}, compc...)
if len(newch) != len(c.Ch) || style != c.Style || c.Dirty {
c.Dirty = true
} else {
for i := range newch {
if newch[i] != c.Ch[i] {
c.Dirty = true
}
}
}
c.Ch = newch
c.Style = style
c.Width = width
*/
}
func (c *Cell) PutChars(ch []rune) {
var mainc rune
var width uint8
var compc []rune
width = 1
mainc = ' '
for _, r := range ch {
if r < ' ' {
// skip over non-printable control characters
continue
}
switch runewidth.RuneWidth(r) {
case 1:
mainc = r
width = 1
case 2:
mainc = r
width = 2
case 0:
compc = append(compc, r)
}
}
newch := append([]rune{mainc}, compc...)
if len(newch) != len(c.Ch) {
c.Dirty = true
} else {
for i := range newch {
if newch[i] != c.Ch[i] {
c.Dirty = true
}
}
}
c.Ch = newch
c.Width = width
}
func (c *Cell) PutChar(ch rune) {
c.PutChars([]rune{ch})
}
func (c *Cell) PutStyle(style Style) {
if c.Style != style {
c.Style = style
c.Dirty = true
}
}

View File

@ -17,11 +17,10 @@
package tcell
import (
// "fmt"
"sync"
"syscall"
"unicode/utf16"
"unsafe"
"github.com/mattn/go-runewidth"
)
type cScreen struct {
@ -30,13 +29,21 @@ type cScreen struct {
mbtns uint32 // debounce mouse buttons
evch chan Event
quit chan struct{}
curx int
cury int
style Style
clear bool
w int
h int
oscreen consoleInfo
ocursor cursorInfo
omode uint32
oimode uint32
oomode uint32
cells []Cell
sync.Mutex
}
// all Windows systems are little endian
@ -46,19 +53,18 @@ var k32 = syscall.NewLazyDLL("kernel32.dll")
// characters (Unicode) are in use. The documentation refers to them
// without this suffix, as the resolution is made via preprocessor.
var (
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = k32.NewProc("SetConsoleMode")
procGetConsoleMode = k32.NewProc("GetConsoleMode")
procWriteConsoleOutput = k32.NewProc("WriteConsoleOutputW")
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
procSetConsoleActiveScreenBuffer = k32.NewProc("SetConsoleActiveScreenBuffer")
procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = k32.NewProc("SetConsoleMode")
procGetConsoleMode = k32.NewProc("GetConsoleMode")
procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
)
// We have to bring in the kernel32.dll directly, so we can get access to some
@ -85,38 +91,45 @@ func (s *cScreen) Init() error {
s.out = out
}
s.curx = -1
s.cury = -1
s.getCursorInfo(&s.ocursor)
s.getConsoleInfo(&s.oscreen)
s.getMode(&s.omode)
if err := s.setMode(modeResizeEn); err != nil {
syscall.Close(s.in)
syscall.Close(s.out)
return err
}
s.getOutMode(&s.oomode)
s.getInMode(&s.oimode)
s.resize()
s.Clear()
s.HideCursor()
//procSetConsoleActiveScreenBuffer.Call(uintptr(s.out))
s.setInMode(modeResizeEn)
s.setOutMode(0)
s.clearScreen(s.style)
s.hideCursor()
go s.scanInput()
return nil
}
func (s *cScreen) EnableMouse() {
s.setMode(modeResizeEn | modeMouseEn)
s.setInMode(modeResizeEn | modeMouseEn)
}
func (s *cScreen) DisableMouse() {
s.setMode(modeResizeEn)
s.setInMode(modeResizeEn)
}
func (s *cScreen) Fini() {
s.setCursorPos(0, 0)
s.style = StyleDefault
s.curx = -1
s.cury = -1
s.setCursorInfo(&s.ocursor)
s.setMode(s.omode)
s.setInMode(s.oimode)
s.setOutMode(s.oomode)
s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
s.Clear()
s.clearScreen(StyleDefault)
s.setCursorPos(0, 0)
procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(mapStyle(StyleDefault)))
close(s.quit)
syscall.Close(s.in)
@ -161,25 +174,35 @@ type rect struct {
bottom int16
}
func (s *cScreen) ShowCursor(x, y int) {
var curinfo cursorInfo
func (s *cScreen) showCursor() {
s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
}
s.getCursorInfo(&curinfo)
if x < 0 || y < 0 {
if curinfo.visible == 0 {
return
}
curinfo.visible = 0
s.setCursorInfo(&curinfo)
func (s *cScreen) hideCursor() {
s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
}
func (s *cScreen) ShowCursor(x, y int) {
s.Lock()
s.curx = x
s.cury = y
s.Unlock()
}
func (s *cScreen) doCursor() {
x, y := s.curx, s.cury
if x < 0 || y < 0 || x >= s.w || y >= s.h {
s.setCursorPos(0, 0)
s.hideCursor()
} else {
curinfo.visible = 1
s.setCursorPos(x, y)
s.setCursorInfo(&curinfo)
s.showCursor()
}
}
func (c *cScreen) HideCursor() {
c.ShowCursor(10, 5)
c.ShowCursor(-1, -1)
}
type charInfo struct {
@ -208,6 +231,12 @@ type mouseRecord struct {
mod uint32
flags uint32
}
const (
mouseDoubleClick uint32 = 0x2
mouseHWheeled uint32 = 0x8
mouseVWheeled uint32 = 0x4
mouseMoved uint32 = 0x1
)
type resizeRecord struct {
x int16
@ -422,7 +451,8 @@ func (s *cScreen) getConsoleInput() error {
return nil
}
for krec.repeat > 0 {
s.PostEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod)))
s.PostEvent(NewEventKey(key, rune(krec.ch),
mod2mask(krec.mod)))
krec.repeat--
}
@ -435,13 +465,6 @@ func (s *cScreen) getConsoleInput() error {
mrec.flags = getu32(rec.data[12:]) // not using yet
btns := ButtonNone
if mrec.btns == s.mbtns {
// If the buttons have not changed,
// then don't report the event. We aren't
// reporting motion events at this time.
return nil
}
s.mbtns = mrec.btns
if mrec.btns&0x1 != 0 {
btns |= Button1
@ -459,7 +482,23 @@ func (s *cScreen) getConsoleInput() error {
btns |= Button5
}
s.PostEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod)))
if mrec.flags & mouseVWheeled != 0 {
if mrec.btns & 0x80000000 == 0 {
btns |= WheelUp
} else {
btns |= WheelDown
}
}
if mrec.flags & mouseHWheeled != 0 {
if mrec.btns & 0x80000000 == 0 {
btns |= WheelRight
} else {
btns |= WheelLeft
}
}
// we ignore double click, events are delivered normally
s.PostEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns,
mod2mask(mrec.mod)))
case resizeEvent:
var rrec resizeRecord
@ -554,32 +593,120 @@ func mapStyle(style Style) uint16 {
}
func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) {
r := ' '
w := 0
for i := range ch {
if w = runewidth.RuneWidth(ch[i]); w != 0 {
r = ch[i]
break
}
s.Lock()
if x < 0 || y < 0 || x >= int(s.w) || y >= int(s.h) {
s.Unlock()
return
}
// Windows console lacks support for combining chars
if w == 0 {
r = ' '
w = 1
cell := &s.cells[(y*int(s.w))+x]
cell.SetCell(ch, style)
s.Unlock()
}
func (s *cScreen) PutCell(x, y int, cell *Cell) {
s.Lock()
if x < 0 || y < 0 || x >= int(s.w) || y >= int(s.h) {
s.Unlock()
return
}
cptr := &s.cells[(y*int(s.w))+x]
cptr.PutChars(cell.Ch)
cptr.PutStyle(cell.Style)
s.Unlock()
}
rec := rect{left: int16(x), right: int16(x), top: int16(y), bottom: int16(y)}
pos := coord{x: int16(0), y: int16(0)}
siz := coord{x: 1, y: 1}
dat := charInfo{ch: uint16(r), attr: mapStyle(style)}
func (s *cScreen) GetCell(x, y int) *Cell {
s.Lock()
if x < 0 || y < 0 || x >= int(s.w) || y >= int(s.h) {
s.Unlock()
return nil
}
cell := s.cells[(y*int(s.w))+x]
s.Unlock()
return &cell
}
procWriteConsoleOutput.Call(
func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
// we assume the caller has hidden the cursor
if len(ch) == 0 {
return
}
nw := uint32(len(ch))
procSetConsoleTextAttribute.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(&dat)),
siz.uintptr(),
pos.uintptr(),
uintptr(unsafe.Pointer(&rec)))
uintptr(mapStyle(style)))
s.setCursorPos(x, y)
syscall.WriteConsole(s.out, &ch[0], nw, &nw, nil)
}
func (s *cScreen) draw() {
// allocate a scratch line bit enough for no combining chars.
// if you have combining characters, you may pay for extra allocs.
if s.clear {
s.clearScreen(s.style)
s.clear = false
}
buf := make([]uint16, 0, s.w)
wcs := buf[:]
style := Style(-1) // invalid attribute
x, y := -1, -1
for row := 0; row < int(s.h); row++ {
width := 1
for col := 0; col < int(s.w); col += width {
cell := &s.cells[(row*s.w)+col]
width = int(cell.Width)
if width < 1 {
width = 1
}
if !cell.Dirty || style != cell.Style {
s.writeString(x, y, style, wcs)
wcs = buf[0:0]
style = Style(-1)
if !cell.Dirty {
continue
}
}
if len(wcs) == 0 {
style = cell.Style
x = col
y = row
}
if len(cell.Ch) < 1 {
wcs = append(wcs, uint16(' '))
} else {
wcs = append(wcs, utf16.Encode(cell.Ch)...)
}
cell.Dirty = false
}
s.writeString(x, y, style, wcs)
wcs = buf[0:0]
style = Style(-1)
}
}
func (s *cScreen) Show() {
s.Lock()
s.hideCursor()
s.resize()
s.draw()
s.doCursor()
s.Unlock()
}
func (s *cScreen) Sync() {
s.Lock()
InvalidateCells(s.cells)
s.hideCursor()
s.resize()
s.draw()
s.doCursor()
s.Unlock()
}
type consoleInfo struct {
@ -622,10 +749,9 @@ func (s *cScreen) setBufferSize(x, y int) {
func (s *cScreen) Size() (int, int) {
info := consoleInfo{}
s.getConsoleInfo(&info)
w := int((info.win.right - info.win.left) + 1)
h := int((info.win.bottom - info.win.top) + 1)
s.Lock()
w, h := s.w, s.h
s.Unlock()
return w, h
}
@ -637,9 +763,15 @@ func (s *cScreen) resize() {
w := int((info.win.right - info.win.left) + 1)
h := int((info.win.bottom - info.win.top) + 1)
if s.w == w && s.h == h {
return
}
s.cells = ResizeCells(s.cells, s.w, s.h, w, h)
s.w = w
s.h = h
r := rect{0, 0, int16(w - 1), int16(h - 1)}
procSetConsoleWindowInfo.Call(
uintptr(s.out),
@ -647,12 +779,21 @@ func (s *cScreen) resize() {
uintptr(unsafe.Pointer(&r)))
s.setBufferSize(w, h)
s.PostEvent(NewEventResize(w, h))
}
func (s *cScreen) Clear() {
s.Lock()
ClearCells(s.cells, s.style)
s.clear = true
s.Unlock()
}
func (s *cScreen) clearScreen(style Style) {
pos := coord{0, 0}
attr := uint16(0x7) // default white fg, black bg)
x, y := s.Size()
attr := mapStyle(style)
x, y := s.w, s.h
scratch := uint32(0)
count := uint32(x * y)
@ -673,9 +814,11 @@ func (s *cScreen) Clear() {
const (
modeMouseEn uint32 = 0x0010
modeResizeEn uint32 = 0x0008
modeWrapEOL uint32 = 0x0002
modeCooked uint32 = 0x0001
)
func (s *cScreen) setMode(mode uint32) error {
func (s *cScreen) setInMode(mode uint32) error {
rv, _, err := procSetConsoleMode.Call(
uintptr(s.in),
uintptr(mode))
@ -685,8 +828,30 @@ func (s *cScreen) setMode(mode uint32) error {
return nil
}
func (s *cScreen) getMode(v *uint32) {
func (s *cScreen) setOutMode(mode uint32) error {
rv, _, err := procSetConsoleMode.Call(
uintptr(s.out),
uintptr(mode))
if rv == 0 {
return err
}
return nil
}
func (s *cScreen) getInMode(v *uint32) {
procGetConsoleMode.Call(
uintptr(s.in),
uintptr(unsafe.Pointer(v)))
}
func (s *cScreen) getOutMode(v *uint32) {
procGetConsoleMode.Call(
uintptr(s.out),
uintptr(unsafe.Pointer(v)))
}
func (s *cScreen) SetStyle(style Style) {
s.Lock()
s.style = style
s.Unlock()
}

View File

@ -1,27 +1,28 @@
// Generated by ./mkinfo (darwin/amd64) on Sat Sep 26 22:40:41 PDT 2015.
// Generated by ./mkinfo (darwin/amd64) on Fri Oct 2 21:03:12 PDT 2015.
// DO NOT HAND-EDIT
package tcell
func init() {
AddTerminfo(&Terminfo{
Name: "adm3a",
Columns: 80,
Lines: 24,
Bell: "\a",
Clear: "\x1a$<1/>",
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",
Columns: 80,
Lines: 24,
Lines: 25,
Colors: 8,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
@ -31,6 +32,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -72,6 +74,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\x1b[D",
CursorUp1: "\x1b[A",
@ -103,9 +106,10 @@ func init() {
ExitKeypad: "\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -136,7 +140,7 @@ func init() {
AddTerminfo(&Terminfo{
Name: "beterm",
Columns: 80,
Lines: 24,
Lines: 25,
Colors: 8,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
@ -148,6 +152,7 @@ func init() {
ExitKeypad: "\x1b[?4l",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -178,7 +183,7 @@ func init() {
AddTerminfo(&Terminfo{
Name: "bsdos-pc",
Columns: 80,
Lines: 24,
Lines: 25,
Colors: 8,
Bell: "\a",
Clear: "\x1bc",
@ -189,6 +194,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -204,8 +210,8 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "cygwin",
Columns: 80,
Lines: 24,
Columns: -1,
Lines: -1,
Colors: 8,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
@ -217,6 +223,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -245,72 +252,74 @@ func init() {
KeyF12: "\x1b[24~",
})
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",
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|",
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|",
})
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",
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",
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",
})
AddTerminfo(&Terminfo{
Name: "dtterm",
@ -329,6 +338,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -356,7 +366,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "Eterm",
Aliases: []string{"Eterm-color"},
Aliases: []string{ "Eterm-color" },
Columns: 80,
Lines: 24,
Colors: 8,
@ -373,9 +383,10 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -421,9 +432,10 @@ func init() {
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",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -452,20 +464,21 @@ func init() {
KeyF12: "\x1b[24~",
})
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",
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",
@ -486,9 +499,10 @@ func init() {
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -535,9 +549,10 @@ func init() {
ExitKeypad: "\x1b[?1l\x1b>",
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",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -567,7 +582,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "hpterm",
Aliases: []string{"X-hpterm"},
Aliases: []string{ "X-hpterm" },
Columns: 80,
Lines: 24,
Bell: "\a",
@ -579,6 +594,7 @@ func init() {
Reverse: "\x1b&dB",
EnterKeypad: "\x1b&s1A",
ExitKeypad: "\x1b&s0A",
PadChar: "\x00",
SetCursor: "\x1b&a%p1%dy%p2%dC",
CursorBack1: "\b",
CursorUp1: "\x1bA",
@ -602,19 +618,20 @@ func init() {
KeyF8: "\x1bw",
})
AddTerminfo(&Terminfo{
Name: "hz1500",
Columns: 80,
Lines: 24,
Bell: "\a",
Clear: "~\x1c",
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",
@ -636,8 +653,8 @@ func init() {
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -682,9 +699,10 @@ func init() {
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -712,8 +730,8 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "linux",
Columns: 80,
Lines: 24,
Columns: -1,
Lines: -1,
Colors: 8,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
@ -727,9 +745,10 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -771,6 +790,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\x1b[D",
CursorUp1: "\x1b[A",
@ -801,9 +821,10 @@ func init() {
ExitKeypad: "\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -851,9 +872,10 @@ func init() {
ExitKeypad: "\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t%p1%{30}%+%e%p1%'R'%+%;%dm",
SetBg: "\x1b[%?%p1%{8}%<%t%p1%'('%+%e%p1%{92}%+%;%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -901,9 +923,10 @@ func init() {
ExitKeypad: "\x1b>",
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",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -951,9 +974,10 @@ func init() {
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1bM",
@ -983,13 +1007,14 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "sun",
Aliases: []string{"sun1", "sun2"},
Aliases: []string{ "sun1", "sun2" },
Columns: 80,
Lines: 24,
Lines: 34,
Bell: "\a",
Clear: "\f",
AttrOff: "\x1b[m",
Reverse: "\x1b[7m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1019,7 +1044,7 @@ func init() {
AddTerminfo(&Terminfo{
Name: "sun-color",
Columns: 80,
Lines: 24,
Lines: 34,
Colors: 8,
Bell: "\a",
Clear: "\f",
@ -1027,6 +1052,7 @@ func init() {
Reverse: "\x1b[7m",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1062,6 +1088,7 @@ func init() {
AttrOff: "\x1bG0",
Underline: "\x1bG8",
Reverse: "\x1bG4",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1083,12 +1110,13 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "tvi912",
Aliases: []string{"tvi920", "tvi914"},
Aliases: []string{ "tvi920", "tvi914" },
Columns: 80,
Lines: 24,
Bell: "\a",
Clear: "\x1a",
Underline: "\x1bl",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1117,6 +1145,7 @@ func init() {
AttrOff: "\x1bG0",
Underline: "\x1bG8",
Reverse: "\x1bG4",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c$<3/>",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1138,6 +1167,7 @@ func init() {
AttrOff: "\x1bG0",
Underline: "\x1bG8",
Reverse: "\x1bG4",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1168,6 +1198,7 @@ func init() {
AttrOff: "\x1bG0",
Underline: "\x1bG8",
Reverse: "\x1bG4",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1197,6 +1228,7 @@ func init() {
EnterCA: "\x1b[?20l\x1b[?7h\x1b[1Q",
AttrOff: "\x1b[m",
Underline: "\x1b[4m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%df",
CursorBack1: "\b",
CursorUp1: "\x1bM",
@ -1222,6 +1254,7 @@ func init() {
Lines: 24,
Bell: "\a",
Clear: "\x1bH\x1bJ",
PadChar: "\x00",
SetCursor: "\x1bY%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\x1bD",
CursorUp1: "\x1bA",
@ -1233,7 +1266,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt100",
Aliases: []string{"vt100-am"},
Aliases: []string{ "vt100-am" },
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1245,6 +1278,7 @@ func init() {
Reverse: "\x1b[7m$<2>",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>",
CursorBack1: "\b",
CursorUp1: "\x1b[A$<2>",
@ -1277,6 +1311,7 @@ func init() {
Reverse: "\x1b[7m$<2>",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH$<5>",
CursorBack1: "\b",
CursorUp1: "\x1b[A$<2>",
@ -1298,7 +1333,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt220",
Aliases: []string{"vt200"},
Aliases: []string{ "vt200" },
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1308,6 +1343,7 @@ func init() {
Bold: "\x1b[1m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1333,7 +1369,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt320",
Aliases: []string{"vt300"},
Aliases: []string{ "vt300" },
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1347,6 +1383,7 @@ func init() {
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1374,7 +1411,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "vt400",
Aliases: []string{"vt400-24", "dec-vt400"},
Aliases: []string{ "vt400-24", "dec-vt400" },
Columns: 80,
Lines: 24,
Clear: "\x1b[H\x1b[J$<10/>",
@ -1387,6 +1424,7 @@ func init() {
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1417,6 +1455,7 @@ func init() {
Reverse: "\x1b[7m$<2>",
EnterKeypad: "\x1b=",
ExitKeypad: "\x1b>",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH$<10>",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1442,7 +1481,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "wy50",
Aliases: []string{"wyse50"},
Aliases: []string{ "wyse50" },
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1452,6 +1491,7 @@ func init() {
AttrOff: "\x1b(\x1bH\x03",
Dim: "\x1b`7\x1b)",
Reverse: "\x1b`6\x1b)",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1480,7 +1520,7 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "wy60",
Aliases: []string{"wyse60"},
Aliases: []string{ "wyse60" },
Columns: 80,
Lines: 24,
Bell: "\a",
@ -1494,6 +1534,7 @@ func init() {
Dim: "\x1bGp",
Blink: "\x1bG2",
Reverse: "\x1bG4",
PadChar: "\x00",
SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c",
CursorBack1: "\b",
CursorUp1: "\v",
@ -1523,7 +1564,7 @@ func init() {
AddTerminfo(&Terminfo{
Name: "wy99-ansi",
Columns: 80,
Lines: 24,
Lines: 25,
Bell: "\a",
Clear: "\x1b[H\x1b[J$<200>",
ShowCursor: "\x1b[34h\x1b[?25h",
@ -1536,6 +1577,7 @@ func init() {
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h",
ExitKeypad: "\x1b[?1l",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b$<1>",
CursorUp1: "\x1bM",
@ -1560,7 +1602,7 @@ func init() {
AddTerminfo(&Terminfo{
Name: "wy99a-ansi",
Columns: 80,
Lines: 24,
Lines: 25,
Bell: "\a",
Clear: "\x1b[H\x1b[J$<200>",
ShowCursor: "\x1b[34h\x1b[?25h",
@ -1573,6 +1615,7 @@ func init() {
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h",
ExitKeypad: "\x1b[?1l",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b$<1>",
CursorUp1: "\x1bM",
@ -1613,9 +1656,10 @@ func init() {
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1645,8 +1689,8 @@ func init() {
})
AddTerminfo(&Terminfo{
Name: "xnuppc",
Columns: 80,
Lines: 24,
Columns: -1,
Lines: -1,
Colors: 8,
Clear: "\x1b[H\x1b[J",
AttrOff: "\x1b[m\x0f",
@ -1657,6 +1701,7 @@ func init() {
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
PadChar: "\x00",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\x1b[D",
CursorUp1: "\x1b[A",
@ -1687,8 +1732,8 @@ func init() {
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
@ -1737,8 +1782,8 @@ func init() {
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",
Mouse: "\x1b[M",
EnterMouse: "\x1b[?1000h",
ExitMouse: "\x1b[?1000l",
EnterMouse: "\x1b[?1000h\x1b[?1003h\x1b[?1006h",
ExitMouse: "\x1b[?1006l\x1b[?1003l\x1b[?1000l",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,10 @@ import (
// #include <curses.h>
// #include <term.h>
// #cgo LDFLAGS: -lcurses
//
// void noenv() {
// use_env(FALSE);
// }
import "C"
func tigetnum(s string) int {
@ -53,6 +57,11 @@ func tigetnum(s string) int {
return int(n)
}
func tigetflag(s string) bool {
n := C.tigetflag(C.CString(s))
return n != 0
}
func tigetstr(s string) string {
// NB: If the string is invalid, we'll get back -1, which causes
// no end of grief. So make sure your capability strings are correct!
@ -70,7 +79,8 @@ func tigetstr(s string) string {
// terminal types.
func getinfo(name string) (*tcell.Terminfo, error) {
rsn := C.int(0)
rv, e := C.setupterm(C.CString(name), 1, &rsn)
C.noenv()
rv, _ := C.setupterm(C.CString(name), 1, &rsn)
if rv == C.ERR {
switch rsn {
case 1:
@ -83,9 +93,6 @@ func getinfo(name string) (*tcell.Terminfo, error) {
return nil, errors.New("setupterm failed (other)")
}
}
if e != nil {
return nil, e
}
t := &tcell.Terminfo{}
t.Name = name
t.Colors = tigetnum("colors")
@ -141,8 +148,13 @@ func getinfo(name string) (*tcell.Terminfo, error) {
// manual, and all terminals that have kmous are expected to
// use these same codes.
if t.Mouse != "" {
t.EnterMouse = "\x1b[?1000h"
t.ExitMouse = "\x1b[?1000l"
// we anticipate that all xterm mouse tracking compatible
// terminals understand mouse tracking (1000), but we hope
// that those that don't understand any-event tracking (1003)
// will at least ignore it. Likewise we hope that terminals
// that don't understand SGR reporting (1006) just ignore it.
t.EnterMouse = "\x1b[?1000h\x1b[?1003h\x1b[?1006h"
t.ExitMouse = "\x1b[?1006l\x1b[?1003l\x1b[?1000l"
}
// We only support colors in ANSI 8 or 256 color mode.
if t.Colors < 8 || t.SetFg == "" {
@ -151,6 +163,16 @@ func getinfo(name string) (*tcell.Terminfo, error) {
if t.SetCursor == "" {
return nil, errors.New("terminal not cursor addressable")
}
// For padding, we lookup the pad char. If that isn't present,
// and npc is *not* set, then we assume a null byte.
t.PadChar = tigetstr("pad")
if t.PadChar == "" {
if !tigetflag("npc") {
t.PadChar = "\u0000"
}
}
return t, nil
}
@ -223,6 +245,7 @@ func dotGoInfo(w io.Writer, t *tcell.Terminfo) {
dotGoAddStr(w, "ExitKeypad", t.ExitKeypad)
dotGoAddStr(w, "SetFg", t.SetFg)
dotGoAddStr(w, "SetBg", t.SetBg)
dotGoAddStr(w, "PadChar", t.PadChar)
dotGoAddStr(w, "Mouse", t.Mouse)
dotGoAddStr(w, "EnterMouse", t.EnterMouse)
dotGoAddStr(w, "ExitMouse", t.ExitMouse)

View File

@ -19,10 +19,19 @@ import (
)
// EventMouse is a mouse event. It is sent on either mouse up or mouse down
// events. (Eventually we can also arrange for mouse motion events, but only
// with genuine xterm -- other emulators lack support for tracking this.)
// 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.
//
// Mouse wheel events, when reported, may appear on their own as individual
// impulses.
//
// Most terminals cannot report the state of more than one button at a time --
// that is buttons are seen together.
// and few can report motion events.
//
// Applications can inspect the time between events to figure out double clicks
// and such.
type EventMouse struct {
t time.Time
btn ButtonMask
@ -63,5 +72,9 @@ const (
Button6
Button7
Button8
WheelUp
WheelDown
WheelLeft
WheelRight
)
const ButtonNone ButtonMask = 0

View File

@ -29,7 +29,7 @@ type Screen interface {
Clear()
// SetCell sets the cell at the given location.
// The ch array contains at most one rune of width > 0, and the
// 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.)
@ -39,15 +39,32 @@ type Screen interface {
// undefined effects. Double wide runes that are printed in the
// last column will be replaced with a single width space on output.
//
// Note that unlike the higher level interfaces, this operates
// immediately, without any buffering.
//
// 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(x int, y int, style Style, ch ...rune)
// PutCell stores the contents of the given cell at the given location.
// The Dirty flag on the stored cell is set to true if the contents
// do not match.
PutCell(x, y int, cell *Cell)
// GetCell returns the contents of the given cell. If the coordinates
// are out of range, then nil will be returned for the rune array.
// This will also be the case if no content has been written to that
// location. Note that the returned Cell object is a copy, and
// modifications made will not change the display.
GetCell(x, y int) *Cell
// SetStyle sets the default style to use when clearing the screen
// or when StyleDefault is specified. If it is also STyleDefault,
// then whatever system/terminal default is relevant will be used.
SetStyle(style Style)
// ShowCursor is used to display the cursor at a given location.
// If the coordinates -1, -1 are given or are otherwise outside the
// dimensions of the screen, the cursor will be hidden.
@ -78,6 +95,21 @@ type Screen interface {
// Colors returns the number of colors. All colors are assumed to
// use the ANSI color map.
Colors() int
// Show takes any output that was deferred due to buffering, and
// flushes it to the physical display. It does so in the least
// expensive and disruptive manner possible, only making writes that
// are believed to actually be necessary.
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.
Sync()
}
func NewScreen() (Screen, error) {

View File

@ -22,12 +22,12 @@ import (
"github.com/gdamore/tcell"
)
var screen tcell.BufferedScreen
var screen tcell.Screen
var outMode OutputMode
func Init() error {
outMode = OutputNormal
if s, e := tcell.NewBufferedScreen(); e != nil {
if s, e := tcell.NewScreen(); e != nil {
return e
} else if e = s.Init(); e != nil {
return e
@ -59,6 +59,7 @@ func Size() (int, int) {
}
type Attribute uint16
const (
ColorDefault Attribute = iota
ColorBlack
@ -71,7 +72,7 @@ const (
ColorWhite
)
const (
AttrBold Attribute = 1 << (9+iota)
AttrBold Attribute = 1 << (9 + iota)
AttrUnderline
AttrReverse
)
@ -113,13 +114,13 @@ func mkStyle(fg, bg Attribute) tcell.Style {
}
st = st.Foreground(tcell.Color(f))
st = st.Background(tcell.Color(b))
if (fg & AttrBold != 0 ) || (bg & AttrBold != 0) {
if (fg&AttrBold != 0) || (bg&AttrBold != 0) {
st = st.Bold(true)
}
if (fg & AttrUnderline != 0 ) || (bg & AttrUnderline != 0) {
if (fg&AttrUnderline != 0) || (bg&AttrUnderline != 0) {
st = st.Underline(true)
}
if (fg & AttrReverse != 0 ) || (bg & AttrReverse != 0) {
if (fg&AttrReverse != 0) || (bg&AttrReverse != 0) {
st = st.Reverse(true)
}
return st
@ -137,6 +138,7 @@ func Clear(fg, bg Attribute) {
}
type InputMode int
const (
InputCurrent InputMode = iota
InputEsc
@ -150,6 +152,7 @@ func SetInputMode(mode InputMode) InputMode {
}
type OutputMode int
const (
OutputCurrent OutputMode = iota
OutputNormal
@ -159,6 +162,9 @@ const (
)
func SetOutputMode(mode OutputMode) OutputMode {
if screen.Colors() < 256 {
mode = OutputNormal
}
switch mode {
case OutputCurrent:
return outMode
@ -185,16 +191,16 @@ type Modifier tcell.ModMask
type Key tcell.Key
type Event struct {
Type EventType
Mod Modifier
Key Key
Ch rune
Width int
Height int
Err error
MouseX int
MouseY int
N int
Type EventType
Mod Modifier
Key Key
Ch rune
Width int
Height int
Err error
MouseX int
MouseY int
N int
}
const (
@ -208,68 +214,68 @@ const (
)
const (
KeyF1 = Key(tcell.KeyF1)
KeyF2 = Key(tcell.KeyF2)
KeyF3 = Key(tcell.KeyF3)
KeyF4 = Key(tcell.KeyF4)
KeyF5 = Key(tcell.KeyF5)
KeyF6 = Key(tcell.KeyF6)
KeyF7 = Key(tcell.KeyF7)
KeyF8 = Key(tcell.KeyF8)
KeyF9 = Key(tcell.KeyF9)
KeyF10 = Key(tcell.KeyF10)
KeyF11 = Key(tcell.KeyF11)
KeyF12 = Key(tcell.KeyF12)
KeyInsert = Key(tcell.KeyInsert)
KeyDelete = Key(tcell.KeyDelete)
KeyHome = Key(tcell.KeyHome)
KeyEnd = Key(tcell.KeyEnd)
KeyArrowUp = Key(tcell.KeyUp)
KeyArrowDown = Key(tcell.KeyDown)
KeyF1 = Key(tcell.KeyF1)
KeyF2 = Key(tcell.KeyF2)
KeyF3 = Key(tcell.KeyF3)
KeyF4 = Key(tcell.KeyF4)
KeyF5 = Key(tcell.KeyF5)
KeyF6 = Key(tcell.KeyF6)
KeyF7 = Key(tcell.KeyF7)
KeyF8 = Key(tcell.KeyF8)
KeyF9 = Key(tcell.KeyF9)
KeyF10 = Key(tcell.KeyF10)
KeyF11 = Key(tcell.KeyF11)
KeyF12 = Key(tcell.KeyF12)
KeyInsert = Key(tcell.KeyInsert)
KeyDelete = Key(tcell.KeyDelete)
KeyHome = Key(tcell.KeyHome)
KeyEnd = Key(tcell.KeyEnd)
KeyArrowUp = Key(tcell.KeyUp)
KeyArrowDown = Key(tcell.KeyDown)
KeyArrowRight = Key(tcell.KeyRight)
KeyArrowLeft = Key(tcell.KeyLeft)
KeyCtrlA = Key(tcell.KeyCtrlA)
KeyCtrlB = Key(tcell.KeyCtrlB)
KeyCtrlC = Key(tcell.KeyCtrlC)
KeyCtrlD = Key(tcell.KeyCtrlD)
KeyCtrlE = Key(tcell.KeyCtrlE)
KeyCtrlF = Key(tcell.KeyCtrlF)
KeyCtrlG = Key(tcell.KeyCtrlG)
KeyCtrlH = Key(tcell.KeyCtrlH)
KeyCtrlI = Key(tcell.KeyCtrlI)
KeyCtrlJ = Key(tcell.KeyCtrlJ)
KeyCtrlK = Key(tcell.KeyCtrlK)
KeyCtrlL = Key(tcell.KeyCtrlL)
KeyCtrlM = Key(tcell.KeyCtrlM)
KeyCtrlN = Key(tcell.KeyCtrlN)
KeyCtrlO = Key(tcell.KeyCtrlO)
KeyCtrlP = Key(tcell.KeyCtrlP)
KeyCtrlQ = Key(tcell.KeyCtrlQ)
KeyCtrlR = Key(tcell.KeyCtrlR)
KeyCtrlS = Key(tcell.KeyCtrlS)
KeyCtrlT = Key(tcell.KeyCtrlT)
KeyCtrlU = Key(tcell.KeyCtrlU)
KeyCtrlV = Key(tcell.KeyCtrlV)
KeyCtrlW = Key(tcell.KeyCtrlW)
KeyCtrlX = Key(tcell.KeyCtrlX)
KeyCtrlY = Key(tcell.KeyCtrlY)
KeyCtrlZ = Key(tcell.KeyCtrlZ)
KeyBackspace = Key(tcell.KeyBackspace)
KeyArrowLeft = Key(tcell.KeyLeft)
KeyCtrlA = Key(tcell.KeyCtrlA)
KeyCtrlB = Key(tcell.KeyCtrlB)
KeyCtrlC = Key(tcell.KeyCtrlC)
KeyCtrlD = Key(tcell.KeyCtrlD)
KeyCtrlE = Key(tcell.KeyCtrlE)
KeyCtrlF = Key(tcell.KeyCtrlF)
KeyCtrlG = Key(tcell.KeyCtrlG)
KeyCtrlH = Key(tcell.KeyCtrlH)
KeyCtrlI = Key(tcell.KeyCtrlI)
KeyCtrlJ = Key(tcell.KeyCtrlJ)
KeyCtrlK = Key(tcell.KeyCtrlK)
KeyCtrlL = Key(tcell.KeyCtrlL)
KeyCtrlM = Key(tcell.KeyCtrlM)
KeyCtrlN = Key(tcell.KeyCtrlN)
KeyCtrlO = Key(tcell.KeyCtrlO)
KeyCtrlP = Key(tcell.KeyCtrlP)
KeyCtrlQ = Key(tcell.KeyCtrlQ)
KeyCtrlR = Key(tcell.KeyCtrlR)
KeyCtrlS = Key(tcell.KeyCtrlS)
KeyCtrlT = Key(tcell.KeyCtrlT)
KeyCtrlU = Key(tcell.KeyCtrlU)
KeyCtrlV = Key(tcell.KeyCtrlV)
KeyCtrlW = Key(tcell.KeyCtrlW)
KeyCtrlX = Key(tcell.KeyCtrlX)
KeyCtrlY = Key(tcell.KeyCtrlY)
KeyCtrlZ = Key(tcell.KeyCtrlZ)
KeyBackspace = Key(tcell.KeyBackspace)
KeyBackspace2 = Key(tcell.KeyBackspace2)
KeyTab = Key(tcell.KeyTab)
KeyEnter = Key(tcell.KeyEnter)
KeySpace = Key(tcell.KeySpace)
KeyEsc = Key(tcell.KeyEscape)
MouseLeft = Key(tcell.KeyF63) // arbitrary assignments
MouseRight = Key(tcell.KeyF62)
MouseMiddle = Key(tcell.KeyF61)
KeyTab = Key(tcell.KeyTab)
KeyEnter = Key(tcell.KeyEnter)
KeySpace = Key(tcell.KeySpace)
KeyEsc = Key(tcell.KeyEscape)
MouseLeft = Key(tcell.KeyF63) // arbitrary assignments
MouseRight = Key(tcell.KeyF62)
MouseMiddle = Key(tcell.KeyF61)
)
const (
ModAlt = Modifier(tcell.ModAlt)
)
func makeEvent(tev tcell.Event) Event {
func makeEvent(tev tcell.Event) Event {
switch tev := tev.(type) {
case *tcell.EventInterrupt:
return Event{Type: EventInterrupt}
@ -282,9 +288,9 @@ func makeEvent(tev tcell.Event) Event {
mod := tev.Mod()
return Event{
Type: EventKey,
Key: Key(k),
Ch: ch,
Mod: Modifier(mod),
Key: Key(k),
Ch: ch,
Mod: Modifier(mod),
}
default:
return Event{Type: EventNone}

View File

@ -21,6 +21,7 @@ import (
"io"
"os"
"path"
"strings"
"strconv"
"sync"
)
@ -53,6 +54,7 @@ type Terminfo struct {
SetCursor string `json:"cup,omitempty"` // cup
CursorBack1 string `json:"cub1,omitempty"` // cub1
CursorUp1 string `json:"cuu1,omitempty"` // cuu1
PadChar string `json:"pad,omitempty"` // pad
KeyBackspace string `json:"kbs,omitempty"` // kbs
KeyF1 string `json:"kf1,omitempty"` // kf1
KeyF2 string `json:"kf2,omitempty"` // kf2
@ -408,6 +410,54 @@ func (t *Terminfo) TParm(s string, p ...int) string {
return out.String()
}
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
for {
beg := strings.Index(s, "$<")
if beg < 0 {
// Most strings don't need padding, which is good news!
io.WriteString(w, s)
return
}
io.WriteString(w, s[:beg])
s = s[beg+2:]
end := strings.Index(s, ">")
if end < 0 {
// unterminated.. just emit bytes unadulterated
io.WriteString(w, "$<" + s)
return
}
val := s[:end]
s = s[end+1:]
padus := 0
unit := 1000
dot := false
loop:
for i := range val {
switch val[i] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
padus *= 10
padus += int(val[i] - '0')
if dot {
unit *= 10
}
case '.':
if !dot {
dot = true
} else {
break loop
}
default:
break loop
}
}
cnt := int(((baud/8)*padus)/unit)
for cnt > 0 {
io.WriteString(w, t.PadChar)
cnt--
}
}
}
// TGoto returns a string suitable for addressing the cursor at the given
// row and column. The origin 0, 0 is in the upper left corner of the screen.
func (t *Terminfo) TGoto(col, row int) string {

View File

@ -21,8 +21,6 @@ import (
"strconv"
"sync"
"unicode/utf8"
"github.com/mattn/go-runewidth"
)
func NewTerminfoScreen() (Screen, error) {
@ -58,37 +56,49 @@ type tScreen struct {
h int
in *os.File
out *os.File
cinvis bool
curstyle Style
style Style
evch chan Event
sigwinch chan os.Signal
quit chan struct{}
indoneq chan struct{}
keys map[Key][]byte
cx int
cy int
mouse []byte
cells []Cell
clear bool
cursorx int
cursory int
tiosp *termiosPrivate
baud int
wasbtn bool
sync.Mutex
}
func (t *tScreen) Init() error {
t.evch = make(chan Event, 2)
t.indoneq = make(chan struct{})
if e := t.termioInit(); e != nil {
return e
}
out := t.out
ti := t.ti
io.WriteString(out, ti.EnterCA)
io.WriteString(out, ti.EnterKeypad)
io.WriteString(out, ti.HideCursor)
io.WriteString(out, ti.Clear)
t.TPuts(ti.EnterCA)
t.TPuts(ti.EnterKeypad)
t.TPuts(ti.HideCursor)
t.TPuts(ti.Clear)
t.quit = make(chan struct{})
t.cx = -1
t.cy = -1
t.style = StyleDefault
t.cells = ResizeCells(nil, 0, 0, t.w, t.h)
t.cursorx = -1
t.cursory = -1
go t.inputLoop()
return nil
@ -138,129 +148,139 @@ func (t *tScreen) prepareKeys() {
func (t *tScreen) Fini() {
ti := t.ti
out := t.out
io.WriteString(out, ti.ShowCursor)
io.WriteString(out, ti.AttrOff)
io.WriteString(out, ti.Clear)
io.WriteString(out, ti.ExitCA)
io.WriteString(out, ti.ExitKeypad)
io.WriteString(out, ti.ExitMouse)
if t.quit != nil {
close(t.quit)
}
t.TPuts(ti.ShowCursor)
t.TPuts(ti.AttrOff)
t.TPuts(ti.Clear)
t.TPuts(ti.ExitCA)
t.TPuts(ti.ExitKeypad)
t.TPuts(ti.ExitMouse)
t.w = 0
t.h = 0
t.cells = nil
t.curstyle = Style(-1)
t.cinvis = false
if t.quit != nil {
close(t.quit)
} else {
t.termioFini()
}
t.clear = false
t.termioFini()
}
func (t *tScreen) SetStyle(style Style) {
t.Lock()
t.style = style
t.Unlock()
}
func (t *tScreen) Clear() {
return
t.Lock()
t.curstyle = Style(-1)
t.cx = -1
t.cy = -1
io.WriteString(t.out, t.ti.Clear)
ClearCells(t.cells, t.style)
t.Unlock()
}
func (t *tScreen) SetCell(x, y int, style Style, ch ...rune) {
// XXX: this would be a place to check for hazeltine not being able
// to display ~, or possibly non-UTF-8 locales, etc.
t.Lock()
if x < 0 || y < 0 || x >= t.w || y >= t.h {
t.Unlock()
return
}
cell := &t.cells[(y*t.w)+x]
cell.SetCell(ch, style)
t.Unlock()
}
func (t *tScreen) PutCell(x, y int, cell *Cell) {
t.Lock()
if x < 0 || y < 0 || x >= t.w || y >= t.h {
t.Unlock()
return
}
cp := &t.cells[(y*t.w)+x]
cp.PutStyle(cell.Style)
cp.PutChars(cell.Ch)
t.Unlock()
}
func (t *tScreen) GetCell(x, y int) *Cell {
t.Lock()
if x < 0 || y < 0 || x >= t.w || y >= t.h {
t.Unlock()
return nil
}
cell := t.cells[(y*t.w)+x]
t.Unlock()
return &cell
}
func (t *tScreen) drawCell(x, y int, cell *Cell) {
// XXX: this would be a place to check for hazeltine not being able
// to display ~, or possibly non-UTF-8 locales, etc.
ti := t.ti
if t.cy != y || t.cx != x {
io.WriteString(t.out, ti.TGoto(x, y))
t.TPuts(ti.TGoto(x, y))
}
style := cell.Style
if style == StyleDefault {
style = t.style
}
if style != t.curstyle {
fg, bg, attrs := style.Decompose()
io.WriteString(t.out, ti.AttrOff)
t.TPuts(ti.AttrOff)
if attrs&AttrBold != 0 {
io.WriteString(t.out, ti.Bold)
t.TPuts(ti.Bold)
}
if attrs&AttrUnderline != 0 {
io.WriteString(t.out, ti.Underline)
t.TPuts(ti.Underline)
}
if attrs&AttrReverse != 0 {
io.WriteString(t.out, ti.Reverse)
t.TPuts(ti.Reverse)
}
if attrs&AttrBlink != 0 {
io.WriteString(t.out, ti.Blink)
t.TPuts(ti.Blink)
}
if attrs&AttrDim != 0 {
io.WriteString(t.out, ti.Dim)
t.TPuts(ti.Dim)
}
if fg != ColorDefault {
c := int(fg) - 1
io.WriteString(t.out, ti.TParm(ti.SetFg, c))
t.TPuts(ti.TParm(ti.SetFg, c))
}
if bg != ColorDefault {
c := int(bg) - 1
io.WriteString(t.out, ti.TParm(ti.SetBg, c))
t.TPuts(ti.TParm(ti.SetBg, c))
}
t.curstyle = style
}
// now emit a character - taking care to not overrun width with a
// 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
mainc := ' '
combc := ""
width := 1
for _, c := range ch {
if c < ' ' {
// no control charcters allowed
continue
}
switch runewidth.RuneWidth(c) {
case 0:
combc = combc + string(c)
case 1:
mainc = c
width = 1
case 2:
mainc = c
width = 2
if x >= t.w-1 {
// too wide to fit; emit space instead
mainc = ' '
width = 1
}
}
width := int(cell.Width)
var str string
if len(cell.Ch) == 0 {
str = " "
} else {
str = string(cell.Ch)
}
io.WriteString(t.out, string(mainc))
io.WriteString(t.out, combc)
if width == 2 && x >= t.w-1 {
// too wide to fit; emit space instead
width = 1
str = " "
}
io.WriteString(t.out, str)
t.cy = y
t.cx = x + width
t.Unlock()
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
if x < 0 || y < 0 || x >= t.w || y >= t.h {
t.cinvis = true
io.WriteString(t.out, t.ti.HideCursor)
t.Unlock()
return
}
if t.cx != x || t.cy != y {
io.WriteString(t.out, t.ti.TGoto(x, y))
}
io.WriteString(t.out, t.ti.ShowCursor)
t.cinvis = false
t.cx = x
t.cy = y
t.cursorx = x
t.cursory = y
t.Unlock()
}
@ -268,20 +288,82 @@ func (t *tScreen) HideCursor() {
t.ShowCursor(-1, -1)
}
func (t *tScreen) showCursor() {
x, y := t.cursorx, t.cursory
if x < 0 || y < 0 || x >= t.w || y >= t.h {
t.TPuts(t.ti.HideCursor)
return
}
if t.cx != x || t.cy != y {
t.TPuts(t.ti.TGoto(x, y))
}
t.TPuts(t.ti.ShowCursor)
t.cx = x
t.cy = y
}
func (t *tScreen) TPuts(s string) {
t.ti.TPuts(t.out, s, t.baud)
}
func (t *tScreen) Show() {
t.Lock()
t.resize()
t.draw()
t.Unlock()
}
func (t *tScreen) clearScreen() {
t.TPuts(t.ti.Clear)
t.clear = false
}
func (t *tScreen) hideCursor() {
// does not update cursor position
t.TPuts(t.ti.HideCursor)
}
func (t *tScreen) draw() {
// clobber cursor position, because we're gonna change it all
t.cx = -1
t.cy = -1
// hide the cursor while we move stuff around
t.hideCursor()
if t.clear {
t.clearScreen()
}
for row := 0; row < t.h; row++ {
for col := 0; col < t.w; col++ {
cell := &t.cells[(row*t.w)+col]
if !cell.Dirty {
continue
}
t.drawCell(col, row, cell)
cell.Dirty = false
}
}
// restore the cursor
t.showCursor()
}
func (t *tScreen) EnableMouse() {
if len(t.mouse) != 0 {
io.WriteString(t.out, t.ti.EnterMouse)
t.TPuts(t.ti.EnterMouse)
}
}
func (t *tScreen) DisableMouse() {
if len(t.mouse) != 0 {
io.WriteString(t.out, t.ti.ExitMouse)
t.TPuts(t.ti.ExitMouse)
}
}
func (t *tScreen) Size() (int, int) {
// XXX: get underlying size
t.Lock()
w, h := t.w, t.h
t.Unlock()
@ -290,17 +372,17 @@ func (t *tScreen) Size() (int, int) {
func (t *tScreen) resize() {
var ev Event
t.Lock()
if w, h, e := t.getWinSize(); e == nil {
if w != t.w || h != t.h {
ev = NewEventResize(w, h)
t.w = w
t.h = h
t.cx = -1
t.cy = -1
t.cells = ResizeCells(t.cells, t.w, t.h, w, h)
t.w = w
t.h = h
}
}
t.Unlock()
if ev != nil {
t.PostEvent(ev)
}
@ -324,6 +406,282 @@ func (t *tScreen) PostEvent(ev Event) {
t.evch <- ev
}
func (t *tScreen) postMouseEvent(x, y, btn int) {
// XTerm mouse events only report at most one button at a time,
// which may include a wheel button. Wheel motion events are
// reported as single impulses, while other button events are reported
// as separate press & release events.
button := ButtonNone
mod := ModNone
// Mouse wheel has bit 6 set, no release events. It should be noted
// that wheel events are sometimes misdelivered as mouse button events
// during a click-drag, so we debounce these, considering them to be
// button press events unless we see an intervening release event.
switch btn & 0x43 {
case 0:
button = Button1
t.wasbtn = true
case 1:
button = Button2
t.wasbtn = true
case 2:
button = Button3
t.wasbtn = true
case 3:
button = ButtonNone
t.wasbtn = false
case 0x40:
if !t.wasbtn {
button = WheelUp
} else {
button = Button1
}
case 0x41:
if !t.wasbtn {
button = WheelDown
} else {
button = Button2
}
}
if btn & 0x4 != 0 {
mod |= ModShift
}
if btn & 0x8 != 0 {
mod |= ModMeta
}
if btn & 0x10 != 0 {
mod |= ModCtrl
}
// Some terminals will report mouse coordinates outside the
// screen, especially with click-drag events. Clip the coordinates
// to the screen in that case.
if x < 0 {
x = 0
}
if x > t.w-1 {
x = t.w-1
}
if y < 0 {
y = 0
}
if y > t.h-1 {
y = t.h-1
}
ev := NewEventMouse(x, y, button, mod)
t.PostEvent(ev)
}
// parseSgrMouse attempts to locate an SGR mouse record at the start of the
// buffer. It returns true, true if it found one, and the associated bytes
// be removed from the buffer. It returns true, false if the buffer might
// contain such an event, but more bytes are necessary (partial match), and
// false, false if the content is definitely *not* an SGR mouse record.
func (t *tScreen) parseSgrMouse(buf *bytes.Buffer) (bool, bool) {
b := buf.Bytes()
var x, y, btn, state int
dig := false
neg := false
i := 0
val := 0
for i = range b {
switch b[i] {
case '\x1b':
if state != 0 {
return false, false
}
state = 1
case '\x9b':
if state != 0 {
return false, false
}
state = 2
case '[':
if state != 1 {
return false, false
}
state = 2
case '<':
if state != 2 {
return false, false
}
val = 0
dig = false
neg = false
state = 3
case '-':
if state != 3 || state != 4 || state != 5 {
return false, false
}
if dig || neg {
return false, false
}
neg = true // stay in state
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
if state != 3 && state != 4 && state != 5 {
return false, false
}
val *= 10
val += int(b[i]-'0')
dig = true // stay in state
case ';':
if neg {
val = -val
}
switch state {
case 3:
btn, val = val, 0
neg, dig, state = false, false, 4
case 4:
x, val = val, 0
neg, dig, state = false, false, 5
default:
return false, false
}
case 'm', 'M':
if state != 5 {
return false, false
}
if neg {
val = -val
}
y = val
// We don't care about the motion bit
btn &^= 32
if b[i] == 'm' {
// mouse release, clear all buttons
btn |= 3
btn &^= 0x40
}
// consume the event bytes
for i >= 0 {
buf.ReadByte()
i--
}
t.postMouseEvent(x, y, btn)
return true, true
}
}
// incomplete & inconclusve at this point
return true, false
}
// parseXtermMouse is like parseSgrMouse, but it parses a legacy
// X11 mouse record.
func (t *tScreen) parseXtermMouse(buf *bytes.Buffer) (bool, bool) {
b := buf.Bytes()
state := 0
btn := 0
x := 0
y := 0
for i := range b {
switch state {
case 0:
switch b[i] {
case '\x1b':
state = 1
case '\x9b':
state = 2
default:
return false, false
}
case 1:
if b[i] != '[' {
return false, false
}
state = 2
case 2:
if b[i] != 'M' {
return false, false
}
state++
case 3:
btn = int(b[i])
state++
case 4:
x = int(b[i]) - 32 - 1
state++
case 5:
y = int(b[i]) - 32 - 1
for i >= 0 {
buf.ReadByte()
i--
}
t.postMouseEvent(x, y, btn)
return true, true
}
}
return true, false
}
func (t *tScreen) parseFunctionKey(buf *bytes.Buffer) (bool, bool) {
b := buf.Bytes()
partial := false
for k, esc := range t.keys {
if bytes.HasPrefix(b, esc) {
// matched
var r rune
if len(esc) == 1 {
r = rune(b[0])
}
ev := NewEventKey(k, r, ModNone)
t.PostEvent(ev)
for i := 0; i < len(esc); i++ {
buf.ReadByte()
}
return true, true
}
if bytes.HasPrefix(esc, b) {
partial = true
}
}
return partial, false
}
func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) {
b := buf.Bytes()
if b[0] >= ' ' && b[0] <= 0x7F {
// printable ASCII easy to deal with -- no encodings
buf.ReadByte()
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
t.PostEvent(ev)
return true, true
}
if b[0] >= 0x80 {
if utf8.FullRune(b) {
r, _, e := buf.ReadRune()
if e == nil {
ev := NewEventKey(KeyRune, r, ModNone)
t.PostEvent(ev)
return true, true
}
}
// Looks like potential escape
return true, false
}
return false, false
}
func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
for {
@ -332,108 +690,46 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
buf.Reset()
return
}
if b[0] >= ' ' && b[0] <= 0x7F {
// printable ASCII easy to deal with -- no encodings
buf.ReadByte()
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
t.PostEvent(ev)
continue
}
// We assume that the first character of any terminal escape
// sequence will be in ASCII -- most often (by far) it is ESC.
if b[0] >= 0x80 && utf8.FullRune(b) {
r, _, e := buf.ReadRune()
if e == nil {
ev := NewEventKey(KeyRune, r, ModNone)
t.PostEvent(ev)
continue
}
}
// Now check the codes we know about
partials := 0
matched := false
for k, esc := range t.keys {
if bytes.HasPrefix(b, esc) {
// matched
var r rune
if len(esc) == 1 {
r = rune(b[0])
}
ev := NewEventKey(k, r, ModNone)
t.PostEvent(ev)
matched = true
for i := 0; i < len(esc); i++ {
buf.ReadByte()
}
break
}
if bytes.HasPrefix(esc, b) {
partials++
}
}
// Mouse events are special, as they carry parameters
if !matched && len(t.mouse) != 0 &&
bytes.HasPrefix(b, t.mouse) {
if len(b) >= len(t.mouse)+3 {
// mouse record
b = b[len(t.mouse):]
btns := ButtonNone
mod := ModNone
switch b[0] & 3 {
case 0:
btns = Button1
case 1:
btns = Button2
case 2:
btns = Button3
case 3:
btns = 0
}
if b[0]&4 != 0 {
mod |= ModShift
}
if b[0]&8 != 0 {
mod |= ModMeta
}
if b[0]&16 != 0 {
mod |= ModCtrl
}
x := int(b[1]) - 33
y := int(b[2]) - 33
for i := 0; i < len(t.mouse)+3; i++ {
buf.ReadByte()
}
matched = true
ev := NewEventMouse(x, y, btns, mod)
t.PostEvent(ev)
continue
} else {
partials++
}
} else {
if part, comp := t.parseRune(buf); comp {
continue
} else if part {
partials++
}
// if we expired, we implicitly fail matches
if expire {
partials = 0
}
// If we had no partial matches, just send first character as
// a rune. Others might still work.
if partials == 0 && !matched {
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
t.PostEvent(ev)
buf.ReadByte()
if part, comp := t.parseFunctionKey(buf); comp {
continue
} else if part {
partials++
}
if partials > 0 {
// We had one or more partial matches, wait for more
// data.
return
if part, comp := t.parseXtermMouse(buf); comp {
continue
} else if part {
partials++
}
if part, comp := t.parseSgrMouse(buf); comp {
continue
} else if part {
partials++
}
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.
by, _ := buf.ReadByte()
ev := NewEventKey(KeyRune, rune(by), ModNone)
t.PostEvent(ev)
continue
}
// well we have some partial data, wait until we get
// some more
break
}
}
@ -443,10 +739,12 @@ func (t *tScreen) inputLoop() {
for {
select {
case <-t.quit:
t.termioFini()
close(t.indoneq)
return
case <-t.sigwinch:
t.Lock()
t.resize()
t.Unlock()
continue
default:
}
@ -462,7 +760,7 @@ func (t *tScreen) inputLoop() {
continue
case nil:
default:
// XXX: post error event?
close(t.indoneq)
return
}
buf.Write(chunk[:n])
@ -470,3 +768,12 @@ func (t *tScreen) inputLoop() {
t.scanInput(buf, false)
}
}
func (t *tScreen) Sync() {
t.Lock()
t.resize()
t.clear = true
InvalidateCells(t.cells)
t.draw()
t.Unlock()
}

View File

@ -38,18 +38,93 @@ import (
// return (-1);
// #endif
// }
//
// int getbaud(struct termios *tios) {
// switch (cfgetospeed(tios)) {
// #ifdef B0
// case B0: return (0);
// #endif
// #ifdef B50
// case B50: return (50);
// #endif
// #ifdef B75
// case B75: return (75);
// #endif
// #ifdef B110
// case B110: return (110);
// #endif
// #ifdef B134
// case B134: return (134);
// #endif
// #ifdef B150
// case B150: return (150);
// #endif
// #ifdef B200
// case B200: return (200);
// #endif
// #ifdef B300
// case B300: return (300);
// #endif
// #ifdef B600
// case B600: return (600);
// #endif
// #ifdef B1200
// case B1200: return (1200);
// #endif
// #ifdef B1800
// case B1800: return (1800);
// #endif
// #ifdef B2400
// case B2400: return (2400);
// #endif
// #ifdef B4800
// case B4800: return (4800);
// #endif
// #ifdef B9600
// case B9600: return (9600);
// #endif
// #ifdef B19200
// case B19200: return (19200);
// #endif
// #ifdef B38400
// case B38400: return (38400);
// #endif
// #ifdef B57600
// case B57600: return (57600);
// #endif
// #ifdef B76800
// case B76800: return (76800);
// #endif
// #ifdef B115200
// case B115200: return (115200);
// #endif
// #ifdef B153600
// case B153600: return (153600);
// #endif
// #ifdef B230400
// case B230400: return (230400);
// #endif
// #ifdef B307200
// case B307200: return (307200);
// #endif
// #ifdef B460800
// case B460800: return (460800);
// #endif
// #ifdef B921600
// case B921600: return (921600);
// #endif
// }
// return (0);
// }
import "C"
var savedtios map[*tScreen]*C.struct_termios
func init() {
savedtios = make(map[*tScreen]*C.struct_termios)
type termiosPrivate struct {
tios C.struct_termios
}
func (t *tScreen) termioInit() error {
var e error
var rv C.int
var oldtios C.struct_termios
var newtios C.struct_termios
var fd C.int
@ -60,11 +135,14 @@ func (t *tScreen) termioInit() error {
goto failed
}
t.tiosp = &termiosPrivate{}
fd = C.int(t.out.Fd())
if rv, e = C.tcgetattr(fd, &oldtios); rv != 0 {
if rv, e = C.tcgetattr(fd, &t.tiosp.tios); rv != 0 {
goto failed
}
newtios = oldtios
t.baud = int(C.getbaud(&t.tiosp.tios))
newtios = t.tiosp.tios
newtios.c_iflag &^= C.IGNBRK | C.BRKINT | C.PARMRK |
C.ISTRIP | C.INLCR | C.IGNCR |
C.ICRNL | C.IXON
@ -86,7 +164,6 @@ func (t *tScreen) termioInit() error {
goto failed
}
savedtios[t] = &oldtios
signal.Notify(t.sigwinch, syscall.SIGWINCH)
if w, h, e := t.getWinSize(); e == nil && w != 0 && h != 0 {
@ -110,12 +187,11 @@ func (t *tScreen) termioFini() {
signal.Stop(t.sigwinch)
<-t.indoneq
if t.out != nil {
if oldtios, ok := savedtios[t]; ok {
fd := C.int(t.out.Fd())
C.tcsetattr(fd, C.TCSANOW, oldtios)
delete(savedtios, t)
}
fd := C.int(t.out.Fd())
C.tcsetattr(fd, C.TCSANOW|C.TCSAFLUSH, &t.tiosp.tios)
t.out.Close()
}
if t.in != nil {

View File

@ -37,3 +37,5 @@ func (t *tScreen) termioFini() {
func (t *tScreen) getWinSize() (int, int, error) {
return 0, 0, errors.New("no temrios on Windows")
}
type termiosPrivate struct{}