mirror of
				https://github.com/taigrr/wtf
				synced 2025-01-18 04:03:14 -08:00 
			
		
		
		
	
		
			
				
	
	
		
			509 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			509 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2016 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
 | 
						|
	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) 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
 | 
						|
}
 |