libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
ansistream.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
13#include <cstring>
14#include <promeki/namespace.h>
15#include <promeki/string.h>
16#include <promeki/util.h>
17#include <promeki/color.h>
18#include <promeki/terminal.h>
19
20PROMEKI_NAMESPACE_BEGIN
21
22class IODevice;
23
31class AnsiStream {
32 public:
36 enum TextStyle {
37 Bold = 1,
38 Dim = 2,
39 Italic = 3,
40 Underlined = 4,
41 Blink = 5,
42 Inverted = 7,
43 Hidden = 8,
44 Strikethrough = 9
45 };
46
53 enum MouseTracking {
54 MouseButtons,
55 MouseDrag,
56 MouseAny
57 };
58
65 enum CursorStyle {
66 DefaultCursor = 0,
67 BlinkingBlock = 1,
68 SteadyBlock = 2,
69 BlinkingUnderline = 3,
70 SteadyUnderline = 4,
71 BlinkingBar = 5,
72 SteadyBar = 6
73 };
74
89 enum AnsiColor : uint8_t {
90 // -- System colors (0-15) --
91 Black = 0,
92 Maroon = 1,
93 DarkRed = 1,
94 Green = 2,
95 DarkGreen = 2,
96 Olive = 3,
97 DarkYellow = 3,
98 Navy = 4,
99 DarkBlue = 4,
100 Purple = 5,
101 DarkMagenta = 5,
102 Teal = 6,
103 DarkCyan = 6,
104 Silver = 7,
105 LightGray = 7,
106 Grey = 8,
107 DarkGray = 8,
108 Red = 9,
109 Lime = 10,
110 Yellow = 11,
111 Blue = 12,
112 Fuchsia = 13,
113 Magenta = 13,
114 Aqua = 14,
115 Cyan = 14,
116 White = 15,
117
118 // -- Color cube (16-231) --
119 Grey0 = 16,
120 NavyBlue = 17,
121 DarkBlue_18 = 18,
122 Blue3_19 = 19,
123 Blue3_20 = 20,
124 Blue1 = 21,
125 DarkGreen_22 = 22,
126 DeepSkyBlue4_23 = 23,
127 DeepSkyBlue4_24 = 24,
128 DeepSkyBlue4_25 = 25,
129 DodgerBlue3 = 26,
130 DodgerBlue2 = 27,
131 Green4 = 28,
132 SpringGreen4 = 29,
133 Turquoise4 = 30,
134 DeepSkyBlue3_31 = 31,
135 DeepSkyBlue3_32 = 32,
136 DodgerBlue1 = 33,
137 Green3_34 = 34,
138 SpringGreen3_35 = 35,
139 DarkCyan_36 = 36,
140 LightSeaGreen = 37,
141 DeepSkyBlue2 = 38,
142 DeepSkyBlue1 = 39,
143 Green3_40 = 40,
144 SpringGreen3_41 = 41,
145 SpringGreen2_42 = 42,
146 Cyan3 = 43,
147 DarkTurquoise = 44,
148 Turquoise2 = 45,
149 Green1 = 46,
150 SpringGreen2_47 = 47,
151 SpringGreen1 = 48,
152 MediumSpringGreen = 49,
153 Cyan2 = 50,
154 Cyan1 = 51,
155 DarkRed_52 = 52,
156 DeepPink4_53 = 53,
157 Purple4_54 = 54,
158 Purple4_55 = 55,
159 Purple3 = 56,
160 BlueViolet = 57,
161 Orange4_58 = 58,
162 Grey37 = 59,
163 MediumPurple4 = 60,
164 SlateBlue3_61 = 61,
165 SlateBlue3_62 = 62,
166 RoyalBlue1 = 63,
167 Chartreuse4 = 64,
168 DarkSeaGreen4_65 = 65,
169 PaleTurquoise4 = 66,
170 SteelBlue = 67,
171 SteelBlue3 = 68,
172 CornflowerBlue = 69,
173 Chartreuse3_70 = 70,
174 DarkSeaGreen4_71 = 71,
175 CadetBlue_72 = 72,
176 CadetBlue_73 = 73,
177 SkyBlue3 = 74,
178 SteelBlue1_75 = 75,
179 Chartreuse3_76 = 76,
180 PaleGreen3_77 = 77,
181 SeaGreen3 = 78,
182 Aquamarine3 = 79,
183 MediumTurquoise = 80,
184 SteelBlue1_81 = 81,
185 Chartreuse2_82 = 82,
186 SeaGreen2 = 83,
187 SeaGreen1_84 = 84,
188 SeaGreen1_85 = 85,
189 Aquamarine1_86 = 86,
190 DarkSlateGray2 = 87,
191 DarkRed_88 = 88,
192 DeepPink4_89 = 89,
193 DarkMagenta_90 = 90,
194 DarkMagenta_91 = 91,
195 DarkViolet_92 = 92,
196 Purple_93 = 93,
197 Orange4_94 = 94,
198 LightPink4 = 95,
199 Plum4 = 96,
200 MediumPurple3_97 = 97,
201 MediumPurple3_98 = 98,
202 SlateBlue1 = 99,
203 Yellow4_100 = 100,
204 Wheat4 = 101,
205 Grey53 = 102,
206 LightSlateGrey = 103,
207 MediumPurple = 104,
208 LightSlateBlue = 105,
209 Yellow4_106 = 106,
210 DarkOliveGreen3_107 = 107,
211 DarkSeaGreen = 108,
212 LightSkyBlue3_109 = 109,
213 LightSkyBlue3_110 = 110,
214 SkyBlue2 = 111,
215 Chartreuse2_112 = 112,
216 DarkOliveGreen3_113 = 113,
217 PaleGreen3_114 = 114,
218 DarkSeaGreen3_115 = 115,
219 DarkSlateGray3 = 116,
220 SkyBlue1 = 117,
221 Chartreuse1 = 118,
222 LightGreen_119 = 119,
223 LightGreen_120 = 120,
224 PaleGreen1_121 = 121,
225 Aquamarine1_122 = 122,
226 DarkSlateGray1 = 123,
227 Red3_124 = 124,
228 DeepPink4_125 = 125,
229 MediumVioletRed = 126,
230 Magenta3_127 = 127,
231 DarkViolet_128 = 128,
232 Purple_129 = 129,
233 DarkOrange3_130 = 130,
234 IndianRed_131 = 131,
235 HotPink3_132 = 132,
236 MediumOrchid3 = 133,
237 MediumOrchid = 134,
238 MediumPurple2_135 = 135,
239 DarkGoldenrod = 136,
240 LightSalmon3_137 = 137,
241 RosyBrown = 138,
242 Grey63 = 139,
243 MediumPurple2_140 = 140,
244 MediumPurple1 = 141,
245 Gold3_142 = 142,
246 DarkKhaki = 143,
247 NavajoWhite3 = 144,
248 Grey69 = 145,
249 LightSteelBlue3 = 146,
250 LightSteelBlue = 147,
251 Yellow3_148 = 148,
252 DarkOliveGreen3_149 = 149,
253 DarkSeaGreen3_150 = 150,
254 DarkSeaGreen2_151 = 151,
255 LightCyan3 = 152,
256 LightSkyBlue1 = 153,
257 GreenYellow = 154,
258 DarkOliveGreen2 = 155,
259 PaleGreen1_156 = 156,
260 DarkSeaGreen2_157 = 157,
261 DarkSeaGreen1_158 = 158,
262 PaleTurquoise1 = 159,
263 Red3_160 = 160,
264 DeepPink3_161 = 161,
265 DeepPink3_162 = 162,
266 Magenta3_163 = 163,
267 Magenta3_164 = 164,
268 Magenta2_165 = 165,
269 DarkOrange3_166 = 166,
270 IndianRed_167 = 167,
271 HotPink3_168 = 168,
272 HotPink2 = 169,
273 Orchid = 170,
274 MediumOrchid1_171 = 171,
275 Orange3 = 172,
276 LightSalmon3_173 = 173,
277 LightPink3 = 174,
278 Pink3 = 175,
279 Plum3 = 176,
280 Violet = 177,
281 Gold3_178 = 178,
282 LightGoldenrod3 = 179,
283 Tan = 180,
284 MistyRose3 = 181,
285 Thistle3 = 182,
286 Plum2 = 183,
287 Yellow3_184 = 184,
288 Khaki3 = 185,
289 LightGoldenrod2_186 = 186,
290 LightYellow3 = 187,
291 Grey84 = 188,
292 LightSteelBlue1 = 189,
293 Yellow2 = 190,
294 DarkOliveGreen1_191 = 191,
295 DarkOliveGreen1_192 = 192,
296 DarkSeaGreen1_193 = 193,
297 Honeydew2 = 194,
298 LightCyan1 = 195,
299 Red1 = 196,
300 DeepPink2 = 197,
301 DeepPink1_198 = 198,
302 DeepPink1_199 = 199,
303 Magenta2_200 = 200,
304 Magenta1 = 201,
305 OrangeRed1 = 202,
306 IndianRed1_203 = 203,
307 IndianRed1_204 = 204,
308 HotPink_205 = 205,
309 HotPink_206 = 206,
310 MediumOrchid1_207 = 207,
311 DarkOrange = 208,
312 Salmon1 = 209,
313 LightCoral = 210,
314 PaleVioletRed1 = 211,
315 Orchid2 = 212,
316 Orchid1 = 213,
317 Orange1 = 214,
318 SandyBrown = 215,
319 LightSalmon1 = 216,
320 LightPink1 = 217,
321 Pink1 = 218,
322 Plum1 = 219,
323 Gold1 = 220,
324 LightGoldenrod2_221 = 221,
325 LightGoldenrod2_222 = 222,
326 NavajoWhite1 = 223,
327 MistyRose1 = 224,
328 Thistle1 = 225,
329 Yellow1 = 226,
330 LightGoldenrod1 = 227,
331 Khaki1 = 228,
332 Wheat1 = 229,
333 Cornsilk1 = 230,
334 Grey100 = 231,
335
336 // -- Grayscale ramp (232-255) --
337 Grey3 = 232,
338 Grey7 = 233,
339 Grey11 = 234,
340 Grey15 = 235,
341 Grey19 = 236,
342 Grey23 = 237,
343 Grey27 = 238,
344 Grey30 = 239,
345 Grey35 = 240,
346 Grey39 = 241,
347 Grey42 = 242,
348 Grey46 = 243,
349 Grey50 = 244,
350 Grey54 = 245,
351 Grey58 = 246,
352 Grey62 = 247,
353 Grey66 = 248,
354 Grey70 = 249,
355 Grey74 = 250,
356 Grey78 = 251,
357 Grey82 = 252,
358 Grey85 = 253,
359 Grey89 = 254,
360 Grey93 = 255
361 };
362
368 static Color ansiColor(int index);
369
375 static Color ansiColor(AnsiColor color) { return ansiColor(static_cast<int>(color)); }
376
389 static AnsiColor findClosestAnsiColor(const Color &color, int maxIndex = 255);
390
391 // ---------------------------------------------------------------
392 // Static escape-sequence builders.
393 //
394 // These produce the raw escape bytes as a String without needing
395 // an IODevice, so code paths that compose styled text into a
396 // buffer (the Logger console formatter, TUI cell flushing, help
397 // text colorizers) can share the single source of truth instead
398 // of hand-rolling "\033[...m" literals. The instance methods
399 // below are thin wrappers that write() these sequences.
400 // ---------------------------------------------------------------
401
406 static String resetSeq();
407
415 static String styleSeq(TextStyle style, bool enable = true);
416
418 static String foregroundSeq(AnsiColor color);
420 static String backgroundSeq(AnsiColor color);
422 static String foreground256Seq(uint8_t index);
424 static String background256Seq(uint8_t index);
426 static String foregroundRGBSeq(uint8_t r, uint8_t g, uint8_t b);
428 static String backgroundRGBSeq(uint8_t r, uint8_t g, uint8_t b);
429
444 static String foregroundSeq(const Color &color, Terminal::ColorSupport support);
446 static String backgroundSeq(const Color &color, Terminal::ColorSupport support);
447
454 static String hyperlinkSeq(const String &url, const String &text);
455
462 static bool stdoutWindowSize(int &rows, int &cols);
463
468 static bool stdoutSupportsANSI();
469
474 static bool stderrSupportsANSI();
475
480 AnsiStream(IODevice *device) : _device(device), _enabled(true) {}
481
488 void setAnsiEnabled(bool val) {
489 _enabled = val;
490 return;
491 }
492
497 IODevice *device() const { return _device; }
498
504 AnsiStream &write(const String &text);
505
511 AnsiStream &write(const char *text);
512
518 AnsiStream &write(char ch);
519
525 AnsiStream &write(int val);
526
532 AnsiStream &write(int64_t val);
533
542 AnsiStream &write(uint64_t val);
543
549 AnsiStream &write(double val);
550
554 void flush();
555
557 AnsiStream &operator<<(const String &text) { return write(text); }
559 AnsiStream &operator<<(const char *text) { return write(text); }
561 AnsiStream &operator<<(char ch) { return write(ch); }
563 AnsiStream &operator<<(int val) { return write(val); }
565 AnsiStream &operator<<(int64_t val) { return write(val); }
567 AnsiStream &operator<<(uint64_t val) { return write(val); }
569 AnsiStream &operator<<(double val) { return write(val); }
570
581 AnsiStream &setForeground(AnsiColor color);
582
593 AnsiStream &setBackground(AnsiColor color);
594
601 AnsiStream &setForeground(const Color &color, int maxIndex);
602
609 AnsiStream &setBackground(const Color &color, int maxIndex);
610
624 AnsiStream &setForeground(const Color &color, Terminal::ColorSupport support);
625
627 AnsiStream &setBackground(const Color &color, Terminal::ColorSupport support);
628
636 AnsiStream &setForeground(const Color &color) {
637 return setForeground(color, Terminal::colorSupport());
638 }
639
647 AnsiStream &setBackground(const Color &color) {
648 return setBackground(color, Terminal::colorSupport());
649 }
650
658 AnsiStream &setStyle(TextStyle style, bool enable = true) {
659 if (!_enabled) return *this;
660 return write(styleSeq(style, enable));
661 }
662
668 AnsiStream &cursorUp(int n) {
669 if (!_enabled) return *this;
670 *this << "\033[" << n << "A";
671 return *this;
672 }
673
679 AnsiStream &cursorDown(int n) {
680 if (!_enabled) return *this;
681 *this << "\033[" << n << "B";
682 return *this;
683 }
684
690 AnsiStream &cursorRight(int n) {
691 if (!_enabled) return *this;
692 *this << "\033[" << n << "C";
693 return *this;
694 }
695
701 AnsiStream &cursorLeft(int n) {
702 if (!_enabled) return *this;
703 *this << "\033[" << n << "D";
704 return *this;
705 }
706
712 AnsiStream &cursorNextLine(int n) {
713 if (!_enabled) return *this;
714 *this << "\033[" << n << "E";
715 return *this;
716 }
717
723 AnsiStream &cursorPrevLine(int n) {
724 if (!_enabled) return *this;
725 *this << "\033[" << n << "F";
726 return *this;
727 }
728
734 AnsiStream &setCursorColumn(int c) {
735 if (!_enabled) return *this;
736 *this << "\033[" << c << "G";
737 return *this;
738 }
739
744 AnsiStream &cursorHome() {
745 if (!_enabled) return *this;
746 *this << "\033[H";
747 return *this;
748 }
749
755 AnsiStream &setCursorStyle(CursorStyle style) {
756 if (!_enabled) return *this;
757 *this << "\033[" << static_cast<int>(style) << " q";
758 return *this;
759 }
760
767 AnsiStream &setCursorPosition(int r, int c) {
768 if (!_enabled) return *this;
769 *this << "\033[" << r << ";" << c << "H";
770 return *this;
771 }
772
777 AnsiStream &clearScreen() {
778 if (!_enabled) return *this;
779 *this << "\033[2J";
780 return *this;
781 }
782
787 AnsiStream &clearScreenAndHome() {
788 if (!_enabled) return *this;
789 *this << "\033[2J\033[H";
790 return *this;
791 }
792
797 AnsiStream &clearScreenBeforeCursor() {
798 if (!_enabled) return *this;
799 *this << "\033[1J";
800 return *this;
801 }
802
807 AnsiStream &clearScreenAfterCursor() {
808 if (!_enabled) return *this;
809 *this << "\033[0J";
810 return *this;
811 }
812
817 AnsiStream &clearScrollback() {
818 if (!_enabled) return *this;
819 *this << "\033[3J";
820 return *this;
821 }
822
828 AnsiStream &insertLines(int n) {
829 if (!_enabled) return *this;
830 *this << "\033[" << n << "L";
831 return *this;
832 }
833
839 AnsiStream &deleteLines(int n) {
840 if (!_enabled) return *this;
841 *this << "\033[" << n << "M";
842 return *this;
843 }
844
850 AnsiStream &insertCharacters(int n) {
851 if (!_enabled) return *this;
852 *this << "\033[" << n << "@";
853 return *this;
854 }
855
861 AnsiStream &deleteCharacters(int n) {
862 if (!_enabled) return *this;
863 *this << "\033[" << n << "P";
864 return *this;
865 }
866
871 AnsiStream &moveToStartOfLine() {
872 if (!_enabled) return *this;
873 *this << "\033[0G";
874 return *this;
875 }
876
881 AnsiStream &moveToEndOfLine() {
882 if (!_enabled) return *this;
883 *this << "\033[999G";
884 return *this;
885 }
886
891 AnsiStream &clearLine() {
892 if (!_enabled) return *this;
893 *this << "\033[2K";
894 return *this;
895 }
896
901 AnsiStream &clearLineBeforeCursor() {
902 if (!_enabled) return *this;
903 *this << "\033[1K";
904 return *this;
905 }
906
911 AnsiStream &clearLineAfterCursor() {
912 if (!_enabled) return *this;
913 *this << "\033[0K";
914 return *this;
915 }
916
921 AnsiStream &reset() {
922 if (!_enabled) return *this;
923 *this << "\033[0m";
924 return *this;
925 }
926
931 AnsiStream &resetForeground() {
932 if (!_enabled) return *this;
933 *this << "\033[39m";
934 return *this;
935 }
936
941 AnsiStream &resetBackground() {
942 if (!_enabled) return *this;
943 *this << "\033[49m";
944 return *this;
945 }
946
951 AnsiStream &showCursor() {
952 if (!_enabled) return *this;
953 *this << "\033[?25h";
954 return *this;
955 }
956
961 AnsiStream &hideCursor() {
962 if (!_enabled) return *this;
963 *this << "\033[?25l";
964 return *this;
965 }
966
971 AnsiStream &saveCursorPosition() {
972 if (!_enabled) return *this;
973 *this << "\033[s";
974 return *this;
975 }
976
982 AnsiStream &restoreCursorPosition() {
983 if (!_enabled) return *this;
984 *this << "\033[u";
985 return *this;
986 }
987
994 AnsiStream &enableScrollingRegion(int startRow, int endRow) {
995 if (!_enabled) return *this;
996 *this << "\033[" << startRow << ";" << endRow << "r";
997 return *this;
998 }
999
1004 AnsiStream &resetScrollingRegion() {
1005 if (!_enabled) return *this;
1006 *this << "\033[r";
1007 return *this;
1008 }
1009
1015 AnsiStream &scrollUp(int n) {
1016 if (!_enabled) return *this;
1017 *this << "\033[" << n << "S";
1018 return *this;
1019 }
1020
1026 AnsiStream &scrollDown(int n) {
1027 if (!_enabled) return *this;
1028 *this << "\033[" << n << "T";
1029 return *this;
1030 }
1031
1037 AnsiStream &eraseCharacters(int n) {
1038 if (!_enabled) return *this;
1039 *this << "\033[" << n << "X";
1040 return *this;
1041 }
1042
1053 AnsiStream &setForeground256(uint8_t index) {
1054 if (!_enabled) return *this;
1055 return write(foreground256Seq(index));
1056 }
1057
1068 AnsiStream &setBackground256(uint8_t index) {
1069 if (!_enabled) return *this;
1070 return write(background256Seq(index));
1071 }
1072
1080 AnsiStream &setForegroundRGB(uint8_t r, uint8_t g, uint8_t b) {
1081 if (!_enabled) return *this;
1082 return write(foregroundRGBSeq(r, g, b));
1083 }
1084
1092 AnsiStream &setBackgroundRGB(uint8_t r, uint8_t g, uint8_t b) {
1093 if (!_enabled) return *this;
1094 return write(backgroundRGBSeq(r, g, b));
1095 }
1096
1107 AnsiStream &hyperlink(const String &url, const String &text);
1108
1114 AnsiStream &setWindowTitle(const String &title);
1115
1125 AnsiStream &copyToClipboard(const String &text);
1126
1136 AnsiStream &enableMouseTracking(MouseTracking mode = MouseDrag) {
1137 if (!_enabled) return *this;
1138 *this << "\033[?1000h";
1139 if (mode == MouseDrag) *this << "\033[?1002h";
1140 if (mode == MouseAny) *this << "\033[?1003h";
1141 *this << "\033[?1006h";
1142 return *this;
1143 }
1144
1153 AnsiStream &disableMouseTracking() {
1154 if (!_enabled) return *this;
1155 *this << "\033[?1006l\033[?1003l\033[?1002l\033[?1000l";
1156 return *this;
1157 }
1158
1170 AnsiStream &beginSynchronizedUpdate() {
1171 if (!_enabled) return *this;
1172 *this << "\033[?2026h";
1173 return *this;
1174 }
1175
1181 AnsiStream &endSynchronizedUpdate() {
1182 if (!_enabled) return *this;
1183 *this << "\033[?2026l";
1184 return *this;
1185 }
1186
1191 AnsiStream &enableBracketedPaste() {
1192 if (!_enabled) return *this;
1193 *this << "\033[?2004h";
1194 return *this;
1195 }
1196
1201 AnsiStream &disableBracketedPaste() {
1202 if (!_enabled) return *this;
1203 *this << "\033[?2004l";
1204 return *this;
1205 }
1206
1217 AnsiStream &enableFocusReporting() {
1218 if (!_enabled) return *this;
1219 *this << "\033[?1004h";
1220 return *this;
1221 }
1222
1227 AnsiStream &disableFocusReporting() {
1228 if (!_enabled) return *this;
1229 *this << "\033[?1004l";
1230 return *this;
1231 }
1232
1241 AnsiStream &bell() {
1242 *this << '\a';
1243 return *this;
1244 }
1245
1250 AnsiStream &softReset() {
1251 if (!_enabled) return *this;
1252 *this << "\033[!p";
1253 return *this;
1254 }
1255
1260 AnsiStream &hardReset() {
1261 if (!_enabled) return *this;
1262 *this << "\033c";
1263 return *this;
1264 }
1265
1270 AnsiStream &useAlternateScreenBuffer() {
1271 if (!_enabled) return *this;
1272 *this << "\033[?1049h";
1273 return *this;
1274 }
1275
1280 AnsiStream &useMainScreenBuffer() {
1281 if (!_enabled) return *this;
1282 *this << "\033[?1049l";
1283 return *this;
1284 }
1285
1293 bool getCursorPosition(IODevice *input, int &row, int &col);
1294
1310 class StyleScope {
1311 public:
1313 explicit StyleScope(AnsiStream &stream) : _stream(stream) {}
1315 ~StyleScope() { _stream.reset(); }
1316 StyleScope(const StyleScope &) = delete;
1317 StyleScope &operator=(const StyleScope &) = delete;
1318
1319 private:
1320 AnsiStream &_stream;
1321 };
1322
1323 private:
1324 IODevice *_device;
1325 bool _enabled;
1326};
1327
1328PROMEKI_NAMESPACE_END
1329
1330#endif // PROMEKI_ENABLE_CORE