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