ollama/editor/editor.go
2023-11-14 15:59:35 -08:00

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
}