11const std = @import ("std" );
22const builtin = @import ("builtin" );
33const os = std .os ;
4+ const fs = std .fs ;
45
56pub const ColorSupport = struct {
67 truecolor : bool = false ,
78 color256 : bool = false ,
89 basic : bool = false ,
910
11+ const TermInfo = struct {
12+ fn readTermInfo (term : []const u8 ) ! bool {
13+ if (term .len == 0 ) return false ;
14+
15+ var path_buf : [std .fs .max_path_bytes ]u8 = undefined ;
16+ const path = std .fmt .bufPrint (& path_buf , "/usr/share/terminfo/{c}/{s}" , .{ term [0 ], term [1.. ] }) catch return false ;
17+
18+ const file = fs .openFileAbsolute (path , .{ .mode = .read_only }) catch | err | switch (err ) {
19+ error .FileNotFound = > return false ,
20+ else = > | e | return e ,
21+ };
22+ defer file .close ();
23+
24+ var header_buf : [12 ]u8 = undefined ;
25+ if ((try file .readAll (& header_buf )) < 12 ) return false ;
26+ if (header_buf [0 ] != 0x1a and header_buf [1 ] != 0x01 ) return false ;
27+
28+ return (@as (u16 , @intCast (header_buf [10 ])) | (@as (u16 , @intCast (header_buf [11 ])) << 8 )) > 0 ;
29+ }
30+ };
31+
32+ fn parseColorTag (tag : []const u8 ) ? Color {
33+ return switch (tag [0 ]) {
34+ 'b' = > if (std .mem .eql (u8 , tag , "black" )) .black else if (std .mem .eql (u8 , tag , "blue" )) .blue else null ,
35+ 'r' = > if (std .mem .eql (u8 , tag , "red" )) .red else null ,
36+ 'g' = > if (std .mem .eql (u8 , tag , "green" )) .green else null ,
37+ 'y' = > if (std .mem .eql (u8 , tag , "yellow" )) .yellow else null ,
38+ 'm' = > if (std .mem .eql (u8 , tag , "magenta" )) .magenta else null ,
39+ 'c' = > if (std .mem .eql (u8 , tag , "cyan" )) .cyan else null ,
40+ 'w' = > if (std .mem .eql (u8 , tag , "white" )) .white else null ,
41+ else = > null ,
42+ };
43+ }
44+
45+ fn parseStyleTag (tag : []const u8 ) ? []const u8 {
46+ return switch (tag [0 ]) {
47+ 'b' = > if (std .mem .eql (u8 , tag , "bold" )) Style .bold else if (std .mem .eql (u8 , tag , "blink" )) Style .blink else null ,
48+ 'd' = > if (std .mem .eql (u8 , tag , "dim" )) Style .dim else null ,
49+ 'i' = > if (std .mem .eql (u8 , tag , "italic" )) Style .italic else null ,
50+ 'u' = > if (std .mem .eql (u8 , tag , "underline" )) Style .underline else null ,
51+ 'r' = > if (std .mem .eql (u8 , tag , "reverse" )) Style .reverse else null ,
52+ 'h' = > if (std .mem .eql (u8 , tag , "hidden" )) Style .hidden else null ,
53+ 's' = > if (std .mem .eql (u8 , tag , "strike" )) Style .strike else null ,
54+ else = > null ,
55+ };
56+ }
57+
58+ fn parseRgbTag (tag : []const u8 ) ! Rgb {
59+ if (! std .mem .startsWith (u8 , tag , "rgb(" ) or ! std .mem .endsWith (u8 , tag , ")" )) {
60+ return error .InvalidColorFormat ;
61+ }
62+
63+ const rgb_content = tag [4 .. tag .len - 1 ];
64+ var values : [3 ]u8 = undefined ;
65+ var value_idx : usize = 0 ;
66+ var num_start : usize = 0 ;
67+
68+ for (rgb_content , 0.. ) | c , i | {
69+ if (c == ',' or i == rgb_content .len - 1 ) {
70+ const num_end = if (i == rgb_content .len - 1 ) i + 1 else i ;
71+ const num_str = std .mem .trim (u8 , rgb_content [num_start .. num_end ], & std .ascii .whitespace );
72+ values [value_idx ] = std .fmt .parseInt (u8 , num_str , 10 ) catch return error .InvalidColorFormat ;
73+ value_idx += 1 ;
74+ if (value_idx > 2 ) break ;
75+ num_start = i + 1 ;
76+ }
77+ }
78+
79+ if (value_idx != 3 ) return error .InvalidColorFormat ;
80+ return Rgb .init (values [0 ], values [1 ], values [2 ]);
81+ }
82+
1083 pub fn init () ColorSupport {
1184 var self = ColorSupport {};
85+ const env = os .environ ;
86+
87+ for (env ) | entry | {
88+ const entry_str = std .mem .span (entry );
89+ if (std .mem .startsWith (u8 , entry_str , "NO_COLOR=" )) return self ;
90+ }
91+
92+ for (env ) | entry | {
93+ const entry_str = std .mem .span (entry );
94+ if (std .mem .startsWith (u8 , entry_str , "COLORTERM=" )) {
95+ const value = entry_str ["COLORTERM=" .len .. ];
96+ if (std .mem .eql (u8 , value , "truecolor" ) or std .mem .eql (u8 , value , "24bit" )) {
97+ self .truecolor = true ;
98+ self .basic = true ;
99+ }
100+ break ;
101+ }
102+ }
12103
13- if (std .process .getEnvVarOwned (std .heap .page_allocator , "COLORTERM" )) | colorterm | {
14- defer std .heap .page_allocator .free (colorterm );
15- self .truecolor = std .mem .eql (u8 , colorterm , "truecolor" ) or
16- std .mem .eql (u8 , colorterm , "24bit" );
17- } else | _ | {}
18-
19- if (std .process .getEnvVarOwned (std .heap .page_allocator , "TERM" )) | term | {
20- defer std .heap .page_allocator .free (term );
21- self .color256 = std .mem .indexOf (u8 , term , "256color" ) != null ;
22- self .basic = std .mem .indexOf (u8 , term , "color" ) != null or
23- self .color256 or self .truecolor ;
24- } else | _ | {}
25-
26- if (std .process .getEnvVarOwned (std .heap .page_allocator , "NO_COLOR" )) | _ | {
27- self .truecolor = false ;
28- self .color256 = false ;
29- self .basic = false ;
30- } else | _ | {}
104+ for (env ) | entry | {
105+ const entry_str = std .mem .span (entry );
106+ if (std .mem .startsWith (u8 , entry_str , "TERM=" )) {
107+ const term = entry_str ["TERM=" .len .. ];
108+ if (TermInfo .readTermInfo (term )) | has_colors | {
109+ if (has_colors ) {
110+ self .basic = true ;
111+ self .color256 = std .mem .indexOf (u8 , term , "256color" ) != null ;
112+ }
113+ } else | _ | {
114+ self .color256 = std .mem .indexOf (u8 , term , "256color" ) != null ;
115+ if (self .color256 ) self .basic = true ;
116+ if (! self .basic ) {
117+ self .basic = std .mem .indexOf (u8 , term , "color" ) != null or self .truecolor ;
118+ }
119+ }
120+ break ;
121+ }
122+ }
31123
32124 return self ;
33125 }
126+
127+ pub fn formatText (self : * const ColorSupport , text : []const u8 , writer : anytype ) ! void {
128+ var i : usize = 0 ;
129+ while (i < text .len ) : (i += 1 ) {
130+ if (text [i ] == '{' ) {
131+ const end_idx = std .mem .indexOfScalarPos (u8 , text , i , '}' ) orelse break ;
132+ const tag = text [i + 1 .. end_idx ];
133+
134+ if (std .mem .startsWith (u8 , tag , "/" )) {
135+ try writer .writeAll ("\x1b [0m" );
136+ i = end_idx ;
137+ continue ;
138+ }
139+
140+ if (std .mem .startsWith (u8 , tag , "rgb(" )) {
141+ if (self .truecolor ) {
142+ const rgb = ColorSupport .parseRgbTag (tag ) catch continue ;
143+ try writer .print ("\x1b [38;2;{};{};{}m" , .{ rgb .r , rgb .g , rgb .b });
144+ }
145+ } else if (ColorSupport .parseColorTag (tag )) | c | {
146+ if (self .basic ) {
147+ try writer .writeAll (c .ansiSequence ());
148+ }
149+ } else if (ColorSupport .parseStyleTag (tag )) | s | {
150+ try writer .writeAll (s );
151+ } else {
152+ try writer .writeAll (text [i .. end_idx + 1 ]);
153+ }
154+ i = end_idx ;
155+ } else {
156+ try writer .writeByte (text [i ]);
157+ }
158+ }
159+ }
34160};
35161
36162pub const Rgb = struct {
@@ -70,14 +196,7 @@ pub const Color = enum(u8) {
70196 white = 37 ,
71197 reset = 0 ,
72198
73- pub inline fn bright (self : Color ) u8 {
74- return switch (self ) {
75- .reset = > 0 ,
76- else = > @intFromEnum (self ) + 60 ,
77- };
78- }
79-
80- pub inline fn ansiSequence (self : Color ) []const u8 {
199+ pub fn ansiSequence (self : Color ) []const u8 {
81200 return switch (self ) {
82201 .black = > "\x1b [30m" ,
83202 .red = > "\x1b [31m" ,
@@ -90,33 +209,6 @@ pub const Color = enum(u8) {
90209 .reset = > "\x1b [0m" ,
91210 };
92211 }
93-
94- pub inline fn format (
95- self : Color ,
96- comptime fmt : []const u8 ,
97- options : std.fmt.FormatOptions ,
98- writer : anytype ,
99- ) ! void {
100- _ = fmt ;
101- _ = options ;
102- try writer .writeAll (self .ansiSequence ());
103- }
104-
105- pub inline fn fg (self : Color ) Formatter {
106- return .{ .kind = .basic_fg , .code = @intFromEnum (self ) };
107- }
108-
109- pub inline fn bg (self : Color ) Formatter {
110- return .{ .kind = .basic_bg , .code = @intFromEnum (self ) + 10 };
111- }
112-
113- pub inline fn bright_fg (self : Color ) Formatter {
114- return .{ .kind = .basic_fg , .code = self .bright () };
115- }
116-
117- pub inline fn bright_bg (self : Color ) Formatter {
118- return .{ .kind = .basic_bg , .code = self .bright () + 10 };
119- }
120212};
121213
122214const AnsiBuffer = struct {
@@ -213,12 +305,12 @@ pub const Style = struct {
213305 }
214306};
215307
308+ // Initialize color support at runtime
216309var color_support : ? ColorSupport = null ;
217310
218311pub fn getColorSupport () ColorSupport {
219- if (color_support ) | cs | {
220- return cs ;
221- }
222- color_support = ColorSupport .init ();
223- return color_support .? ;
312+ return color_support orelse {
313+ color_support = ColorSupport .init ();
314+ return color_support .? ;
315+ };
224316}
0 commit comments