Skip to content

NO_COLOR suppresses non-color attributes, not just color #337

Description

@levitte

NO_COLOR suppresses non-color attributes, not just color

Summary

When NO_COLOR is set, anstream strips all ANSI escape sequences —
bold, dim, italic, underline, reverse, strikethrough, etc. — not only
color. This diverges from no-color.org
(or its source),
whose FAQ is explicit:

Q: Should the presence of NO_COLOR disable other styling such as
bold, underline, and italic?

A: No. This standard only signals the user's intention regarding
adding ANSI color to text output.

Reproduction

NO_COLOR resolves to ColorChoice::NeverStripStream, whose
StripBytes engine (driven by anstyle_parse) discards every CSI escape
without inspecting its SGR parameters. Verified on anstream 1.0.0:

use std::io::Write as _;

let mut out = anstream::AutoStream::new(Vec::new(), anstream::ColorChoice::Never);
write!(out, "\x1b[1mbold\x1b[22m \x1b[4munderline\x1b[24m \x1b[31mred\x1b[39m").unwrap();
assert_eq!(String::from_utf8(out.into_inner()).unwrap(), "bold underline red");

Bold and underline are stripped along with the red color. Per the FAQ, only
color should be suppressed; the expected output is
\x1b[1mbold\x1b[22m \x1b[4munderline\x1b[24m red.

Suggested direction

A spec-correct color-only strip needs to distinguish color SGR parameters
from attribute parameters and re-emit only the latter. Most of the
primitives for this already live in the workspace: anstyle::Style
separates fg/bg/underline-color from effects, and anstyle-parse
already produces the SGR parameter list via csi_dispatch. The missing
piece is a decode step (param list → Style delta) so a strip adapter can
track the active style and render only effects. If a shared decoder
lived in anstyle, other consumers could reuse it for the same spec
alignment — but the shape is of course the maintainers' call.

One design point worth flagging: ColorChoice::Never is reached today for
NO_COLOR, for non-terminals, and for CLICOLOR disabled. The FAQ
constrains only the NO_COLOR case, so it may be cleanest for NO_COLOR
to resolve to a color-only path while the other Never callers keep the
full strip (pipe-to-file output today is fully flat, which is arguably
desirable for logs).

Notes

  • The empty-string trigger is already handled correctly upstream:
    anstyle_query::no_color() is non_empty(NO_COLOR), so NO_COLOR=""
    disables nothing, per spec. This issue is only about the scope of
    suppression.
  • This cascades to dependents routing through anstream (env_logger,
    clap with its color feature), which would inherit the fix.
  • crossterm is a working example of color-only NO_COLOR handling —
    it gates Colored (fg/bg/underline-color) but emits SetAttributes
    unconditionally.
  • Related: Supporting FORCE_COLOR as an alias to CLICOLOR_FORCE #192 (env-var support context).

Versioning

I'd frame this as a bug fix (patch bump) with a one-line CHANGELOG note
acknowledging the observable output change, but defer to the project's
own version policy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-streamArea: anstreamC-bugCategory: Things not working as expectedS-triageStatus: New; needs maintainer attention.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions