mirror of
https://github.com/Hopiu/micro.git
synced 2026-05-13 09:13:09 +00:00
Add prompts
This commit is contained in:
parent
bcb5cf5b55
commit
26e2851876
6 changed files with 174 additions and 41 deletions
|
|
@ -67,6 +67,11 @@ func (b *Buffer) SaveAs(filename string) error {
|
||||||
return err
|
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
|
// Insert a string into the rope
|
||||||
func (b *Buffer) Insert(idx int, value string) {
|
func (b *Buffer) Insert(idx int, value string) {
|
||||||
b.r.Insert(idx, value)
|
b.r.Insert(idx, value)
|
||||||
|
|
|
||||||
143
src/message.go
143
src/message.go
|
|
@ -4,36 +4,143 @@ import (
|
||||||
"github.com/zyedidia/tcell"
|
"github.com/zyedidia/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// Messenger is an object that can send messages to the user and get input from the user (with a prompt)
|
||||||
curMessage string
|
type Messenger struct {
|
||||||
curStyle tcell.Style
|
hasPrompt bool
|
||||||
)
|
hasMessage bool
|
||||||
|
|
||||||
func Message(msg string) {
|
message string
|
||||||
curMessage = msg
|
response string
|
||||||
curStyle = tcell.StyleDefault
|
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 {
|
if _, ok := colorscheme["message"]; ok {
|
||||||
curStyle = colorscheme["message"]
|
m.style = colorscheme["message"]
|
||||||
}
|
}
|
||||||
|
m.hasMessage = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(msg string) {
|
// Error sends an error message to the user
|
||||||
curMessage = msg
|
func (m *Messenger) Error(msg string) {
|
||||||
curStyle = tcell.StyleDefault.
|
m.message = msg
|
||||||
|
m.style = tcell.StyleDefault.
|
||||||
Foreground(tcell.ColorBlack).
|
Foreground(tcell.ColorBlack).
|
||||||
Background(tcell.ColorRed)
|
Background(tcell.ColorMaroon)
|
||||||
|
|
||||||
if _, ok := colorscheme["error-message"]; ok {
|
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) {
|
// Reset resets the messenger's cursor, message and response
|
||||||
_, h := s.Size()
|
func (m *Messenger) Reset() {
|
||||||
|
m.cursorx = 0
|
||||||
|
m.message = ""
|
||||||
|
m.response = ""
|
||||||
|
}
|
||||||
|
|
||||||
runes := []rune(curMessage)
|
// Clear clears the line at the bottom of the editor
|
||||||
for x := 0; x < len(runes); x++ {
|
func (m *Messenger) Clear() {
|
||||||
s.SetContent(x, h-1, runes[x], nil, curStyle)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/micro.go
18
src/micro.go
|
|
@ -82,9 +82,8 @@ func main() {
|
||||||
s.SetStyle(defStyle)
|
s.SetStyle(defStyle)
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
|
|
||||||
v := NewView(NewBuffer(string(input), filename), s)
|
m := NewMessenger(s)
|
||||||
|
v := NewView(NewBuffer(string(input), filename), m, s)
|
||||||
Message("welcome to micro")
|
|
||||||
|
|
||||||
// Initially everything needs to be drawn
|
// Initially everything needs to be drawn
|
||||||
redraw := 2
|
redraw := 2
|
||||||
|
|
@ -92,28 +91,19 @@ func main() {
|
||||||
if redraw == 2 {
|
if redraw == 2 {
|
||||||
v.matches = Match(v.buf.rules, v.buf, v)
|
v.matches = Match(v.buf.rules, v.buf, v)
|
||||||
s.Clear()
|
s.Clear()
|
||||||
DisplayMessage(s)
|
|
||||||
v.Display()
|
v.Display()
|
||||||
v.cursor.Display()
|
v.cursor.Display()
|
||||||
v.sl.Display()
|
v.sl.Display()
|
||||||
|
m.Display()
|
||||||
s.Show()
|
s.Show()
|
||||||
} else if redraw == 1 {
|
} else if redraw == 1 {
|
||||||
v.cursor.Display()
|
v.cursor.Display()
|
||||||
DisplayMessage(s)
|
|
||||||
v.sl.Display()
|
v.sl.Display()
|
||||||
|
m.Display()
|
||||||
s.Show()
|
s.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
event := s.PollEvent()
|
event := s.PollEvent()
|
||||||
|
|
||||||
switch e := event.(type) {
|
|
||||||
case *tcell.EventKey:
|
|
||||||
if e.Key() == tcell.KeyCtrlQ {
|
|
||||||
s.Fini()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redraw = v.HandleEvent(event)
|
redraw = v.HandleEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func (sl *Statusline) Display() {
|
||||||
if file == "" {
|
if file == "" {
|
||||||
file = "Untitled"
|
file = "Untitled"
|
||||||
}
|
}
|
||||||
if sl.v.buf.text != sl.v.buf.savedText {
|
if sl.v.buf.IsDirty() {
|
||||||
file += " +"
|
file += " +"
|
||||||
}
|
}
|
||||||
file += " (" + strconv.Itoa(sl.v.cursor.y+1) + "," + strconv.Itoa(sl.v.cursor.GetVisualX()+1) + ")"
|
file += " (" + strconv.Itoa(sl.v.cursor.y+1) + "," + strconv.Itoa(sl.v.cursor.GetVisualX()+1) + ")"
|
||||||
|
|
|
||||||
39
src/view.go
39
src/view.go
|
|
@ -3,7 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/atotto/clipboard"
|
"github.com/atotto/clipboard"
|
||||||
"github.com/zyedidia/tcell"
|
"github.com/zyedidia/tcell"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The View struct stores information about a view into a buffer.
|
// The View struct stores information about a view into a buffer.
|
||||||
|
|
@ -34,20 +36,23 @@ type View struct {
|
||||||
// Syntax highlighting matches
|
// Syntax highlighting matches
|
||||||
matches map[int]tcell.Style
|
matches map[int]tcell.Style
|
||||||
|
|
||||||
|
m *Messenger
|
||||||
|
|
||||||
s tcell.Screen
|
s tcell.Screen
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewView returns a new view with fullscreen width and height
|
// NewView returns a new view with fullscreen width and height
|
||||||
func NewView(buf *Buffer, s tcell.Screen) *View {
|
func NewView(buf *Buffer, m *Messenger, s tcell.Screen) *View {
|
||||||
return NewViewWidthHeight(buf, s, 1, 1)
|
return NewViewWidthHeight(buf, m, s, 1, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewViewWidthHeight returns a new view with the specified width and height percentages
|
// 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 := new(View)
|
||||||
|
|
||||||
v.buf = buf
|
v.buf = buf
|
||||||
v.s = s
|
v.s = s
|
||||||
|
v.m = m
|
||||||
|
|
||||||
v.widthPercent = w
|
v.widthPercent = w
|
||||||
v.heightPercent = h
|
v.heightPercent = h
|
||||||
|
|
@ -146,6 +151,23 @@ func (v *View) HandleEvent(event tcell.Event) int {
|
||||||
ret = 2
|
ret = 2
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
switch e.Key() {
|
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:
|
case tcell.KeyUp:
|
||||||
v.cursor.Up()
|
v.cursor.Up()
|
||||||
ret = 1
|
ret = 1
|
||||||
|
|
@ -189,9 +211,18 @@ func (v *View) HandleEvent(event tcell.Event) int {
|
||||||
v.cursor.Right()
|
v.cursor.Right()
|
||||||
ret = 2
|
ret = 2
|
||||||
case tcell.KeyCtrlS:
|
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()
|
err := v.buf.Save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error(err.Error())
|
v.m.Error(err.Error())
|
||||||
}
|
}
|
||||||
// Need to redraw the status line
|
// Need to redraw the status line
|
||||||
ret = 1
|
ret = 1
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,6 @@
|
||||||
- [ ] Help screen which lists keybindings and commands
|
- [ ] Help screen which lists keybindings and commands
|
||||||
- [ ] Opened with Ctrl-h
|
- [ ] Opened with Ctrl-h
|
||||||
|
|
||||||
- [ ] Messages/Prompts
|
|
||||||
- [x] Messages at the bottom of the screen
|
|
||||||
- [ ] Prompts at the bottom of the screen
|
|
||||||
|
|
||||||
- [ ] Command execution
|
- [ ] Command execution
|
||||||
- [ ] Allow executing simple commands at the bottom of the editor
|
- [ ] Allow executing simple commands at the bottom of the editor
|
||||||
(like vim or emacs)
|
(like vim or emacs)
|
||||||
|
|
@ -48,3 +44,7 @@
|
||||||
|
|
||||||
- [x] Colorschemes
|
- [x] Colorschemes
|
||||||
- [x] Support for 256 color and true color
|
- [x] Support for 256 color and true color
|
||||||
|
|
||||||
|
- [x] Messages/Prompts
|
||||||
|
- [x] Messages at the bottom of the screen
|
||||||
|
- [x] Prompts at the bottom of the screen
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue