Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 76 additions & 11 deletions commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,71 @@ import (
"github.com/spf13/cobra"
)

// readMultilineInput reads input from stdin, supporting both single-line and multiline input.
// For multiline input, it detects triple-quoted strings and shows continuation prompts.
func readMultilineInput(cmd *cobra.Command, scanner *bufio.Scanner) (string, error) {
cmd.Print("> ")

if !scanner.Scan() {
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("error reading input: %v", err)
}
return "", fmt.Errorf("EOF")
}

line := scanner.Text()

// Check if this is the start of a multiline input (triple quotes)
tripleQuoteStart := ""
if strings.HasPrefix(line, `"""`) {
tripleQuoteStart = `"""`
} else if strings.HasPrefix(line, "'''") {
tripleQuoteStart = "'''"
}

// If no triple quotes, return a single line
if tripleQuoteStart == "" {
return line, nil
}

// Check if the triple quotes are closed on the same line
restOfLine := line[3:]
if strings.HasSuffix(restOfLine, tripleQuoteStart) && len(restOfLine) >= 3 {
// Complete multiline string on single line
return line, nil
}

// Start collecting multiline input
var multilineInput strings.Builder
multilineInput.WriteString(line)
multilineInput.WriteString("\n")

// Continue reading lines until we find the closing triple quotes
for {
cmd.Print("... ")

if !scanner.Scan() {
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("error reading input: %v", err)
}
return "", fmt.Errorf("unclosed multiline input (EOF)")
}

line = scanner.Text()
multilineInput.WriteString(line)

// Check if this line contains the closing triple quotes
if strings.Contains(line, tripleQuoteStart) {
// Found closing quotes, we're done
break
}

multilineInput.WriteString("\n")
}

return multilineInput.String(), nil
}

func newRunCmd() *cobra.Command {
var debug bool

Expand Down Expand Up @@ -58,32 +123,32 @@ func newRunCmd() *cobra.Command {

scanner := bufio.NewScanner(os.Stdin)
cmd.Println("Interactive chat mode started. Type '/bye' to exit.")
cmd.Print("> ")

for scanner.Scan() {
userInput := scanner.Text()
for {
userInput, err := readMultilineInput(cmd, scanner)
if err != nil {
if err.Error() == "EOF" {
cmd.Println("\nChat session ended.")
break
}
return fmt.Errorf("Error reading input: %v", err)
}

if strings.ToLower(userInput) == "/bye" {
if strings.ToLower(strings.TrimSpace(userInput)) == "/bye" {
cmd.Println("Chat session ended.")
break
}

if strings.TrimSpace(userInput) == "" {
cmd.Print("> ")
continue
}

if err := desktopClient.Chat(model, userInput); err != nil {
cmd.PrintErr(handleClientError(err, "Failed to generate a response"))
cmd.Print("> ")
continue
}

cmd.Print("\n> ")
}

if err := scanner.Err(); err != nil {
return fmt.Errorf("Error reading input: %v\n", err)
cmd.Println()
}
return nil
},
Expand Down
115 changes: 115 additions & 0 deletions commands/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package commands

import (
"bufio"
"strings"
"testing"

"github.com/spf13/cobra"
)

func TestReadMultilineInput(t *testing.T) {
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{
name: "single line input",
input: "hello world",
expected: "hello world",
wantErr: false,
},
{
name: "single line with triple quotes",
input: `"""hello world"""`,
expected: `"""hello world"""`,
wantErr: false,
},
{
name: "multiline input with double quotes",
input: `"""tell
me
a
joke"""`,
expected: `"""tell
me
a
joke"""`,
wantErr: false,
},
{
name: "multiline input with single quotes",
input: `'''tell
me
a
joke'''`,
expected: `'''tell
me
a
joke'''`,
wantErr: false,
},
{
name: "empty input",
input: "",
expected: "",
wantErr: true, // EOF should be treated as an error
},
{
name: "multiline with empty lines",
input: `"""first line

third line"""`,
expected: `"""first line

third line"""`,
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a mock command for testing
cmd := &cobra.Command{}

// Create a scanner from the test input
scanner := bufio.NewScanner(strings.NewReader(tt.input))

// Capture output to avoid printing during tests
var output strings.Builder
cmd.SetOut(&output)

result, err := readMultilineInput(cmd, scanner)

if (err != nil) != tt.wantErr {
t.Errorf("readMultilineInput() error = %v, wantErr %v", err, tt.wantErr)
return
}

if result != tt.expected {
t.Errorf("readMultilineInput() = %q, want %q", result, tt.expected)
}
})
}
}

func TestReadMultilineInputUnclosed(t *testing.T) {
// Test unclosed multiline input (should return error)
input := `"""unclosed multiline`
cmd := &cobra.Command{}
var output strings.Builder
cmd.SetOut(&output)

scanner := bufio.NewScanner(strings.NewReader(input))

_, err := readMultilineInput(cmd, scanner)
if err == nil {
t.Error("readMultilineInput() should return error for unclosed multiline input")
}

if !strings.Contains(err.Error(), "unclosed multiline input") {
t.Errorf("readMultilineInput() error should mention unclosed multiline input, got: %v", err)
}
}