10 "github.com/zyedidia/clipboard"
11 "github.com/zyedidia/tcell"
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
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 {
48 // Are we currently prompting the user?
50 // Is there a message to print
55 // The user's response to a prompt
57 // style to use when drawing the message
60 // We have to keep track of the cursor for prompting
63 // This map stores the history for all the different kinds of uses Prompt has
64 // It's a map of history type -> history array
65 history map[string][]string
68 // Is the current message a message from the gutter
72 func (m *Messenger) AddLog(msg string) {
73 buffer := m.getBuffer()
74 buffer.insert(buffer.End(), []byte(msg+"\n"))
75 buffer.Cursor.Loc = buffer.End()
76 buffer.Cursor.Relocate()
79 func (m *Messenger) getBuffer() *Buffer {
81 m.log = NewBufferFromString("", "")
87 // Message sends a message to the user
88 func (m *Messenger) Message(msg ...interface{}) {
89 displayMessage := fmt.Sprint(msg...)
90 // only display a new message if there isn't an active prompt
91 // this is to prevent overwriting an existing prompt to the user
92 if m.hasPrompt == false {
93 // if there is no active prompt then style and display the message as normal
94 m.message = displayMessage
98 if _, ok := colorscheme["message"]; ok {
99 m.style = colorscheme["message"]
104 // add the message to the log regardless of active prompts
105 m.AddLog(displayMessage)
108 // Error sends an error message to the user
109 func (m *Messenger) Error(msg ...interface{}) {
110 buf := new(bytes.Buffer)
111 fmt.Fprint(buf, msg...)
113 // only display a new message if there isn't an active prompt
114 // this is to prevent overwriting an existing prompt to the user
115 if m.hasPrompt == false {
116 // if there is no active prompt then style and display the message as normal
117 m.message = buf.String()
119 Foreground(tcell.ColorBlack).
120 Background(tcell.ColorMaroon)
122 if _, ok := colorscheme["error-message"]; ok {
123 m.style = colorscheme["error-message"]
127 // add the message to the log regardless of active prompts
128 m.AddLog(buf.String())
131 func (m *Messenger) PromptText(msg ...interface{}) {
132 displayMessage := fmt.Sprint(msg...)
133 // if there is no active prompt then style and display the message as normal
134 m.message = displayMessage
138 if _, ok := colorscheme["message"]; ok {
139 m.style = colorscheme["message"]
143 // add the message to the log regardless of active prompts
144 m.AddLog(displayMessage)
147 // YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
148 func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
152 _, h := screen.Size()
156 ShowCursor(Count(m.message), h-1)
160 switch e := event.(type) {
161 case *tcell.EventKey:
164 if e.Rune() == 'y' || e.Rune() == 'Y' {
168 } else if e.Rune() == 'n' || e.Rune() == 'N' {
173 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
174 m.AddLog("\t--> (cancel)")
184 // LetterPrompt gives the user a prompt and waits for a one letter response
185 func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
189 _, h := screen.Size()
193 ShowCursor(Count(m.message), h-1)
197 switch e := event.(type) {
198 case *tcell.EventKey:
201 for _, r := range responses {
203 m.AddLog("\t--> " + string(r))
210 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
211 m.AddLog("\t--> (cancel)")
224 NoCompletion Completion = iota
233 // Prompt sends the user a message and waits for a response to be typed in
234 // This function blocks the main loop while waiting for input
235 func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
238 if _, ok := m.history[historyType]; !ok {
239 m.history[historyType] = []string{""}
241 m.history[historyType] = append(m.history[historyType], "")
243 m.historyNum = len(m.history[historyType]) - 1
245 response, canceled := placeholder, true
246 m.response = response
247 m.cursorx = Count(placeholder)
251 var suggestions []string
256 switch e := event.(type) {
257 case *tcell.EventKey:
259 case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
261 m.AddLog("\t--> (cancel)")
264 // User is done entering their response
265 m.AddLog("\t--> " + m.response)
267 response, canceled = m.response, false
268 m.history[historyType][len(m.history[historyType])-1] = response
270 args := SplitCommandArgs(m.response)
271 currentArgNum := len(args) - 1
272 currentArg := args[currentArgNum]
273 var completionType Completion
275 if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
276 if command, ok := commands[args[0]]; ok {
277 completionTypes = append([]Completion{CommandCompletion}, command.completions...)
281 if currentArgNum >= len(completionTypes) {
282 completionType = completionTypes[len(completionTypes)-1]
284 completionType = completionTypes[currentArgNum]
288 if completionType == FileCompletion {
289 chosen, suggestions = FileComplete(currentArg)
290 } else if completionType == CommandCompletion {
291 chosen, suggestions = CommandComplete(currentArg)
292 } else if completionType == HelpCompletion {
293 chosen, suggestions = HelpComplete(currentArg)
294 } else if completionType == OptionCompletion {
295 chosen, suggestions = OptionComplete(currentArg)
296 } else if completionType == PluginCmdCompletion {
297 chosen, suggestions = PluginCmdComplete(currentArg)
298 } else if completionType == PluginNameCompletion {
299 chosen, suggestions = PluginNameComplete(currentArg)
300 } else if completionType < NoCompletion {
301 chosen, suggestions = PluginComplete(completionType, currentArg)
304 if len(suggestions) > 1 {
305 chosen = chosen + CommonSubstring(suggestions...)
309 m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
310 m.cursorx = Count(m.response)
315 m.HandleEvent(event, m.history[historyType])
318 for _, v := range tabs[curTab].views {
323 if len(suggestions) > 1 {
324 m.DisplaySuggestions(suggestions)
331 return response, canceled
334 // HandleEvent handles an event for the prompter
335 func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
336 switch e := event.(type) {
337 case *tcell.EventKey:
338 if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
339 for key, actions := range bindings {
340 if e.Key() == key.keyCode {
341 if e.Key() == tcell.KeyRune {
342 if e.Rune() != key.r {
346 if e.Modifiers() == key.modifiers {
347 for _, action := range actions {
348 funcName := FuncName(action)
350 case "main.(*View).CursorUp":
351 if m.historyNum > 0 {
353 m.response = history[m.historyNum]
354 m.cursorx = Count(m.response)
356 case "main.(*View).CursorDown":
357 if m.historyNum < len(history)-1 {
359 m.response = history[m.historyNum]
360 m.cursorx = Count(m.response)
362 case "main.(*View).CursorLeft":
366 case "main.(*View).CursorRight":
367 if m.cursorx < Count(m.response) {
370 case "main.(*View).CursorStart", "main.(*View).StartOfLine":
372 case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
373 m.cursorx = Count(m.response)
374 case "main.(*View).Backspace":
376 m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
379 case "main.(*View).Paste":
380 clip, _ := clipboard.ReadAll("clipboard")
381 m.response = Insert(m.response, m.cursorx, clip)
382 m.cursorx += Count(clip)
391 m.response = Insert(m.response, m.cursorx, string(e.Rune()))
394 history[m.historyNum] = m.response
396 case *tcell.EventPaste:
398 m.response = Insert(m.response, m.cursorx, clip)
399 m.cursorx += Count(clip)
400 case *tcell.EventMouse:
402 x -= Count(m.message)
403 button := e.Buttons()
404 _, screenH := screen.Size()
412 } else if m.cursorx > Count(m.response) {
413 m.cursorx = Count(m.response)
420 // Reset resets the messenger's cursor, message and response
421 func (m *Messenger) Reset() {
427 // Clear clears the line at the bottom of the editor
428 func (m *Messenger) Clear() {
429 w, h := screen.Size()
430 for x := 0; x < w; x++ {
431 screen.SetContent(x, h-1, ' ', nil, defStyle)
435 func (m *Messenger) DisplaySuggestions(suggestions []string) {
436 w, screenH := screen.Size()
440 statusLineStyle := defStyle.Reverse(true)
441 if style, ok := colorscheme["statusline"]; ok {
442 statusLineStyle = style
445 for x := 0; x < w; x++ {
446 screen.SetContent(x, y, ' ', nil, statusLineStyle)
450 for _, suggestion := range suggestions {
451 for _, c := range suggestion {
452 screen.SetContent(x, y, c, nil, statusLineStyle)
455 screen.SetContent(x, y, ' ', nil, statusLineStyle)
460 // Display displays messages or prompts
461 func (m *Messenger) Display() {
462 _, h := screen.Size()
464 if m.hasPrompt || globalSettings["infobar"].(bool) {
465 runes := []rune(m.message + m.response)
466 for x := 0; x < len(runes); x++ {
467 screen.SetContent(x, h-1, runes[x], nil, m.style)
473 ShowCursor(Count(m.message)+m.cursorx, h-1)
478 // A GutterMessage is a message displayed on the side of the editor
479 type GutterMessage struct {
485 // These are the different types of messages
487 // GutterInfo represents a simple info message
489 // GutterWarning represents a compiler warning
491 // GutterError represents a compiler error