import (
"bufio"
+ "bytes"
"fmt"
- "github.com/gdamore/tcell"
+ "io/ioutil"
"os"
"strconv"
+ "strings"
+
+ "github.com/zyedidia/tcell"
)
// TermMessage sends a message to the user in the terminal. This usually occurs before
// The function must be called when the screen is not initialized
// This will write the message, and wait for the user
// to press and key to continue
-func TermMessage(msg string) {
- fmt.Println(msg)
+func TermMessage(msg ...interface{}) {
+ screenWasNil := screen == nil
+ if !screenWasNil {
+ screen.Fini()
+ }
+
+ fmt.Println(msg...)
fmt.Print("\nPress enter to continue")
reader := bufio.NewReader(os.Stdin)
reader.ReadString('\n')
+
+ if !screenWasNil {
+ InitScreen()
+ }
}
// TermError sends an error to the user in the terminal. Like TermMessage except formatted
// We have to keep track of the cursor for prompting
cursorx int
+
+ // This map stores the history for all the different kinds of uses Prompt has
+ // It's a map of history type -> history array
+ history map[string][]string
+ historyNum int
+
+ // Is the current message a message from the gutter
+ gutterMessage bool
}
// Message sends a message to the user
-func (m *Messenger) Message(msg string) {
- m.message = msg
+func (m *Messenger) Message(msg ...interface{}) {
+ buf := new(bytes.Buffer)
+ fmt.Fprint(buf, msg...)
+ m.message = buf.String()
m.style = defStyle
if _, ok := colorscheme["message"]; ok {
}
// Error sends an error message to the user
-func (m *Messenger) Error(msg string) {
- m.message = msg
+func (m *Messenger) Error(msg ...interface{}) {
+ buf := new(bytes.Buffer)
+ fmt.Fprint(buf, msg...)
+ m.message = buf.String()
m.style = defStyle.
Foreground(tcell.ColorBlack).
Background(tcell.ColorMaroon)
}
// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
-func (m *Messenger) YesNoPrompt(prompt string) bool {
+func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
m.Message(prompt)
+ _, h := screen.Size()
for {
m.Clear()
m.Display()
+ screen.ShowCursor(Count(m.message), h-1)
screen.Show()
- event := screen.PollEvent()
+ event := <-events
switch e := event.(type) {
case *tcell.EventKey:
- if e.Key() == tcell.KeyRune {
+ switch e.Key() {
+ case tcell.KeyRune:
if e.Rune() == 'y' {
- return true
+ return true, false
} else if e.Rune() == 'n' {
- return false
+ return false, false
}
+ case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
+ return false, true
}
}
}
}
+type Completion int
+
+const (
+ NoCompletion Completion = iota
+ FileCompletion
+)
+
// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
-func (m *Messenger) Prompt(prompt string) (string, bool) {
+func (m *Messenger) Prompt(prompt, historyType string, completionType Completion) (string, bool) {
m.hasPrompt = true
m.Message(prompt)
+ if _, ok := m.history[historyType]; !ok {
+ m.history[historyType] = []string{""}
+ } else {
+ m.history[historyType] = append(m.history[historyType], "")
+ }
+ m.historyNum = len(m.history[historyType]) - 1
response, canceled := "", true
m.Clear()
m.Display()
- event := screen.PollEvent()
+ event := <-events
switch e := event.(type) {
case *tcell.EventKey:
// User is done entering their response
m.hasPrompt = false
response, canceled = m.response, false
+ m.history[historyType][len(m.history[historyType])-1] = response
+ case tcell.KeyTab:
+ if completionType == FileCompletion {
+ dirs := strings.Split(m.response, "/")
+ var files []os.FileInfo
+ var err error
+ if len(dirs) > 1 {
+ files, err = ioutil.ReadDir(strings.Join(dirs[:len(dirs)-1], "/"))
+ } else {
+ files, err = ioutil.ReadDir(".")
+ }
+ if err != nil {
+ continue
+ }
+ var suggestions []string
+ for _, f := range files {
+ name := f.Name()
+ if f.IsDir() {
+ name += "/"
+ }
+ if strings.HasPrefix(name, dirs[len(dirs)-1]) {
+ suggestions = append(suggestions, name)
+ }
+ }
+ if len(suggestions) == 1 {
+ if len(dirs) > 1 {
+ m.response = strings.Join(dirs[:len(dirs)-1], "/") + "/" + suggestions[0]
+ } else {
+ m.response = suggestions[0]
+ }
+ m.cursorx = Count(m.response)
+ }
+ }
}
}
- m.HandleEvent(event)
+ m.HandleEvent(event, m.history[historyType])
if m.cursorx < 0 {
// Cancel
}
// HandleEvent handles an event for the prompter
-func (m *Messenger) HandleEvent(event tcell.Event) {
+func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
+ case tcell.KeyUp:
+ if m.historyNum > 0 {
+ m.historyNum--
+ m.response = history[m.historyNum]
+ m.cursorx = Count(m.response)
+ }
+ case tcell.KeyDown:
+ if m.historyNum < len(history)-1 {
+ m.historyNum++
+ m.response = history[m.historyNum]
+ m.cursorx = Count(m.response)
+ }
case tcell.KeyLeft:
if m.cursorx > 0 {
m.cursorx--
if m.cursorx < Count(m.response) {
m.cursorx++
}
- case tcell.KeyBackspace2:
+ case tcell.KeyBackspace2, tcell.KeyBackspace:
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:m.cursorx-1]) + string(m.response[m.cursorx:])
}
m.cursorx--
- case tcell.KeySpace:
- m.response += " "
- m.cursorx++
case tcell.KeyRune:
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
m.cursorx++
}
+ history[m.historyNum] = m.response
}
}
screen.Show()
}
}
+
+// A GutterMessage is a message displayed on the side of the editor
+type GutterMessage struct {
+ lineNum int
+ msg string
+ kind int
+}
+
+// These are the different types of messages
+const (
+ // GutterInfo represents a simple info message
+ GutterInfo = iota
+ // GutterWarning represents a compiler warning
+ GutterWarning
+ // GutterError represents a compiler error
+ GutterError
+)