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 {
46 // Are we currently prompting the user?
48 // Is there a message to print
53 // The user's response to a prompt
55 // style to use when drawing the message
58 // We have to keep track of the cursor for prompting
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
66 // Is the current message a message from the gutter
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()
77 if _, ok := colorscheme["message"]; ok {
78 m.style = colorscheme["message"]
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()
89 Foreground(tcell.ColorBlack).
90 Background(tcell.ColorMaroon)
92 if _, ok := colorscheme["error-message"]; ok {
93 m.style = colorscheme["error-message"]
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) {
103 _, h := screen.Size()
107 screen.ShowCursor(Count(m.message), h-1)
111 switch e := event.(type) {
112 case *tcell.EventKey:
118 } else if e.Rune() == 'n' {
122 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
130 // LetterPrompt gives the user a prompt and waits for a one letter response
131 func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
135 _, h := screen.Size()
139 screen.ShowCursor(Count(m.message), h-1)
143 switch e := event.(type) {
144 case *tcell.EventKey:
147 for _, r := range responses {
155 case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
168 NoCompletion Completion = iota
175 // Prompt sends the user a message and waits for a response to be typed in
176 // This function blocks the main loop while waiting for input
177 func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Completion) (string, bool) {
180 if _, ok := m.history[historyType]; !ok {
181 m.history[historyType] = []string{""}
183 m.history[historyType] = append(m.history[historyType], "")
185 m.historyNum = len(m.history[historyType]) - 1
187 response, canceled := "", true
191 var suggestions []string
196 switch e := event.(type) {
197 case *tcell.EventKey:
199 case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
203 // User is done entering their response
205 response, canceled = m.response, false
206 m.history[historyType][len(m.history[historyType])-1] = response
208 args := SplitCommandArgs(m.response)
209 currentArgNum := len(args) - 1
210 currentArg := args[currentArgNum]
211 var completionType Completion
213 if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
214 if command, ok := commands[args[0]]; ok {
215 completionTypes = append([]Completion{CommandCompletion}, command.completions...)
219 if currentArgNum >= len(completionTypes) {
220 completionType = completionTypes[len(completionTypes)-1]
222 completionType = completionTypes[currentArgNum]
226 if completionType == FileCompletion {
227 chosen, suggestions = FileComplete(currentArg)
228 } else if completionType == CommandCompletion {
229 chosen, suggestions = CommandComplete(currentArg)
230 } else if completionType == HelpCompletion {
231 chosen, suggestions = HelpComplete(currentArg)
232 } else if completionType == OptionCompletion {
233 chosen, suggestions = OptionComplete(currentArg)
234 } else if completionType < NoCompletion {
235 chosen, suggestions = PluginComplete(completionType, currentArg)
238 if len(suggestions) > 1 {
239 chosen = chosen + CommonSubstring(suggestions...)
243 m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
244 m.cursorx = Count(m.response)
249 m.HandleEvent(event, m.history[historyType])
252 for _, v := range tabs[curTab].views {
257 if len(suggestions) > 1 {
258 m.DisplaySuggestions(suggestions)
265 return response, canceled
268 // HandleEvent handles an event for the prompter
269 func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
270 switch e := event.(type) {
271 case *tcell.EventKey:
274 if m.historyNum > 0 {
276 m.response = history[m.historyNum]
277 m.cursorx = Count(m.response)
280 if m.historyNum < len(history)-1 {
282 m.response = history[m.historyNum]
283 m.cursorx = Count(m.response)
290 if m.cursorx < Count(m.response) {
293 case tcell.KeyBackspace2, tcell.KeyBackspace:
295 m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
299 clip, _ := clipboard.ReadAll("clipboard")
300 m.response = Insert(m.response, m.cursorx, clip)
301 m.cursorx += Count(clip)
303 m.response = Insert(m.response, m.cursorx, string(e.Rune()))
306 history[m.historyNum] = m.response
308 case *tcell.EventPaste:
310 m.response = Insert(m.response, m.cursorx, clip)
311 m.cursorx += Count(clip)
315 // Reset resets the messenger's cursor, message and response
316 func (m *Messenger) Reset() {
322 // Clear clears the line at the bottom of the editor
323 func (m *Messenger) Clear() {
324 w, h := screen.Size()
325 for x := 0; x < w; x++ {
326 screen.SetContent(x, h-1, ' ', nil, defStyle)
330 func (m *Messenger) DisplaySuggestions(suggestions []string) {
331 w, screenH := screen.Size()
335 statusLineStyle := defStyle.Reverse(true)
336 if style, ok := colorscheme["statusline"]; ok {
337 statusLineStyle = style
340 for x := 0; x < w; x++ {
341 screen.SetContent(x, y, ' ', nil, statusLineStyle)
345 for _, suggestion := range suggestions {
346 for _, c := range suggestion {
347 screen.SetContent(x, y, c, nil, statusLineStyle)
350 screen.SetContent(x, y, ' ', nil, statusLineStyle)
355 // Display displays messages or prompts
356 func (m *Messenger) Display() {
357 _, h := screen.Size()
359 if m.hasPrompt || globalSettings["infobar"].(bool) {
360 runes := []rune(m.message + m.response)
361 for x := 0; x < len(runes); x++ {
362 screen.SetContent(x, h-1, runes[x], nil, m.style)
368 screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
373 // A GutterMessage is a message displayed on the side of the editor
374 type GutterMessage struct {
380 // These are the different types of messages
382 // GutterInfo represents a simple info message
384 // GutterWarning represents a compiler warning
386 // GutterError represents a compiler error