Add prompts

This commit is contained in:
Zachary Yedidia
2016-03-23 11:41:04 -04:00
parent bcb5cf5b55
commit 26e2851876
6 changed files with 174 additions and 41 deletions

View File

@@ -67,6 +67,11 @@ func (b *Buffer) SaveAs(filename string) error {
return err
}
// IsDirty returns whether or not the buffer has been modified compared to the one on disk
func (b *Buffer) IsDirty() bool {
return b.savedText != b.text
}
// Insert a string into the rope
func (b *Buffer) Insert(idx int, value string) {
b.r.Insert(idx, value)

View File

@@ -4,36 +4,143 @@ import (
"github.com/zyedidia/tcell"
)
var (
curMessage string
curStyle tcell.Style
)
// Messenger is an object that can send messages to the user and get input from the user (with a prompt)
type Messenger struct {
hasPrompt bool
hasMessage bool
func Message(msg string) {
curMessage = msg
curStyle = tcell.StyleDefault
message string
response string
style tcell.Style
cursorx int
s tcell.Screen
}
// NewMessenger returns a new Messenger struct
func NewMessenger(s tcell.Screen) *Messenger {
m := new(Messenger)
m.s = s
return m
}
// Message sends a message to the user
func (m *Messenger) Message(msg string) {
m.message = msg
m.style = tcell.StyleDefault
if _, ok := colorscheme["message"]; ok {
curStyle = colorscheme["message"]
m.style = colorscheme["message"]
}
m.hasMessage = true
}
func Error(msg string) {
curMessage = msg
curStyle = tcell.StyleDefault.
// Error sends an error message to the user
func (m *Messenger) Error(msg string) {
m.message = msg
m.style = tcell.StyleDefault.
Foreground(tcell.ColorBlack).
Background(tcell.ColorRed)
Background(tcell.ColorMaroon)
if _, ok := colorscheme["error-message"]; ok {
curStyle = colorscheme["error-message"]
m.style = colorscheme["error-message"]
}
m.hasMessage = true
}
// 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) {
m.hasPrompt = true
m.Message(prompt)
response, canceled := "", true
for m.hasPrompt {
m.Clear()
m.Display()
event := m.s.PollEvent()
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() == tcell.KeyEscape {
// Cancel
m.hasPrompt = false
} else if e.Key() == tcell.KeyCtrlC {
// Cancel
m.hasPrompt = false
} else if e.Key() == tcell.KeyCtrlQ {
// Cancel
m.hasPrompt = false
} else if e.Key() == tcell.KeyEnter {
// User is done entering their response
m.hasPrompt = false
response, canceled = m.response, false
}
}
m.HandleEvent(event)
}
m.Reset()
return response, canceled
}
// HandleEvent handles an event for the prompter
func (m *Messenger) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyLeft:
m.cursorx--
case tcell.KeyRight:
m.cursorx++
case tcell.KeyBackspace2:
if m.cursorx > 0 {
m.response = string([]rune(m.response)[:Count(m.response)-1])
m.cursorx--
}
case tcell.KeySpace:
m.response += " "
m.cursorx++
case tcell.KeyRune:
m.response += string(e.Rune())
m.cursorx++
}
}
if m.cursorx < 0 {
m.cursorx = 0
}
}
func DisplayMessage(s tcell.Screen) {
_, h := s.Size()
// Reset resets the messenger's cursor, message and response
func (m *Messenger) Reset() {
m.cursorx = 0
m.message = ""
m.response = ""
}
runes := []rune(curMessage)
for x := 0; x < len(runes); x++ {
s.SetContent(x, h-1, runes[x], nil, curStyle)
// Clear clears the line at the bottom of the editor
func (m *Messenger) Clear() {
w, h := m.s.Size()
for x := 0; x < w; x++ {
m.s.SetContent(x, h-1, ' ', nil, tcell.StyleDefault)
}
}
// Display displays and messages or prompts
func (m *Messenger) Display() {
_, h := m.s.Size()
if m.hasMessage {
runes := []rune(m.message + m.response)
for x := 0; x < len(runes); x++ {
m.s.SetContent(x, h-1, runes[x], nil, m.style)
}
}
if m.hasPrompt {
m.s.ShowCursor(Count(m.message)+m.cursorx, h-1)
m.s.Show()
}
}

View File

@@ -82,9 +82,8 @@ func main() {
s.SetStyle(defStyle)
s.EnableMouse()
v := NewView(NewBuffer(string(input), filename), s)
Message("welcome to micro")
m := NewMessenger(s)
v := NewView(NewBuffer(string(input), filename), m, s)
// Initially everything needs to be drawn
redraw := 2
@@ -92,28 +91,19 @@ func main() {
if redraw == 2 {
v.matches = Match(v.buf.rules, v.buf, v)
s.Clear()
DisplayMessage(s)
v.Display()
v.cursor.Display()
v.sl.Display()
m.Display()
s.Show()
} else if redraw == 1 {
v.cursor.Display()
DisplayMessage(s)
v.sl.Display()
m.Display()
s.Show()
}
event := s.PollEvent()
switch e := event.(type) {
case *tcell.EventKey:
if e.Key() == tcell.KeyCtrlQ {
s.Fini()
os.Exit(0)
}
}
redraw = v.HandleEvent(event)
}
}

View File

@@ -19,7 +19,7 @@ func (sl *Statusline) Display() {
if file == "" {
file = "Untitled"
}
if sl.v.buf.text != sl.v.buf.savedText {
if sl.v.buf.IsDirty() {
file += " +"
}
file += " (" + strconv.Itoa(sl.v.cursor.y+1) + "," + strconv.Itoa(sl.v.cursor.GetVisualX()+1) + ")"

View File

@@ -3,7 +3,9 @@ package main
import (
"github.com/atotto/clipboard"
"github.com/zyedidia/tcell"
"os"
"strconv"
"strings"
)
// The View struct stores information about a view into a buffer.
@@ -34,20 +36,23 @@ type View struct {
// Syntax highlighting matches
matches map[int]tcell.Style
m *Messenger
s tcell.Screen
}
// NewView returns a new view with fullscreen width and height
func NewView(buf *Buffer, s tcell.Screen) *View {
return NewViewWidthHeight(buf, s, 1, 1)
func NewView(buf *Buffer, m *Messenger, s tcell.Screen) *View {
return NewViewWidthHeight(buf, m, s, 1, 1)
}
// NewViewWidthHeight returns a new view with the specified width and height percentages
func NewViewWidthHeight(buf *Buffer, s tcell.Screen, w, h float32) *View {
func NewViewWidthHeight(buf *Buffer, m *Messenger, s tcell.Screen, w, h float32) *View {
v := new(View)
v.buf = buf
v.s = s
v.m = m
v.widthPercent = w
v.heightPercent = h
@@ -146,6 +151,23 @@ func (v *View) HandleEvent(event tcell.Event) int {
ret = 2
case *tcell.EventKey:
switch e.Key() {
case tcell.KeyCtrlQ:
if v.buf.IsDirty() {
quit, canceled := v.m.Prompt("You have unsaved changes. Quit anyway? ")
if !canceled {
if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" {
v.s.Fini()
os.Exit(0)
} else {
return 2
}
} else {
return 2
}
} else {
v.s.Fini()
os.Exit(0)
}
case tcell.KeyUp:
v.cursor.Up()
ret = 1
@@ -189,9 +211,18 @@ func (v *View) HandleEvent(event tcell.Event) int {
v.cursor.Right()
ret = 2
case tcell.KeyCtrlS:
if v.buf.path == "" {
filename, canceled := v.m.Prompt("Filename: ")
if !canceled {
v.buf.path = filename
v.buf.name = filename
} else {
return 2
}
}
err := v.buf.Save()
if err != nil {
Error(err.Error())
v.m.Error(err.Error())
}
// Need to redraw the status line
ret = 1