]> git.lizzy.rs Git - micro.git/blob - cmd/micro/messenger.go
b75a779fa768bd83143c387abf070e77f52d844c
[micro.git] / cmd / micro / messenger.go
1 package main
2
3 import (
4         "bufio"
5         "bytes"
6         "fmt"
7         "os"
8         "strconv"
9
10         "github.com/zyedidia/tcell"
11 )
12
13 // TermMessage sends a message to the user in the terminal. This usually occurs before
14 // micro has been fully initialized -- ie if there is an error in the syntax highlighting
15 // regular expressions
16 // The function must be called when the screen is not initialized
17 // This will write the message, and wait for the user
18 // to press and key to continue
19 func TermMessage(msg ...interface{}) {
20         screenWasNil := screen == nil
21         if !screenWasNil {
22                 screen.Fini()
23         }
24
25         fmt.Println(msg...)
26         fmt.Print("\nPress enter to continue")
27
28         reader := bufio.NewReader(os.Stdin)
29         reader.ReadString('\n')
30
31         if !screenWasNil {
32                 InitScreen()
33         }
34 }
35
36 // TermError sends an error to the user in the terminal. Like TermMessage except formatted
37 // as an error
38 func TermError(filename string, lineNum int, err string) {
39         TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
40 }
41
42 // Messenger is an object that makes it easy to send messages to the user
43 // and get input from the user
44 type Messenger struct {
45         // Are we currently prompting the user?
46         hasPrompt bool
47         // Is there a message to print
48         hasMessage bool
49
50         // Message to print
51         message string
52         // The user's response to a prompt
53         response string
54         // style to use when drawing the message
55         style tcell.Style
56
57         // We have to keep track of the cursor for prompting
58         cursorx int
59
60         // This map stores the history for all the different kinds of uses Prompt has
61         // It's a map of history type -> history array
62         history    map[string][]string
63         historyNum int
64
65         // Is the current message a message from the gutter
66         gutterMessage bool
67 }
68
69 // Message sends a message to the user
70 func (m *Messenger) Message(msg ...interface{}) {
71         buf := new(bytes.Buffer)
72         fmt.Fprint(buf, msg...)
73         m.message = buf.String()
74         m.style = defStyle
75
76         if _, ok := colorscheme["message"]; ok {
77                 m.style = colorscheme["message"]
78         }
79         m.hasMessage = true
80 }
81
82 // Error sends an error message to the user
83 func (m *Messenger) Error(msg ...interface{}) {
84         buf := new(bytes.Buffer)
85         fmt.Fprint(buf, msg...)
86         m.message = buf.String()
87         m.style = defStyle.
88                 Foreground(tcell.ColorBlack).
89                 Background(tcell.ColorMaroon)
90
91         if _, ok := colorscheme["error-message"]; ok {
92                 m.style = colorscheme["error-message"]
93         }
94         m.hasMessage = true
95 }
96
97 // YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
98 func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
99         m.Message(prompt)
100
101         _, h := screen.Size()
102         for {
103                 m.Clear()
104                 m.Display()
105                 screen.ShowCursor(Count(m.message), h-1)
106                 screen.Show()
107                 event := screen.PollEvent()
108
109                 switch e := event.(type) {
110                 case *tcell.EventKey:
111                         switch e.Key() {
112                         case tcell.KeyRune:
113                                 if e.Rune() == 'y' {
114                                         return true, false
115                                 } else if e.Rune() == 'n' {
116                                         return false, false
117                                 }
118                         case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
119                                 return false, true
120                         }
121                 }
122         }
123 }
124
125 // Prompt sends the user a message and waits for a response to be typed in
126 // This function blocks the main loop while waiting for input
127 func (m *Messenger) Prompt(prompt, historyType string) (string, bool) {
128         m.hasPrompt = true
129         m.Message(prompt)
130         if _, ok := m.history[historyType]; !ok {
131                 m.history[historyType] = []string{""}
132         } else {
133                 m.history[historyType] = append(m.history[historyType], "")
134         }
135         m.historyNum = len(m.history[historyType]) - 1
136
137         response, canceled := "", true
138
139         for m.hasPrompt {
140                 m.Clear()
141                 m.Display()
142
143                 event := screen.PollEvent()
144
145                 switch e := event.(type) {
146                 case *tcell.EventKey:
147                         switch e.Key() {
148                         case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
149                                 // Cancel
150                                 m.hasPrompt = false
151                         case tcell.KeyEnter:
152                                 // User is done entering their response
153                                 m.hasPrompt = false
154                                 response, canceled = m.response, false
155                                 m.history[historyType][len(m.history[historyType])-1] = response
156                         }
157                 }
158
159                 m.HandleEvent(event, m.history[historyType])
160
161                 if m.cursorx < 0 {
162                         // Cancel
163                         m.hasPrompt = false
164                 }
165         }
166
167         m.Reset()
168         return response, canceled
169 }
170
171 // HandleEvent handles an event for the prompter
172 func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
173         switch e := event.(type) {
174         case *tcell.EventKey:
175                 switch e.Key() {
176                 case tcell.KeyUp:
177                         if m.historyNum > 0 {
178                                 m.historyNum--
179                                 m.response = history[m.historyNum]
180                                 m.cursorx = len(m.response)
181                         }
182                 case tcell.KeyDown:
183                         if m.historyNum < len(history)-1 {
184                                 m.historyNum++
185                                 m.response = history[m.historyNum]
186                                 m.cursorx = len(m.response)
187                         }
188                 case tcell.KeyLeft:
189                         if m.cursorx > 0 {
190                                 m.cursorx--
191                         }
192                 case tcell.KeyRight:
193                         if m.cursorx < Count(m.response) {
194                                 m.cursorx++
195                         }
196                 case tcell.KeyBackspace2, tcell.KeyBackspace:
197                         if m.cursorx > 0 {
198                                 m.response = string([]rune(m.response)[:m.cursorx-1]) + string(m.response[m.cursorx:])
199                         }
200                         m.cursorx--
201                 case tcell.KeyRune:
202                         m.response = Insert(m.response, m.cursorx, string(e.Rune()))
203                         m.cursorx++
204                 }
205                 history[m.historyNum] = m.response
206         }
207 }
208
209 // Reset resets the messenger's cursor, message and response
210 func (m *Messenger) Reset() {
211         m.cursorx = 0
212         m.message = ""
213         m.response = ""
214 }
215
216 // Clear clears the line at the bottom of the editor
217 func (m *Messenger) Clear() {
218         w, h := screen.Size()
219         for x := 0; x < w; x++ {
220                 screen.SetContent(x, h-1, ' ', nil, defStyle)
221         }
222 }
223
224 // Display displays messages or prompts
225 func (m *Messenger) Display() {
226         _, h := screen.Size()
227         if m.hasMessage {
228                 runes := []rune(m.message + m.response)
229                 for x := 0; x < len(runes); x++ {
230                         screen.SetContent(x, h-1, runes[x], nil, m.style)
231                 }
232         }
233         if m.hasPrompt {
234                 screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
235                 screen.Show()
236         }
237 }
238
239 // A GutterMessage is a message displayed on the side of the editor
240 type GutterMessage struct {
241         lineNum int
242         msg     string
243         kind    int
244 }
245
246 // These are the different types of messages
247 const (
248         // GutterInfo represents a simple info message
249         GutterInfo = iota
250         // GutterWarning represents a compiler warning
251         GutterWarning
252         // GutterError represents a compiler error
253         GutterError
254 )