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