add word wrapping for lines which are longer than the terminal width (#553)

This commit is contained in:
Patrick Devine 2023-09-22 13:36:08 -07:00 committed by GitHub
parent e1a0846483
commit c928ceb927
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 25 deletions

View file

@ -18,11 +18,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/pdevine/readline"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/pdevine/readline"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/term"
"github.com/jmorganca/ollama/api" "github.com/jmorganca/ollama/api"
"github.com/jmorganca/ollama/format" "github.com/jmorganca/ollama/format"
@ -400,6 +401,29 @@ func generate(cmd *cobra.Command, model, prompt string) error {
generateContext = []int{} generateContext = []int{}
} }
var wrapTerm bool
termType := os.Getenv("TERM")
if termType == "xterm-256color" {
wrapTerm = true
}
termWidth, _, err := term.GetSize(int(0))
if err != nil {
wrapTerm = false
}
// override wrapping if the user turned it off
nowrap, err := cmd.Flags().GetBool("nowordwrap")
if err != nil {
return err
}
if nowrap {
wrapTerm = false
}
var currentLineLength int
var wordBuffer string
request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext} request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
fn := func(response api.GenerateResponse) error { fn := func(response api.GenerateResponse) error {
if !spinner.IsFinished() { if !spinner.IsFinished() {
@ -408,7 +432,31 @@ func generate(cmd *cobra.Command, model, prompt string) error {
latest = response latest = response
fmt.Print(response.Response) if wrapTerm {
for _, ch := range response.Response {
if currentLineLength+1 > termWidth-5 {
// backtrack the length of the last word and clear to the end of the line
fmt.Printf("\x1b[%dD\x1b[K\n", len(wordBuffer))
fmt.Printf("%s%c", wordBuffer, ch)
currentLineLength = len(wordBuffer) + 1
} else {
fmt.Print(string(ch))
currentLineLength += 1
switch ch {
case ' ':
wordBuffer = ""
case '\n':
currentLineLength = 0
default:
wordBuffer += string(ch)
}
}
}
} else {
fmt.Print(response.Response)
}
return nil return nil
} }
@ -427,7 +475,6 @@ func generate(cmd *cobra.Command, model, prompt string) error {
} }
return err return err
} }
if prompt != "" { if prompt != "" {
fmt.Println() fmt.Println()
fmt.Println() fmt.Println()
@ -470,13 +517,10 @@ func generateInteractive(cmd *cobra.Command, model string) error {
readline.PcItem("/set", readline.PcItem("/set",
readline.PcItem("history"), readline.PcItem("history"),
readline.PcItem("nohistory"), readline.PcItem("nohistory"),
readline.PcItem("wordwrap"),
readline.PcItem("nowordwrap"),
readline.PcItem("verbose"), readline.PcItem("verbose"),
readline.PcItem("quiet"), readline.PcItem("quiet"),
readline.PcItem("mode",
readline.PcItem("vim"),
readline.PcItem("emacs"),
readline.PcItem("default"),
),
), ),
readline.PcItem("/show", readline.PcItem("/show",
readline.PcItem("license"), readline.PcItem("license"),
@ -535,6 +579,7 @@ func generateInteractive(cmd *cobra.Command, model string) error {
line = multiLineBuffer line = multiLineBuffer
multiLineBuffer = "" multiLineBuffer = ""
scanner.SetPrompt(">>> ") scanner.SetPrompt(">>> ")
continue
} else { } else {
multiLineBuffer += line + " " multiLineBuffer += line + " "
continue continue
@ -549,45 +594,42 @@ func generateInteractive(cmd *cobra.Command, model string) error {
if err := ListHandler(cmd, args[1:]); err != nil { if err := ListHandler(cmd, args[1:]); err != nil {
return err return err
} }
continue
case strings.HasPrefix(line, "/set"): case strings.HasPrefix(line, "/set"):
args := strings.Fields(line) args := strings.Fields(line)
if len(args) > 1 { if len(args) > 1 {
switch args[1] { switch args[1] {
case "history": case "history":
scanner.HistoryEnable() scanner.HistoryEnable()
continue
case "nohistory": case "nohistory":
scanner.HistoryDisable() scanner.HistoryDisable()
continue case "wordwrap":
cmd.Flags().Set("nowordwrap", "false")
fmt.Println("Set 'wordwrap' mode.")
case "nowordwrap":
cmd.Flags().Set("nowordwrap", "true")
fmt.Println("Set 'nowordwrap' mode.")
case "verbose": case "verbose":
cmd.Flags().Set("verbose", "true") cmd.Flags().Set("verbose", "true")
continue fmt.Println("Set 'verbose' mode.")
case "quiet": case "quiet":
cmd.Flags().Set("verbose", "false") cmd.Flags().Set("verbose", "false")
continue fmt.Println("Set 'quiet' mode.")
case "mode": case "mode":
if len(args) > 2 { if len(args) > 2 {
switch args[2] { switch args[2] {
case "vim": case "vim":
scanner.SetVimMode(true) scanner.SetVimMode(true)
continue
case "emacs", "default": case "emacs", "default":
scanner.SetVimMode(false) scanner.SetVimMode(false)
continue
default: default:
usage() usage()
continue
} }
} else { } else {
usage() usage()
continue
} }
} }
} else { } else {
usage() usage()
continue
} }
case strings.HasPrefix(line, "/show"): case strings.HasPrefix(line, "/show"):
args := strings.Fields(line) args := strings.Fields(line)
@ -595,7 +637,6 @@ func generateInteractive(cmd *cobra.Command, model string) error {
resp, err := server.GetModelInfo(model) resp, err := server.GetModelInfo(model)
if err != nil { if err != nil {
fmt.Println("error: couldn't get model") fmt.Println("error: couldn't get model")
continue
} }
switch args[1] { switch args[1] {
@ -612,17 +653,16 @@ func generateInteractive(cmd *cobra.Command, model string) error {
default: default:
fmt.Println("error: unknown command") fmt.Println("error: unknown command")
} }
continue
} else { } else {
usage() usage()
continue
} }
case line == "/help", line == "/?": case line == "/help", line == "/?":
usage() usage()
continue
case line == "/exit", line == "/bye": case line == "/exit", line == "/bye":
return nil return nil
case strings.HasPrefix(line, "/"):
args := strings.Fields(line)
fmt.Printf("Unknown command '%s'. Type /? for help\n", args[0])
} }
if len(line) > 0 && line[0] != '/' { if len(line) > 0 && line[0] != '/' {
@ -828,6 +868,7 @@ func NewCLI() *cobra.Command {
runCmd.Flags().Bool("verbose", false, "Show timings for response") runCmd.Flags().Bool("verbose", false, "Show timings for response")
runCmd.Flags().Bool("insecure", false, "Use an insecure registry") runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
serveCmd := &cobra.Command{ serveCmd := &cobra.Command{
Use: "serve", Use: "serve",

1
go.sum
View file

@ -120,7 +120,6 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=