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
27 fmt.Print("\nPress enter to continue")
29 reader := bufio.NewReader(os.Stdin)
30 reader.ReadString('\n')
37 // TermError sends an error to the user in the terminal. Like TermMessage except formatted
39 func TermError(filename string, lineNum int, err string) {
40 TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
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 {
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 func (m *Messenger) addLog(msg string) {
72 buffer := m.getBuffer()
73 buffer.Insert(buffer.End(), msg+"\n")
76 func (m *Messenger) getBuffer() *Buffer {
78 m.log = NewBuffer([]byte{}, "")
84 // Message sends a message to the user
85 func (m *Messenger) Message(msg ...interface{}) {
86 buf := new(bytes.Buffer)
87 fmt.Fprint(buf, msg...)
88 m.message = buf.String()
91 if _, ok := colorscheme["message"]; ok {
92 m.style = colorscheme["message"]
98 // Error sends an error message to the user
99 func (m *Messenger) Error(msg ...interface{}) {
100 buf := new(bytes.Buffer)
101 fmt.Fprint(buf, msg...)
102 m.message = buf.String()
104 Foreground(tcell.ColorBlack).
105 Background(tcell.ColorMaroon)
107 if _, ok := colorscheme["error-message"]; ok {
108 m.style = colorscheme["error-message"]
114 // YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
115 func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
119 _, h := screen.Size()
123 screen.ShowCursor(Count(m.message), h-1)
127 switch e := event.(type) {
128 case *tcell.EventKey:
135 } else if e.Rune() == 'n' {
140 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
141 m.addLog("\t--> (cancel)")
149 // LetterPrompt gives the user a prompt and waits for a one letter response
150 func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
154 _, h := screen.Size()
158 screen.ShowCursor(Count(m.message), h-1)
162 switch e := event.(type) {
163 case *tcell.EventKey:
166 for _, r := range responses {
168 m.addLog("\t--> " + string(r))
175 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
176 m.addLog("\t--> (cancel)")
189 NoCompletion Completion = iota
196 // Prompt sends the user a message and waits for a response to be typed in
197 // This function blocks the main loop while waiting for input
198 func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Completion) (string, bool) {
201 if _, ok := m.history[historyType]; !ok {
202 m.history[historyType] = []string{""}
204 m.history[historyType] = append(m.history[historyType], "")
206 m.historyNum = len(m.history[historyType]) - 1
208 response, canceled := "", true
212 var suggestions []string
217 switch e := event.(type) {
218 case *tcell.EventKey:
220 case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
222 m.addLog("\t--> (cancel)")
225 // User is done entering their response
226 m.addLog("\t--> " + m.response)
228 response, canceled = m.response, false
229 m.history[historyType][len(m.history[historyType])-1] = response
231 args := SplitCommandArgs(m.response)
232 currentArgNum := len(args) - 1
233 currentArg := args[currentArgNum]
234 var completionType Completion
236 if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
237 if command, ok := commands[args[0]]; ok {
238 completionTypes = append([]Completion{CommandCompletion}, command.completions...)
242 if currentArgNum >= len(completionTypes) {
243 completionType = completionTypes[len(completionTypes)-1]
245 completionType = completionTypes[currentArgNum]
249 if completionType == FileCompletion {
250 chosen, suggestions = FileComplete(currentArg)
251 } else if completionType == CommandCompletion {
252 chosen, suggestions = CommandComplete(currentArg)
253 } else if completionType == HelpCompletion {
254 chosen, suggestions = HelpComplete(currentArg)
255 } else if completionType == OptionCompletion {
256 chosen, suggestions = OptionComplete(currentArg)
257 } else if completionType < NoCompletion {
258 chosen, suggestions = PluginComplete(completionType, currentArg)
261 if len(suggestions) > 1 {
262 chosen = chosen + CommonSubstring(suggestions...)
266 m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
267 m.cursorx = Count(m.response)
272 m.HandleEvent(event, m.history[historyType])
275 for _, v := range tabs[curTab].views {
280 if len(suggestions) > 1 {
281 m.DisplaySuggestions(suggestions)
288 return response, canceled
291 // HandleEvent handles an event for the prompter
292 func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
293 switch e := event.(type) {
294 case *tcell.EventKey:
295 if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
296 for key, actions := range bindings {
297 if e.Key() == key.keyCode {
298 if e.Key() == tcell.KeyRune {
299 if e.Rune() != key.r {
303 if e.Modifiers() == key.modifiers {
304 for _, action := range actions {
305 funcName := FuncName(action)
307 case "main.(*View).CursorUp":
308 if m.historyNum > 0 {
310 m.response = history[m.historyNum]
311 m.cursorx = Count(m.response)
313 case "main.(*View).CursorDown":
314 if m.historyNum < len(history)-1 {
316 m.response = history[m.historyNum]
317 m.cursorx = Count(m.response)
319 case "main.(*View).CursorLeft":
323 case "main.(*View).CursorRight":
324 if m.cursorx < Count(m.response) {
327 case "main.(*View).CursorStart", "main.(*View).StartOfLine":
329 case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
330 m.cursorx = Count(m.response)
331 case "main.(*View).Backspace":
333 m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
336 case "main.(*View).Paste":
337 clip, _ := clipboard.ReadAll("clipboard")
338 m.response = Insert(m.response, m.cursorx, clip)
339 m.cursorx += Count(clip)
348 m.response = Insert(m.response, m.cursorx, string(e.Rune()))
351 history[m.historyNum] = m.response
353 case *tcell.EventPaste:
355 m.response = Insert(m.response, m.cursorx, clip)
356 m.cursorx += Count(clip)
357 case *tcell.EventMouse:
359 x -= Count(m.message)
360 button := e.Buttons()
361 _, screenH := screen.Size()
369 } else if m.cursorx > Count(m.response) {
370 m.cursorx = Count(m.response)
377 // Reset resets the messenger's cursor, message and response
378 func (m *Messenger) Reset() {
384 // Clear clears the line at the bottom of the editor
385 func (m *Messenger) Clear() {
386 w, h := screen.Size()
387 for x := 0; x < w; x++ {
388 screen.SetContent(x, h-1, ' ', nil, defStyle)
392 func (m *Messenger) DisplaySuggestions(suggestions []string) {
393 w, screenH := screen.Size()
397 statusLineStyle := defStyle.Reverse(true)
398 if style, ok := colorscheme["statusline"]; ok {
399 statusLineStyle = style
402 for x := 0; x < w; x++ {
403 screen.SetContent(x, y, ' ', nil, statusLineStyle)
407 for _, suggestion := range suggestions {
408 for _, c := range suggestion {
409 screen.SetContent(x, y, c, nil, statusLineStyle)
412 screen.SetContent(x, y, ' ', nil, statusLineStyle)
417 // Display displays messages or prompts
418 func (m *Messenger) Display() {
419 _, h := screen.Size()
421 if m.hasPrompt || globalSettings["infobar"].(bool) {
422 runes := []rune(m.message + m.response)
423 for x := 0; x < len(runes); x++ {
424 screen.SetContent(x, h-1, runes[x], nil, m.style)
430 screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
435 // A GutterMessage is a message displayed on the side of the editor
436 type GutterMessage struct {
442 // These are the different types of messages
444 // GutterInfo represents a simple info message
446 // GutterWarning represents a compiler warning
448 // GutterError represents a compiler error