10 "github.com/mattn/go-runewidth"
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
29 fmt.Print("\nPress enter to continue")
31 reader := bufio.NewReader(os.Stdin)
32 reader.ReadString('\n')
39 // TermError sends an error to the user in the terminal. Like TermMessage except formatted
41 func TermError(filename string, lineNum int, err string) {
42 TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
45 // Messenger is an object that makes it easy to send messages to the user
46 // and get input from the user
47 type Messenger struct {
49 // Are we currently prompting the user?
51 // Is there a message to print
56 // The user's response to a prompt
58 // style to use when drawing the message
61 // We have to keep track of the cursor for prompting
64 // This map stores the history for all the different kinds of uses Prompt has
65 // It's a map of history type -> history array
66 history map[string][]string
69 // Is the current message a message from the gutter
73 // AddLog sends a message to the log view
74 func (m *Messenger) AddLog(msg ...interface{}) {
75 logMessage := fmt.Sprint(msg...)
76 buffer := m.getBuffer()
77 buffer.insert(buffer.End(), []byte(logMessage+"\n"))
78 buffer.Cursor.Loc = buffer.End()
79 buffer.Cursor.Relocate()
82 func (m *Messenger) getBuffer() *Buffer {
84 m.log = NewBufferFromString("", "")
90 // Message sends a message to the user
91 func (m *Messenger) Message(msg ...interface{}) {
92 displayMessage := fmt.Sprint(msg...)
93 // only display a new message if there isn't an active prompt
94 // this is to prevent overwriting an existing prompt to the user
95 if m.hasPrompt == false {
96 // if there is no active prompt then style and display the message as normal
97 m.message = displayMessage
101 if _, ok := colorscheme["message"]; ok {
102 m.style = colorscheme["message"]
107 // add the message to the log regardless of active prompts
108 m.AddLog(displayMessage)
111 // Error sends an error message to the user
112 func (m *Messenger) Error(msg ...interface{}) {
113 buf := new(bytes.Buffer)
114 fmt.Fprint(buf, msg...)
116 // only display a new message if there isn't an active prompt
117 // this is to prevent overwriting an existing prompt to the user
118 if m.hasPrompt == false {
119 // if there is no active prompt then style and display the message as normal
120 m.message = buf.String()
122 Foreground(tcell.ColorBlack).
123 Background(tcell.ColorMaroon)
125 if _, ok := colorscheme["error-message"]; ok {
126 m.style = colorscheme["error-message"]
130 // add the message to the log regardless of active prompts
131 m.AddLog(buf.String())
134 func (m *Messenger) PromptText(msg ...interface{}) {
135 displayMessage := fmt.Sprint(msg...)
136 // if there is no active prompt then style and display the message as normal
137 m.message = displayMessage
141 if _, ok := colorscheme["message"]; ok {
142 m.style = colorscheme["message"]
146 // add the message to the log regardless of active prompts
147 m.AddLog(displayMessage)
150 // YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
151 func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
155 _, h := screen.Size()
159 screen.ShowCursor(Count(m.message), h-1)
163 switch e := event.(type) {
164 case *tcell.EventKey:
167 if e.Rune() == 'y' || e.Rune() == 'Y' {
171 } else if e.Rune() == 'n' || e.Rune() == 'N' {
176 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
177 m.AddLog("\t--> (cancel)")
187 // LetterPrompt gives the user a prompt and waits for a one letter response
188 func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
192 _, h := screen.Size()
196 screen.ShowCursor(Count(m.message), h-1)
200 switch e := event.(type) {
201 case *tcell.EventKey:
204 for _, r := range responses {
206 m.AddLog("\t--> " + string(r))
213 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
214 m.AddLog("\t--> (cancel)")
227 NoCompletion Completion = iota
234 OptionValueCompletion
237 // Prompt sends the user a message and waits for a response to be typed in
238 // This function blocks the main loop while waiting for input
239 func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
242 if _, ok := m.history[historyType]; !ok {
243 m.history[historyType] = []string{""}
245 m.history[historyType] = append(m.history[historyType], "")
247 m.historyNum = len(m.history[historyType]) - 1
249 response, canceled := placeholder, true
250 m.response = response
251 m.cursorx = Count(placeholder)
255 var suggestions []string
260 switch e := event.(type) {
261 case *tcell.EventKey:
263 case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
265 m.AddLog("\t--> (cancel)")
268 // User is done entering their response
269 m.AddLog("\t--> " + m.response)
271 response, canceled = m.response, false
272 m.history[historyType][len(m.history[historyType])-1] = response
274 args := SplitCommandArgs(m.response)
275 currentArgNum := len(args) - 1
276 currentArg := args[currentArgNum]
277 var completionType Completion
279 if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
280 if command, ok := commands[args[0]]; ok {
281 completionTypes = append([]Completion{CommandCompletion}, command.completions...)
285 if currentArgNum >= len(completionTypes) {
286 completionType = completionTypes[len(completionTypes)-1]
288 completionType = completionTypes[currentArgNum]
292 if completionType == FileCompletion {
293 chosen, suggestions = FileComplete(currentArg)
294 } else if completionType == CommandCompletion {
295 chosen, suggestions = CommandComplete(currentArg)
296 } else if completionType == HelpCompletion {
297 chosen, suggestions = HelpComplete(currentArg)
298 } else if completionType == OptionCompletion {
299 chosen, suggestions = OptionComplete(currentArg)
300 } else if completionType == OptionValueCompletion {
301 if currentArgNum-1 > 0 {
302 chosen, suggestions = OptionValueComplete(args[currentArgNum-1], currentArg)
304 } else if completionType == PluginCmdCompletion {
305 chosen, suggestions = PluginCmdComplete(currentArg)
306 } else if completionType == PluginNameCompletion {
307 chosen, suggestions = PluginNameComplete(currentArg)
308 } else if completionType < NoCompletion {
309 chosen, suggestions = PluginComplete(completionType, currentArg)
312 if len(suggestions) > 1 {
313 chosen = chosen + CommonSubstring(suggestions...)
317 m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
318 m.cursorx = Count(m.response)
323 m.HandleEvent(event, m.history[historyType])
326 for _, v := range tabs[curTab].views {
331 if len(suggestions) > 1 {
332 m.DisplaySuggestions(suggestions)
339 return response, canceled
342 // HandleEvent handles an event for the prompter
343 func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
344 switch e := event.(type) {
345 case *tcell.EventKey:
346 if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
347 for key, actions := range bindings {
348 if e.Key() == key.keyCode {
349 if e.Key() == tcell.KeyRune {
350 if e.Rune() != key.r {
354 if e.Modifiers() == key.modifiers {
355 for _, action := range actions {
356 funcName := FuncName(action)
358 case "main.(*View).CursorUp":
359 if m.historyNum > 0 {
361 m.response = history[m.historyNum]
362 m.cursorx = Count(m.response)
364 case "main.(*View).CursorDown":
365 if m.historyNum < len(history)-1 {
367 m.response = history[m.historyNum]
368 m.cursorx = Count(m.response)
370 case "main.(*View).CursorLeft":
374 case "main.(*View).CursorRight":
375 if m.cursorx < Count(m.response) {
378 case "main.(*View).CursorStart", "main.(*View).StartOfLine":
380 case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
381 m.cursorx = Count(m.response)
382 case "main.(*View).Backspace":
384 m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
387 case "main.(*View).Paste":
388 clip, _ := clipboard.ReadAll("clipboard")
389 m.response = Insert(m.response, m.cursorx, clip)
390 m.cursorx += Count(clip)
399 m.response = Insert(m.response, m.cursorx, string(e.Rune()))
402 history[m.historyNum] = m.response
404 case *tcell.EventPaste:
406 m.response = Insert(m.response, m.cursorx, clip)
407 m.cursorx += Count(clip)
408 case *tcell.EventMouse:
410 x -= Count(m.message)
411 button := e.Buttons()
412 _, screenH := screen.Size()
420 } else if m.cursorx > Count(m.response) {
421 m.cursorx = Count(m.response)
428 // Reset resets the messenger's cursor, message and response
429 func (m *Messenger) Reset() {
435 // Clear clears the line at the bottom of the editor
436 func (m *Messenger) Clear() {
437 w, h := screen.Size()
438 for x := 0; x < w; x++ {
439 screen.SetContent(x, h-1, ' ', nil, defStyle)
443 func (m *Messenger) DisplaySuggestions(suggestions []string) {
444 w, screenH := screen.Size()
448 statusLineStyle := defStyle.Reverse(true)
449 if style, ok := colorscheme["statusline"]; ok {
450 statusLineStyle = style
453 for x := 0; x < w; x++ {
454 screen.SetContent(x, y, ' ', nil, statusLineStyle)
458 for _, suggestion := range suggestions {
459 for _, c := range suggestion {
460 screen.SetContent(x, y, c, nil, statusLineStyle)
463 screen.SetContent(x, y, ' ', nil, statusLineStyle)
468 // Display displays messages or prompts
469 func (m *Messenger) Display() {
470 _, h := screen.Size()
472 if m.hasPrompt || globalSettings["infobar"].(bool) {
473 runes := []rune(m.message + m.response)
475 for x := 0; x < len(runes); x++ {
476 screen.SetContent(posx, h-1, runes[x], nil, m.style)
477 posx += runewidth.RuneWidth(runes[x])
483 screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
488 // A GutterMessage is a message displayed on the side of the editor
489 type GutterMessage struct {
495 // These are the different types of messages
497 // GutterInfo represents a simple info message
499 // GutterWarning represents a compiler warning
501 // GutterError represents a compiler error