nt

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

screen.go (16967B)


      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 "sync"
     18 
     19 // Screen represents the physical (or emulated) screen.
     20 // This can be a terminal window or a physical console.  Platforms implement
     21 // this differently.
     22 type Screen interface {
     23 	// Init initializes the screen for use.
     24 	Init() error
     25 
     26 	// Fini finalizes the screen also releasing resources.
     27 	Fini()
     28 
     29 	// Clear logically erases the screen.
     30 	// This is effectively a short-cut for Fill(' ', StyleDefault).
     31 	Clear()
     32 
     33 	// Fill fills the screen with the given character and style.
     34 	// The effect of filling the screen is not visible until Show
     35 	// is called (or Sync).
     36 	Fill(rune, Style)
     37 
     38 	// SetCell is an older API, and will be removed.  Please use
     39 	// SetContent instead; SetCell is implemented in terms of SetContent.
     40 	SetCell(x int, y int, style Style, ch ...rune)
     41 
     42 	// GetContent returns the contents at the given location.  If the
     43 	// coordinates are out of range, then the values will be 0, nil,
     44 	// StyleDefault.  Note that the contents returned are logical contents
     45 	// and may not actually be what is displayed, but rather are what will
     46 	// be displayed if Show() or Sync() is called.  The width is the width
     47 	// in screen cells; most often this will be 1, but some East Asian
     48 	// characters and emoji require two cells.
     49 	GetContent(x, y int) (primary rune, combining []rune, style Style, width int)
     50 
     51 	// SetContent sets the contents of the given cell location.  If
     52 	// the coordinates are out of range, then the operation is ignored.
     53 	//
     54 	// The first rune is the primary non-zero width rune.  The array
     55 	// that follows is a possible list of combining characters to append,
     56 	// and will usually be nil (no combining characters.)
     57 	//
     58 	// The results are not displayed until Show() or Sync() is called.
     59 	//
     60 	// Note that wide (East Asian full width and emoji) runes occupy two cells,
     61 	// and attempts to place character at next cell to the right will have
     62 	// undefined effects.  Wide runes that are printed in the
     63 	// last column will be replaced with a single width space on output.
     64 	SetContent(x int, y int, primary rune, combining []rune, style Style)
     65 
     66 	// SetStyle sets the default style to use when clearing the screen
     67 	// or when StyleDefault is specified.  If it is also StyleDefault,
     68 	// then whatever system/terminal default is relevant will be used.
     69 	SetStyle(style Style)
     70 
     71 	// ShowCursor is used to display the cursor at a given location.
     72 	// If the coordinates -1, -1 are given or are otherwise outside the
     73 	// dimensions of the screen, the cursor will be hidden.
     74 	ShowCursor(x int, y int)
     75 
     76 	// HideCursor is used to hide the cursor.  It's an alias for
     77 	// ShowCursor(-1, -1).sim
     78 	HideCursor()
     79 
     80 	// SetCursorStyle is used to set the cursor style.  If the style
     81 	// is not supported (or cursor styles are not supported at all),
     82 	// then this will have no effect.  Color will be changed if supplied,
     83 	// and the terminal supports doing so.
     84 	SetCursorStyle(CursorStyle, ...Color)
     85 
     86 	// Size returns the screen size as width, height.  This changes in
     87 	// response to a call to Clear or Flush.
     88 	Size() (width, height int)
     89 
     90 	// ChannelEvents is an infinite loop that waits for an event and
     91 	// channels it into the user provided channel ch.  Closing the
     92 	// quit channel and calling the Fini method are cancellation
     93 	// signals.  When a cancellation signal is received the method
     94 	// returns after closing ch.
     95 	//
     96 	// This method should be used as a goroutine.
     97 	//
     98 	// NOTE: PollEvent should not be called while this method is running.
     99 	ChannelEvents(ch chan<- Event, quit <-chan struct{})
    100 
    101 	// PollEvent waits for events to arrive.  Main application loops
    102 	// must spin on this to prevent the application from stalling.
    103 	// Furthermore, this will return nil if the Screen is finalized.
    104 	PollEvent() Event
    105 
    106 	// HasPendingEvent returns true if PollEvent would return an event
    107 	// without blocking.  If the screen is stopped and PollEvent would
    108 	// return nil, then the return value from this function is unspecified.
    109 	// The purpose of this function is to allow multiple events to be collected
    110 	// at once, to minimize screen redraws.
    111 	HasPendingEvent() bool
    112 
    113 	// PostEvent tries to post an event into the event stream.  This
    114 	// can fail if the event queue is full.  In that case, the event
    115 	// is dropped, and ErrEventQFull is returned.
    116 	PostEvent(ev Event) error
    117 
    118 	// Deprecated: PostEventWait is unsafe, and will be removed
    119 	// in the future.
    120 	//
    121 	// PostEventWait is like PostEvent, but if the queue is full, it
    122 	// blocks until there is space in the queue, making delivery
    123 	// reliable.  However, it is VERY important that this function
    124 	// never be called from within whatever event loop is polling
    125 	// with PollEvent(), otherwise a deadlock may arise.
    126 	//
    127 	// For this reason, when using this function, the use of a
    128 	// Goroutine is recommended to ensure no deadlock can occur.
    129 	PostEventWait(ev Event)
    130 
    131 	// EnableMouse enables the mouse.  (If your terminal supports it.)
    132 	// If no flags are specified, then all events are reported, if the
    133 	// terminal supports them.
    134 	EnableMouse(...MouseFlags)
    135 
    136 	// DisableMouse disables the mouse.
    137 	DisableMouse()
    138 
    139 	// EnablePaste enables bracketed paste mode, if supported.
    140 	EnablePaste()
    141 
    142 	// DisablePaste disables bracketed paste mode.
    143 	DisablePaste()
    144 
    145 	// EnableFocus enables reporting of focus events, if your terminal supports it.
    146 	EnableFocus()
    147 
    148 	// DisableFocus disables reporting of focus events.
    149 	DisableFocus()
    150 
    151 	// HasMouse returns true if the terminal (apparently) supports a
    152 	// mouse.  Note that the return value of true doesn't guarantee that
    153 	// a mouse/pointing device is present; a false return definitely
    154 	// indicates no mouse support is available.
    155 	HasMouse() bool
    156 
    157 	// Colors returns the number of colors.  All colors are assumed to
    158 	// use the ANSI color map.  If a terminal is monochrome, it will
    159 	// return 0.
    160 	Colors() int
    161 
    162 	// Show makes all the content changes made using SetContent() visible
    163 	// on the display.
    164 	//
    165 	// It does so in the most efficient and least visually disruptive
    166 	// manner possible.
    167 	Show()
    168 
    169 	// Sync works like Show(), but it updates every visible cell on the
    170 	// physical display, assuming that it is not synchronized with any
    171 	// internal model.  This may be both expensive and visually jarring,
    172 	// so it should only be used when believed to actually be necessary.
    173 	//
    174 	// Typically, this is called as a result of a user-requested redraw
    175 	// (e.g. to clear up on-screen corruption caused by some other program),
    176 	// or during a resize event.
    177 	Sync()
    178 
    179 	// CharacterSet returns information about the character set.
    180 	// This isn't the full locale, but it does give us the input/output
    181 	// character set.  Note that this is just for diagnostic purposes,
    182 	// we normally translate input/output to/from UTF-8, regardless of
    183 	// what the user's environment is.
    184 	CharacterSet() string
    185 
    186 	// RegisterRuneFallback adds a fallback for runes that are not
    187 	// part of the character set -- for example one could register
    188 	// o as a fallback for ΓΈ.  This should be done cautiously for
    189 	// characters that might be displayed ordinarily in language
    190 	// specific text -- characters that could change the meaning of
    191 	// written text would be dangerous.  The intention here is to
    192 	// facilitate fallback characters in pseudo-graphical applications.
    193 	//
    194 	// If the terminal has fallbacks already in place via an alternate
    195 	// character set, those are used in preference.  Also, standard
    196 	// fallbacks for graphical characters in the alternate character set
    197 	// terminfo string are registered implicitly.
    198 	//
    199 	// The display string should be the same width as original rune.
    200 	// This makes it possible to register two character replacements
    201 	// for full width East Asian characters, for example.
    202 	//
    203 	// It is recommended that replacement strings consist only of
    204 	// 7-bit ASCII, since other characters may not display everywhere.
    205 	RegisterRuneFallback(r rune, subst string)
    206 
    207 	// UnregisterRuneFallback unmaps a replacement.  It will unmap
    208 	// the implicit ASCII replacements for alternate characters as well.
    209 	// When an unmapped char needs to be displayed, but no suitable
    210 	// glyph is available, '?' is emitted instead.  It is not possible
    211 	// to "disable" the use of alternate characters that are supported
    212 	// by your terminal except by changing the terminal database.
    213 	UnregisterRuneFallback(r rune)
    214 
    215 	// CanDisplay returns true if the given rune can be displayed on
    216 	// this screen.  Note that this is a best-guess effort -- whether
    217 	// your fonts support the character or not may be questionable.
    218 	// Mostly this is for folks who work outside of Unicode.
    219 	//
    220 	// If checkFallbacks is true, then if any (possibly imperfect)
    221 	// fallbacks are registered, this will return true.  This will
    222 	// also return true if the terminal can replace the glyph with
    223 	// one that is visually indistinguishable from the one requested.
    224 	CanDisplay(r rune, checkFallbacks bool) bool
    225 
    226 	// Resize does nothing, since it's generally not possible to
    227 	// ask a screen to resize, but it allows the Screen to implement
    228 	// the View interface.
    229 	Resize(int, int, int, int)
    230 
    231 	// HasKey returns true if the keyboard is believed to have the
    232 	// key.  In some cases a keyboard may have keys with this name
    233 	// but no support for them, while in others a key may be reported
    234 	// as supported but not actually be usable (such as some emulators
    235 	// that hijack certain keys).  Its best not to depend to strictly
    236 	// on this function, but it can be used for hinting when building
    237 	// menus, displayed hot-keys, etc.  Note that KeyRune (literal
    238 	// runes) is always true.
    239 	HasKey(Key) bool
    240 
    241 	// Suspend pauses input and output processing.  It also restores the
    242 	// terminal settings to what they were when the application started.
    243 	// This can be used to, for example, run a sub-shell.
    244 	Suspend() error
    245 
    246 	// Resume resumes after Suspend().
    247 	Resume() error
    248 
    249 	// Beep attempts to sound an OS-dependent audible alert and returns an error
    250 	// when unsuccessful.
    251 	Beep() error
    252 
    253 	// SetSize attempts to resize the window.  It also invalidates the cells and
    254 	// calls the resize function.  Note that if the window size is changed, it will
    255 	// not be restored upon application exit.
    256 	//
    257 	// Many terminals cannot support this.  Perversely, the "modern" Windows Terminal
    258 	// does not support application-initiated resizing, whereas the legacy terminal does.
    259 	// Also, some emulators can support this but may have it disabled by default.
    260 	SetSize(int, int)
    261 
    262 	// LockRegion sets or unsets a lock on a region of cells. A lock on a
    263 	// cell prevents the cell from being redrawn.
    264 	LockRegion(x, y, width, height int, lock bool)
    265 
    266 	// Tty returns the underlying Tty. If the screen is not a terminal, the
    267 	// returned bool will be false
    268 	Tty() (Tty, bool)
    269 
    270 	// SetTitle sets a window title on the screen.
    271 	// Terminals may be configured to ignore this, or unable to.
    272 	// Tcell may attempt to save and restore the window title on entry and exit, but
    273 	// the results may vary.  Use of unicode characters may not be supported.
    274 	SetTitle(string)
    275 
    276 	// SetClipboard is used to post arbitrary data to the system clipboard.
    277 	// This need not be UTF-8 string data.  It's up to the recipient to decode the
    278 	// data meaningfully.  Terminals may prevent this for security reasons.
    279 	SetClipboard([]byte)
    280 
    281 	// GetClipboard is used to request the clipboard contents.  It may be ignored.
    282 	// If the terminal is willing, it will be post the clipboard contents using an
    283 	// EventPaste with the clipboard content as the Data() field.  Terminals may
    284 	// prevent this for security reasons.
    285 	GetClipboard()
    286 }
    287 
    288 // NewScreen returns a default Screen suitable for the user's terminal
    289 // environment.
    290 func NewScreen() (Screen, error) {
    291 	// Windows is happier if we try for a console screen first.
    292 	if s, _ := NewConsoleScreen(); s != nil {
    293 		return s, nil
    294 	} else if s, e := NewTerminfoScreen(); s != nil {
    295 		return s, nil
    296 	} else {
    297 		return nil, e
    298 	}
    299 }
    300 
    301 // MouseFlags are options to modify the handling of mouse events.
    302 // Actual events can be ORed together.
    303 type MouseFlags int
    304 
    305 const (
    306 	MouseButtonEvents = MouseFlags(1) // Click events only
    307 	MouseDragEvents   = MouseFlags(2) // Click-drag events (includes button events)
    308 	MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
    309 )
    310 
    311 // CursorStyle represents a given cursor style, which can include the shape and
    312 // whether the cursor blinks or is solid.  Support for changing this is not universal.
    313 type CursorStyle int
    314 
    315 const (
    316 	CursorStyleDefault = CursorStyle(iota) // The default
    317 	CursorStyleBlinkingBlock
    318 	CursorStyleSteadyBlock
    319 	CursorStyleBlinkingUnderline
    320 	CursorStyleSteadyUnderline
    321 	CursorStyleBlinkingBar
    322 	CursorStyleSteadyBar
    323 )
    324 
    325 // screenImpl is a subset of Screen that can be used with baseScreen to formulate
    326 // a complete implementation of Screen.  See Screen for doc comments about methods.
    327 type screenImpl interface {
    328 	Init() error
    329 	Fini()
    330 	SetStyle(style Style)
    331 	ShowCursor(x int, y int)
    332 	HideCursor()
    333 	SetCursor(CursorStyle, Color)
    334 	Size() (width, height int)
    335 	EnableMouse(...MouseFlags)
    336 	DisableMouse()
    337 	EnablePaste()
    338 	DisablePaste()
    339 	EnableFocus()
    340 	DisableFocus()
    341 	HasMouse() bool
    342 	Colors() int
    343 	Show()
    344 	Sync()
    345 	CharacterSet() string
    346 	RegisterRuneFallback(r rune, subst string)
    347 	UnregisterRuneFallback(r rune)
    348 	CanDisplay(r rune, checkFallbacks bool) bool
    349 	Resize(int, int, int, int)
    350 	HasKey(Key) bool
    351 	Suspend() error
    352 	Resume() error
    353 	Beep() error
    354 	SetSize(int, int)
    355 	SetTitle(string)
    356 	Tty() (Tty, bool)
    357 	SetClipboard([]byte)
    358 	GetClipboard()
    359 
    360 	// Following methods are not part of the Screen api, but are used for interaction with
    361 	// the common layer code.
    362 
    363 	// Locker locks the underlying data structures so that we can access them
    364 	// in a thread-safe way.
    365 	sync.Locker
    366 
    367 	// GetCells returns a pointer to the underlying CellBuffer that the implementation uses.
    368 	// Various methods will write to these for performance, but will use the lock to do so.
    369 	GetCells() *CellBuffer
    370 
    371 	// StopQ is closed when the screen is shut down via Fini.  It remains open if the screen
    372 	// is merely suspended.
    373 	StopQ() <-chan struct{}
    374 
    375 	// EventQ delivers events.  Events are posted to this by the screen in response to
    376 	// key presses, resizes, etc.  Application code receives events from this via the
    377 	// Screen.PollEvent, Screen.ChannelEvents APIs.
    378 	EventQ() chan Event
    379 }
    380 
    381 type baseScreen struct {
    382 	screenImpl
    383 }
    384 
    385 func (b *baseScreen) SetCell(x int, y int, style Style, ch ...rune) {
    386 	if len(ch) > 0 {
    387 		b.SetContent(x, y, ch[0], ch[1:], style)
    388 	} else {
    389 		b.SetContent(x, y, ' ', nil, style)
    390 	}
    391 }
    392 
    393 func (b *baseScreen) Clear() {
    394 	b.Fill(' ', StyleDefault)
    395 }
    396 
    397 func (b *baseScreen) Fill(r rune, style Style) {
    398 	cb := b.GetCells()
    399 	b.Lock()
    400 	cb.Fill(r, style)
    401 	b.Unlock()
    402 }
    403 
    404 func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, st Style) {
    405 
    406 	cells := b.GetCells()
    407 	b.Lock()
    408 	cells.SetContent(x, y, mainc, combc, st)
    409 	b.Unlock()
    410 }
    411 
    412 func (b *baseScreen) GetContent(x, y int) (rune, []rune, Style, int) {
    413 	var primary rune
    414 	var combining []rune
    415 	var style Style
    416 	var width int
    417 	cells := b.GetCells()
    418 	b.Lock()
    419 	primary, combining, style, width = cells.GetContent(x, y)
    420 	b.Unlock()
    421 	return primary, combining, style, width
    422 }
    423 
    424 func (b *baseScreen) LockRegion(x, y, width, height int, lock bool) {
    425 	cells := b.GetCells()
    426 	b.Lock()
    427 	for j := y; j < (y + height); j += 1 {
    428 		for i := x; i < (x + width); i += 1 {
    429 			switch lock {
    430 			case true:
    431 				cells.LockCell(i, j)
    432 			case false:
    433 				cells.UnlockCell(i, j)
    434 			}
    435 		}
    436 	}
    437 	b.Unlock()
    438 }
    439 
    440 func (b *baseScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
    441 	defer close(ch)
    442 	for {
    443 		select {
    444 		case <-quit:
    445 			return
    446 		case <-b.StopQ():
    447 			return
    448 		case ev := <-b.EventQ():
    449 			select {
    450 			case <-quit:
    451 				return
    452 			case <-b.StopQ():
    453 				return
    454 			case ch <- ev:
    455 			}
    456 		}
    457 	}
    458 }
    459 
    460 func (b *baseScreen) PollEvent() Event {
    461 	select {
    462 	case <-b.StopQ():
    463 		return nil
    464 	case ev := <-b.EventQ():
    465 		return ev
    466 	}
    467 }
    468 
    469 func (b *baseScreen) HasPendingEvent() bool {
    470 	return len(b.EventQ()) > 0
    471 }
    472 
    473 func (b *baseScreen) PostEventWait(ev Event) {
    474 	select {
    475 	case b.EventQ() <- ev:
    476 	case <-b.StopQ():
    477 	}
    478 }
    479 
    480 func (b *baseScreen) PostEvent(ev Event) error {
    481 	select {
    482 	case b.EventQ() <- ev:
    483 		return nil
    484 	default:
    485 		return ErrEventQFull
    486 	}
    487 }
    488 
    489 func (b *baseScreen) SetCursorStyle(cs CursorStyle, ccs ...Color) {
    490 	if len(ccs) > 0 {
    491 		b.SetCursor(cs, ccs[0])
    492 	} else {
    493 		b.SetCursor(cs, ColorNone)
    494 	}
    495 }