Skip to content

[io] Add support for RGB ansi codes#2323

Draft
natebosch wants to merge 8 commits into
mainfrom
rgb-ansi
Draft

[io] Add support for RGB ansi codes#2323
natebosch wants to merge 8 commits into
mainfrom
rgb-ansi

Conversation

@natebosch
Copy link
Copy Markdown
Member

@natebosch natebosch commented Feb 10, 2026

Add an RgbAnsiCode class which hold the red, green, and blue channels
as separate fields.

Add a codes field to AnsiCode and update the comment for the code
field indicating that some ansi codes with more than one integer code
will return -1 for the code field. All uses should go through
codes.

@github-actions
Copy link
Copy Markdown

Package publishing

Package Version Status Publish tag (post-merge)
package:bazel_worker 1.1.5 already published at pub.dev
package:benchmark_harness 2.4.0 already published at pub.dev
package:boolean_selector 2.1.2 already published at pub.dev
package:browser_launcher 1.2.0-wip WIP (no publish necessary)
package:cli_config 0.2.1-wip WIP (no publish necessary)
package:cli_util 0.5.0-wip WIP (no publish necessary)
package:clock 1.1.3-wip WIP (no publish necessary)
package:code_builder 4.11.1 already published at pub.dev
package:coverage 1.15.0 already published at pub.dev
package:csslib 1.0.2 already published at pub.dev
package:extension_discovery 2.1.0 already published at pub.dev
package:file 7.0.2-wip WIP (no publish necessary)
package:file_testing 3.1.0-wip WIP (no publish necessary)
package:glob 2.1.3 already published at pub.dev
package:graphs 2.4.0-wip WIP (no publish necessary)
package:html 0.15.7-wip WIP (no publish necessary)
package:io 1.1.0-wip (error) pubspec version (1.1.0-wip) and changelog (1.2.0-wip) don't agree
package:json_rpc_2 4.1.0-wip WIP (no publish necessary)
package:markdown 7.3.1-wip WIP (no publish necessary)
package:mime 2.1.0-wip WIP (no publish necessary)
package:oauth2 2.0.5 already published at pub.dev
package:package_config 2.3.0-wip WIP (no publish necessary)
package:pool 1.5.2 already published at pub.dev
package:process 5.0.5 already published at pub.dev
package:pub_semver 2.2.0 already published at pub.dev
package:pubspec_parse 1.6.0-wip WIP (no publish necessary)
package:source_map_stack_trace 2.1.3-wip WIP (no publish necessary)
package:source_maps 0.10.14-wip WIP (no publish necessary)
package:source_span 1.10.2 already published at pub.dev
package:sse 4.1.8 already published at pub.dev
package:stack_trace 1.12.2-wip (error) pubspec version (1.12.2-wip) and changelog (1.12.2-dev) don't agree
package:stream_channel 2.1.4 already published at pub.dev
package:stream_transform 2.1.2-wip WIP (no publish necessary)
package:string_scanner 1.4.2-wip WIP (no publish necessary)
package:term_glyph 1.2.3-wip WIP (no publish necessary)
package:test_reflective_loader 0.5.0 ready to publish test_reflective_loader-v0.5.0
package:timing 1.0.2 already published at pub.dev
package:unified_analytics 8.0.11 already published at pub.dev
package:watcher 1.2.2-wip WIP (no publish necessary)
package:yaml 3.1.3 already published at pub.dev
package:yaml_edit 2.2.3 (error) pubspec version (2.2.3) and changelog (2.3.0) don't agree

Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 10, 2026

PR Health

License Headers ✔️
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
Files
no missing headers

All source files should start with a license header.

