Repository: ollama/ollama
Pull request: Don't quit ioloop on `NUL` character

The `stdin` read loop would stop on receiving a `NUL` character, triggered by ctrl+space or other key combos.

Changed files:
filename: readline/buffer.go
patch: @@ -45,7 +45,7 @@ func (b *Buffer) MoveLeft() {
 		if b.Pos%b.LineWidth == 0 {
 			fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width))
 		} else {
-			fmt.Printf(CursorLeft)
+			fmt.Print(CursorLeft)
 		}
 		b.Pos -= 1
 	}
@@ -78,7 +78,7 @@ func (b *Buffer) MoveRight() {
 		if b.Pos%b.LineWidth == 0 {
 			fmt.Printf(CursorDown + CursorBOL + cursorRightN(b.PromptSize()))
 		} else {
-			fmt.Printf(CursorRight)
+			fmt.Print(CursorRight)
 		}
 	}
 }
@@ -104,7 +104,7 @@ func (b *Buffer) MoveToStart() {
 		currLine := b.Pos / b.LineWidth
 		if currLine > 0 {
 			for cnt := 0; cnt < currLine; cnt++ {
-				fmt.Printf(CursorUp)
+				fmt.Print(CursorUp)
 			}
 		}
 		fmt.Printf(CursorBOL + cursorRightN(b.PromptSize()))
@@ -118,12 +118,12 @@ func (b *Buffer) MoveToEnd() {
 		totalLines := b.Size() / b.LineWidth
 		if currLine < totalLines {
 			for cnt := 0; cnt < totalLines-currLine; cnt++ {
-				fmt.Printf(CursorDown)
+				fmt.Print(CursorDown)
 			}
 			remainder := b.Size() % b.LineWidth
 			fmt.Printf(CursorBOL + cursorRightN(b.PromptSize()+remainder))
 		} else {
-			fmt.Printf(cursorRightN(b.Size() - b.Pos))
+			fmt.Print(cursorRightN(b.Size() - b.Pos))
 		}
 
 		b.Pos = b.Size()
@@ -173,15 +173,15 @@ func (b *Buffer) drawRemaining() {
 	if b.Pos > 0 {
 		place = b.Pos % b.LineWidth
 	}
-	fmt.Printf(CursorHide)
+	fmt.Print(CursorHide)
 
 	// render the rest of the current line
 	currLine := remainingText[:min(b.LineWidth-place, len(remainingText))]
 	if len(currLine) > 0 {
 		fmt.Printf(ClearToEOL + currLine)
-		fmt.Printf(cursorLeftN(len(currLine)))
+		fmt.Print(cursorLeftN(len(currLine)))
 	} else {
-		fmt.Printf(ClearToEOL)
+		fmt.Print(ClearToEOL)
 	}
 
 	// render the other lines
@@ -195,12 +195,12 @@ func (b *Buffer) drawRemaining() {
 			}
 			fmt.Printf("%c", c)
 		}
-		fmt.Printf(ClearToEOL)
-		fmt.Printf(cursorUpN(totalLines))
+		fmt.Print(ClearToEOL)
+		fmt.Print(cursorUpN(totalLines))
 		fmt.Printf(CursorBOL + cursorRightN(b.Width-len(currLine)))
 	}
 
-	fmt.Printf(CursorShow)
+	fmt.Print(CursorShow)
 }
 
 func (b *Buffer) Remove() {
@@ -305,12 +305,12 @@ func (b *Buffer) ClearScreen() {
 			targetLine := currPos / b.LineWidth
 			if targetLine > 0 {
 				for cnt := 0; cnt < targetLine; cnt++ {
-					fmt.Printf(CursorDown)
+					fmt.Print(CursorDown)
 				}
 			}
 			remainder := currPos % b.LineWidth
 			if remainder > 0 {
-				fmt.Printf(cursorRightN(remainder))
+				fmt.Print(cursorRightN(remainder))
 			}
 			if currPos%b.LineWidth == 0 {
 				fmt.Printf(CursorBOL + b.Prompt.AltPrompt)

filename: readline/readline.go
patch: @@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"sync"
 	"syscall"
 )
 
@@ -18,8 +17,6 @@ type Prompt struct {
 }
 
 type Terminal struct {
-	m       sync.Mutex
-	wg      sync.WaitGroup
 	outchan chan rune
 }
 
@@ -52,7 +49,7 @@ func (i *Instance) Readline() (string, error) {
 	if i.Prompt.UseAlt {
 		prompt = i.Prompt.AltPrompt
 	}
-	fmt.Printf(prompt)
+	fmt.Print(prompt)
 
 	termios, err := SetRawMode(syscall.Stdin)
 	if err != nil {
@@ -78,13 +75,13 @@ func (i *Instance) Readline() (string, error) {
 			fmt.Printf(ColorGrey + ph + fmt.Sprintf(CursorLeftN, len(ph)) + ColorDefault)
 		}
 
-		r := i.Terminal.ReadRune()
-		if buf.IsEmpty() {
-			fmt.Printf(ClearToEOL)
+		r, err := i.Terminal.Read()
+		if err != nil {
+			return "", io.EOF
 		}
 
-		if r == 0 { // io.EOF
-			break
+		if buf.IsEmpty() {
+			fmt.Print(ClearToEOL)
 		}
 
 		if escex {
@@ -112,7 +109,11 @@ func (i *Instance) Readline() (string, error) {
 			case CharBracketedPaste:
 				var code string
 				for cnt := 0; cnt < 3; cnt++ {
-					r = i.Terminal.ReadRune()
+					r, err = i.Terminal.Read()
+					if err != nil {
+						return "", io.EOF
+					}
+
 					code += string(r)
 				}
 				if code == CharBracketedPasteStart {
@@ -149,6 +150,8 @@ func (i *Instance) Readline() (string, error) {
 		}
 
 		switch r {
+		case CharNull:
+			continue
 		case CharEsc:
 			esc = true
 		case CharInterrupt:
@@ -206,11 +209,10 @@ func (i *Instance) Readline() (string, error) {
 			}
 		}
 	}
-	return "", nil
 }
 
-func (i *Instance) Close() error {
-	return i.Terminal.Close()
+func (i *Instance) Close() {
+	i.Terminal.Close()
 }
 
 func (i *Instance) HistoryEnable() {
@@ -240,22 +242,18 @@ func (t *Terminal) ioloop() {
 			break
 		}
 		t.outchan <- r
-		if r == 0 { // EOF
-			break
-		}
 	}
-
 }
 
-func (t *Terminal) ReadRune() rune {
+func (t *Terminal) Read() (rune, error) {
 	r, ok := <-t.outchan
 	if !ok {
-		return rune(0)
+		return 0, io.EOF
 	}
-	return r
+
+	return r, nil
 }
 
-func (t *Terminal) Close() error {
+func (t *Terminal) Close() {
 	close(t.outchan)
-	return nil
 }

filename: readline/term.go
patch: @@ -1,4 +1,6 @@
+//go:build aix || darwin || dragonfly || freebsd || (linux && !appengine) || netbsd || openbsd || os400 || solaris
 // +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris
+
 package readline
 
 import (
@@ -30,6 +32,6 @@ func UnsetRawMode(fd int, termios *Termios) error {
 
 // IsTerminal returns true if the given file descriptor is a terminal.
 func IsTerminal(fd int) bool {
-        _, err := getTermios(fd)
-        return err == nil
+	_, err := getTermios(fd)
+	return err == nil
 }

filename: readline/types.go
patch: @@ -1,6 +1,7 @@
 package readline
 
 const (
+	CharNull      = 0
 	CharLineStart = 1
 	CharBackward  = 2
 	CharInterrupt = 3