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