diff --git a/cmd/project/create.go b/cmd/project/create.go index 8836a345..ab856cc2 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -142,8 +142,9 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] if appNameArg == "" { if clients.IO.IsTTY() { defaultName := generateRandomAppName() - cmd.Print(style.Secondary(fmt.Sprintf(" Press Enter to use the generated name: %s", defaultName)), "\n") - name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{}) + name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{ + Placeholder: defaultName, + }) if err != nil { return err } diff --git a/cmd/project/create_test.go b/cmd/project/create_test.go index 8c1cf493..5951429d 100644 --- a/cmd/project/create_test.go +++ b/cmd/project/create_test.go @@ -348,6 +348,38 @@ func TestCreateCommand(t *testing.T) { cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything) }, }, + "name prompt includes placeholder with generated name": { + Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { + cm.IO.On("IsTTY").Return(true) + cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, + }, + nil, + ) + cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything). + Return( + iostreams.SelectPromptResponse{ + Prompt: true, + Index: 0, + }, + nil, + ) + cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything). + Return("my-app", nil) + createClientMock = new(CreateClientMock) + createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil) + CreateFunc = createClientMock.Create + }, + ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { + // Verify that InputPrompt was called with a config that has a non-empty Placeholder + cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.MatchedBy(func(cfg iostreams.InputPromptConfig) bool { + return cfg.Placeholder != "" + })) + }, + }, "user accepts default name from prompt": { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { cm.IO.On("IsTTY").Return(true) diff --git a/internal/iostreams/charm.go b/internal/iostreams/charm.go index d55b336e..0e0f4350 100644 --- a/internal/iostreams/charm.go +++ b/internal/iostreams/charm.go @@ -29,6 +29,7 @@ import ( func buildInputForm(message string, cfg InputPromptConfig, input *string) *huh.Form { field := huh.NewInput(). Title(message). + Placeholder(cfg.Placeholder). Value(input) if cfg.Required { field.Validate(huh.ValidateMinLength(1)) diff --git a/internal/iostreams/charm_test.go b/internal/iostreams/charm_test.go index 1075c8b1..1aef7176 100644 --- a/internal/iostreams/charm_test.go +++ b/internal/iostreams/charm_test.go @@ -53,6 +53,15 @@ func TestCharmInput(t *testing.T) { assert.Contains(t, view, "Huh") }) + t.Run("renders placeholder text", func(t *testing.T) { + var input string + f := buildInputForm("Name?", InputPromptConfig{Placeholder: "my-cool-app"}, &input) + f.Update(f.Init()) + + view := ansi.Strip(f.View()) + assert.Contains(t, view, "my-cool-app") + }) + t.Run("stores typed value", func(t *testing.T) { var input string f := buildInputForm("Name?", InputPromptConfig{}, &input) diff --git a/internal/iostreams/survey.go b/internal/iostreams/survey.go index 8e2d95a1..aebd9774 100644 --- a/internal/iostreams/survey.go +++ b/internal/iostreams/survey.go @@ -174,12 +174,13 @@ var InputQuestionTemplate = fmt.Sprintf(` {{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}} {{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}} ]{{color "reset"}} {{end}} - {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} -{{- end}}`, blue()) + {{- if .Default}}{{color "%s"}}({{.Default}}) {{color "reset"}}{{end}} +{{- end}}`, blue(), gray()) // InputPromptConfig holds additional config for an Input prompt type InputPromptConfig struct { - Required bool // Whether the input must be non-empty + Required bool // Whether the input must be non-empty + Placeholder string // Placeholder text shown when input is empty } // GetFlags returns all flags for the Input prompt @@ -208,6 +209,7 @@ func (io *IOStreams) InputPrompt(ctx context.Context, message string, cfg InputP var input string err := survey.AskOne(&survey.Input{ Message: message, + Default: cfg.Placeholder, }, &input, SurveyOptions(cfg)...) if err != nil {