ollama/readline/readline.go

289 lines
4.9 KiB
Go
Raw Normal View History

2023-10-25 23:41:18 +00:00
package readline
import (
"bufio"
"fmt"
"io"
"os"
"syscall"
)
type Prompt struct {
Prompt string
AltPrompt string
Placeholder string
AltPlaceholder string
UseAlt bool
}
2024-01-06 00:19:37 +00:00
func (p *Prompt) prompt() string {
if p.UseAlt {
return p.AltPrompt
}
return p.Prompt
}
func (p *Prompt) placeholder() string {
if p.UseAlt {
return p.AltPlaceholder
}
return p.Placeholder
}
2023-10-25 23:41:18 +00:00
type Terminal struct {
outchan chan rune
rawmode bool
termios any
2023-10-25 23:41:18 +00:00
}
type Instance struct {
Prompt *Prompt
Terminal *Terminal
History *History
Pasting bool
2023-10-25 23:41:18 +00:00
}
func New(prompt Prompt) (*Instance, error) {
term, err := NewTerminal()
if err != nil {
return nil, err
}
history, err := NewHistory()
if err != nil {
return nil, err
}
return &Instance{
Prompt: &prompt,
Terminal: term,
History: history,
}, nil
}
func (i *Instance) Readline() (string, error) {
if !i.Terminal.rawmode {
fd := int(syscall.Stdin)
termios, err := SetRawMode(fd)
if err != nil {
return "", err
}
i.Terminal.rawmode = true
i.Terminal.termios = termios
}
2024-01-06 00:19:37 +00:00
prompt := i.Prompt.prompt()
if i.Pasting {
// force alt prompt when pasting
2023-10-25 23:41:18 +00:00
prompt = i.Prompt.AltPrompt
}
fmt.Print(prompt)
2023-10-25 23:41:18 +00:00
defer func() {
fd := int(syscall.Stdin)
// nolint: errcheck
UnsetRawMode(fd, i.Terminal.termios)
i.Terminal.rawmode = false
}()
2023-10-25 23:41:18 +00:00
buf, _ := NewBuffer(i.Prompt)
var esc bool
var escex bool
var metaDel bool
var currentLineBuf []rune
for {
// don't show placeholder when pasting unless we're in multiline mode
showPlaceholder := !i.Pasting || i.Prompt.UseAlt
if buf.IsEmpty() && showPlaceholder {
2024-01-06 00:19:37 +00:00
ph := i.Prompt.placeholder()
2023-10-25 23:41:18 +00:00
fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault)
}
r, err := i.Terminal.Read()
2023-10-25 23:41:18 +00:00
if buf.IsEmpty() {
fmt.Print(ClearToEOL)
2023-10-25 23:41:18 +00:00
}
if err != nil {
return "", io.EOF
}
2023-10-25 23:41:18 +00:00
if escex {
escex = false
switch r {
case KeyUp:
if i.History.Pos > 0 {
if i.History.Pos == i.History.Size() {
currentLineBuf = []rune(buf.String())
}
buf.Replace(i.History.Prev())
}
case KeyDown:
if i.History.Pos < i.History.Size() {
buf.Replace(i.History.Next())
if i.History.Pos == i.History.Size() {
buf.Replace(currentLineBuf)
}
}
case KeyLeft:
buf.MoveLeft()
case KeyRight:
buf.MoveRight()
case CharBracketedPaste:
2023-10-26 22:57:00 +00:00
var code string
for cnt := 0; cnt < 3; cnt++ {
r, err = i.Terminal.Read()
if err != nil {
return "", io.EOF
}
2023-10-26 22:57:00 +00:00
code += string(r)
}
if code == CharBracketedPasteStart {
i.Pasting = true
2023-10-26 22:57:00 +00:00
} else if code == CharBracketedPasteEnd {
i.Pasting = false
2023-10-26 22:57:00 +00:00
}
2023-10-25 23:41:18 +00:00
case KeyDel:
if buf.Size() > 0 {
buf.Delete()
}
metaDel = true
case MetaStart:
buf.MoveToStart()
case MetaEnd:
buf.MoveToEnd()
default:
// skip any keys we don't know about
continue
}
continue
} else if esc {
esc = false
switch r {
case 'b':
buf.MoveLeftWord()
case 'f':
buf.MoveRightWord()
2023-11-21 20:26:47 +00:00
case CharBackspace:
buf.DeleteWord()
2023-10-25 23:41:18 +00:00
case CharEscapeEx:
escex = true
}
continue
}
switch r {
case CharNull:
continue
2023-10-25 23:41:18 +00:00
case CharEsc:
esc = true
case CharInterrupt:
return "", ErrInterrupt
case CharLineStart:
buf.MoveToStart()
case CharLineEnd:
buf.MoveToEnd()
case CharBackward:
buf.MoveLeft()
case CharForward:
buf.MoveRight()
case CharBackspace, CharCtrlH:
buf.Remove()
case CharTab:
// todo: convert back to real tabs
for cnt := 0; cnt < 8; cnt++ {
buf.Add(' ')
}
case CharDelete:
if buf.Size() > 0 {
buf.Delete()
} else {
return "", io.EOF
}
case CharKill:
buf.DeleteRemaining()
case CharCtrlU:
buf.DeleteBefore()
case CharCtrlL:
buf.ClearScreen()
case CharCtrlW:
buf.DeleteWord()
2023-12-02 00:04:09 +00:00
case CharCtrlZ:
fd := int(syscall.Stdin)
return handleCharCtrlZ(fd, i.Terminal.termios)
case CharEnter, CharCtrlJ:
2023-10-26 22:57:00 +00:00
output := buf.String()
if output != "" {
i.History.Add([]rune(output))
}
buf.MoveToEnd()
fmt.Println()
2023-10-26 22:57:00 +00:00
return output, nil
2023-10-25 23:41:18 +00:00
default:
if metaDel {
metaDel = false
continue
}
if r >= CharSpace || r == CharEnter || r == CharCtrlJ {
2023-10-25 23:41:18 +00:00
buf.Add(r)
}
}
}
}
func (i *Instance) HistoryEnable() {
i.History.Enabled = true
}
func (i *Instance) HistoryDisable() {
i.History.Enabled = false
}
func NewTerminal() (*Terminal, error) {
fd := int(syscall.Stdin)
termios, err := SetRawMode(fd)
if err != nil {
return nil, err
}
2023-10-25 23:41:18 +00:00
t := &Terminal{
outchan: make(chan rune),
rawmode: true,
termios: termios,
2023-10-25 23:41:18 +00:00
}
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)
2023-10-25 23:41:18 +00:00
break
}
t.outchan <- r
}
}
func (t *Terminal) Read() (rune, error) {
2023-10-25 23:41:18 +00:00
r, ok := <-t.outchan
if !ok {
return 0, io.EOF
2023-10-25 23:41:18 +00:00
}
return r, nil
2023-10-25 23:41:18 +00:00
}