nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

simulation.go (10670B)


      1 // Copyright 2024 The TCell Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use file except in compliance with the License.
      5 // You may obtain a copy of the license at
      6 //
      7 //    http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package tcell
     16 
     17 import (
     18 	"sync"
     19 	"unicode/utf8"
     20 
     21 	"golang.org/x/text/transform"
     22 )
     23 
     24 // NewSimulationScreen returns a SimulationScreen.  Note that
     25 // SimulationScreen is also a Screen.
     26 func NewSimulationScreen(charset string) SimulationScreen {
     27 	if charset == "" {
     28 		charset = "UTF-8"
     29 	}
     30 	ss := &simscreen{charset: charset}
     31 	ss.Screen = &baseScreen{screenImpl: ss}
     32 	return ss
     33 }
     34 
     35 // SimulationScreen represents a screen simulation.  This is intended to
     36 // be a superset of normal Screens, but also adds some important interfaces
     37 // for testing.
     38 type SimulationScreen interface {
     39 	Screen
     40 
     41 	// InjectKeyBytes injects a stream of bytes corresponding to
     42 	// the native encoding (see charset).  It turns true if the entire
     43 	// set of bytes were processed and delivered as KeyEvents, false
     44 	// if any bytes were not fully understood.  Any bytes that are not
     45 	// fully converted are discarded.
     46 	InjectKeyBytes(buf []byte) bool
     47 
     48 	// InjectKey injects a key event.  The rune is a UTF-8 rune, post
     49 	// any translation.
     50 	InjectKey(key Key, r rune, mod ModMask)
     51 
     52 	// InjectMouse injects a mouse event.
     53 	InjectMouse(x, y int, buttons ButtonMask, mod ModMask)
     54 
     55 	// GetContents returns screen contents as an array of
     56 	// cells, along with the physical width & height.   Note that the
     57 	// physical contents will be used until the next time SetSize()
     58 	// is called.
     59 	GetContents() (cells []SimCell, width int, height int)
     60 
     61 	// GetCursor returns the cursor details.
     62 	GetCursor() (x int, y int, visible bool)
     63 
     64 	// GetTitle gets the previously set title.
     65 	GetTitle() string
     66 
     67 	// GetClipboardData gets the actual data for the clipboard.
     68 	GetClipboardData() []byte
     69 }
     70 
     71 // SimCell represents a simulated screen cell.  The purpose of this
     72 // is to track on screen content.
     73 type SimCell struct {
     74 	// Bytes is the actual character bytes.  Normally this is
     75 	// rune data, but it could be be data in another encoding system.
     76 	Bytes []byte
     77 
     78 	// Style is the style used to display the data.
     79 	Style Style
     80 
     81 	// Runes is the list of runes, unadulterated, in UTF-8.
     82 	Runes []rune
     83 }
     84 
     85 type simscreen struct {
     86 	physw int
     87 	physh int
     88 	fini  bool
     89 	style Style
     90 	evch  chan Event
     91 	quit  chan struct{}
     92 
     93 	front     []SimCell
     94 	back      CellBuffer
     95 	clear     bool
     96 	cursorx   int
     97 	cursory   int
     98 	cursorvis bool
     99 	mouse     bool
    100 	paste     bool
    101 	charset   string
    102 	encoder   transform.Transformer
    103 	decoder   transform.Transformer
    104 	fillchar  rune
    105 	fillstyle Style
    106 	fallback  map[rune]string
    107 	title     string
    108 	clipboard []byte
    109 
    110 	Screen
    111 	sync.Mutex
    112 }
    113 
    114 func (s *simscreen) Init() error {
    115 	s.evch = make(chan Event, 10)
    116 	s.quit = make(chan struct{})
    117 	s.fillchar = 'X'
    118 	s.fillstyle = StyleDefault
    119 	s.mouse = false
    120 	s.physw = 80
    121 	s.physh = 25
    122 	s.cursorx = -1
    123 	s.cursory = -1
    124 	s.style = StyleDefault
    125 
    126 	if enc := GetEncoding(s.charset); enc != nil {
    127 		s.encoder = enc.NewEncoder()
    128 		s.decoder = enc.NewDecoder()
    129 	} else {
    130 		return ErrNoCharset
    131 	}
    132 
    133 	s.front = make([]SimCell, s.physw*s.physh)
    134 	s.back.Resize(80, 25)
    135 
    136 	// default fallbacks
    137 	s.fallback = make(map[rune]string)
    138 	for k, v := range RuneFallbacks {
    139 		s.fallback[k] = v
    140 	}
    141 	return nil
    142 }
    143 
    144 func (s *simscreen) Fini() {
    145 	s.Lock()
    146 	s.fini = true
    147 	s.back.Resize(0, 0)
    148 	s.Unlock()
    149 	if s.quit != nil {
    150 		close(s.quit)
    151 	}
    152 	s.physw = 0
    153 	s.physh = 0
    154 	s.front = nil
    155 }
    156 
    157 func (s *simscreen) SetStyle(style Style) {
    158 	s.Lock()
    159 	s.style = style
    160 	s.Unlock()
    161 }
    162 
    163 func (s *simscreen) drawCell(x, y int) int {
    164 
    165 	mainc, combc, style, width := s.back.GetContent(x, y)
    166 	if !s.back.Dirty(x, y) {
    167 		return width
    168 	}
    169 	if x >= s.physw || y >= s.physh || x < 0 || y < 0 {
    170 		return width
    171 	}
    172 	simc := &s.front[(y*s.physw)+x]
    173 
    174 	if style == StyleDefault {
    175 		style = s.style
    176 	}
    177 	simc.Style = style
    178 	simc.Runes = append([]rune{mainc}, combc...)
    179 
    180 	// now emit runes - taking care to not overrun width with a
    181 	// wide character, and to ensure that we emit exactly one regular
    182 	// character followed up by any residual combing characters
    183 
    184 	simc.Bytes = nil
    185 
    186 	if x > s.physw-width {
    187 		simc.Runes = []rune{' '}
    188 		simc.Bytes = []byte{' '}
    189 		return width
    190 	}
    191 
    192 	lbuf := make([]byte, 12)
    193 	ubuf := make([]byte, 12)
    194 	nout := 0
    195 
    196 	for _, r := range simc.Runes {
    197 
    198 		l := utf8.EncodeRune(ubuf, r)
    199 
    200 		nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true)
    201 
    202 		if nout == 0 || lbuf[0] == '\x1a' {
    203 
    204 			// skip combining
    205 
    206 			if subst, ok := s.fallback[r]; ok {
    207 				simc.Bytes = append(simc.Bytes,
    208 					[]byte(subst)...)
    209 
    210 			} else if r >= ' ' && r <= '~' {
    211 				simc.Bytes = append(simc.Bytes, byte(r))
    212 
    213 			} else if simc.Bytes == nil {
    214 				simc.Bytes = append(simc.Bytes, '?')
    215 			}
    216 		} else {
    217 			simc.Bytes = append(simc.Bytes, lbuf[:nout]...)
    218 		}
    219 	}
    220 	s.back.SetDirty(x, y, false)
    221 	return width
    222 }
    223 
    224 func (s *simscreen) ShowCursor(x, y int) {
    225 	s.Lock()
    226 	s.cursorx, s.cursory = x, y
    227 	s.showCursor()
    228 	s.Unlock()
    229 }
    230 
    231 func (s *simscreen) HideCursor() {
    232 	s.ShowCursor(-1, -1)
    233 }
    234 
    235 func (s *simscreen) showCursor() {
    236 
    237 	x, y := s.cursorx, s.cursory
    238 	if x < 0 || y < 0 || x >= s.physw || y >= s.physh {
    239 		s.cursorvis = false
    240 	} else {
    241 		s.cursorvis = true
    242 	}
    243 }
    244 
    245 func (s *simscreen) hideCursor() {
    246 	// does not update cursor position
    247 	s.cursorvis = false
    248 }
    249 
    250 func (s *simscreen) SetCursor(CursorStyle, Color) {}
    251 
    252 func (s *simscreen) Show() {
    253 	s.Lock()
    254 	s.resize()
    255 	s.draw()
    256 	s.Unlock()
    257 }
    258 
    259 func (s *simscreen) clearScreen() {
    260 	// We emulate a hardware clear by filling with a specific pattern
    261 	for i := range s.front {
    262 		s.front[i].Style = s.fillstyle
    263 		s.front[i].Runes = []rune{s.fillchar}
    264 		s.front[i].Bytes = []byte{byte(s.fillchar)}
    265 	}
    266 	s.clear = false
    267 }
    268 
    269 func (s *simscreen) draw() {
    270 	s.hideCursor()
    271 	if s.clear {
    272 		s.clearScreen()
    273 	}
    274 
    275 	w, h := s.back.Size()
    276 	for y := 0; y < h; y++ {
    277 		for x := 0; x < w; x++ {
    278 			width := s.drawCell(x, y)
    279 			x += width - 1
    280 		}
    281 	}
    282 	s.showCursor()
    283 }
    284 
    285 func (s *simscreen) EnableMouse(...MouseFlags) {
    286 	s.mouse = true
    287 }
    288 
    289 func (s *simscreen) DisableMouse() {
    290 	s.mouse = false
    291 }
    292 
    293 func (s *simscreen) EnablePaste() {
    294 	s.paste = true
    295 }
    296 
    297 func (s *simscreen) DisablePaste() {
    298 	s.paste = false
    299 }
    300 
    301 func (s *simscreen) EnableFocus() {
    302 }
    303 
    304 func (s *simscreen) DisableFocus() {
    305 }
    306 
    307 func (s *simscreen) Size() (int, int) {
    308 	s.Lock()
    309 	w, h := s.back.Size()
    310 	s.Unlock()
    311 	return w, h
    312 }
    313 
    314 func (s *simscreen) resize() {
    315 	w, h := s.physw, s.physh
    316 	ow, oh := s.back.Size()
    317 	if w != ow || h != oh {
    318 		s.back.Resize(w, h)
    319 		ev := NewEventResize(w, h)
    320 		s.postEvent(ev)
    321 	}
    322 }
    323 
    324 func (s *simscreen) Colors() int {
    325 	return 256
    326 }
    327 
    328 func (s *simscreen) postEvent(ev Event) {
    329 	select {
    330 	case s.evch <- ev:
    331 	case <-s.quit:
    332 	}
    333 }
    334 
    335 func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) {
    336 	ev := NewEventMouse(x, y, buttons, mod)
    337 	s.postEvent(ev)
    338 }
    339 
    340 func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) {
    341 	ev := NewEventKey(key, r, mod)
    342 	s.postEvent(ev)
    343 }
    344 
    345 func (s *simscreen) InjectKeyBytes(b []byte) bool {
    346 	failed := false
    347 
    348 outer:
    349 	for len(b) > 0 {
    350 		if b[0] >= ' ' && b[0] <= 0x7F {
    351 			// printable ASCII easy to deal with -- no encodings
    352 			ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
    353 			s.postEvent(ev)
    354 			b = b[1:]
    355 			continue
    356 		}
    357 
    358 		if b[0] < 0x80 {
    359 			mod := ModNone
    360 			// No encodings start with low numbered values
    361 			if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ {
    362 				mod = ModCtrl
    363 			}
    364 			ev := NewEventKey(Key(b[0]), 0, mod)
    365 			s.postEvent(ev)
    366 			b = b[1:]
    367 			continue
    368 		}
    369 
    370 		utfb := make([]byte, len(b)*4) // worst case
    371 		for l := 1; l < len(b); l++ {
    372 			s.decoder.Reset()
    373 			nout, nin, _ := s.decoder.Transform(utfb, b[:l], true)
    374 
    375 			if nout != 0 {
    376 				r, _ := utf8.DecodeRune(utfb[:nout])
    377 				if r != utf8.RuneError {
    378 					ev := NewEventKey(KeyRune, r, ModNone)
    379 					s.postEvent(ev)
    380 				}
    381 				b = b[nin:]
    382 				continue outer
    383 			}
    384 		}
    385 		failed = true
    386 		b = b[1:]
    387 		continue
    388 	}
    389 
    390 	return !failed
    391 }
    392 
    393 func (s *simscreen) Sync() {
    394 	s.Lock()
    395 	s.clear = true
    396 	s.resize()
    397 	s.back.Invalidate()
    398 	s.draw()
    399 	s.Unlock()
    400 }
    401 
    402 func (s *simscreen) CharacterSet() string {
    403 	return s.charset
    404 }
    405 
    406 func (s *simscreen) SetSize(w, h int) {
    407 	s.Lock()
    408 	newc := make([]SimCell, w*h)
    409 	for row := 0; row < h && row < s.physh; row++ {
    410 		for col := 0; col < w && col < s.physw; col++ {
    411 			newc[(row*w)+col] = s.front[(row*s.physw)+col]
    412 		}
    413 	}
    414 	s.cursorx, s.cursory = -1, -1
    415 	s.physw, s.physh = w, h
    416 	s.front = newc
    417 	s.back.Resize(w, h)
    418 	s.Unlock()
    419 }
    420 
    421 func (s *simscreen) GetContents() ([]SimCell, int, int) {
    422 	s.Lock()
    423 	cells, w, h := s.front, s.physw, s.physh
    424 	s.Unlock()
    425 	return cells, w, h
    426 }
    427 
    428 func (s *simscreen) GetCursor() (int, int, bool) {
    429 	s.Lock()
    430 	x, y, vis := s.cursorx, s.cursory, s.cursorvis
    431 	s.Unlock()
    432 	return x, y, vis
    433 }
    434 
    435 func (s *simscreen) RegisterRuneFallback(r rune, subst string) {
    436 	s.Lock()
    437 	s.fallback[r] = subst
    438 	s.Unlock()
    439 }
    440 
    441 func (s *simscreen) UnregisterRuneFallback(r rune) {
    442 	s.Lock()
    443 	delete(s.fallback, r)
    444 	s.Unlock()
    445 }
    446 
    447 func (s *simscreen) CanDisplay(r rune, checkFallbacks bool) bool {
    448 
    449 	if enc := s.encoder; enc != nil {
    450 		nb := make([]byte, 6)
    451 		ob := make([]byte, 6)
    452 		num := utf8.EncodeRune(ob, r)
    453 
    454 		enc.Reset()
    455 		dst, _, err := enc.Transform(nb, ob[:num], true)
    456 		if dst != 0 && err == nil && nb[0] != '\x1A' {
    457 			return true
    458 		}
    459 	}
    460 	if !checkFallbacks {
    461 		return false
    462 	}
    463 	if _, ok := s.fallback[r]; ok {
    464 		return true
    465 	}
    466 	return false
    467 }
    468 
    469 func (s *simscreen) HasMouse() bool {
    470 	return false
    471 }
    472 
    473 func (s *simscreen) Resize(int, int, int, int) {}
    474 
    475 func (s *simscreen) HasKey(Key) bool {
    476 	return true
    477 }
    478 
    479 func (s *simscreen) Beep() error {
    480 	return nil
    481 }
    482 
    483 func (s *simscreen) Suspend() error {
    484 	return nil
    485 }
    486 
    487 func (s *simscreen) Resume() error {
    488 	return nil
    489 }
    490 
    491 func (s *simscreen) Tty() (Tty, bool) {
    492 	return nil, false
    493 }
    494 
    495 func (s *simscreen) GetCells() *CellBuffer {
    496 	return &s.back
    497 }
    498 
    499 func (s *simscreen) EventQ() chan Event {
    500 	return s.evch
    501 }
    502 
    503 func (s *simscreen) StopQ() <-chan struct{} {
    504 	return s.quit
    505 }
    506 
    507 func (s *simscreen) SetTitle(title string) {
    508 	s.title = title
    509 }
    510 
    511 func (s *simscreen) GetTitle() string {
    512 	return s.title
    513 }
    514 
    515 func (s *simscreen) SetClipboard(data []byte) {
    516 	s.clipboard = data
    517 }
    518 
    519 func (s *simscreen) GetClipboard() {
    520 	if s.clipboard != nil {
    521 		ev := NewEventClipboard(s.clipboard)
    522 		s.postEvent(ev)
    523 	}
    524 }
    525 
    526 func (s *simscreen) GetClipboardData() []byte {
    527 	return s.clipboard
    528 }