1 /** ANSI escape codes and sequences.
2  *
3  * See_Also: https://en.wikipedia.org/wiki/ANSI_escape_code
4  */
5 module nxt.ansi_escape;
6 
7 public import nxt.color : ColorRGB8;
8 
9 @safe:
10 
11 /** Visual attributes.
12  */
13 struct Attrs
14 {
15 @safe:
16     immutable(SGR)[] sgrs;      ///< Ordered set of SGR, typically initialized from `static immutable(SGR)[]`.
17     ColorRGB8 foregroundColor;  ///< Foreground color.
18     ColorRGB8 backgroundColor;  ///< Background color.
19     bool useForegroundColor;    ///< Indicate if 'foregroundColor is to be used.
20     bool useBackgroundColor;    ///< Indicate if 'backgroundColor is to be used.
21 
22     void set(scope void delegate(scope const(char)[]) @safe sink)
23     {
24         setSGRs(sink, sgrs);
25         if (useForegroundColor)
26             setForegroundColorRGB8(sink, foregroundColor);
27         if (useBackgroundColor)
28             setBackgroundColorRGB8(sink, backgroundColor);
29     }
30 
31     void reset(scope void delegate(scope const(char)[]) @safe sink)
32     {
33         resetSGRs(sink);
34     }
35 }
36 
37 /** SGR (Select Graphic Rendition) sets display attributes.
38  *
39  * See_Also: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
40  */
41 enum SGR : uint
42 {
43     init         = 0,              ///< Default.
44     bold         = 1,              ///< Bold or increased intensity.
45     faint        = 2,              ///< Faint (decreased intensity)
46     italic       = 3,              ///< Italic. Not widely supported. Sometimes treated as inverse.
47     underline    = 4,              ///< Underline.
48     slowBlink    = 5,              ///< Slow blink.
49     rapidBlink   = 6,              ///< Rapid blink.
50     reverseVideo = 7,              ///< Reversed video (swap). Swap foreground with background color.
51     hide         = 8,              ///< Conceal (Hide). Not widely supported.
52     crossedOut   = 9,              ///< Crossed-out. Characters legible, but marked for deletion.
53     primaryDefaultFont = 10,       ///< Primary (default) font.
54     fraktur      = 20,             ///< Fraktur. Rarely supported
55 
56     blackForegroundColor   = 30,  ///< Black foreground color.
57     redForegroundColor     = 31,  ///< Red foreground color.
58     greenForegroundColor   = 32,  ///< Green foreground color.
59     yellowForegroundColor  = 33,  ///< Yellow foreground color.
60     blueForegroundColor    = 34,  ///< Blue foreground color.
61     magentaForegroundColor = 35,  ///< Magenta foreground color.
62     cyanForegroundColor    = 36,  ///< Cyan foreground color.
63     whiteForegroundColor   = 37,  ///< White foreground color.
64 
65     defaultForegroundColor = 39,  ///< Default foreground color.
66 
67     lightBlackForegroundColor   = 90, ///< Light black foreground color.
68     lightRedForegroundColor     = 91, ///< Light red foreground color.
69     lightGreenForegroundColor   = 92, ///< Light green foreground color.
70     lightYellowForegroundColor  = 93, ///< Light yellow foreground color.
71     lightBlueForegroundColor    = 94, ///< Light blue foreground color.
72     lightMagentaForegroundColor = 95, ///< Light magenta foreground color.
73     lightCyanForegroundColor    = 96, ///< Light cyan foreground color.
74     lightWhiteForegroundColor   = 97, ///< Light white foreground color.
75 
76     defaultBackgroundColor = 49, ///< Default background color.
77 
78     framed       = 51,             ///< Framed.
79     encircled    = 52,             ///< Encircled.
80     overlined    = 53,             ///< Overlined.
81     notFramedOrEncircled = 54,     ///< Not framed or encircled.
82     notOverlined = 55,             ///< Not overlined.
83     IdeogramUnderlineOrRightSideLine = 60, ///< Ideogram underline or right side line.
84 }
85 
86 private void setSGR(scope void delegate(scope const(char)[]) @safe sink,
87                     const SGR sgr)
88 {
89     final switch (sgr)
90     {
91         static foreach (member; __traits(allMembers, SGR))
92         {
93         case __traits(getMember, SGR, member):
94             enum _ = cast(int)__traits(getMember, SGR, member); // avoids `std.conv.to`
95             sink(_.stringof);
96             return;
97         }
98     }
99 }
100 
101 void setSGRs(scope void delegate(scope const(char)[]) @safe sink,
102              scope const SGR[] sgrs...) @safe
103 {
104     sink("\033[");
105     const n = sgrs.length;
106     foreach (const index, const sgr; sgrs)
107     {
108         setSGR(sink, sgr);      // needs to be first
109         if (index != n - 1)     // if not last
110             sink(";");          // separator
111     }
112     sink("m");
113 }
114 
115 void resetSGRs(scope void delegate(scope const(char)[]) @safe sink)
116 {
117     sink("\033[0m");
118 }
119 
120 void putWithSGRs(scope void delegate(scope const(char)[]) @safe sink,
121                  scope const(char)[] text,
122                  scope const SGR[] sgrs...) @safe
123 {
124     setSGRs(sink, sgrs);        // set
125     sink(text);
126     resetSGRs(sink);            // reset
127 }
128 
129 /** Set foreground color to `rgb`.
130  *
131  * See_Also: https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit
132  */
133 void setForegroundColorRGB8(scope void delegate(scope const(char)[]) @safe sink,
134                             const ColorRGB8 rgb) @safe
135 {
136     sink("\033[38;2;");
137     setColorRGB8Component(sink, rgb.redC);
138     sink(";");
139     setColorRGB8Component(sink, rgb.greenC);
140     sink(";");
141     setColorRGB8Component(sink, rgb.blueC);
142     sink("m");
143 }
144 
145 /** Set background color to `rgb`.
146  *
147  * See_Also: https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit
148  */
149 void setBackgroundColorRGB8(scope void delegate(scope const(char)[]) @safe sink,
150                             const ColorRGB8 rgb) @safe
151 {
152     sink("\033[48;2;");
153     setColorRGB8Component(sink, rgb.redC);
154     sink(";");
155     setColorRGB8Component(sink, rgb.greenC);
156     sink(";");
157     setColorRGB8Component(sink, rgb.blueC);
158     sink("m");
159 }
160 
161 /** Set RGB 24-bit color component `component`.
162  *
163  * See_Also: https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit
164  */
165 static private void setColorRGB8Component(scope void delegate(scope const(char)[]) @safe sink,
166                                           const ubyte component) @safe
167 {
168     final switch (component)
169     {
170         static foreach (value; 0 .. 256)
171         {
172         case value:
173             sink(value.stringof); // avoids `std.conv.to`
174             return;
175         }
176     }
177 }
178 
179 version(none):
180 
181 version(unittest)
182 class C
183 {
184 @safe:
185     @property void toString(scope void delegate(scope const(char)[]) @safe sink) const @trusted
186     {
187         putWithSGRs(sink, "XXX", SGR.yellowForegroundColor);
188     }
189     this() {}
190 }
191 
192 @safe unittest
193 {
194     import std.stdio : writeln;
195     import std.conv : to;
196     auto c = new C();
197     writeln(c.to!string);
198 }