Unrelated files missing license headers
Files
pkgs/bazel_worker/benchmark/benchmark.dart
pkgs/benchmark_harness/integration_test/perf_benchmark_test.dart
pkgs/boolean_selector/example/example.dart
pkgs/clock/lib/clock.dart
pkgs/clock/lib/src/clock.dart
pkgs/clock/lib/src/default.dart
pkgs/clock/lib/src/stopwatch.dart
pkgs/clock/lib/src/utils.dart
pkgs/clock/test/clock_test.dart
pkgs/clock/test/default_test.dart
pkgs/clock/test/stopwatch_test.dart
pkgs/clock/test/utils.dart
pkgs/coverage/lib/src/coverage_options.dart
pkgs/html/example/main.dart
pkgs/html/lib/dom.dart
pkgs/html/lib/dom_parsing.dart
pkgs/html/lib/html_escape.dart
pkgs/html/lib/parser.dart
pkgs/html/lib/src/constants.dart
pkgs/html/lib/src/encoding_parser.dart
pkgs/html/lib/src/html_input_stream.dart
pkgs/html/lib/src/list_proxy.dart
pkgs/html/lib/src/query_selector.dart
pkgs/html/lib/src/token.dart
pkgs/html/lib/src/tokenizer.dart
pkgs/html/lib/src/treebuilder.dart
pkgs/html/lib/src/utils.dart
pkgs/html/test/dom_test.dart
pkgs/html/test/parser_feature_test.dart
pkgs/html/test/parser_test.dart
pkgs/html/test/query_selector_test.dart
pkgs/html/test/selectors/level1_baseline_test.dart
pkgs/html/test/selectors/level1_lib.dart
pkgs/html/test/selectors/selectors.dart
pkgs/html/test/support.dart
pkgs/html/test/tokenizer_test.dart
pkgs/html/test/trie_test.dart
pkgs/html/tool/generate_trie.dart
pkgs/pubspec_parse/test/git_uri_test.dart
pkgs/stack_trace/example/example.dart
pkgs/watcher/test/custom_watcher_factory_test.dart
pkgs/yaml_edit/example/example.dart

This check can be disabled by tagging the PR with skip-license-check.

API leaks ✔️

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

Package Leaked API symbol Leaking sources

This check can be disabled by tagging the PR with skip-leaking-check.

Breaking changes ✔️
Package Change Current Version New Version Needed Version Looking good?
io Non-Breaking 1.0.5 1.1.0-wip 1.1.0-wip ✔️

This check can be disabled by tagging the PR with skip-breaking-check.

Unused Dependencies ✔️
Package Status
io ✔️ All dependencies utilized correctly.

For details on how to fix these, see dependency_validator.

This check can be disabled by tagging the PR with skip-unused-dependencies-check.

Coverage ⚠️
File Coverage
pkgs/io/example/example.dart 💔 Not covered
pkgs/io/lib/src/ansi_code.dart 💔 82 % ⬇️ 3 %

This check for test coverage is informational (issues shown here will not fail the PR).

This check can be disabled by tagging the PR with skip-coverage-check.

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for 24-bit RGB colors for ANSI escape codes, which is a great enhancement. The implementation includes a new AnsiRgbCode class, a convenient rgb factory function, and updates to wrapWith to handle composite codes. The accompanying tests and example are helpful.

I've found a bug in the new example code that can lead to a runtime error, and I have a couple of suggestions for improving the code's maintainability in ansi_code.dart. Overall, this is a solid contribution.

