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