diff --git a/0.20.0-release-notes.md b/0.20.0-release-notes.md index 35df35963..94b9fa2f4 100644 --- a/0.20.0-release-notes.md +++ b/0.20.0-release-notes.md @@ -25,6 +25,10 @@ Draft release notes for Elvish 0.20.0. - The language server now supports showing the documentation of builtin functions and variables on hover ([#1684](https://b.elv.sh/1684)). +- Elvish now respects the [`NO_COLOR`](https://no-color.org) environment + variable. Builtin UI elements as well as styled texts will no have colors if + it is set and non-empty. + # Notable bugfixes - `has-value $li $v` now works correctly when `$li` is a list and `$v` is a diff --git a/pkg/env/env.go b/pkg/env/env.go index 1d980c3ff..02ed3d2f8 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -6,6 +6,7 @@ package env const ( HOME = "HOME" LS_COLORS = "LS_COLORS" + NO_COLOR = "NO_COLOR" PATH = "PATH" PWD = "PWD" SHLVL = "SHLVL" diff --git a/pkg/eval/builtin_fn_styled.d.elv b/pkg/eval/builtin_fn_styled.d.elv index 15a74d20d..8798bd77b 100644 --- a/pkg/eval/builtin_fn_styled.d.elv +++ b/pkg/eval/builtin_fn_styled.d.elv @@ -77,6 +77,11 @@ fn styled-segment {|object &fg-color=default &bg-color=default &bold=$false &dim # [ANSI SGR code](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_.28Select_Graphic_Rendition.29_parameters) # is built to render the style. # +# If the [`NO_COLOR`](https://no-color.org) environment variable is set and +# non-empty when Elvish starts, color output is suppressed. Modifications to +# `NO_COLOR` within Elvish (including from `rc.elv`) do not affect the current +# process, but will affect child Elvish processes. +# # Examples: # # ```elvish diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index 873862480..000082363 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -62,7 +62,8 @@ func (p *Program) Run(fds [3]*os.File, args []string) error { cleanup2 := initSignal(fds) defer cleanup2() - ui.NoColor = os.Getenv("NO_COLOR") != "" + // https://no-color.org + ui.NoColor = os.Getenv(env.NO_COLOR) != "" interactive := len(args) == 0 ev := p.makeEvaler(fds[2], interactive) defer ev.PreExit() diff --git a/pkg/shell/shell_test.go b/pkg/shell/shell_test.go index ff5c24bbe..022480cce 100644 --- a/pkg/shell/shell_test.go +++ b/pkg/shell/shell_test.go @@ -79,6 +79,28 @@ func TestShell_LibPath_Legacy(t *testing.T) { // Most high-level tests against Program are specific to either script mode or // interactive mode, and are found in script_test.go and interact_test.go. +var noColorTests = []struct { + name string + value string + unset bool + wantRedFoo string +}{ + {name: "unset", unset: true, wantRedFoo: "\033[;31mfoo\033[m"}, + {name: "empty", value: "", wantRedFoo: "\033[;31mfoo\033[m"}, + {name: "non-empty", value: "yes", wantRedFoo: "\033[mfoo"}, +} + +func TestShell_NO_COLOR(t *testing.T) { + for _, test := range noColorTests { + t.Run(test.name, func(t *testing.T) { + setOrUnsetenv(t, env.NO_COLOR, test.unset, test.value) + Test(t, &Program{}, + ThatElvish("-c", "print (styled foo red)"). + WritesStdout(test.wantRedFoo)) + }) + } +} + var incSHLVLTests = []struct { name string old string @@ -104,12 +126,7 @@ var incSHLVLTests = []struct { func TestShell_SHLVL(t *testing.T) { for _, test := range incSHLVLTests { t.Run(test.name, func(t *testing.T) { - if test.unset { - testutil.Unsetenv(t, env.SHLVL) - } else { - testutil.Setenv(t, env.SHLVL, test.old) - } - + setOrUnsetenv(t, env.SHLVL, test.unset, test.old) Test(t, &Program{}, ThatElvish("-c", "print $E:SHLVL").WritesStdout(test.wantNew)) @@ -128,6 +145,14 @@ func TestShell_SHLVL(t *testing.T) { } } +func setOrUnsetenv(t *testing.T, name string, unset bool, set string) { + if unset { + testutil.Unsetenv(t, name) + } else { + testutil.Setenv(t, name, set) + } +} + // Common test utilities. func setupCleanHomePaths(t testutil.Cleanuper) string { diff --git a/pkg/ui/style.go b/pkg/ui/style.go index 249da0e9f..b9c72f456 100644 --- a/pkg/ui/style.go +++ b/pkg/ui/style.go @@ -5,6 +5,8 @@ import ( "strings" ) +// NoColor can be set to true to suppress foreground and background colors when +// writing text to the terminal. var NoColor bool = false // Style specifies how something (mostly a string) shall be displayed. diff --git a/pkg/ui/style_test.go b/pkg/ui/style_test.go index 58ecdaea5..15c19c53b 100644 --- a/pkg/ui/style_test.go +++ b/pkg/ui/style_test.go @@ -2,6 +2,8 @@ package ui import ( "testing" + + "src.elv.sh/pkg/testutil" ) func TestStyleSGR(t *testing.T) { @@ -20,6 +22,15 @@ func TestStyleSGR(t *testing.T) { }) } +func TestStyleSGR_NoColor(t *testing.T) { + testutil.Set(t, &NoColor, true) + testTextVTString(t, []textVTStringTest{ + {T("foo", FgRed), "\033[mfoo"}, + {T("foo", BgRed), "\033[mfoo"}, + {T("foo", FgRed, BgBlue), "\033[mfoo"}, + }) +} + type mergeFromOptionsTest struct { style Style options map[string]any