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