Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@garazdawi
Copy link
Contributor

This PR adds a new module called io_ansi that allows the user to emit Virtual Terminal Sequences (aka ansi sequences) to the terminal in order to add colors/styling to text, or create fullyfledged terminal applications.

io_ansi uses the local terminfo database in order to be as cross-platform compatibly as possible.

It also works across nodes so that if functions on a remote node calls io_ansi:fwrite/1 it will use the destination terminals terminfo database to determine which sequences to emit. In practice this means that you can call things in a remote shell session that uses io_ansi and it will properly detects that terminal sequences the target terminal can handle and will print using them correctly.

At the same time all at-hoc ANSI escape sequence usage in Erlang/OTP has been migrated to use io_ansi and the terminal application user's guide has been updated.

@garazdawi garazdawi added this to the OTP-29.0 milestone Jun 13, 2025
@garazdawi garazdawi requested review from dgud and frazze-jobb June 13, 2025 13:26
@garazdawi garazdawi self-assigned this Jun 13, 2025
@garazdawi garazdawi added team:VM Assigned to OTP team VM feature labels Jun 13, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Jun 13, 2025

CT Test Results

    5 files    288 suites   2h 58m 4s ⏱️
5 168 tests 4 817 ✅ 351 💤 0 ❌
6 589 runs  6 152 ✅ 437 💤 0 ❌

Results for commit a6e4754.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@garazdawi garazdawi force-pushed the lukas/stdlib/io_ansi branch from b17ffb1 to a6e4754 Compare June 17, 2025 08:27
@garazdawi garazdawi added the testing currently being tested, tag is used by OTP internal CI label Jun 17, 2025
Copy link
Contributor

@dgud dgud left a comment

Choose a reason for hiding this comment

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

Great work

Comment on lines +574 to +577
-doc """
Change background color to index color. `Index` 0-15 are equivilant to
the named colors in `t:background_color/0` in the order that they are listed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess you have already thought about this but why not have:
foreground/1 and background/1 accept color atoms directly?
Then you could remove all Color() and Color_background() functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, good point! we can definitely do that!

Comment on lines +877 to +879
?FUNCTION(negative_off).

-doc """
Copy link
Contributor

Choose a reason for hiding this comment

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

Similiar to previous comment, do we want a style(Style::atom()) function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in this case I don't think so as the style does not add any additional information while background/foreground does.

Comment on lines +1297 to +1298
?SPEC(cursor_report_position).
?FUNCTION(cursor_report_position).
Copy link
Contributor

Choose a reason for hiding this comment

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

Is a cursor_get_position() -> {Col:integer(), Row:integer()} feasible?
So I don't have to parse the ANSI codes, when debugging?
Or do I do that with one of the terminfo functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It might be possible. The problem I have found is that this function is rather slow, so I'm not sure it has any practical usecases... also there are no terminfo function for parsing stuff... so I'd have to do that myself.

Comment on lines +1301 to +1302
Activate the alternate screen.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a short description of what alternate screen do?
Most of the other stuff is self explanatory but this I would have to go to other docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do!

Comment on lines +1650 to +1656
[case Data of
<<Value:(byte_size(Value))/binary, Rest/binary>> ->
throw({Key, Value, Rest});
_ ->
ok
end || Key := Values <- get_vts_mappings(),
Value <- Values],
Copy link
Contributor

Choose a reason for hiding this comment

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

This code is un-parseable for me :-)
Re-write with a fun() in the map-comprehension or something?

@garazdawi garazdawi removed the testing currently being tested, tag is used by OTP internal CI label Jun 18, 2025
@garazdawi garazdawi assigned frazze-jobb and unassigned garazdawi Jun 18, 2025
@eproxus
Copy link
Contributor

eproxus commented Oct 9, 2025

Great to finally see ANSI support in Erlang! 🙌

API

In general, I think it would be nice if there was an API that is a bit more programmable. For example, consider the following hypothetical configuration:

#{
    background => blue,
    position => {3, down}
}

Code that uses this would have to be a bit convoluted:

render_background(#{background := blue}) ->
    io_ansi:blue_background();
render_background(#{background := red}) ->
    io_ansi:red_background();
render_background(#{background := green}) ->
    io_ansi:green_background();
% etc.

place_cursor(#{position := {N, down}}) ->
    io_ansi:cursor_down(N);
place_cursor(#{position := {N, up}}) ->
    io_ansi:cursor_down(N);
place_cursor(#{position := {N, forward}}) ->
    io_ansi:cursor_forward(N);
% etc.

or the unreadable apply(io_ansi, list_to_atom(atom_to_list(Color) ++ "_background", []).

A better API would perhaps be to parameterize as much of the different values as possible? E.g.:

  • io_ansi:foreground(Color) (or something shorter like fg and bg even?)
  • io_ansi:background(Color)
  • io_ansi:move_cursor(Steps, Direction)
  • io_ansi:scroll(forward)

One could even go further with the API in certain places to make it more command based:

  • io_ansi:cursor(show)
  • io_ansi:cursor(hide)
  • io_ansi:cursor({3, up})

Another feature I'd like to see is support for the widely accepted NO_COLOR environment variable, that disables ANSI colors completely if set.

Name

io_ansi is a tiny bit long. What about just ansi instead? ☺️

Additional Support

Reset

There are individual reset codes for many of the formatting options that are sometimes useful.

For example, \e[3m is italic and \e[23m is reset italic. With this, one can open and close individual formatting options that overlap (think overlapping HTML tags).

Formatting

Also, I don't see italic support in the module, that would be nice too 😉. There are a few additional formatting codes that are widely supported:

  • dim/faint \e[2m
  • italic \e[3m
  • hidden/invisible \e[8m
  • strikethrough \[e9m
  • double underline \e[21m

Screen State

One thing useful when building terminal UIs is the possibility to save/restore the screen state:

#!/bin/sh
tput smcup #save previous state
echo hello
sleep 3
tput rmcup #restore previous state

These are \e[?47h and \e[?47l respectively.

In addition, there are also the alternate screen buffer that can be activated (\e[?1049h and \e[?1049l).

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

Labels

feature team:VM Assigned to OTP team VM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants