micro/internal/screen/screen.go

265 lines
6.3 KiB
Go
Raw Normal View History

2018-08-27 19:53:10 +00:00
package screen
import (
2020-06-20 22:24:12 +00:00
"errors"
"log"
2018-08-27 19:53:10 +00:00
"os"
2018-08-28 18:24:59 +00:00
"sync"
2018-08-27 19:53:10 +00:00
"github.com/micro-editor/tcell/v2"
2020-05-04 14:16:15 +00:00
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
2018-08-27 19:53:10 +00:00
)
2018-12-31 19:46:04 +00:00
// Screen is the tcell screen we use to draw to the terminal
// Synchronization is used because we poll the screen on a separate
// thread and sometimes the screen is shut down by the main thread
// (for example on TermMessage) so we don't want to poll a nil/shutdown
// screen. TODO: maybe we should worry about polling and drawing at the
// same time too.
2018-08-27 19:53:10 +00:00
var Screen tcell.Screen
2020-01-02 03:40:51 +00:00
// Events is the channel of tcell events
var Events chan (tcell.Event)
// RestartCallback is called when the screen is restarted after it was
// temporarily shut down
var RestartCallback func()
2020-01-02 03:40:51 +00:00
// The lock is necessary since the screen is polled on a separate thread
2018-08-28 18:24:59 +00:00
var lock sync.Mutex
2020-01-02 03:40:51 +00:00
// drawChan is a channel that will cause the screen to redraw when
2020-01-02 03:40:51 +00:00
// written to even if no event user event has occurred
var drawChan chan bool
2018-08-28 18:24:59 +00:00
// rawSeq is the list of raw escape sequences that are bound to some actions
// via keybindings and thus should be parsed by tcell. We need to register
// them in tcell every time we reinitialize the screen, so we need to remember
// them in a list
var rawSeq = make([]string, 0)
2020-01-02 03:40:51 +00:00
// Lock locks the screen lock
2018-08-28 18:24:59 +00:00
func Lock() {
lock.Lock()
}
2020-01-02 03:40:51 +00:00
// Unlock unlocks the screen lock
2018-08-28 18:24:59 +00:00
func Unlock() {
lock.Unlock()
}
2020-01-02 03:40:51 +00:00
// Redraw schedules a redraw with the draw channel
2019-01-10 21:37:05 +00:00
func Redraw() {
select {
case drawChan <- true:
default:
// channel is full
}
2019-01-10 21:37:05 +00:00
}
2018-08-28 18:24:59 +00:00
// DrawChan returns the draw channel
func DrawChan() chan bool {
return drawChan
}
2020-01-02 03:40:51 +00:00
type screenCell struct {
x, y int
r rune
combc []rune
style tcell.Style
}
var lastCursor screenCell
// ShowFakeCursor displays a cursor at the given position by modifying the
// style of the given column instead of actually using the terminal cursor
// This can be useful in certain terminals such as the windows console where
// modifying the cursor location is slow and frequent modifications cause flashing
// This keeps track of the most recent fake cursor location and resets it when
// a new fake cursor location is specified
2020-01-02 02:29:18 +00:00
func ShowFakeCursor(x, y int) {
2020-01-02 03:40:51 +00:00
r, combc, style, _ := Screen.GetContent(x, y)
Screen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)
Screen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))
lastCursor.x, lastCursor.y = x, y
lastCursor.r = r
lastCursor.combc = combc
lastCursor.style = style
}
func UseFake() bool {
return util.FakeCursor || config.GetGlobalOption("fakecursor").(bool)
}
2020-01-02 03:40:51 +00:00
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
// reset previous locations of the cursor
// Fake cursors are also necessary to display multiple cursors
func ShowFakeCursorMulti(x, y int) {
2020-01-02 02:29:18 +00:00
r, _, _, _ := Screen.GetContent(x, y)
Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
}
2020-01-02 03:40:51 +00:00
// ShowCursor puts the cursor at the given location using a fake cursor
// if enabled or using the terminal cursor otherwise
// By default only the windows console will use a fake cursor
2020-01-02 02:29:18 +00:00
func ShowCursor(x, y int) {
if UseFake() {
2020-01-02 02:29:18 +00:00
ShowFakeCursor(x, y)
} else {
Screen.ShowCursor(x, y)
}
}
2020-01-02 03:40:51 +00:00
// SetContent sets a cell at a point on the screen and makes sure that it is
// synced with the last cursor location
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
if !Screen.CanDisplay(mainc, true) {
mainc = '<27>'
}
2020-01-02 03:40:51 +00:00
Screen.SetContent(x, y, mainc, combc, style)
if UseFake() && lastCursor.x == x && lastCursor.y == y {
2020-01-02 03:40:51 +00:00
lastCursor.r = mainc
lastCursor.style = style
lastCursor.combc = combc
}
}
// RegisterRawSeq registers a raw escape sequence that should be parsed by tcell
func RegisterRawSeq(r string) {
for _, seq := range rawSeq {
if seq == r {
return
}
}
rawSeq = append(rawSeq, r)
if Screen != nil {
Screen.RegisterRawSeq(r)
}
}
// UnregisterRawSeq unregisters a raw escape sequence that should be parsed by tcell
func UnregisterRawSeq(r string) {
for i, seq := range rawSeq {
if seq == r {
rawSeq[i] = rawSeq[len(rawSeq)-1]
rawSeq = rawSeq[:len(rawSeq)-1]
}
}
if Screen != nil {
Screen.UnregisterRawSeq(r)
}
}
2018-12-31 19:46:04 +00:00
// TempFini shuts the screen down temporarily
2019-01-10 21:37:05 +00:00
func TempFini() bool {
screenWasNil := Screen == nil
2018-08-28 18:24:59 +00:00
if !screenWasNil {
Screen.Fini()
2019-01-10 21:37:05 +00:00
Lock()
2018-08-28 18:24:59 +00:00
Screen = nil
}
2019-01-10 21:37:05 +00:00
return screenWasNil
2018-08-28 18:24:59 +00:00
}
2018-12-31 19:46:04 +00:00
// TempStart restarts the screen after it was temporarily disabled
2019-01-10 21:37:05 +00:00
func TempStart(screenWasNil bool) {
2018-08-28 18:24:59 +00:00
if !screenWasNil {
Init()
Unlock()
if RestartCallback != nil {
RestartCallback()
}
2018-08-28 18:24:59 +00:00
}
}
2018-08-27 19:53:10 +00:00
// Init creates and initializes the tcell screen
2020-06-20 22:24:12 +00:00
func Init() error {
drawChan = make(chan bool, 8)
2019-01-10 21:37:05 +00:00
2018-08-27 19:53:10 +00:00
// Should we enable true color?
truecolor := config.GetGlobalOption("truecolor").(string)
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
os.Setenv("TCELL_TRUECOLOR", "enable")
} else if truecolor == "off" {
2020-01-01 04:09:33 +00:00
os.Setenv("TCELL_TRUECOLOR", "disable")
} else {
// For "auto", tcell already autodetects truecolor by default
2018-08-27 19:53:10 +00:00
}
2020-02-15 17:53:17 +00:00
var oldTerm string
modifiedTerm := false
setXterm := func() {
2020-02-15 17:53:17 +00:00
oldTerm = os.Getenv("TERM")
os.Setenv("TERM", "xterm-256color")
modifiedTerm = true
}
if config.GetGlobalOption("xterm").(bool) {
setXterm()
2020-02-15 17:53:17 +00:00
}
2018-08-27 19:53:10 +00:00
// Initilize tcell
var err error
Screen, err = tcell.NewScreen()
if err != nil {
log.Println("Warning: during screen initialization:", err)
log.Println("Falling back to TERM=xterm-256color")
setXterm()
Screen, err = tcell.NewScreen()
if err != nil {
return err
}
2018-08-27 19:53:10 +00:00
}
if err = Screen.Init(); err != nil {
2020-06-20 22:24:12 +00:00
return err
2018-08-27 19:53:10 +00:00
}
Screen.SetPaste(config.GetGlobalOption("paste").(bool))
2020-02-15 17:53:17 +00:00
// restore TERM
if modifiedTerm {
2020-02-15 17:53:17 +00:00
os.Setenv("TERM", oldTerm)
}
2018-08-27 19:53:10 +00:00
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
2020-06-20 22:24:12 +00:00
for _, r := range rawSeq {
Screen.RegisterRawSeq(r)
}
2020-06-20 22:24:12 +00:00
return nil
}
// InitSimScreen initializes a simulation screen for testing purposes
func InitSimScreen() (tcell.SimulationScreen, error) {
drawChan = make(chan bool, 8)
// Initilize tcell
var err error
s := tcell.NewSimulationScreen("")
if s == nil {
return nil, errors.New("Failed to get a simulation screen")
}
if err = s.Init(); err != nil {
return nil, err
}
s.SetSize(80, 24)
Screen = s
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
return s, nil
2018-08-27 19:53:10 +00:00
}