206 lines
3.3 KiB
Go
206 lines
3.3 KiB
Go
package editor
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
type Prompt struct {
|
|
Prompt string
|
|
AltPrompt string
|
|
Placeholder string
|
|
AltPlaceholder string
|
|
UseAlt bool
|
|
}
|
|
|
|
type Terminal struct {
|
|
outchan chan rune
|
|
}
|
|
|
|
type Instance struct {
|
|
Prompt *Prompt
|
|
Terminal *Terminal
|
|
}
|
|
|
|
func New(prompt Prompt) (*Instance, error) {
|
|
term, err := NewTerminal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Instance{
|
|
Prompt: &prompt,
|
|
Terminal: term,
|
|
}, nil
|
|
}
|
|
|
|
func (i *Instance) HandleInput() (string, error) {
|
|
prompt := i.Prompt.Prompt
|
|
if i.Prompt.UseAlt {
|
|
prompt = i.Prompt.AltPrompt
|
|
}
|
|
fmt.Print(prompt)
|
|
|
|
termios, err := SetRawMode(syscall.Stdin)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer UnsetRawMode(syscall.Stdin, termios)
|
|
|
|
buf, _ := NewBuffer(i.Prompt)
|
|
|
|
var esc bool
|
|
var escex bool
|
|
var pasteMode PasteMode
|
|
|
|
fmt.Print(StartBracketedPaste)
|
|
defer fmt.Printf(EndBracketedPaste)
|
|
|
|
for {
|
|
if buf.IsEmpty() {
|
|
ph := i.Prompt.Placeholder
|
|
if i.Prompt.UseAlt {
|
|
ph = i.Prompt.AltPlaceholder
|
|
}
|
|
fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault)
|
|
}
|
|
|
|
r, err := i.Terminal.Read()
|
|
if err != nil {
|
|
return "", io.EOF
|
|
}
|
|
|
|
if buf.IsEmpty() {
|
|
fmt.Print(ClearToEOL)
|
|
}
|
|
|
|
if escex {
|
|
escex = false
|
|
|
|
switch r {
|
|
case KeyUp:
|
|
buf.MoveUp()
|
|
case KeyDown:
|
|
buf.MoveDown()
|
|
case KeyLeft:
|
|
buf.MoveLeft()
|
|
case KeyRight:
|
|
buf.MoveRight()
|
|
case CharBracketedPaste:
|
|
var code string
|
|
for cnt := 0; cnt < 3; cnt++ {
|
|
r, err = i.Terminal.Read()
|
|
if err != nil {
|
|
return "", io.EOF
|
|
}
|
|
|
|
code += string(r)
|
|
}
|
|
if code == CharBracketedPasteStart {
|
|
pasteMode = PasteModeStart
|
|
} else if code == CharBracketedPasteEnd {
|
|
pasteMode = PasteModeEnd
|
|
}
|
|
case MetaStart:
|
|
buf.MoveToBOL()
|
|
case MetaEnd:
|
|
buf.MoveToEOL()
|
|
}
|
|
continue
|
|
} else if esc {
|
|
esc = false
|
|
|
|
switch r {
|
|
case CharEscapeEx:
|
|
escex = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
switch r {
|
|
case CharNull:
|
|
continue
|
|
case CharEsc:
|
|
esc = true
|
|
case CharInterrupt:
|
|
return "", ErrInterrupt
|
|
case CharLineStart:
|
|
buf.MoveToBOL()
|
|
case CharLineEnd:
|
|
buf.MoveToEOL()
|
|
case CharBackward:
|
|
buf.MoveLeft()
|
|
case CharForward:
|
|
buf.MoveRight()
|
|
case CharBackspace, CharCtrlH:
|
|
buf.Remove()
|
|
case CharTab:
|
|
for cnt := 0; cnt < 8; cnt++ {
|
|
buf.Add(' ')
|
|
}
|
|
case CharDelete:
|
|
if len(buf.Buf) > 0 && buf.Buf[0].Size() > 0 {
|
|
buf.Delete()
|
|
} else {
|
|
return "", io.EOF
|
|
}
|
|
case CharCtrlU:
|
|
buf.RemoveBefore()
|
|
case CharCtrlL:
|
|
buf.ClearScreen()
|
|
case CharCtrlW:
|
|
buf.RemoveWordBefore()
|
|
case CharCtrlJ:
|
|
buf.Add(r)
|
|
case CharEnter:
|
|
if pasteMode == PasteModeStart {
|
|
buf.Add(r)
|
|
continue
|
|
}
|
|
buf.MoveToEnd()
|
|
fmt.Println()
|
|
return buf.String(), nil
|
|
default:
|
|
if r >= CharSpace || r == CharEnter {
|
|
buf.Add(r)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func NewTerminal() (*Terminal, error) {
|
|
t := &Terminal{
|
|
outchan: make(chan rune),
|
|
}
|
|
|
|
go t.ioloop()
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (t *Terminal) ioloop() {
|
|
buf := bufio.NewReader(os.Stdin)
|
|
|
|
for {
|
|
r, _, err := buf.ReadRune()
|
|
if err != nil {
|
|
close(t.outchan)
|
|
break
|
|
}
|
|
t.outchan <- r
|
|
}
|
|
}
|
|
|
|
func (t *Terminal) Read() (rune, error) {
|
|
r, ok := <-t.outchan
|
|
if !ok {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
return r, nil
|
|
}
|