ollama
1package readline2
3import (4"bufio"5"fmt"6"io"7"os"8"syscall"9)
10
11type Prompt struct {12Prompt string13AltPrompt string14Placeholder string15AltPlaceholder string16UseAlt bool17}
18
19func (p *Prompt) prompt() string {20if p.UseAlt {21return p.AltPrompt22}23return p.Prompt24}
25
26func (p *Prompt) placeholder() string {27if p.UseAlt {28return p.AltPlaceholder29}30return p.Placeholder31}
32
33type Terminal struct {34outchan chan rune35rawmode bool36termios any
37}
38
39type Instance struct {40Prompt *Prompt41Terminal *Terminal42History *History43Pasting bool44}
45
46func New(prompt Prompt) (*Instance, error) {47term, err := NewTerminal()48if err != nil {49return nil, err50}51
52history, err := NewHistory()53if err != nil {54return nil, err55}56
57return &Instance{58Prompt: &prompt,59Terminal: term,60History: history,61}, nil62}
63
64func (i *Instance) Readline() (string, error) {65if !i.Terminal.rawmode {66fd := int(syscall.Stdin)67termios, err := SetRawMode(fd)68if err != nil {69return "", err70}71i.Terminal.rawmode = true72i.Terminal.termios = termios73}74
75prompt := i.Prompt.prompt()76if i.Pasting {77// force alt prompt when pasting78prompt = i.Prompt.AltPrompt79}80fmt.Print(prompt)81
82defer func() {83fd := int(syscall.Stdin)84// nolint: errcheck85UnsetRawMode(fd, i.Terminal.termios)86i.Terminal.rawmode = false87}()88
89buf, _ := NewBuffer(i.Prompt)90
91var esc bool92var escex bool93var metaDel bool94
95var currentLineBuf []rune96
97for {98// don't show placeholder when pasting unless we're in multiline mode99showPlaceholder := !i.Pasting || i.Prompt.UseAlt100if buf.IsEmpty() && showPlaceholder {101ph := i.Prompt.placeholder()102fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault)103}104
105r, err := i.Terminal.Read()106
107if buf.IsEmpty() {108fmt.Print(ClearToEOL)109}110
111if err != nil {112return "", io.EOF113}114
115if escex {116escex = false117
118switch r {119case KeyUp:120if i.History.Pos > 0 {121if i.History.Pos == i.History.Size() {122currentLineBuf = []rune(buf.String())123}124buf.Replace(i.History.Prev())125}126case KeyDown:127if i.History.Pos < i.History.Size() {128buf.Replace(i.History.Next())129if i.History.Pos == i.History.Size() {130buf.Replace(currentLineBuf)131}132}133case KeyLeft:134buf.MoveLeft()135case KeyRight:136buf.MoveRight()137case CharBracketedPaste:138var code string139for cnt := 0; cnt < 3; cnt++ {140r, err = i.Terminal.Read()141if err != nil {142return "", io.EOF143}144
145code += string(r)146}147if code == CharBracketedPasteStart {148i.Pasting = true149} else if code == CharBracketedPasteEnd {150i.Pasting = false151}152case KeyDel:153if buf.Size() > 0 {154buf.Delete()155}156metaDel = true157case MetaStart:158buf.MoveToStart()159case MetaEnd:160buf.MoveToEnd()161default:162// skip any keys we don't know about163continue164}165continue166} else if esc {167esc = false168
169switch r {170case 'b':171buf.MoveLeftWord()172case 'f':173buf.MoveRightWord()174case CharBackspace:175buf.DeleteWord()176case CharEscapeEx:177escex = true178}179continue180}181
182switch r {183case CharNull:184continue185case CharEsc:186esc = true187case CharInterrupt:188return "", ErrInterrupt189case CharLineStart:190buf.MoveToStart()191case CharLineEnd:192buf.MoveToEnd()193case CharBackward:194buf.MoveLeft()195case CharForward:196buf.MoveRight()197case CharBackspace, CharCtrlH:198buf.Remove()199case CharTab:200// todo: convert back to real tabs201for cnt := 0; cnt < 8; cnt++ {202buf.Add(' ')203}204case CharDelete:205if buf.Size() > 0 {206buf.Delete()207} else {208return "", io.EOF209}210case CharKill:211buf.DeleteRemaining()212case CharCtrlU:213buf.DeleteBefore()214case CharCtrlL:215buf.ClearScreen()216case CharCtrlW:217buf.DeleteWord()218case CharCtrlZ:219fd := int(syscall.Stdin)220return handleCharCtrlZ(fd, i.Terminal.termios)221case CharEnter:222output := buf.String()223if output != "" {224i.History.Add([]rune(output))225}226buf.MoveToEnd()227fmt.Println()228
229return output, nil230default:231if metaDel {232metaDel = false233continue234}235if r >= CharSpace || r == CharEnter {236buf.Add(r)237}238}239}240}
241
242func (i *Instance) HistoryEnable() {243i.History.Enabled = true244}
245
246func (i *Instance) HistoryDisable() {247i.History.Enabled = false248}
249
250func NewTerminal() (*Terminal, error) {251fd := int(syscall.Stdin)252termios, err := SetRawMode(fd)253if err != nil {254return nil, err255}256
257t := &Terminal{258outchan: make(chan rune),259rawmode: true,260termios: termios,261}262
263go t.ioloop()264
265return t, nil266}
267
268func (t *Terminal) ioloop() {269buf := bufio.NewReader(os.Stdin)270
271for {272r, _, err := buf.ReadRune()273if err != nil {274close(t.outchan)275break276}277t.outchan <- r278}279}
280
281func (t *Terminal) Read() (rune, error) {282r, ok := <-t.outchan283if !ok {284return 0, io.EOF285}286
287return r, nil288}
289