mirror of
https://github.com/zyedidia/micro.git
synced 2025-06-19 07:15:34 -04:00

When we temporarily disable the screen (e.g. during TermMessage or RunInteractiveShell), if the mouse is pressed when the screen is still active and then released when the screen is already stopped, we aren't able to catch this mouse release event, so we erroneously think that the mouse is still pressed after the screen is restarted. This results in wrong behavior due to a mouse press event treated as a mouse move event, e.g. upon the left button click we see an unexpected text selection. So need to reset the mouse release state to "released" after restarting the screen, assuming it is always released when the screen is restarted.
224 lines
5.2 KiB
Go
224 lines
5.2 KiB
Go
package screen
|
||
|
||
import (
|
||
"errors"
|
||
"log"
|
||
"os"
|
||
"sync"
|
||
|
||
"github.com/zyedidia/micro/v2/internal/config"
|
||
"github.com/zyedidia/micro/v2/internal/util"
|
||
"github.com/zyedidia/tcell/v2"
|
||
)
|
||
|
||
// 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.
|
||
var Screen tcell.Screen
|
||
|
||
// 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()
|
||
|
||
// The lock is necessary since the screen is polled on a separate thread
|
||
var lock sync.Mutex
|
||
|
||
// drawChan is a channel that will cause the screen to redraw when
|
||
// written to even if no event user event has occurred
|
||
var drawChan chan bool
|
||
|
||
// Lock locks the screen lock
|
||
func Lock() {
|
||
lock.Lock()
|
||
}
|
||
|
||
// Unlock unlocks the screen lock
|
||
func Unlock() {
|
||
lock.Unlock()
|
||
}
|
||
|
||
// Redraw schedules a redraw with the draw channel
|
||
func Redraw() {
|
||
select {
|
||
case drawChan <- true:
|
||
default:
|
||
// channel is full
|
||
}
|
||
}
|
||
|
||
// DrawChan returns the draw channel
|
||
func DrawChan() chan bool {
|
||
return drawChan
|
||
}
|
||
|
||
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
|
||
func ShowFakeCursor(x, y int) {
|
||
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)
|
||
}
|
||
|
||
// 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) {
|
||
r, _, _, _ := Screen.GetContent(x, y)
|
||
Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
|
||
}
|
||
|
||
// 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
|
||
func ShowCursor(x, y int) {
|
||
if UseFake() {
|
||
ShowFakeCursor(x, y)
|
||
} else {
|
||
Screen.ShowCursor(x, y)
|
||
}
|
||
}
|
||
|
||
// 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>'
|
||
}
|
||
|
||
Screen.SetContent(x, y, mainc, combc, style)
|
||
if UseFake() && lastCursor.x == x && lastCursor.y == y {
|
||
lastCursor.r = mainc
|
||
lastCursor.style = style
|
||
lastCursor.combc = combc
|
||
}
|
||
}
|
||
|
||
// TempFini shuts the screen down temporarily
|
||
func TempFini() bool {
|
||
screenWasNil := Screen == nil
|
||
|
||
if !screenWasNil {
|
||
Screen.Fini()
|
||
Lock()
|
||
Screen = nil
|
||
}
|
||
return screenWasNil
|
||
}
|
||
|
||
// TempStart restarts the screen after it was temporarily disabled
|
||
func TempStart(screenWasNil bool) {
|
||
if !screenWasNil {
|
||
Init()
|
||
Unlock()
|
||
|
||
if RestartCallback != nil {
|
||
RestartCallback()
|
||
}
|
||
}
|
||
}
|
||
|
||
// Init creates and initializes the tcell screen
|
||
func Init() error {
|
||
drawChan = make(chan bool, 8)
|
||
|
||
// Should we enable true color?
|
||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||
|
||
if !truecolor {
|
||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||
}
|
||
|
||
var oldTerm string
|
||
modifiedTerm := false
|
||
setXterm := func() {
|
||
oldTerm = os.Getenv("TERM")
|
||
os.Setenv("TERM", "xterm-256color")
|
||
modifiedTerm = true
|
||
}
|
||
|
||
if config.GetGlobalOption("xterm").(bool) {
|
||
setXterm()
|
||
}
|
||
|
||
// 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
|
||
}
|
||
}
|
||
if err = Screen.Init(); err != nil {
|
||
return err
|
||
}
|
||
|
||
Screen.SetPaste(config.GetGlobalOption("paste").(bool))
|
||
|
||
// restore TERM
|
||
if modifiedTerm {
|
||
os.Setenv("TERM", oldTerm)
|
||
}
|
||
|
||
if config.GetGlobalOption("mouse").(bool) {
|
||
Screen.EnableMouse()
|
||
}
|
||
|
||
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
|
||
}
|