-module(ansi).
% API
-export([style/1]).
-export([style/2]).
-export([clear/1]).
-export([cursor/1]).
-export([screen/1]).
-export([terminal/1]).
-export([detect_mode/0]).
-on_load(on_load/0).
-define(format(N, Format, Code),
format(Mode, Format) when Mode >= N -> Code;
format(_Mode, Format) -> []
).
%--- API -----------------------------------------------------------------------
style(Format) ->
case persistent_term:get(ansi_term, undefined) of
no_color ->
[];
Mode ->
case sequence(Mode, Format) of
[] -> [];
Sequence -> [control_sequence(Sequence), $m]
end
end.
style(Format, IOData) -> [style(Format), IOData, style(reset)].
clear(Format) -> control_sequence(clear_(Format)).
clear_(display) -> $J;
clear_(to_end_of_screen) -> "0J";
clear_(to_beginning_of_screen) -> "1J";
clear_(screen) -> "2J";
clear_(saved_lines) -> "3J";
clear_(in_line) -> $K;
clear_(end_of_line) -> "0K";
clear_(beginning_of_line) -> "1K";
clear_(line) -> "2K";
clear_(Format) -> error({invalid_erase, Format}).
cursor(Format) -> control_sequence(cursor_(Format)).
cursor_(home) ->
$H;
cursor_({Direction, 0}) when
Direction == up; Direction == down; Direction == left; Direction == right
->
[];
cursor_({up, Lines}) when Lines > 0 ->
[integer_to_list(Lines), $A];
cursor_({up, Lines}) when Lines < 0 ->
cursor_({down, abs(Lines)});
cursor_({down, Lines}) when Lines > 0 ->
[integer_to_list(Lines), $B];
cursor_({down, Lines}) when Lines < 0 ->
cursor_({up, abs(Lines)});
cursor_({left, Lines}) when Lines > 0 ->
[integer_to_list(Lines), $C];
cursor_({left, Lines}) when Lines < 0 ->
cursor_({right, abs(Lines)});
cursor_({right, Lines}) when Lines > 0 ->
[integer_to_list(Lines), $D];
cursor_({right, Lines}) when Lines < 0 ->
cursor_({left, abs(Lines)});
cursor_({Direction, beginning, 0}) when Direction == up; Direction == down ->
[];
cursor_({down, beginning, Lines}) when is_integer(Lines), Lines > 0 ->
[integer_to_list(Lines), $E];
cursor_({down, beginning, Lines}) when Lines < 0 ->
cursor_({up, beginning, abs(Lines)});
cursor_({up, beginning, Lines}) when is_integer(Lines), Lines > 0 ->
[integer_to_list(Lines), $F];
cursor_({up, beginning, Lines}) when Lines < 0 ->
cursor_({down, beginning, abs(Lines)});
cursor_({column, Column}) when is_integer(Column), Column > 0 ->
[integer_to_list(Column), $G];
cursor_({line, Line}) when is_integer(Line), Line > 0 ->
[integer_to_list(Line), $d];
cursor_({Line, Column}) when
is_integer(Line), is_integer(Column), Line > 0, Column > 0
->
[integer_to_list(Line), $;, integer_to_list(Column), $H];
cursor_(up_scroll) ->
$M;
cursor_(save) ->
$s;
cursor_(restore) ->
$u;
cursor_(invisible) ->
"?25l";
cursor_(visible) ->
"?25h";
cursor_(Format) ->
error({invalid_cursor, Format}).
screen(Format) -> control_sequence(screen_(Format)).
screen_(save) ->
"?47h";
screen_(restore) ->
"?47l";
screen_({alternate_buffer, enable}) ->
"?1049h";
screen_({alternate_buffer, disable}) ->
"?1049l";
screen_({scrolling_region, Top, Bottom}) when Top >= 1 ->
[integer_to_list(Top), $;, integer_to_list(Bottom), $r];
screen_({scrolling_region, reset}) ->
$r;
screen_(Format) ->
error({invalid_screen, Format}).
terminal(height) ->
{ok, Rows} = io:rows(),
Rows;
terminal(width) ->
{ok, Columns} = io:columns(),
Columns;
terminal(mode) ->
persistent_term:get(ansi_term, undefined);
terminal({mode, Mode}) when
Mode =:= 8; Mode =:= 16; Mode =:= 256; Mode == true; Mode == no_color
->
persistent_term:put(ansi_term, Mode);
terminal({mode, Mode}) ->
error({invalid_mode, Mode}).
% @private
detect_mode() ->
case terminal(mode) of
undefined ->
Mode =
case os:getenv("NO_COLOR") of
false ->
case os:getenv("COLORTERM") of
"truecolor" ->
true;
_Else ->
case tput_colors() of
N when N =:= 8; N =:= 16; N =:= 256 -> N;
N when N > 256 -> true;
false -> term_color(os:getenv("TERM"))
end
end;
_ ->
no_color
end,
terminal({mode, Mode});
_Mode ->
ok
end.
%--- Callbacks -----------------------------------------------------------------
on_load() -> detect_mode().
%--- Internal ------------------------------------------------------------------
control_sequence(Sequence) -> [$\e, $[, Sequence].
sequence(Mode, [Format]) ->
sequence(Mode, Format);
sequence(Mode, [Format | Rest]) ->
case format(Mode, Format) of
[] -> sequence(Mode, Rest);
Code -> [Code, $;, sequence(Mode, Rest)]
end;
sequence(Mode, Format) ->
format(Mode, Format).
% Modes
?format(8, bold, "1");
?format(8, dim, "2");
?format(8, faint, "2");
?format(8, italic, "3");
?format(8, underline, "4");
?format(8, blinking, "5");
?format(8, inverse, "7");
?format(8, reverse, "7");
?format(8, hidden, "8");
?format(8, invisible, "8");
?format(8, strikethrough, "9");
?format(8, reset_bold, "21");
?format(8, reset_dim, "22");
?format(8, reset_faint, "22");
?format(8, reset_italic, "23");
?format(8, reset_underline, "24");
?format(8, reset_blinking, "25");
?format(8, reset_inverse, "27");
?format(8, reset_reverse, "27");
?format(8, reset_hidden, "28");
?format(8, reset_invisible, "28");
?format(8, reset_strikethrough, "29");
?format(8, reset, $0);
% Foreground
?format(8, black, "30");
?format(8, red, "31");
?format(8, green, "32");
?format(8, yellow, "33");
?format(8, blue, "34");
?format(8, magenta, "35");
?format(8, cyan, "36");
?format(8, white, "37");
?format(8, default, "39");
% Background
?format(8, bg_black, "40");
?format(8, bg_red, "41");
?format(8, bg_green, "42");
?format(8, bg_yellow, "43");
?format(8, bg_blue, "44");
?format(8, bg_magenta, "45");
?format(8, bg_cyan, "46");
?format(8, bg_white, "47");
?format(8, bg_default, "49");
?format(16, bright_black, "90");
?format(16, bg_bright_black, "100");
?format(16, bright_red, "91");
?format(16, bg_bright_red, "101");
?format(16, bright_green, "92");
?format(16, bg_bright_green, "102");
?format(16, bright_yellow, "93");
?format(16, bg_bright_yellow, "103");
?format(16, bright_blue, "94");
?format(16, bg_bright_blue, "104");
?format(16, bright_magenta, "95");
?format(16, bg_bright_magenta, "105");
?format(16, bright_cyan, "96");
?format(16, bg_bright_cyan, "106");
?format(16, bright_white, "97");
?format(16, bg_bright_white, "107");
format(Mode, {fg, N}) when Mode >= 256, N =< 255 ->
["38;5;", integer_to_list(N)];
format(_Mode, {fg, N}) when N =< 255 ->
[];
format(Mode, {bg, N}) when Mode >= 256, N =< 255 ->
["48;5;", integer_to_list(N)];
format(_Mode, {bg, N}) when N =< 255 ->
[];
format(Mode, {fg, {R, G, B}}) when Mode >= true, R =< 255, G =< 255, B =< 255 ->
[
"38;2;",
integer_to_list(R),
$;,
integer_to_list(G),
$;,
integer_to_list(B)
];
format(_Mode, {fg, {R, G, B}}) when R =< 255, G =< 255, B =< 255 ->
[];
format(Mode, {bg, {R, G, B}}) when Mode >= true, R =< 255, G =< 255, B =< 255 ->
[
"48;2;",
integer_to_list(R),
$;,
integer_to_list(G),
$;,
integer_to_list(B)
];
format(_Mode, {bg, {R, G, B}}) when R =< 255, G =< 255, B =< 255 ->
[];
format(_Mode, bel) ->
7;
format(_Mode, Format) ->
error({invalid_style, Format}).
tput_colors() ->
case os:find_executable("tput") of
false ->
false;
_Tput ->
case string:to_integer(string:trim(os:cmd("tput colors"))) of
{Int, _Rest} -> Int;
_Error -> false
end
end.
term_color("screen-256color") ->
color_256;
term_color("xterm-kitty") ->
color_256;
term_color(_Term) ->
io:format("TERM: ~p~n", [_Term]),
no_color.