11 "github.com/zyedidia/clipboard"
12 "github.com/zyedidia/tcell"
15 // TermMessage sends a message to the user in the terminal. This usually occurs before
16 // micro has been fully initialized -- ie if there is an error in the syntax highlighting
17 // regular expressions
18 // The function must be called when the screen is not initialized
19 // This will write the message, and wait for the user
20 // to press and key to continue
21 func TermMessage(msg ...interface{}) {
22 screenWasNil := screen == nil
28 fmt.Print("\nPress enter to continue")
30 reader := bufio.NewReader(os.Stdin)
31 reader.ReadString('\n')
38 // TermError sends an error to the user in the terminal. Like TermMessage except formatted
40 func TermError(filename string, lineNum int, err string) {
41 TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
44 // Messenger is an object that makes it easy to send messages to the user
45 // and get input from the user
46 type Messenger struct {
47 // Are we currently prompting the user?
49 // Is there a message to print
54 // The user's response to a prompt
56 // style to use when drawing the message
59 // We have to keep track of the cursor for prompting
62 // This map stores the history for all the different kinds of uses Prompt has
63 // It's a map of history type -> history array
64 history map[string][]string
67 // Is the current message a message from the gutter
71 // Message sends a message to the user
72 func (m *Messenger) Message(msg ...interface{}) {
73 buf := new(bytes.Buffer)
74 fmt.Fprint(buf, msg...)
75 m.message = buf.String()
78 if _, ok := colorscheme["message"]; ok {
79 m.style = colorscheme["message"]
84 // Error sends an error message to the user
85 func (m *Messenger) Error(msg ...interface{}) {
86 buf := new(bytes.Buffer)
87 fmt.Fprint(buf, msg...)
88 m.message = buf.String()
90 Foreground(tcell.ColorBlack).
91 Background(tcell.ColorMaroon)
93 if _, ok := colorscheme["error-message"]; ok {
94 m.style = colorscheme["error-message"]
99 // YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
100 func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
103 _, h := screen.Size()
107 screen.ShowCursor(Count(m.message), h-1)
111 switch e := event.(type) {
112 case *tcell.EventKey:
117 } else if e.Rune() == 'n' {
120 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
127 // LetterPrompt gives the user a prompt and waits for a one letter response
128 func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
131 _, h := screen.Size()
135 screen.ShowCursor(Count(m.message), h-1)
139 switch e := event.(type) {
140 case *tcell.EventKey:
143 for _, r := range responses {
149 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
159 NoCompletion Completion = iota
166 // Prompt sends the user a message and waits for a response to be typed in
167 // This function blocks the main loop while waiting for input
168 func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Completion) (string, bool) {
171 if _, ok := m.history[historyType]; !ok {
172 m.history[historyType] = []string{""}
174 m.history[historyType] = append(m.history[historyType], "")
176 m.historyNum = len(m.history[historyType]) - 1
178 response, canceled := "", true
182 var suggestions []string
187 switch e := event.(type) {
188 case *tcell.EventKey:
190 case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
194 // User is done entering their response
196 response, canceled = m.response, false
197 m.history[historyType][len(m.history[historyType])-1] = response
199 args := strings.Split(m.response, " ")
200 currentArgNum := len(args) - 1
201 currentArg := args[currentArgNum]
202 var completionType Completion
204 if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
205 if command, ok := commands[args[0]]; ok {
206 completionTypes = append([]Completion{CommandCompletion}, command.completions...)
210 if currentArgNum >= len(completionTypes) {
211 completionType = completionTypes[len(completionTypes)-1]
213 completionType = completionTypes[currentArgNum]
217 if completionType == FileCompletion {
218 chosen, suggestions = FileComplete(currentArg)
219 } else if completionType == CommandCompletion {
220 chosen, suggestions = CommandComplete(currentArg)
221 } else if completionType == HelpCompletion {
222 chosen, suggestions = HelpComplete(currentArg)
223 } else if completionType == OptionCompletion {
224 chosen, suggestions = OptionComplete(currentArg)
227 if len(suggestions) > 1 {
228 chosen = chosen + CommonSubstring(suggestions...)
233 chosen = " " + chosen
235 m.response = strings.Join(args[:len(args)-1], " ") + chosen
236 m.cursorx = Count(m.response)
241 m.HandleEvent(event, m.history[historyType])
244 for _, v := range tabs[curTab].views {
249 if len(suggestions) > 1 {
250 m.DisplaySuggestions(suggestions)
256 return response, canceled
259 // HandleEvent handles an event for the prompter
260 func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
261 switch e := event.(type) {
262 case *tcell.EventKey:
265 if m.historyNum > 0 {
267 m.response = history[m.historyNum]
268 m.cursorx = Count(m.response)
271 if m.historyNum < len(history)-1 {
273 m.response = history[m.historyNum]
274 m.cursorx = Count(m.response)
281 if m.cursorx < Count(m.response) {
284 case tcell.KeyBackspace2, tcell.KeyBackspace:
286 m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
290 clip, _ := clipboard.ReadAll("clipboard")
291 m.response = Insert(m.response, m.cursorx, clip)
292 m.cursorx += Count(clip)
294 m.response = Insert(m.response, m.cursorx, string(e.Rune()))
297 history[m.historyNum] = m.response
299 case *tcell.EventPaste:
301 m.response = Insert(m.response, m.cursorx, clip)
302 m.cursorx += Count(clip)
306 // Reset resets the messenger's cursor, message and response
307 func (m *Messenger) Reset() {
313 // Clear clears the line at the bottom of the editor
314 func (m *Messenger) Clear() {
315 w, h := screen.Size()
316 for x := 0; x < w; x++ {
317 screen.SetContent(x, h-1, ' ', nil, defStyle)
321 func (m *Messenger) DisplaySuggestions(suggestions []string) {
322 w, screenH := screen.Size()
326 statusLineStyle := defStyle.Reverse(true)
327 if style, ok := colorscheme["statusline"]; ok {
328 statusLineStyle = style
331 for x := 0; x < w; x++ {
332 screen.SetContent(x, y, ' ', nil, statusLineStyle)
336 for _, suggestion := range suggestions {
337 for _, c := range suggestion {
338 screen.SetContent(x, y, c, nil, statusLineStyle)
341 screen.SetContent(x, y, ' ', nil, statusLineStyle)
346 // Display displays messages or prompts
347 func (m *Messenger) Display() {
348 _, h := screen.Size()
350 if !m.hasPrompt && !globalSettings["infobar"].(bool) {
353 runes := []rune(m.message + m.response)
354 for x := 0; x < len(runes); x++ {
355 screen.SetContent(x, h-1, runes[x], nil, m.style)
359 screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
364 // A GutterMessage is a message displayed on the side of the editor
365 type GutterMessage struct {
371 // These are the different types of messages
373 // GutterInfo represents a simple info message
375 // GutterWarning represents a compiler warning
377 // GutterError represents a compiler error