]> git.lizzy.rs Git - micro.git/blob - internal/screen/screen.go
Fix dropped redraw events (#1675)
[micro.git] / internal / screen / screen.go
1 package screen
2
3 import (
4         "fmt"
5         "os"
6         "sync"
7
8         "github.com/zyedidia/micro/v2/internal/config"
9         "github.com/zyedidia/micro/v2/internal/util"
10         "github.com/zyedidia/tcell"
11 )
12
13 // Screen is the tcell screen we use to draw to the terminal
14 // Synchronization is used because we poll the screen on a separate
15 // thread and sometimes the screen is shut down by the main thread
16 // (for example on TermMessage) so we don't want to poll a nil/shutdown
17 // screen. TODO: maybe we should worry about polling and drawing at the
18 // same time too.
19 var Screen tcell.Screen
20
21 // The lock is necessary since the screen is polled on a separate thread
22 var lock sync.Mutex
23
24 // drawChan is a channel that will cause the screen to redraw when
25 // written to even if no event user event has occurred
26 var drawChan chan bool
27
28 // Lock locks the screen lock
29 func Lock() {
30         lock.Lock()
31 }
32
33 // Unlock unlocks the screen lock
34 func Unlock() {
35         lock.Unlock()
36 }
37
38 // Redraw schedules a redraw with the draw channel
39 func Redraw() {
40         select {
41         case drawChan <- true:
42         default:
43                 // channel is full
44         }
45 }
46
47 // DrawChan returns the draw channel
48 func DrawChan() chan bool {
49         return drawChan
50 }
51
52 type screenCell struct {
53         x, y  int
54         r     rune
55         combc []rune
56         style tcell.Style
57 }
58
59 var lastCursor screenCell
60
61 // ShowFakeCursor displays a cursor at the given position by modifying the
62 // style of the given column instead of actually using the terminal cursor
63 // This can be useful in certain terminals such as the windows console where
64 // modifying the cursor location is slow and frequent modifications cause flashing
65 // This keeps track of the most recent fake cursor location and resets it when
66 // a new fake cursor location is specified
67 func ShowFakeCursor(x, y int) {
68         r, combc, style, _ := Screen.GetContent(x, y)
69         Screen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)
70         Screen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))
71
72         lastCursor.x, lastCursor.y = x, y
73         lastCursor.r = r
74         lastCursor.combc = combc
75         lastCursor.style = style
76 }
77
78 // ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
79 // reset previous locations of the cursor
80 // Fake cursors are also necessary to display multiple cursors
81 func ShowFakeCursorMulti(x, y int) {
82         r, _, _, _ := Screen.GetContent(x, y)
83         Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
84 }
85
86 // ShowCursor puts the cursor at the given location using a fake cursor
87 // if enabled or using the terminal cursor otherwise
88 // By default only the windows console will use a fake cursor
89 func ShowCursor(x, y int) {
90         if util.FakeCursor {
91                 ShowFakeCursor(x, y)
92         } else {
93                 Screen.ShowCursor(x, y)
94         }
95 }
96
97 // SetContent sets a cell at a point on the screen and makes sure that it is
98 // synced with the last cursor location
99 func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
100         Screen.SetContent(x, y, mainc, combc, style)
101         if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
102                 lastCursor.r = mainc
103                 lastCursor.style = style
104                 lastCursor.combc = combc
105         }
106 }
107
108 // TempFini shuts the screen down temporarily
109 func TempFini() bool {
110         screenWasNil := Screen == nil
111
112         if !screenWasNil {
113                 Screen.Fini()
114                 Lock()
115                 Screen = nil
116         }
117         return screenWasNil
118 }
119
120 // TempStart restarts the screen after it was temporarily disabled
121 func TempStart(screenWasNil bool) {
122         if !screenWasNil {
123                 Init()
124                 Unlock()
125         }
126 }
127
128 // Init creates and initializes the tcell screen
129 func Init() {
130         drawChan = make(chan bool, 8)
131
132         // Should we enable true color?
133         truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
134
135         if !truecolor {
136                 os.Setenv("TCELL_TRUECOLOR", "disable")
137         }
138
139         var oldTerm string
140         if config.GetGlobalOption("xterm").(bool) {
141                 oldTerm = os.Getenv("TERM")
142                 os.Setenv("TERM", "xterm-256color")
143         }
144
145         // Initilize tcell
146         var err error
147         Screen, err = tcell.NewScreen()
148         if err != nil {
149                 fmt.Println(err)
150                 fmt.Println("Fatal: Micro could not initialize a Screen.")
151                 os.Exit(1)
152         }
153         if err = Screen.Init(); err != nil {
154                 fmt.Println(err)
155                 os.Exit(1)
156         }
157
158         // restore TERM
159         if config.GetGlobalOption("xterm").(bool) {
160                 os.Setenv("TERM", oldTerm)
161         }
162
163         if config.GetGlobalOption("mouse").(bool) {
164                 Screen.EnableMouse()
165         }
166 }