final length = text.length;
final buffer = StringBuffer();
for (var i = 0; i < length; i++) {
final ratio = i / (length - 1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There's a potential division by zero here if text.length is 1. length - 1 would be 0, and i / (length - 1) would result in NaN. This leads to a runtime error when rgb() is called with NaN values because they cannot be assigned to int. You should handle this edge case.

Suggested change
final ratio = i / (length - 1);
final ratio = length > 1 ? i / (length - 1) : 0.0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or start with if (length == 0) return '';.

Comment thread pkgs/io/lib/src/ansi_code.dart Outdated
Comment on lines +143 to +148
@override
String get escape => '$_ansiEscapeLiteral[${codes.join(';')}m';

@override
String get escapeForScript => '$_ansiEscapeForScript[${codes.join(';')}m';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While these overrides are necessary with the current base class implementation, consider modifying the escape and escapeForScript getters in the AnsiCode base class to use codes.join(';'). This would make them work for both single and composite codes, and you could then remove these overrides, reducing code duplication and simplifying AnsiRgbCode.

Example change in AnsiCode:

// In AnsiCode
String get escape => '$_ansiEscapeLiteral[${codes.join(';')}m';

String get escapeForScript => '$_ansiEscapeForScript[${codes.join(';')}m';

Comment thread pkgs/io/lib/src/ansi_code.dart Outdated
break;
}
}
// myCodes.sort((a, b) => a.code.compareTo(b.code));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This commented-out line appears to be a remnant from a previous implementation. It should be removed to improve code clarity.

@natebosch natebosch changed the title rgb ansi [io] Add support for RGB ansi codes Feb 11, 2026
@natebosch natebosch requested a review from lrhn February 11, 2026 00:01
AnsiCodeType type = AnsiCodeType.foreground,
}) {
if (red < 0 || red > 255) {
throw ArgumentError.value(red, 'red', 'Must be between 0 and 255.');
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lrhn - would RangeError be preferred here?

Copy link
Copy Markdown
Member

@lrhn lrhn Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's relevant, so throw RangeError.range(red, 0, 255, 'red');.

And even use the helper function:

  RangeError.checkValueInInterval(red, 0, 255, 'red');
  RangeError.checkValueInInterval(green, 0, 255, 'green');
  RangeError.checkValueInInterval(blue, 0, 255, 'blue');

It's not important which Error subclass is used, the specialized ones are mainly there for easy reuse and consistent error messages. But that's reason enough to use it here too.

@natebosch
Copy link
Copy Markdown
Member Author

It might make sense to build this on top of #2297

String toString() => '$name ${type._name} ($code)';
}

/// An ANSI escape code for RGB colours.
Copy link
Copy Markdown
Member

@lrhn lrhn Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this class, or it being public.

Could all AnsiCode objects have a codes list, and then AnsiCode.rgb just fills it in.

class AnsiCode {
  /// The numeric value associated with this code.
  ///
  /// `-1` if this code is a composite code with multiple integer values.
  /// See [codes].
  final int code;

  /// The numeric values associated with this code.
  ///
  /// A composite code may have more than one integer value, in which case the
  /// [code] property will be `-1`.
  Iterable<int> get codes => _codes ??= [code];

  AnsiCode._rgb(int red, int green, int blue, AnsiCodeType type) : 
    code = -1,
    _codes = List.unmodifiable([
        type == AnsiCodeType.background ? 48 : 38, 
        2,
        RangeError.checkValueInInterval(red, 0, 255, 'red'),
        RangeError.checkValueInInterval(green, 0, 255, 'green'),
        RangeError.checkValueInInterval(blue, 0, 255, 'blue'),
     ]);

Does codes even need to be public?
Did code? Having code now sometimes being -1 and invalid is a breaking change if anyone uses it, but why would anyone use it?
(Maybe we should make a breaking change to make the class more opaque, then it's easier to extend it.)

/// Use [rgb] to create an instance of this class.
///
/// [See also](https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit)
class AnsiRgbCode extends AnsiCode {
Copy link
Copy Markdown
Member

@lrhn lrhn Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How well-supported is RGB colors in ANSI escapes these days?

I can see xterm and rxvt seem to supports all colors, so does the Windows terminal.
So probably fairly widely supported.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it has wide enough support to be worth landing.

@natebosch
Copy link
Copy Markdown
Member Author

It might make sense to build this on top of #2297

Will take another pass at this after we land the other changes. The sorting detail may need to be revisited but I think it may be OK to skip sorting and update test expectations.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants