Skip to content

Commit 0fd8736

Browse files
srtaalejzimeg
andauthored
fix: apply --no-color flag to huh interactive prompts (#454)
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
1 parent a6658e3 commit 0fd8736

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

internal/iostreams/forms.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import (
3232
// newForm wraps a field in an interactive form with optional Slack theming.
3333
func newForm(io *IOStreams, field huh.Field) *huh.Form {
3434
form := huh.NewForm(huh.NewGroup(field))
35-
if io != nil && io.config.WithExperimentOn(experiment.Lipgloss) {
35+
if io != nil && io.config.NoColor {
36+
form = form.WithTheme(style.ThemePlain())
37+
} else if io != nil && io.config.WithExperimentOn(experiment.Lipgloss) {
3638
form = form.WithTheme(style.ThemeSlack())
3739
} else {
3840
form = form.WithTheme(style.ThemeSurvey())

internal/iostreams/forms_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,47 @@ func TestFormsUseSlackTheme(t *testing.T) {
415415
})
416416
}
417417

418+
func TestFormsNoColor(t *testing.T) {
419+
t.Run("forms use plain theme with no-color", func(t *testing.T) {
420+
fsMock := slackdeps.NewFsMock()
421+
osMock := slackdeps.NewOsMock()
422+
osMock.AddDefaultMocks()
423+
cfg := config.NewConfig(fsMock, osMock)
424+
cfg.NoColor = true
425+
io := NewIOStreams(cfg, fsMock, osMock)
426+
427+
var selected string
428+
f := buildSelectForm(io, "Pick", []string{"A", "B"}, SelectPromptConfig{}, &selected)
429+
f.Update(f.Init())
430+
431+
view := f.View()
432+
// Title and option lines should have no ANSI codes
433+
for _, line := range strings.Split(view, "\n")[:3] {
434+
assert.Equal(t, ansi.Strip(line), line, "content line should have no ANSI codes")
435+
}
436+
})
437+
438+
t.Run("no-color takes priority over lipgloss experiment", func(t *testing.T) {
439+
fsMock := slackdeps.NewFsMock()
440+
osMock := slackdeps.NewOsMock()
441+
osMock.AddDefaultMocks()
442+
cfg := config.NewConfig(fsMock, osMock)
443+
cfg.NoColor = true
444+
cfg.ExperimentsFlag = []string{"lipgloss"}
445+
cfg.LoadExperiments(context.Background(), func(_ context.Context, _ string, _ ...any) {})
446+
io := NewIOStreams(cfg, fsMock, osMock)
447+
448+
var selected string
449+
f := buildSelectForm(io, "Pick", []string{"A", "B"}, SelectPromptConfig{}, &selected)
450+
f.Update(f.Init())
451+
452+
view := f.View()
453+
for _, line := range strings.Split(view, "\n")[:3] {
454+
assert.Equal(t, ansi.Strip(line), line, "content line should have no ANSI codes even with lipgloss experiment on")
455+
}
456+
})
457+
}
458+
418459
func TestFormsUseSurveyTheme(t *testing.T) {
419460
t.Run("multi-select uses survey prefix without lipgloss", func(t *testing.T) {
420461
var selected []string

internal/style/theme.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,42 @@ func Chevron() string {
122122
return "❱"
123123
}
124124

125+
// ThemePlain returns a huh Theme with no colors or formatting.
126+
func ThemePlain() huh.Theme {
127+
return huh.ThemeFunc(func(_ bool) *huh.Styles {
128+
t := huh.ThemeBase(false)
129+
t.Focused.Title = lipgloss.NewStyle()
130+
t.Focused.Description = lipgloss.NewStyle()
131+
t.Focused.ErrorIndicator = lipgloss.NewStyle().SetString(" *")
132+
t.Focused.ErrorMessage = lipgloss.NewStyle()
133+
t.Focused.SelectSelector = lipgloss.NewStyle().SetString("> ")
134+
t.Focused.Option = lipgloss.NewStyle()
135+
t.Focused.MultiSelectSelector = lipgloss.NewStyle().SetString("> ")
136+
t.Focused.SelectedOption = lipgloss.NewStyle()
137+
t.Focused.SelectedPrefix = lipgloss.NewStyle().SetString("[x] ")
138+
t.Focused.UnselectedOption = lipgloss.NewStyle()
139+
t.Focused.UnselectedPrefix = lipgloss.NewStyle().SetString("[ ] ")
140+
t.Focused.FocusedButton = lipgloss.NewStyle().SetString(" >").PaddingRight(3)
141+
t.Focused.BlurredButton = lipgloss.NewStyle().SetString(" ").PaddingRight(3)
142+
t.Focused.TextInput.Cursor = lipgloss.NewStyle()
143+
t.Focused.TextInput.Prompt = lipgloss.NewStyle()
144+
t.Focused.TextInput.Placeholder = lipgloss.NewStyle()
145+
t.Focused.TextInput.Text = lipgloss.NewStyle()
146+
t.Focused.Base = lipgloss.NewStyle().PaddingLeft(1).BorderStyle(lipgloss.ThickBorder()).BorderLeft(true)
147+
t.Blurred = t.Focused
148+
t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder())
149+
t.Blurred.SelectSelector = lipgloss.NewStyle().SetString(" ")
150+
t.Blurred.MultiSelectSelector = lipgloss.NewStyle().SetString(" ")
151+
t.Help.ShortKey = lipgloss.NewStyle()
152+
t.Help.ShortDesc = lipgloss.NewStyle()
153+
t.Help.ShortSeparator = lipgloss.NewStyle()
154+
t.Help.FullKey = lipgloss.NewStyle()
155+
t.Help.FullDesc = lipgloss.NewStyle()
156+
t.Help.FullSeparator = lipgloss.NewStyle()
157+
return t
158+
})
159+
}
160+
125161
// ThemeSurvey returns a huh Theme that matches the legacy survey prompt styling.
126162
// Applied when experiment.Lipgloss is off.
127163
func ThemeSurvey() huh.Theme {

internal/style/theme_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,40 @@ func TestThemeSurvey(t *testing.T) {
115115
}
116116
}
117117

118+
func TestThemePlain(t *testing.T) {
119+
theme := ThemePlain().Theme(false)
120+
tests := map[string]struct {
121+
rendered string
122+
expected string
123+
}{
124+
"title renders plain text": {
125+
rendered: theme.Focused.Title.Render("x"),
126+
expected: "x",
127+
},
128+
"error message renders plain text": {
129+
rendered: theme.Focused.ErrorMessage.Render("err"),
130+
expected: "err",
131+
},
132+
"select selector renders plain >": {
133+
rendered: theme.Focused.SelectSelector.Render(),
134+
expected: "> ",
135+
},
136+
"selected prefix renders [x]": {
137+
rendered: theme.Focused.SelectedPrefix.Render(),
138+
expected: "[x] ",
139+
},
140+
"unselected prefix renders [ ]": {
141+
rendered: theme.Focused.UnselectedPrefix.Render(),
142+
expected: "[ ] ",
143+
},
144+
}
145+
for name, tc := range tests {
146+
t.Run(name, func(t *testing.T) {
147+
assert.Equal(t, tc.expected, tc.rendered)
148+
})
149+
}
150+
}
151+
118152
func TestChevron(t *testing.T) {
119153
tests := map[string]struct {
120154
styleEnabled bool

0 commit comments

Comments
 (0)