diff --git a/cmd/create.go b/cmd/create.go index a5a20dc..9e161c5 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -151,22 +151,23 @@ var createCmd = &cobra.Command{ } } - // Branch — prompt if omitted and in a terminal + // Name — known early so the branch prompt can default to it. + var name string + if len(args) > 0 { + name = args[0] + } + + // Branch — prompt if omitted and in a terminal. branch := createBranch if branch == "" { if console.IsTerminal(os.Stdin) { - branch = console.Prompt("Branch name") + branch = console.PromptDefault("Branch name", name) } if branch == "" { exitError("Branch is required: --branch / -b") } } - - // Name - var name string - if len(args) > 0 { - name = args[0] - } else { + if name == "" { name = deriveName(branch) } diff --git a/internal/console/console.go b/internal/console/console.go index 4a76d97..288b6a1 100644 --- a/internal/console/console.go +++ b/internal/console/console.go @@ -77,10 +77,24 @@ func Confirm(prompt string, defaultYes bool) bool { // Prompt asks the user for text input. func Prompt(label string) string { - fmt.Fprintf(os.Stderr, "%s: ", label) + return PromptDefault(label, "") +} + +// PromptDefault asks the user for text input, showing defaultValue in brackets +// when non-empty. Empty input (just Enter) returns defaultValue. +func PromptDefault(label, defaultValue string) string { + if defaultValue != "" { + fmt.Fprintf(os.Stderr, "%s [%s]: ", label, defaultValue) + } else { + fmt.Fprintf(os.Stderr, "%s: ", label) + } reader := bufio.NewReader(os.Stdin) input, _ := reader.ReadString('\n') - return strings.TrimSpace(input) + input = strings.TrimSpace(input) + if input == "" { + return defaultValue + } + return input } // IsTerminal returns true if the given file is a terminal. diff --git a/internal/console/prompt_test.go b/internal/console/prompt_test.go new file mode 100644 index 0000000..f424187 --- /dev/null +++ b/internal/console/prompt_test.go @@ -0,0 +1,87 @@ +package console + +import ( + "bytes" + "io" + "os" + "testing" +) + +func withStdin(t *testing.T, input string, fn func()) (stderr string) { + t.Helper() + + rIn, wIn, err := os.Pipe() + if err != nil { + t.Fatalf("stdin pipe: %v", err) + } + rErr, wErr, err := os.Pipe() + if err != nil { + t.Fatalf("stderr pipe: %v", err) + } + + oldIn, oldErr := os.Stdin, os.Stderr + os.Stdin, os.Stderr = rIn, wErr + defer func() { + os.Stdin, os.Stderr = oldIn, oldErr + rIn.Close() + rErr.Close() + }() + + go func() { + _, _ = io.WriteString(wIn, input) + wIn.Close() + }() + + fn() + + wErr.Close() + var buf bytes.Buffer + _, _ = io.Copy(&buf, rErr) + return buf.String() +} + +func TestPromptDefault_EmptyAcceptsDefault(t *testing.T) { + var got string + stderr := withStdin(t, "\n", func() { + got = PromptDefault("Branch name", "linear-2026-07-30") + }) + if got != "linear-2026-07-30" { + t.Errorf("got %q, want default", got) + } + if want := "Branch name [linear-2026-07-30]: "; stderr != want { + t.Errorf("prompt = %q, want %q", stderr, want) + } +} + +func TestPromptDefault_Override(t *testing.T) { + var got string + _ = withStdin(t, "feat/other\n", func() { + got = PromptDefault("Branch name", "linear-2026-07-30") + }) + if got != "feat/other" { + t.Errorf("got %q, want override", got) + } +} + +func TestPromptDefault_NoDefault(t *testing.T) { + var got string + stderr := withStdin(t, "feat/login\n", func() { + got = PromptDefault("Branch name", "") + }) + if got != "feat/login" { + t.Errorf("got %q, want typed value", got) + } + if want := "Branch name: "; stderr != want { + t.Errorf("prompt = %q, want %q", stderr, want) + } +} + +func TestPromptDefault_EmptyNoDefault(t *testing.T) { + var got string + _ = withStdin(t, "\n", func() { + got = PromptDefault("Branch name", "") + }) + if got != "" { + t.Errorf("got %q, want empty", got) + } +}