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

This adds Bracketed Paste support for terminals that have mouse support and support it. The bracketing events are EventPaste, with methods to note Start() or End() of the paste. Content comes in as normal rune events. Programs must opt-in to this by calling screen.EnablePaste().
522 lines
10 KiB
Go
522 lines
10 KiB
Go
// Copyright 2020 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"
|
|
"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)
|
|
|
|
// SetSize 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
|
|
SetSize(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 SetSize()
|
|
// 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 {
|
|
physw int
|
|
physh int
|
|
fini bool
|
|
style Style
|
|
evch chan Event
|
|
quit chan struct{}
|
|
|
|
front []SimCell
|
|
back CellBuffer
|
|
clear bool
|
|
cursorx int
|
|
cursory int
|
|
cursorvis bool
|
|
mouse bool
|
|
paste bool
|
|
charset string
|
|
encoder transform.Transformer
|
|
decoder transform.Transformer
|
|
fillchar rune
|
|
fillstyle Style
|
|
fallback map[rune]string
|
|
|
|
sync.Mutex
|
|
}
|
|
|
|
func (s *simscreen) Init() error {
|
|
s.evch = make(chan Event, 10)
|
|
s.quit = make(chan struct{})
|
|
s.fillchar = 'X'
|
|
s.fillstyle = StyleDefault
|
|
s.mouse = false
|
|
s.physw = 80
|
|
s.physh = 25
|
|
s.cursorx = -1
|
|
s.cursory = -1
|
|
s.style = StyleDefault
|
|
|
|
if enc := GetEncoding(s.charset); enc != nil {
|
|
s.encoder = enc.NewEncoder()
|
|
s.decoder = enc.NewDecoder()
|
|
} else {
|
|
return ErrNoCharset
|
|
}
|
|
|
|
s.front = make([]SimCell, s.physw*s.physh)
|
|
s.back.Resize(80, 25)
|
|
|
|
// default fallbacks
|
|
s.fallback = make(map[rune]string)
|
|
for k, v := range RuneFallbacks {
|
|
s.fallback[k] = v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *simscreen) Fini() {
|
|
s.Lock()
|
|
s.fini = true
|
|
s.back.Resize(0, 0)
|
|
s.Unlock()
|
|
if s.quit != nil {
|
|
close(s.quit)
|
|
}
|
|
s.physw = 0
|
|
s.physh = 0
|
|
s.front = nil
|
|
}
|
|
|
|
func (s *simscreen) SetStyle(style Style) {
|
|
s.Lock()
|
|
s.style = style
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *simscreen) Clear() {
|
|
s.Fill(' ', s.style)
|
|
}
|
|
|
|
func (s *simscreen) Fill(r rune, style Style) {
|
|
s.Lock()
|
|
s.back.Fill(r, style)
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *simscreen) SetCell(x, y int, style Style, ch ...rune) {
|
|
|
|
if len(ch) > 0 {
|
|
s.SetContent(x, y, ch[0], ch[1:], style)
|
|
} else {
|
|
s.SetContent(x, y, ' ', nil, style)
|
|
}
|
|
}
|
|
|
|
func (s *simscreen) SetContent(x, y int, mainc rune, combc []rune, st Style) {
|
|
|
|
s.Lock()
|
|
s.back.SetContent(x, y, mainc, combc, st)
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *simscreen) GetContent(x, y int) (rune, []rune, Style, int) {
|
|
var mainc rune
|
|
var combc []rune
|
|
var style Style
|
|
var width int
|
|
s.Lock()
|
|
mainc, combc, style, width = s.back.GetContent(x, y)
|
|
s.Unlock()
|
|
return mainc, combc, style, width
|
|
}
|
|
|
|
func (s *simscreen) drawCell(x, y int) int {
|
|
|
|
mainc, combc, style, width := s.back.GetContent(x, y)
|
|
if !s.back.Dirty(x, y) {
|
|
return width
|
|
}
|
|
if x >= s.physw || y >= s.physh || x < 0 || y < 0 {
|
|
return width
|
|
}
|
|
simc := &s.front[(y*s.physw)+x]
|
|
|
|
if style == StyleDefault {
|
|
style = s.style
|
|
}
|
|
simc.Style = style
|
|
simc.Runes = append([]rune{mainc}, combc...)
|
|
|
|
// 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
|
|
|
|
simc.Bytes = nil
|
|
|
|
if x > s.physw-width {
|
|
simc.Runes = []rune{' '}
|
|
simc.Bytes = []byte{' '}
|
|
return width
|
|
}
|
|
|
|
lbuf := make([]byte, 12)
|
|
ubuf := make([]byte, 12)
|
|
nout := 0
|
|
|
|
for _, r := range simc.Runes {
|
|
|
|
l := utf8.EncodeRune(ubuf, r)
|
|
|
|
nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true)
|
|
|
|
if nout == 0 || lbuf[0] == '\x1a' {
|
|
|
|
// skip combining
|
|
|
|
if subst, ok := s.fallback[r]; ok {
|
|
simc.Bytes = append(simc.Bytes,
|
|
[]byte(subst)...)
|
|
|
|
} else if r >= ' ' && r <= '~' {
|
|
simc.Bytes = append(simc.Bytes, byte(r))
|
|
|
|
} else if simc.Bytes == nil {
|
|
simc.Bytes = append(simc.Bytes, '?')
|
|
}
|
|
} else {
|
|
simc.Bytes = append(simc.Bytes, lbuf[:nout]...)
|
|
}
|
|
}
|
|
s.back.SetDirty(x, y, false)
|
|
return width
|
|
}
|
|
|
|
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() {
|
|
s.hideCursor()
|
|
if s.clear {
|
|
s.clearScreen()
|
|
}
|
|
|
|
w, h := s.back.Size()
|
|
for y := 0; y < h; y++ {
|
|
for x := 0; x < w; x++ {
|
|
width := s.drawCell(x, y)
|
|
x += width - 1
|
|
}
|
|
}
|
|
s.showCursor()
|
|
}
|
|
|
|
func (s *simscreen) EnableMouse() {
|
|
s.mouse = true
|
|
}
|
|
|
|
func (s *simscreen) DisableMouse() {
|
|
s.mouse = false
|
|
}
|
|
|
|
func (s *simscreen) EnablePaste() {
|
|
s.paste = true
|
|
}
|
|
|
|
func (s *simscreen) DisablePaste() {
|
|
s.paste = false
|
|
}
|
|
|
|
func (s *simscreen) Size() (int, int) {
|
|
s.Lock()
|
|
w, h := s.back.Size()
|
|
s.Unlock()
|
|
return w, h
|
|
}
|
|
|
|
func (s *simscreen) resize() {
|
|
w, h := s.physw, s.physh
|
|
ow, oh := s.back.Size()
|
|
if w != ow || h != oh {
|
|
s.back.Resize(w, h)
|
|
ev := NewEventResize(w, h)
|
|
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) PostEventWait(ev Event) {
|
|
s.evch <- ev
|
|
}
|
|
|
|
func (s *simscreen) PostEvent(ev Event) error {
|
|
select {
|
|
case s.evch <- ev:
|
|
return nil
|
|
default:
|
|
return ErrEventQFull
|
|
}
|
|
}
|
|
|
|
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(key, r, mod)
|
|
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
|
|
}
|
|
|
|
utfb := make([]byte, len(b)*4) // worst case
|
|
for l := 1; l < len(b); l++ {
|
|
s.decoder.Reset()
|
|
nout, nin, _ := s.decoder.Transform(utfb, b[:l], true)
|
|
|
|
if nout != 0 {
|
|
r, _ := utf8.DecodeRune(utfb[:nout])
|
|
if r != utf8.RuneError {
|
|
ev := NewEventKey(KeyRune, r, ModNone)
|
|
s.PostEvent(ev)
|
|
}
|
|
b = b[nin:]
|
|
continue outer
|
|
}
|
|
}
|
|
failed = true
|
|
b = b[1:]
|
|
continue
|
|
}
|
|
|
|
return !failed
|
|
}
|
|
|
|
func (s *simscreen) Sync() {
|
|
s.Lock()
|
|
s.clear = true
|
|
s.resize()
|
|
s.back.Invalidate()
|
|
s.draw()
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *simscreen) CharacterSet() string {
|
|
return s.charset
|
|
}
|
|
|
|
func (s *simscreen) SetSize(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.cursorx, s.cursory = -1, -1
|
|
s.physw, s.physh = w, h
|
|
s.front = newc
|
|
s.back.Resize(w, 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
|
|
}
|
|
|
|
func (s *simscreen) RegisterRuneFallback(r rune, subst string) {
|
|
s.Lock()
|
|
s.fallback[r] = subst
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *simscreen) UnregisterRuneFallback(r rune) {
|
|
s.Lock()
|
|
delete(s.fallback, r)
|
|
s.Unlock()
|
|
}
|
|
|
|
func (s *simscreen) CanDisplay(r rune, checkFallbacks bool) bool {
|
|
|
|
if enc := s.encoder; enc != nil {
|
|
nb := make([]byte, 6)
|
|
ob := make([]byte, 6)
|
|
num := utf8.EncodeRune(ob, r)
|
|
|
|
enc.Reset()
|
|
dst, _, err := enc.Transform(nb, ob[:num], true)
|
|
if dst != 0 && err == nil && nb[0] != '\x1A' {
|
|
return true
|
|
}
|
|
}
|
|
if !checkFallbacks {
|
|
return false
|
|
}
|
|
if _, ok := s.fallback[r]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *simscreen) HasMouse() bool {
|
|
return false
|
|
}
|
|
|
|
func (s *simscreen) Resize(int, int, int, int) {}
|
|
|
|
func (s *simscreen) HasKey(Key) bool {
|
|
return true
|
|
}
|
|
|
|
func (s *simscreen) Beep() error {
|
|
return nil
|
|
}
|