Something went wrong. Try again.
Fork of micro-editor/micro
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264package screen
import ( "errors" "log" "os" "sync"
"github.com/micro-editor/micro/v2/internal/config" "github.com/micro-editor/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 eventsvar Events chan (tcell.Event)
// RestartCallback is called when the screen is restarted after it was// temporarily shut downvar RestartCallback func()
// The lock is necessary since the screen is polled on a separate threadvar lock sync.Mutex
// drawChan is a channel that will cause the screen to redraw when// written to even if no event user event has occurredvar drawChan chan bool
// 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 listvar rawSeq = make([]string, 0)
// Lock locks the screen lockfunc Lock() { lock.Lock()}
// Unlock unlocks the screen lockfunc Unlock() { lock.Unlock()}
// Redraw schedules a redraw with the draw channelfunc Redraw() { select { case drawChan <- true: default: // channel is full }}
// DrawChan returns the draw channelfunc 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 specifiedfunc 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 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 cursorsfunc 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 cursorfunc 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 locationfunc SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) { if !Screen.CanDisplay(mainc, true) { mainc = '�' }
Screen.SetContent(x, y, mainc, combc, style) if UseFake() && lastCursor.x == x && lastCursor.y == y { lastCursor.r = mainc lastCursor.style = style lastCursor.combc = combc }}
// RegisterRawSeq registers a raw escape sequence that should be parsed by tcellfunc 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 tcellfunc 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) }}
// TempFini shuts the screen down temporarilyfunc TempFini() bool { screenWasNil := Screen == nil
if !screenWasNil { Screen.Fini() Lock() Screen = nil } return screenWasNil}
// TempStart restarts the screen after it was temporarily disabledfunc TempStart(screenWasNil bool) { if !screenWasNil { Init() Unlock()
if RestartCallback != nil { RestartCallback() } }}
// Init creates and initializes the tcell screenfunc Init() error { drawChan = make(chan bool, 8)
// 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" { os.Setenv("TCELL_TRUECOLOR", "disable") } else { // For "auto", tcell already autodetects truecolor by default }
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() }
for _, r := range rawSeq { Screen.RegisterRawSeq(r) }
return nil}
// InitSimScreen initializes a simulation screen for testing purposesfunc 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}