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