1 /// This is just JANSI, but ported over
2 module libd.console.ansi;
3 
4 import libd.algorithm : isOutputRange;
5 import libd.datastructures..string;
6 
7 /// Used to determine if an `AnsiColour` is a background or foreground colour.
8 enum IsBgColour : bool { no, yes }
9 
10 /// Used by certain functions to determine if they should only output an ANSI sequence, or output their entire sequence + data.
11 enum AnsiOnly : bool { no, yes }
12 
13 /// An 8-bit ANSI colour - an index into the terminal's colour palette.
14 alias Ansi8BitColour = ubyte;
15 
16 /// The string that starts an ANSI command sequence.
17 immutable ANSI_CSI                = "\033[";
18 
19 /// The character that delimits ANSI parameters.
20 immutable ANSI_SEPARATOR          = ';';
21 
22 /// The character used to denote that the sequence is an SGR sequence.
23 immutable ANSI_COLOUR_END         = 'm';
24 
25 /// The sequence used to reset all styling.
26 immutable ANSI_COLOUR_RESET       = ANSI_CSI~"0"~ANSI_COLOUR_END;
27 
28 /// The amount to increment an `Ansi4BitColour` by in order to access the background version of the colour.
29 immutable ANSI_FG_TO_BG_INCREMENT = 10;
30 
31 /+++ COLOUR TYPES +++/
32 
33 /++
34  + Defines what type of colour an `AnsiColour` stores.
35  + ++/
36 enum AnsiColourType
37 {
38     /// Default, failsafe.
39     none,
40 
41     /// 4-bit colours.
42     fourBit,
43 
44     /// 8-bit colours.
45     eightBit,
46 
47     /// 24-bit colours.
48     rgb
49 }
50 
51 /++
52  + An enumeration of standard 4-bit colours.
53  +
54  + These colours will have the widest support between platforms.
55  + ++/
56 enum Ansi4BitColour
57 {
58     black           = 30,
59     red             = 31,
60     green           = 32,
61     /// On Powershell, this is displayed as a very white colour.
62     yellow          = 33,
63     blue            = 34,
64     magenta         = 35,
65     cyan            = 36,
66     /// More gray than true white, use `BrightWhite` for true white.
67     white           = 37,
68     /// Grayer than `White`.
69     brightBlack     = 90,
70     brightRed       = 91,
71     brightGreen     = 92,
72     brightYellow    = 93,
73     brightBlue      = 94,
74     brightMagenta   = 95,
75     brightCyan      = 96,
76     brightWhite     = 97
77 }
78 
79 /++
80  + Contains a 3-byte, RGB colour.
81  + ++/
82 @safe
83 struct AnsiRgbColour
84 {
85     union
86     {
87         /// The RGB components as an array.
88         ubyte[3] components;
89 
90         struct {
91             /// The red component.
92             ubyte r;
93 
94             /// The green component.
95             ubyte g;
96 
97             /// The blue component.
98             ubyte b;
99         }
100     }
101 
102     @safe @nogc nothrow pure:
103 
104     /++
105      + Construct this colour from 3 ubyte components, in RGB order.
106      +
107      + Params:
108      +  components = The components to use.
109      + ++/
110     this(ubyte[3] components)
111     {
112         this.components = components;
113     }
114 
115     /++
116      + Construct this colour from the 3 provided ubyte components.
117      +
118      + Params:
119      +  r = The red component.
120      +  g = The green component.
121      +  b = The blue component.
122      + ++/
123     this(ubyte r, ubyte g, ubyte b)
124     {
125         this.r = r;
126         this.g = g;
127         this.b = b;
128     }
129 }
130 
131 private union AnsiColourUnion
132 {
133     Ansi4BitColour fourBit;
134     Ansi8BitColour eightBit;
135     AnsiRgbColour  rgb;
136 }
137 
138 /++
139  + Contains any type of ANSI colour and provides the ability to create a valid SGR command to set the foreground/background.
140  +
141  + This struct overloads `opAssign` allowing easy assignment from `Ansi4BitColour`, `Ansi8BitColour`, `AnsiRgbColour`, and any user-defined type
142  + that satisfies `isUserDefinedRgbType`.
143  + ++/
144 @safe
145 struct AnsiColour
146 {
147     private static immutable FG_MARKER        = "38";
148     private static immutable BG_MARKER        = "48";
149     private static immutable EIGHT_BIT_MARKER = '5';
150     private static immutable RGB_MARKER       = '2';
151 
152     /++
153      + The maximum amount of characters any singular `AnsiColour` sequence may use.
154      +
155      + This is often used to create a static array to temporarily, and without allocation, store the sequence for an `AnsiColour`.
156      + ++/
157     enum MAX_CHARS_NEEDED = "38;2;255;255;255".length;
158 
159     private
160     {
161         AnsiColourUnion _value;
162         AnsiColourType  _type;
163         IsBgColour      _isBg;
164 
165         @safe @nogc nothrow
166         this(IsBgColour isBg) pure
167         {
168             this._isBg = isBg;
169         }
170     }
171 
172     /// A variant of `.init` that is used for background colours.
173     static immutable bgInit = AnsiColour(IsBgColour.yes);
174 
175     /+++ CTORS AND PROPERTIES +++/
176     @safe @nogc nothrow pure
177     {
178         // Seperate, non-templated constructors as that's a lot more documentation-generator-friendly.
179 
180         /++
181          + Construct a 4-bit colour.
182          +
183          + Params:
184          +  colour = The 4-bit colour to use.
185          +  isBg   = Determines whether this colour sets the foreground or the background.
186          + ++/
187         this(Ansi4BitColour colour, IsBgColour isBg = IsBgColour.no)
188         {
189             this = colour;
190             this._isBg = isBg;
191         }
192         
193         /++
194          + Construct an 8-bit colour.
195          +
196          + Params:
197          +  colour = The 8-bit colour to use.
198          +  isBg   = Determines whether this colour sets the foreground or the background.
199          + ++/
200         this(Ansi8BitColour colour, IsBgColour isBg = IsBgColour.no)
201         {
202             this = colour;
203             this._isBg = isBg;
204         }
205 
206         /++
207          + Construct an RGB colour.
208          +
209          + Params:
210          +  colour = The RGB colour to use.
211          +  isBg   = Determines whether this colour sets the foreground or the background.
212          + ++/
213         this(AnsiRgbColour colour, IsBgColour isBg = IsBgColour.no)
214         {
215             this = colour;
216             this._isBg = isBg;
217         }
218 
219         /++
220          + Construct an RGB colour.
221          +
222          + Params:
223          +  r      = The red component.
224          +  g      = The green component.
225          +  b      = The blue component.
226          +  isBg   = Determines whether this colour sets the foreground or the background.
227          + ++/
228         this(ubyte r, ubyte g, ubyte b, IsBgColour isBg = IsBgColour.no)
229         {
230             this(AnsiRgbColour(r, g, b), isBg);
231         }
232 
233         /++
234          + Construct an RGB colour.
235          +
236          + Params:
237          +  colour = The user-defined colour type that satisfies `isUserDefinedRgbType`.
238          +  isBg   = Determines whether this colour sets the foreground or the background.
239          + ++/
240         this(T)(T colour, IsBgColour isBg = IsBgColour.no)
241         if(isUserDefinedRgbType!T)
242         {
243             this = colour;
244             this._isBg = isBg;
245         }
246 
247         /++
248          + Allows direct assignment from any type that can also be used in any of this struct's ctors.
249          + ++/
250         auto opAssign(T)(T colour) return
251         if(!is(T == typeof(this)))
252         {
253             static if(is(T == Ansi4BitColour))
254             {
255                 this._value.fourBit = colour;
256                 this._type = AnsiColourType.fourBit;
257             }
258             else static if(is(T == Ansi8BitColour))
259             {
260                 this._value.eightBit = colour;
261                 this._type = AnsiColourType.eightBit;
262             }
263             else static if(is(T == AnsiRgbColour))
264             {
265                 this._value.rgb = colour;
266                 this._type = AnsiColourType.rgb;
267             }
268             else static if(isUserDefinedRgbType!T)
269             {
270                 this = colour.to!AnsiColour();
271             }
272             else static assert(false, "Cannot implicitly convert "~T.stringof~" into an AnsiColour.");
273             
274             return this;
275         }
276 
277         /// Returns: The `AnsiColourType` of this `AnsiColour`.
278         @property
279         AnsiColourType type() const
280         {
281             return this._type;
282         }
283 
284         /// Returns: Whether this `AnsiColour` is for a background or not (it affects the output!).
285         @property
286         IsBgColour isBg() const
287         {
288             return this._isBg;
289         }
290 
291         /// ditto
292         @property
293         void isBg(IsBgColour bg)
294         {
295             this._isBg = bg;
296         }
297 
298         /// ditto
299         @property
300         void isBg(bool bg)
301         {
302             this._isBg = cast(IsBgColour)bg;
303         }
304 
305         /++
306         + Assertions:
307         +  This colour's type must be `AnsiColourType.fourBit`
308         +
309         + Returns:
310         +  This `AnsiColour` as an `Ansi4BitColour`.
311         + ++/
312         @property
313         Ansi4BitColour asFourBit() const
314         {
315             assert(this.type == AnsiColourType.fourBit);
316             return this._value.fourBit;
317         }
318 
319         /++
320         + Assertions:
321         +  This colour's type must be `AnsiColourType.eightBit`
322         +
323         + Returns:
324         +  This `AnsiColour` as a `ubyte`.
325         + ++/
326         @property
327         ubyte asEightBit() const
328         {
329             assert(this.type == AnsiColourType.eightBit);
330             return this._value.eightBit;
331         }
332 
333         /++
334         + Assertions:
335         +  This colour's type must be `AnsiColourType.rgb`
336         +
337         + Returns:
338         +  This `AnsiColour` as an `AnsiRgbColour`.
339         + ++/
340         @property
341         AnsiRgbColour asRgb() const
342         {
343             assert(this.type == AnsiColourType.rgb);
344             return this._value.rgb;
345         }
346     }
347     
348     /+++ OUTPUT +++/
349 
350     /++
351     + Converts this `AnsiColour` into a sequence String.
352     +
353     + See_Also:
354     +  `toSequence`
355     + ++/
356     @trusted nothrow
357     String toString() const
358     {
359         char[MAX_CHARS_NEEDED] chars;
360         return String(this.toSequence(chars[0..MAX_CHARS_NEEDED]));
361     }
362     ///
363     @("AnsiColour.toString")
364     unittest
365     {
366         assert(AnsiColour(255, 128, 64).toString() == "38;2;255;128;64");
367     }
368 
369     /++
370      + Creates an ANSI SGR command that either sets the foreground, or the background (`isBg`) to the colour
371      + stored inside of this `AnsiColour`.
372      +
373      + Please note that the CSI (`ANSI_CSI`/`\033[`) and the SGR marker (`ANSI_COLOUR_END`/`m`) are not included
374      + in this output.
375      +
376      + Notes:
377      +  Any characters inside of `buffer` that are not covered by the returned slice, are left unmodified.
378      +
379      +  If this colour hasn't been initialised or assigned a value, then the returned value is simply `null`.
380      +
381      + Params:
382      +  buffer = The statically allocated buffer used to store the result of this function.
383      +
384      + Returns:
385      +  A slice into `buffer` that contains the output of this function.
386      + ++/
387     @safe @nogc
388     char[] toSequence(ref return char[MAX_CHARS_NEEDED] buffer) nothrow const
389     {
390         if(this.type == AnsiColourType.none)
391             return null;
392 
393         size_t cursor;
394 
395         void numIntoBuffer(ubyte num)
396         {
397             char[3] text;
398             const slice = numToStrBase10(text[0..3], num);
399             buffer[cursor..cursor + slice.length] = slice[];
400             cursor += slice.length;
401         }
402 
403         if(this.type != AnsiColourType.fourBit)
404         {
405             // 38; or 48;
406             auto marker = (this.isBg) ? BG_MARKER : FG_MARKER;
407             buffer[cursor..cursor+2] = marker[0..$];
408             cursor += 2;
409             buffer[cursor++] = ANSI_SEPARATOR;
410         }
411 
412         // 4bit, 5;8bit, or 2;r;g;b
413         final switch(this.type) with(AnsiColourType)
414         {
415             case none: assert(false);
416             case fourBit: 
417                 numIntoBuffer(cast(ubyte)((this.isBg) ? this._value.fourBit + 10 : this._value.fourBit)); 
418                 break;
419 
420             case eightBit:
421                 buffer[cursor++] = EIGHT_BIT_MARKER;
422                 buffer[cursor++] = ANSI_SEPARATOR;
423                 numIntoBuffer(this._value.eightBit);
424                 break;
425                 
426             case rgb:
427                 buffer[cursor++] = RGB_MARKER;
428                 buffer[cursor++] = ANSI_SEPARATOR;
429 
430                 numIntoBuffer(this._value.rgb.r); 
431                 buffer[cursor++] = ANSI_SEPARATOR;
432                 numIntoBuffer(this._value.rgb.g); 
433                 buffer[cursor++] = ANSI_SEPARATOR;
434                 numIntoBuffer(this._value.rgb.b); 
435                 break;
436         }
437 
438         return buffer[0..cursor];
439     }
440     ///
441     @("AnsiColour.toSequence(char[])")
442     unittest
443     {
444         char[AnsiColour.MAX_CHARS_NEEDED] buffer;
445 
446         void test(string expected, AnsiColour colour)
447         {
448             const slice = colour.toSequence(buffer);
449             assert(slice == expected);
450         }
451 
452         test("32",               AnsiColour(Ansi4BitColour.green));
453         test("42",               AnsiColour(Ansi4BitColour.green, IsBgColour.yes));
454         test("38;5;1",           AnsiColour(Ansi8BitColour(1)));
455         test("48;5;1",           AnsiColour(Ansi8BitColour(1), IsBgColour.yes));
456         test("38;2;255;255;255", AnsiColour(255, 255, 255));
457         test("48;2;255;128;64",  AnsiColour(255, 128, 64, IsBgColour.yes));
458     }
459 }
460 
461 /+++ MISC TYPES +++/
462 
463 /++
464  + A list of styling options provided by ANSI SGR.
465  +
466  + As a general rule of thumb, assume most of these won't work inside of a Windows command prompt (unless it's the new Windows Terminal).
467  + ++/
468 enum AnsiSgrStyle
469 {
470     none      = 0,
471     bold      = 1,
472     dim       = 2,
473     italic    = 3,
474     underline = 4,
475     slowBlink = 5,
476     fastBlink = 6,
477     invert    = 7,
478     strike    = 9
479 }
480 
481 private template getMaxSgrStyleCharCount()
482 {
483     // Can't even use non-betterC features in CTFE, so no std.conv.to!string :(
484     size_t numberOfChars(int num)
485     {
486         size_t amount;
487 
488         do
489         {
490             amount++;
491             num /= 10;
492         } while(num > 0);
493 
494         return amount;
495     }
496 
497     size_t calculate()
498     {
499         size_t amount;
500         static foreach(name; __traits(allMembers, AnsiSgrStyle))
501             amount += numberOfChars(cast(int)__traits(getMember, AnsiSgrStyle, name)) + 1; // + 1 for the semi-colon after.
502 
503         return amount;
504     }
505 
506     enum getMaxSgrStyleCharCount = calculate();
507 }
508 
509 /++
510  + Contains any number of styling options from `AnsiStyleSgr`, and provides the ability to generate
511  + an ANSI SGR command to apply all of the selected styling options.
512  + ++/
513 @safe
514 struct AnsiStyle
515 {
516     /++
517      + The maximum amount of characters any singular `AnsiStyle` sequence may use.
518      +
519      + This is often used to create a static array to temporarily, and without allocation, store the sequence for an `AnsiStyle`.
520      + ++/
521     enum MAX_CHARS_NEEDED = getMaxSgrStyleCharCount!();
522 
523     private
524     {
525         ushort _sgrBitmask; // Each set bit index corresponds to the value from `AnsiSgrStyle`.
526 
527         @safe @nogc nothrow
528         int sgrToBit(AnsiSgrStyle style) pure const
529         {
530             return 1 << (cast(int)style);
531         }
532 
533         @safe @nogc nothrow
534         void setSgrBit(bool setOrUnset)(AnsiSgrStyle style) pure
535         {
536             static if(setOrUnset)
537                 this._sgrBitmask |= this.sgrToBit(style);
538             else
539                 this._sgrBitmask &= ~this.sgrToBit(style);
540         }
541 
542         @safe @nogc nothrow
543         bool getSgrBit(AnsiSgrStyle style) pure const
544         {
545             return (this._sgrBitmask & this.sgrToBit(style)) > 0;
546         }
547     }
548 
549     // Seperate functions for better documentation generation.
550     //
551     // Tedious, as this otherwise could've all been auto-generated.
552     /+++ SETTERS +++/
553     @safe @nogc nothrow pure
554     {
555         /// Removes all styling from this `AnsiStyle`.
556         AnsiStyle reset() return
557         {
558             this._sgrBitmask = 0;
559             return this;
560         }
561 
562         /++
563          + Enables/Disables a certain styling option.
564          +
565          + Params:
566          +  style  = The styling option to enable/disable.
567          +  enable = If true, enable the option. If false, disable it.
568          +
569          + Returns:
570          +  `this` for chaining.
571          + ++/
572         AnsiStyle set(AnsiSgrStyle style, bool enable) return
573         {
574             if(enable)
575                 this.setSgrBit!true(style);
576             else
577                 this.setSgrBit!false(style);
578             return this;
579         }
580 
581         ///
582         AnsiStyle bold(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.bold); return this; }
583         ///
584         AnsiStyle dim(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.dim); return this; }
585         ///
586         AnsiStyle italic(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.italic); return this; }
587         ///
588         AnsiStyle underline(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.underline); return this; }
589         ///
590         AnsiStyle slowBlink(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.slowBlink); return this; }
591         ///
592         AnsiStyle fastBlink(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.fastBlink); return this; }
593         ///
594         AnsiStyle invert(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.invert); return this; }
595         ///
596         AnsiStyle strike(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.strike); return this; }
597     }
598 
599     /+++ GETTERS +++/
600     @safe @nogc nothrow pure const
601     {
602         /++
603          + Get the status of a certain styling option.
604          +
605          + Params:
606          +  style = The styling option to get.
607          +
608          + Returns:
609          +  `true` if the styling option is enabled, `false` otherwise.
610          + ++/
611         bool get(AnsiSgrStyle style)
612         {
613             return this.getSgrBit(style);
614         }
615         
616         ///
617         bool bold() { return this.getSgrBit(AnsiSgrStyle.bold); }
618         ///
619         bool dim() { return this.getSgrBit(AnsiSgrStyle.dim); }
620         ///
621         bool italic() { return this.getSgrBit(AnsiSgrStyle.italic); }
622         ///
623         bool underline() { return this.getSgrBit(AnsiSgrStyle.underline); }
624         ///
625         bool slowBlink() { return this.getSgrBit(AnsiSgrStyle.slowBlink); }
626         ///
627         bool fastBlink() { return this.getSgrBit(AnsiSgrStyle.fastBlink); }
628         ///
629         bool invert() { return this.getSgrBit(AnsiSgrStyle.invert); }
630         ///
631         bool strike() { return this.getSgrBit(AnsiSgrStyle.strike); }
632     }
633 
634     /+++ OUTPUT +++/
635 
636     /++
637     + Converts this `AnsiStyle` into a sequence string.
638     +
639     + See_Also:
640     +  `toSequence`
641     + ++/
642     @trusted nothrow
643     String toString() const
644     {
645         char[MAX_CHARS_NEEDED] chars;
646         return String(this.toSequence(chars[0..MAX_CHARS_NEEDED]));
647     }
648 
649     /++
650      + Creates an ANSI SGR command that enables all of the desired styling options, while leaving all of the other options unchanged.
651      +
652      + Please note that the CSI (`ANSI_CSI`/`\033[`) and the SGR marker (`ANSI_COLOUR_END`/`m`) are not included
653      + in this output.
654      +
655      + Notes:
656      +  Any characters inside of `buffer` that are not covered by the returned slice, are left unmodified.
657      +
658      +  If this colour hasn't been initialised or assigned a value, then the returned value is simply `null`.
659      +
660      + Params:
661      +  buffer = The statically allocated buffer used to store the result of this function.
662      +
663      + Returns:
664      +  A slice into `buffer` that contains the output of this function.
665      + ++/
666     @safe @nogc
667     char[] toSequence(ref return char[MAX_CHARS_NEEDED] buffer) nothrow const
668     {
669         if(this._sgrBitmask == 0)
670             return null;
671 
672         size_t cursor;
673         void numIntoBuffer(uint num)
674         {
675             char[10] text;
676             const slice = numToStrBase10(text[0..$], num);
677             buffer[cursor..cursor + slice.length] = slice[];
678             cursor += slice.length;
679         }
680 
681         bool isFirstValue = true;
682         static foreach(name; __traits(allMembers, AnsiSgrStyle))
683         {{
684             const flag = __traits(getMember, AnsiSgrStyle, name);
685             if(this.getSgrBit(flag))
686             {
687                 if(!isFirstValue)
688                     buffer[cursor++] = ANSI_SEPARATOR;
689                 isFirstValue = false;
690 
691                 numIntoBuffer(cast(uint)flag);
692             }
693         }}
694 
695         return buffer[0..cursor];
696     }
697     ///
698     @("AnsiStyle.toSequence(char[])")
699     unittest
700     {
701         char[AnsiStyle.MAX_CHARS_NEEDED] buffer;
702         
703         void test(string expected, AnsiStyle style)
704         {
705             const slice = style.toSequence(buffer);
706             assert(slice == expected);
707         }
708 
709         test("", AnsiStyle.init);
710         test("1;2;3", AnsiStyle.init.bold.dim.italic);
711     }
712 }
713 
714 /+++ DATA WITH COLOUR TYPES +++/
715 
716 /++
717  + Contains an `AnsiColour` for the foreground, an `AnsiColour` for the background, and an `AnsiStyle` for additional styling,
718  + and provides the ability to create an ANSI SGR command to set the foreground, background, and overall styling of the terminal.
719  +
720  + A.k.a This is just a container over two `AnsiColour`s and an `AnsiStyle`.
721  + ++/
722 @safe
723 struct AnsiStyleSet
724 {
725     /++
726      + The maximum amount of characters any singular `AnsiStyle` sequence may use.
727      +
728      + This is often used to create a static array to temporarily, and without allocation, store the sequence for an `AnsiStyle`.
729      + ++/
730     enum MAX_CHARS_NEEDED = (AnsiColour.MAX_CHARS_NEEDED * 2) + AnsiStyle.MAX_CHARS_NEEDED;
731 
732     private AnsiColour _fg;
733     private AnsiColour _bg;
734     private AnsiStyle _style;
735 
736     // As usual, functions are manually made for better documentation.
737 
738     /+++ SETTERS +++/
739     @safe @nogc nothrow
740     {
741         ///
742         AnsiStyleSet fg(AnsiColour colour) return { this._fg = colour; this._fg.isBg = IsBgColour.no; return this; }
743         ///
744         AnsiStyleSet fg(Ansi4BitColour colour) return { return this.fg(AnsiColour(colour)); }
745         ///
746         AnsiStyleSet fg(Ansi8BitColour colour) return { return this.fg(AnsiColour(colour)); }
747         ///
748         AnsiStyleSet fg(AnsiRgbColour colour) return { return this.fg(AnsiColour(colour)); }
749 
750         ///
751         AnsiStyleSet bg(AnsiColour colour) return { this._bg = colour; this._bg.isBg = IsBgColour.yes; return this; }
752         ///
753         AnsiStyleSet bg(Ansi4BitColour colour) return { return this.bg(AnsiColour(colour)); }
754         ///
755         AnsiStyleSet bg(Ansi8BitColour colour) return { return this.bg(AnsiColour(colour)); }
756         ///
757         AnsiStyleSet bg(AnsiRgbColour colour) return { return this.bg(AnsiColour(colour)); }
758         ///
759 
760         ///
761         AnsiStyleSet style(AnsiStyle style) return { this._style = style; return this; }
762     }
763 
764     /+++ GETTERS +++/
765     @safe @nogc nothrow const
766     {
767         ///
768         AnsiColour fg() { return this._fg; }
769         ///
770         AnsiColour bg() { return this._bg; }
771         ///
772         AnsiStyle style() { return this._style; }
773     }
774 
775     /+++ OUTPUT ++/
776     /++
777      + Creates an ANSI SGR command that sets the foreground colour, sets the background colour,
778      + and enables all of the desired styling options, while leaving all of the other options unchanged.
779      +
780      + Please note that the CSI (`ANSI_CSI`/`\033[`) and the SGR marker (`ANSI_COLOUR_END`/`m`) are not included
781      + in this output.
782      +
783      + Notes:
784      +  Any characters inside of `buffer` that are not covered by the returned slice, are left unmodified.
785      +
786      +  If this colour hasn't been initialised or assigned a value, then the returned value is simply `null`.
787      +
788      + Params:
789      +  buffer = The statically allocated buffer used to store the result of this function.
790      +
791      + Returns:
792      +  A slice into `buffer` that contains the output of this function.
793      + ++/
794     @safe @nogc
795     char[] toSequence(ref return char[MAX_CHARS_NEEDED] buffer) nothrow const
796     {
797         size_t cursor;
798 
799         char[AnsiColour.MAX_CHARS_NEEDED] colour;
800         char[AnsiStyle.MAX_CHARS_NEEDED] style;
801 
802         auto slice = this._fg.toSequence(colour);
803         buffer[cursor..cursor + slice.length] = slice[];
804         cursor += slice.length;
805 
806         slice = this._bg.toSequence(colour);
807         if(slice.length > 0 && cursor > 0)
808             buffer[cursor++] = ANSI_SEPARATOR;
809         buffer[cursor..cursor + slice.length] = slice[];
810         cursor += slice.length;
811 
812         slice = this.style.toSequence(style);
813         if(slice.length > 0 && cursor > 0)
814             buffer[cursor++] = ANSI_SEPARATOR;
815         buffer[cursor..cursor + slice.length] = slice[];
816         cursor += slice.length;
817 
818         return buffer[0..cursor];
819     }
820     ///
821     @("AnsiStyleSet.toSequence")
822     unittest
823     {
824         char[AnsiStyleSet.MAX_CHARS_NEEDED] buffer;
825 
826         void test(string expected, AnsiStyleSet ch)
827         {
828             auto slice = ch.toSequence(buffer);
829             assert(slice == expected);
830         }
831 
832         test("", AnsiStyleSet.init);
833         test(
834             "32;48;2;255;128;64;1;4", 
835             AnsiStyleSet.init
836                     .fg(Ansi4BitColour.green)
837                     .bg(AnsiRgbColour(255, 128, 64))
838                     .style(AnsiStyle.init.bold.underline)
839         );
840     }
841 }
842 
843 /++
844  + An enumeration used by an `AnsiText` implementation to describe any special features that `AnsiText` needs to mold
845  + itself around.
846  + ++/
847 enum AnsiTextImplementationFeatures
848 {
849     /// Supports at least `.put`, `.toSink`, `char[] .newSlice`, and allows `AnsiText` to handle the encoding.
850     basic = 0, 
851 }
852 
853 /++
854  + A lightweight alternative to `AnsiText` which only supports a singular coloured string, at the cost
855  + of removing most of the other complexity & dynamic allocation needs of `AnsiText`.
856  +
857  + If you only need to style your string in one certain way, or want to avoid `AnsiText` altogether, then this struct
858  + is the way to go.
859  +
860  + Usage_(Manually):
861  +  First, retrieve and the ANSI styling sequence via `AnsiTextLite.toFullStartSequence` and output it.
862  +
863  +  Second, output `AnsiTextLite.text`.
864  +
865  +  Finally, and optionally, retrieve the ANSI reset sequence via `AnsiTextLite.toFullEndSequence` and output it.
866  +
867  + Usage_(Range):
868  +  Call `AnsiTextLite.toRange` to get the range, please read its documentation as it is important (it'll return slices to stack-allocated memory).
869  +
870  + Usage_(GC):
871  +  If you're not compiling under `-betterc`, then `AnsiTextLite.toString()` will provide you with a GC-allocated string containing:
872  +  the start ANSI sequence; the text to display; and the end ANSI sequence. i.e. A string that is just ready to be printed.
873  +
874  +  This struct also implements the sink-based version of `toString`, which means that when used directly with things like `writeln`, this struct
875  +  is able to avoid allocations (unless the sink itself allocates). See the unittest for an example.
876  +
877  + See_Also:
878  +  `ansi` for fluent creation of an `AnsiTextLite`.
879  +
880  +  This struct's unittest for an example of usage.
881  + ++/
882 struct AnsiTextLite
883 {
884     /++
885      + The maximum amount of chars required by the start sequence of an `AnsiTextLite` (`toFullStartSequence`).
886      + ++/
887     enum MAX_CHARS_NEEDED = AnsiStyleSet.MAX_CHARS_NEEDED + ANSI_CSI.length + 1; // + 1 for the ANSI_COLOUR_END
888 
889     /// The text to output.
890     const(char)[] text;
891     
892     /// The styling to apply to the text.
893     AnsiStyleSet styleSet;
894 
895     /+++ SETTERS +++/
896     // TODO: Should probably make a mixin template for this, but I need to see how the documentation generators handle that.
897     //       I also can't just do an `alias this`, as otherwise the style functions wouldn't return `AnsiTextLite`, but instead `AnsiStyleSet`.
898     //       Or just suck it up and make some of the setters templatised, much to the dismay of documentation.
899     @safe @nogc nothrow
900     {
901         ///
902         AnsiTextLite fg(AnsiColour colour) return { this.styleSet.fg = colour; this.styleSet.fg.isBg = IsBgColour.no; return this; }
903         ///
904         AnsiTextLite fg(Ansi4BitColour colour) return { return this.fg(AnsiColour(colour)); }
905         ///
906         AnsiTextLite fg(Ansi8BitColour colour) return { return this.fg(AnsiColour(colour)); }
907         ///
908         AnsiTextLite fg(AnsiRgbColour colour) return { return this.fg(AnsiColour(colour)); }
909 
910         ///
911         AnsiTextLite bg(AnsiColour colour) return { this.styleSet.bg = colour; this.styleSet.bg.isBg = IsBgColour.yes; return this; }
912         ///
913         AnsiTextLite bg(Ansi4BitColour colour) return { return this.bg(AnsiColour(colour)); }
914         ///
915         AnsiTextLite bg(Ansi8BitColour colour) return { return this.bg(AnsiColour(colour)); }
916         ///
917         AnsiTextLite bg(AnsiRgbColour colour) return { return this.bg(AnsiColour(colour)); }
918         ///
919 
920         ///
921         AnsiTextLite style(AnsiStyle style) return { this.styleSet.style = style; return this; }
922     }
923 
924     /+++ GETTERS +++/
925     @safe @nogc nothrow const
926     {
927         ///
928         AnsiColour fg() { return this.styleSet.fg; }
929         ///
930         AnsiColour bg() { return this.styleSet.bg; }
931         ///
932         AnsiStyle style() { return this.styleSet.style; }
933     }
934 
935     @safe @nogc nothrow const
936     {
937         /++
938          + Populates the given buffer with the full ANSI sequence needed to enable the styling
939          + defined within this `AnsiTextLite`
940          +
941          + Unlike the usual `toSequence` functions, this function includes the `ANSI_CSI` and `ANSI_COLOUR_END` markers,
942          + meaning the output from this function is ready to be printed as-is.
943          +
944          + Do note that this function doesn't insert a null-terminator, so if you're using anything based on C strings, you need
945          + to insert that yourself.
946          +
947          + Notes:
948          +  Any parts of the `buffer` that are not populated by this function are left untouched.
949          +
950          + Params:
951          +  buffer = The buffer to populate.
952          +
953          + Returns:
954          +  The slice of `buffer` that has been populated.
955          + ++/
956         char[] toFullStartSequence(ref return char[MAX_CHARS_NEEDED] buffer)
957         {
958             size_t cursor;
959 
960             buffer[0..ANSI_CSI.length] = ANSI_CSI[];
961             cursor += ANSI_CSI.length;
962 
963             char[AnsiStyleSet.MAX_CHARS_NEEDED] styleBuffer;
964             const styleSlice = this.styleSet.toSequence(styleBuffer);
965             buffer[cursor..cursor+styleSlice.length] = styleSlice[];
966             cursor += styleSlice.length;
967 
968             buffer[cursor++] = ANSI_COLOUR_END;
969 
970             return buffer[0..cursor];
971         }
972 
973         /++
974          + Returns:
975          +  The end ANSI sequence for `AnsiTextLite`, which is simply a statically allocated version of the `ANSI_COLOUR_RESET` constant.
976          + ++/
977         char[ANSI_COLOUR_RESET.length] toFullEndSequence()
978         {
979             typeof(return) buffer;
980             buffer[0..$] = ANSI_COLOUR_RESET[];
981             return buffer;
982         }
983 
984         /++
985          + Provides a range that returns, in this order: The start sequence (`.toFullStartSequence`); the output text (`.text`),
986          + and finally the end sequence (`.toFullEndSequence`).
987          +
988          + This range is $(B weakly-safe) as it $(B returns slices to stack memory) so please ensure that $(B any returned slices don't outlive the origin range object).
989          +
990          + Please also note that non of the returned slices contain null terminators.
991          +
992          + Returns:
993          +  An Input Range that returns all the slices required to correctly display this `AnsiTextLite` onto a console.
994          + ++/
995         auto toRange()
996         {
997             static struct Range
998             {
999                 char[MAX_CHARS_NEEDED] start;
1000                 const(char)[] middle;
1001                 char[ANSI_COLOUR_RESET.length] end;
1002                 char[] startSlice;
1003 
1004                 size_t sliceCount;
1005 
1006                 @safe @nogc nothrow:
1007 
1008                 bool empty()
1009                 {
1010                     return this.sliceCount >= 3;
1011                 }
1012 
1013                 void popFront()
1014                 {
1015                     this.sliceCount++;
1016                 }
1017 
1018                 @trusted
1019                 const(char)[] front() return
1020                 {
1021                     switch(sliceCount)
1022                     {
1023                         case 0: return this.startSlice;
1024                         case 1: return this.middle;
1025                         case 2: return this.end[0..$];
1026                         default: assert(false, "Cannot use empty range.");
1027                     }   
1028                 }
1029             }
1030 
1031             Range r;
1032             r.startSlice = this.toFullStartSequence(r.start);
1033             r.middle = this.text;
1034             r.end = this.toFullEndSequence();
1035 
1036             return r;
1037         }
1038     }
1039 
1040     /++
1041      + Notes:
1042      +  This struct implements the sink-based `toString` which performs no allocations, so the likes of `std.stdio.writeln` will
1043      +  automatically use the sink-based version if you pass this struct to it directly.
1044      +
1045      + Returns: 
1046      +  A string containing this `AnsiTextLite` as an ANSI-encoded string, ready for printing.
1047      + ++/
1048     @trusted nothrow // @trusted due to .assumeUnique
1049     String toString() const
1050     {
1051         char[MAX_CHARS_NEEDED] styleBuffer;
1052         const styleSlice = this.toFullStartSequence(styleBuffer);
1053 
1054         String buffer;
1055         buffer.length = styleSlice.length + this.text.length + ANSI_COLOUR_RESET.length;
1056         buffer[0..styleSlice.length]                                  = styleSlice[];
1057         buffer[styleSlice.length..styleSlice.length+this.text.length] = this.text[];
1058         buffer[$-ANSI_COLOUR_RESET.length..$]                         = ANSI_COLOUR_RESET[];
1059 
1060         return buffer;
1061     }
1062 
1063     /++
1064      + The sink-based version of `toString`, which doesn't allocate by itself unless the `sink` decides to allocate.
1065      +
1066      + Params:
1067      +  sink = The sink to output into.
1068      +
1069      + See_Also:
1070      +  `toSink` for a templatised version of this function which can infer attributes, and supports any form of Output Range instead of just a delegate.
1071      + ++/
1072     void toString(scope void delegate(const(char)[]) sink) const
1073     {
1074         foreach(slice; this.toRange())
1075             sink(slice);
1076     }
1077 
1078     /++
1079      + Outputs in order: The start sequence (`.toFullStartSequence`), the output text (`.text`), and the end sequence (`.toFullEndSequence`)
1080      + into the given `sink`.
1081      +
1082      + This function by itself does not allocate memory.
1083      +
1084      + This function will infer attributes, so as to be used in whatever attribute-souped environment your sink supports.
1085      +
1086      + $(B Please read the warnings described in `.toRange`) TLDR; don't persist the slices given to the sink under any circumstance. You must
1087      + copy the data as soon as you get it.
1088      +
1089      + Params:
1090      +  sink = The sink to output into.
1091      + ++/
1092     void toSink(Sink)(scope ref Sink sink) const
1093     {
1094         foreach(slice; this.toRange())
1095             sink.put(slice);
1096     }
1097 }
1098 ///
1099 @("AnsiTextLite")
1100 version(none) unittest
1101 {
1102     auto text = "Hello!".ansi
1103                         .fg(Ansi4BitColour.green)
1104                         .bg(AnsiRgbColour(128, 128, 128))
1105                         .style(AnsiStyle.init.bold.underline);
1106 
1107     // Usage 1: Manually
1108     import core.stdc.stdio : printf;
1109     import std.stdio : writeln, write;
1110     version(JANSI_TestOutput) // Just so test output isn't clogged. This still shows you how to use things though.
1111     {
1112         char[AnsiTextLite.MAX_CHARS_NEEDED + 1] startSequence; // + 1 for null terminator.
1113         const sliceFromStartSequence = text.toFullStartSequence(startSequence[0..AnsiTextLite.MAX_CHARS_NEEDED]);
1114         startSequence[sliceFromStartSequence.length] = '\0';
1115 
1116         char[200] textBuffer;
1117         textBuffer[0..text.text.length] = text.text[];
1118         textBuffer[text.text.length] = '\0';
1119 
1120         char[ANSI_COLOUR_RESET.length + 1] endSequence;
1121         endSequence[0..ANSI_COLOUR_RESET.length] = text.toFullEndSequence()[];
1122         endSequence[$-1] = '\0';
1123 
1124         printf("%s%s%s\n", startSequence.ptr, textBuffer.ptr, endSequence.ptr);
1125     }
1126 
1127     // Usage 2: Range (RETURNS STACK MEMORY, DO NOT ALLOW SLICES TO OUTLIVE RANGE OBJECT WITHOUT EXPLICIT COPY)
1128     version(JANSI_TestOutput)
1129     {
1130         // -betterC
1131         foreach(slice; text.toRange)
1132         {
1133             char[200] buffer;
1134             buffer[0..slice.length] = slice[];
1135             buffer[slice.length] = '\0';
1136             printf("%s", buffer.ptr);
1137         }
1138         printf("\n");
1139 
1140         // GC
1141         foreach(slice; text.toRange)
1142             write(slice);
1143         writeln();
1144     }
1145     
1146     // Usage 3: toString (Sink-based, so AnsiTextLite doesn't allocate, but writeln/the sink might)
1147     version(JANSI_TestOutput)
1148     {
1149         writeln(text); // Calls the sink-based .toString();
1150     }
1151 
1152     // Usage 4: toString (non-sink, non-betterc only)
1153     version(JANSI_TestOutput)
1154     {
1155         writeln(text.toString());
1156     }
1157 
1158     // Usage 5: toSink
1159     version(JANSI_TestOutput)
1160     {
1161         struct CustomOutputRange
1162         {
1163             char[] output;
1164             @safe
1165             void put(const(char)[] slice) nothrow
1166             {
1167                 const start = output.length;
1168                 output.length += slice.length;
1169                 output[start..$] = slice[];
1170             }
1171         }
1172 
1173         CustomOutputRange sink;
1174         ()@safe nothrow{ text.toSink(sink); }();
1175         
1176         writeln(sink.output);
1177     }
1178 }
1179 
1180 /++
1181  + Contains a string that supports the ability for different parts of the string to be styled seperately.
1182  +
1183  + This struct is highly flexible and dynamic, as it requires the use of external code to provide some
1184  + of the implementation.
1185  +
1186  + Because this is provided via a `mixin template`, implementations can also $(B extend) this struct to 
1187  + provide their own functionality, make things non-copyable if needed, allows data to be stored via ref-counting, etc.
1188  +
1189  + This struct itself is mostly just a consistant user-facing interface that all implementations share, while the implementations
1190  + themselves can transform this struct to any level it requires.
1191  +
1192  + Implementations_:
1193  +  While implementations can add whatever functions, operator overloads, constructors, etc. that they want, there is a small
1194  +  set of functions and value that each implmentation must define in order to be useable.
1195  +
1196  +  Every implementation must define an enum called `Features` who's value is one of the values of `AnsiTextImplementationFeatures`.
1197  +  For example: `enum Features = AnsiTextImplementationFeatures.xxx`
1198  +
1199  +  Builtin implementations consist of `AnsiTextGC` (not enabled with -betterC), `AnsiTextStack`, and `AnsiTextMalloc`, which are self-descriptive.
1200  +
1201  + Basic_Implemetations:
1202  +  An implementation that doesn't require anything noteworthy from `AnsiText` itself should define their features as `AnsiTextImplementationFeatures.basic`.
1203  +
1204  +  This type of implementation must implement the following functions (expressed here as an interface for simplicity):
1205  +
1206  +  ```
1207  interface BasicImplementation
1208  {
1209      /// Provides `AnsiText` with a slice that is of at least `minLength` in size.
1210      ///
1211      /// This function is called `AnsiText` needs to insert more styled characters into the string.
1212      ///
1213      /// How this slice is stored and allocated and whatever else, is completely down to the implementation.
1214      /// Remember that because you're a mixin template, you can use referencing counting, disable the copy ctor, etc!
1215      ///
1216      /// The slice will never be escaped by `AnsiText` itself, and will not be stored beyond a single function call.
1217      char[] newSlice(size_t minLength);
1218 
1219      /// Outputs the styled string into the provided sink.
1220      ///
1221      /// Typically this is an OutputRange that can handle `char[]`s, but it can really be whatever the implementation wants to support.
1222      void toSink(Sink)(Sink sink);
1223 
1224      static if(NotCompilingUnderBetterC && ImplementationDoesntDefineToString)
1225      final string toString()
1226      {
1227          // Autogenerated GC-based implementation provided by `AnsiText`.
1228          //
1229          // For implementations where this can be generated, it just makes them a little easier for the user
1230          // to use with things like `writeln`.
1231          //
1232          // The `static if` shows the conditions for this to happen.
1233      }
1234  }
1235  +  ```
1236  + ++/
1237 struct AnsiText(alias ImplementationMixin)
1238 {
1239     mixin ImplementationMixin;
1240     alias ___TEST = TestAnsiTextImpl!(typeof(this));
1241 
1242     void put()(const(char)[] text, AnsiColour fg = AnsiColour.init, AnsiColour bg = AnsiColour.bgInit, AnsiStyle style = AnsiStyle.init)
1243     {
1244         fg.isBg = IsBgColour.no;
1245         bg.isBg = IsBgColour.yes;
1246 
1247         char[AnsiStyleSet.MAX_CHARS_NEEDED] sequence;
1248         auto sequenceSlice = AnsiStyleSet.init.fg(fg).bg(bg).style(style).toSequence(sequence);
1249 
1250         auto minLength = ANSI_CSI.length + sequenceSlice.length + /*ANSI_COLOUR_END*/1 + text.length + ((sequenceSlice.length > 0) ? 2 : 1); // Last one is for the '0' or '0;'
1251         char[] slice = this.newSlice(minLength);
1252         size_t cursor;
1253 
1254         void appendToSlice(const(char)[] source)
1255         {
1256             slice[cursor..cursor+source.length] = source[];
1257             cursor += source.length;
1258         }
1259 
1260         appendToSlice(ANSI_CSI);
1261         appendToSlice("0"); // Reset all previous styling
1262         if(sequenceSlice.length > 0)
1263             slice[cursor++] = ANSI_SEPARATOR;
1264         appendToSlice(sequenceSlice);
1265         slice[cursor++] = ANSI_COLOUR_END;
1266         appendToSlice(text);
1267     }
1268 
1269     /// ditto.
1270     void put()(const(char)[] text, AnsiStyleSet styling)
1271     {
1272         this.put(text, styling.fg, styling.bg, styling.style);
1273     }
1274 
1275     /// ditto.
1276     void put()(AnsiTextLite text)
1277     {
1278         this.put(text.text, text.fg, text.bg, text.style);
1279     }
1280 
1281     // Generate a String-based toString if circumstances allow.
1282     static if(
1283         Features == AnsiTextImplementationFeatures.basic
1284      && !__traits(hasMember, typeof(this), "toString")
1285      && __traits(compiles, { struct S{void put(const(char)[]){}} S s; typeof(this).init.toSink(s); }) // Check if this toSink can take a char[] output range.
1286     )
1287     {
1288         /++
1289          + Provides this `AnsiText` as a printable string.
1290          +
1291          + If the implementation is a basic implementation (see the documentation for `AnsiText`); if the
1292          + implementation doesn't define its own `toString then
1293          + `AnsiText` will generate this function on behalf of the implementation.
1294          +
1295          + Description:
1296          +  For basic implementations this function will call `toSink` with an `Appender!(char[])` as the sink.
1297          +
1298          +  For $(B this default generated) implementation of `toString`, it is a seperate String so is
1299          +  fine for any usage. If an implementation defines its own `toString` then it should also document what the lifetime
1300          +  of its returned string is.
1301          +
1302          + Returns:
1303          +  This `AnsiText` as a useable string.
1304          + ++/
1305         String toString()()
1306         {
1307             String data;
1308             this.toSink(data);
1309 
1310             return data;
1311         }
1312 
1313         /++
1314          + [Not enabled with -betterC] Provides the sink-based version of the autogenerated `toString`.
1315          +
1316          + This functions and is generated under the same conditions as the parameterless `toString`, except it
1317          + supports the sink-based interface certain parts of Phobos recognises, helping to prevent needless allocations.
1318          +
1319          + This function simply wraps the given `sink` and forwards it to the implementation's `toSink` function, so there's no
1320          + implicit GC overhead as with the other `toString`. (At least, not by `AnsiText` itself.)
1321          + ++/
1322         void toString(scope void delegate(const(char)[]) sink)
1323         {
1324             struct Sink
1325             {
1326                 void put(const(char)[] slice)
1327                 {
1328                     sink(slice);
1329                 }
1330             }
1331             
1332             Sink s;
1333             this.toSink(s);
1334         }
1335     }
1336 }
1337 
1338 private template TestAnsiTextImpl(alias TextT)
1339 {
1340     // Ensures that the implementation has the required functions, and that they can be used in every required way.
1341     static assert(__traits(hasMember, TextT, "Features"),
1342         "Implementation must define: `enum Features = AnsiTextImplementationFeatures.xxx;`"
1343     );
1344 
1345     static if(TextT.Features == AnsiTextImplementationFeatures.basic)
1346     {
1347         static assert(__traits(hasMember, TextT, "newSlice"),
1348             "Implementation must define: `char[] newSlice(size_t minLength)`"
1349         );
1350         static assert(__traits(hasMember, TextT, "toSink"),
1351             "Implementation must define: `void toSink(Sink)(Sink sink)`"
1352         );
1353     }
1354 }
1355 
1356 ///
1357 template AnsiTextStackImplementation(size_t Capacity)
1358 {
1359     mixin template AnsiTextStackImplementation()
1360     {
1361         enum Features = AnsiTextImplementationFeatures.basic;
1362 
1363         private char[Capacity] _output;
1364         private size_t _cursor;
1365 
1366         // This code by itself is *technically* safe, but the way the user uses it might not be.
1367 
1368         @safe @nogc
1369         char[] newSlice(size_t minLength) nothrow
1370         {
1371             const end = this._cursor + minLength;
1372             assert(end <= this._output.length, "Ran out of space.");
1373 
1374             auto slice = this._output[this._cursor..end];
1375             this._cursor = end;
1376 
1377             return slice;
1378         }
1379 
1380         void toSink(Sink)(ref Sink sink)
1381         if(isOutputRange!(Sink, char[]))
1382         {
1383             sink.put(this.asStackSlice);
1384             sink.put(ANSI_COLOUR_RESET);
1385         }
1386 
1387         @safe @nogc
1388         char[] asStackSlice() nothrow
1389         {
1390             return this._output[0..this._cursor];    
1391         }
1392 
1393         @safe @nogc
1394         char[Capacity] asStackSliceCopy(ref size_t lengthInUse) nothrow
1395         {
1396             lengthInUse = this._cursor;
1397             return this._output;
1398         }
1399     }
1400 }
1401 
1402 /++
1403  + A basic implementation using a static amount of stack memory.
1404  +
1405  + Sinks should keep in mind that they're being passed a slice to stack memory, so should not persist slices outside of their `.put` function,
1406  + they must instead make a copy of the data.
1407  +
1408  + This implementation will fail an assert if the user attempts to push more data into it than it can handle.
1409  +
1410  + Params:
1411  +  Capacity = The amount of characters to use on the stack.
1412  + ++/
1413 alias AnsiTextStack(size_t Capacity) = AnsiText!(AnsiTextStackImplementation!Capacity);
1414 
1415 /+++ READING/PARSING +++/
1416 
1417 /++
1418  + Executes the SGR sequence found in `input`, and populates the passed in `style` based on the command sequence.
1419  +
1420  + Anything directly provided by this library is supported.
1421  +
1422  + The previous state of `style` is preserved unless specifically untoggled/reset via the command sequence (e.g. `ESC[0m` to reset everything).
1423  +
1424  + If an error occurs during execution of the sequence, the given `style` is left completely unmodified.
1425  +
1426  + Params:
1427  +  input     = The slice containing the command sequence. The first character should be the start (`ANSI_CSI`) character of the sequence (`\033`), and
1428  +              characters will continue to be read until the command sequence has been finished. Any characters after the command sequence are left unread.
1429  +  style     = A reference to an `AnsiStyleSet` to populate. As mentioned, this function will only untoggle styling, or reset the style if the command sequence specifies.
1430  +              This value is left unmodified if an error is encountered.
1431  +  charsRead = This value will be set to the amount of chars read from the given `input`, so the caller knows where to continue reading from (if applicable).
1432  +              This value is populated on both error and success.
1433  +
1434  + Returns:
1435  +  Either `null` on success, or a string describing the error that was encountered.
1436  + ++/
1437 @safe @nogc
1438 string ansiExecuteSgrSequence(const(char)[] input, ref AnsiStyleSet style, out size_t charsRead) nothrow
1439 {
1440     enum ReadResult { foundEndMarker, foundSemiColon, foundEnd, foundBadCharacter }
1441 
1442     if(input.length < 3)
1443         return "A valid SGR is at least 3 characters long: ESC[m";
1444 
1445     if(input[0..ANSI_CSI.length] != ANSI_CSI)
1446         return "Input does not start with the CSI: ESC[";
1447 
1448     auto styleCopy = style;
1449 
1450     charsRead = 2;
1451     ReadResult readToSemiColonOrEndMarker(ref const(char)[] slice)
1452     {
1453         const start = charsRead;
1454         while(true)
1455         {
1456             if(charsRead >= input.length)
1457                 return ReadResult.foundEnd;
1458 
1459             const ch = input[charsRead];
1460             if(ch == 'm')
1461             {
1462                 slice = input[start..charsRead];
1463                 return ReadResult.foundEndMarker;
1464             }
1465             else if(ch == ';')
1466             {
1467                 slice = input[start..charsRead];
1468                 return ReadResult.foundSemiColon;
1469             }
1470             else if(ch >= '0' && ch <= '9')
1471             {
1472                 charsRead++;
1473                 continue;
1474             }
1475             else
1476                 return ReadResult.foundBadCharacter;
1477         }
1478     }
1479 
1480     int toValue(const(char)[] slice)
1481     {
1482         import libd.data.conv;
1483         return (slice.length == 0) ? 0 : fromBase10!int(slice).value;
1484     }
1485 
1486     string resultToString(ReadResult result)
1487     {
1488         final switch(result) with(ReadResult)
1489         {
1490             case foundEnd: return "Unexpected end of input.";
1491             case foundBadCharacter: return "Unexpected character in input.";
1492 
1493             case foundSemiColon: return "Unexpected semi-colon.";
1494             case foundEndMarker: return "Unexpected end marker ('m').";
1495         }
1496     }
1497 
1498     const(char)[] generalSlice;
1499     while(charsRead < input.length)
1500     {
1501         const ch = input[charsRead];
1502 
1503         switch(ch)
1504         {
1505             case '0':..case '9':
1506                 auto result = readToSemiColonOrEndMarker(generalSlice);
1507                 if(result != ReadResult.foundSemiColon && result != ReadResult.foundEndMarker)
1508                     return resultToString(result);
1509 
1510                 const commandAsNum = toValue(generalSlice);
1511                 Switch: switch(commandAsNum)
1512                 {
1513                     // Full reset
1514                     case 0: styleCopy = AnsiStyleSet.init; break;
1515 
1516                     // Basic style flag setters.
1517                     static foreach(name; __traits(allMembers, AnsiSgrStyle))
1518                     {{
1519                         enum member = __traits(getMember, AnsiSgrStyle, name);
1520                         static if(member != AnsiSgrStyle.none)
1521                         {
1522                             case cast(int)member:
1523                                 styleCopy.style = styleCopy.style.set(member, true);
1524                                 break Switch;
1525                         }
1526                     }}
1527 
1528                     // Set foreground to a 4-bit colour.
1529                     case 30:..case 37:
1530                     case 90:..case 97:
1531                         styleCopy.fg = cast(Ansi4BitColour)commandAsNum;
1532                         break;
1533 
1534                     // Set background to a 4-bit colour.
1535                     case 40:..case 47:
1536                     case 100:..case 107:
1537                         styleCopy.bg = cast(Ansi4BitColour)(commandAsNum - ANSI_FG_TO_BG_INCREMENT); // Since we work in the foreground colour until we're outputting to sequences.
1538                         break;
1539                     
1540                     // Set foreground (38) or background (48) to an 8-bit (5) or 24-bit (2) colour.
1541                     case 38:
1542                     case 48:
1543                         if(result == ReadResult.foundEndMarker)
1544                             return "Incomplete 'set foreground/background' command, expected another parameter, got none.";
1545                         charsRead++; // Skip semi-colon.
1546 
1547                         result = readToSemiColonOrEndMarker(generalSlice);
1548                         if(result != ReadResult.foundEndMarker && result != ReadResult.foundSemiColon)
1549                             return resultToString(result);
1550                         if(result == ReadResult.foundSemiColon)
1551                             charsRead++;
1552 
1553                         const subcommand = toValue(generalSlice);
1554                         if(subcommand == 5)
1555                         {
1556                             result = readToSemiColonOrEndMarker(generalSlice);
1557                             if(result != ReadResult.foundEndMarker && result != ReadResult.foundSemiColon)
1558                                 return resultToString(result);
1559                             if(result == ReadResult.foundSemiColon)
1560                                 charsRead++;
1561 
1562                             if(commandAsNum == 38) styleCopy.fg = cast(Ansi8BitColour)toValue(generalSlice);
1563                             else                   styleCopy.bg = cast(Ansi8BitColour)toValue(generalSlice);
1564                         }
1565                         else if(subcommand == 2)
1566                         {
1567                             ubyte[3] components;
1568                             foreach(i; 0..3)
1569                             {
1570                                 result = readToSemiColonOrEndMarker(generalSlice);
1571                                 if(result != ReadResult.foundEndMarker && result != ReadResult.foundSemiColon)
1572                                     return resultToString(result);
1573                                 if(result == ReadResult.foundSemiColon)
1574                                     charsRead++;
1575 
1576                                 components[i] = cast(ubyte)toValue(generalSlice);
1577                             }
1578 
1579                             if(commandAsNum == 38) styleCopy.fg = AnsiRgbColour(components);
1580                             else                   styleCopy.bg = AnsiRgbColour(components);
1581                         }
1582                         else
1583                             break; // Assume it's a valid command, just that we don't support this specific sub command.
1584                         break;
1585 
1586                     default: continue; // Assume it's just a command we don't support.
1587                 }
1588                 break;
1589 
1590             case 'm':
1591                 charsRead++;
1592                 style = styleCopy;
1593                 return null;
1594 
1595             case ';': charsRead++; continue;
1596             default: return null; // Assume we've hit an end-marker we don't support.
1597         }
1598     }
1599 
1600     return "Input did not contain an end marker.";
1601 }
1602 ///
1603 @("ansiExecuteSgrSequence")
1604 unittest
1605 {
1606     import libd.data.conv : to;
1607 
1608     void test(AnsiStyleSet sourceAndExpected)
1609     {
1610         char[AnsiStyleSet.MAX_CHARS_NEEDED] buffer;
1611         String sequence;
1612         sequence.putMany(ANSI_CSI, sourceAndExpected.toSequence(buffer), ANSI_COLOUR_END);
1613 
1614         AnsiStyleSet got;
1615         size_t charsRead;
1616         const error = ansiExecuteSgrSequence(sequence[0..$], got, charsRead);
1617         if(error !is null)
1618             assert(false, error);
1619 
1620         assert(charsRead == sequence.length);
1621         assert(sourceAndExpected == got);
1622     }
1623 
1624     test(AnsiStyleSet.init.fg(Ansi4BitColour.green));
1625     test(AnsiStyleSet.init.fg(Ansi4BitColour.brightGreen));
1626     test(AnsiStyleSet.init.bg(Ansi4BitColour.green));
1627     test(AnsiStyleSet.init.bg(Ansi4BitColour.brightGreen));
1628     test(AnsiStyleSet.init.fg(Ansi4BitColour.green).bg(Ansi4BitColour.brightRed));
1629 
1630     test(AnsiStyleSet.init.fg(20));
1631     test(AnsiStyleSet.init.bg(40));
1632     test(AnsiStyleSet.init.fg(20).bg(40));
1633 
1634     test(AnsiStyleSet.init.fg(AnsiRgbColour(255, 128, 64)));
1635     test(AnsiStyleSet.init.bg(AnsiRgbColour(255, 128, 64)));
1636     test(AnsiStyleSet.init.fg(AnsiRgbColour(255, 128, 64)).bg(AnsiRgbColour(64, 128, 255)));
1637     
1638     static foreach(name; __traits(allMembers, AnsiSgrStyle))
1639     {{
1640         enum member = __traits(getMember, AnsiSgrStyle, name);
1641         static if(member != AnsiSgrStyle.none)
1642             test(AnsiStyleSet.init.style(AnsiStyle.init.set(member, true)));
1643     }}
1644 
1645     test(AnsiStyleSet.init.style(AnsiStyle.init.bold.underline.slowBlink.italic));
1646 }
1647 
1648 /++
1649  + The resulting object from `AnsiSectionRange`, describes whether a slice of text is an ANSI sequence or not.
1650  + ++/
1651 struct AnsiSection
1652 {
1653     /// `true` if the slice is an ANSI sequence, `false` if it's just text.
1654     bool isAnsiSequence;
1655 
1656     /// The slice of text that this section consists of.
1657     const(char)[] slice;
1658 }
1659 
1660 /++
1661  + An input range of `AnsiSection`s that splits a piece of text up into ANSI sequence and plain text sections.
1662  +
1663  + For example, the text "\033[37mABC\033[0m" has three sections: [ANSI "\033[37m", TEXT "ABC", ANSI "\033[0m"].
1664  + ++/
1665 struct AnsiSectionRange
1666 {
1667     private
1668     {
1669         const(char)[] _input;
1670         size_t        _cursor;
1671         AnsiSection   _front;
1672         bool          _empty = true; // So .init.empty is true
1673     }
1674     
1675     @safe @nogc nothrow:
1676 
1677     ///
1678     this(const(char)[] input)
1679     {
1680         this._input = input;
1681         this._empty = false;
1682         this.popFront();
1683     }
1684 
1685     ///
1686     bool empty() const
1687     {
1688         return this._empty;
1689     }
1690     
1691     ///
1692     AnsiSection front() const
1693     {
1694         return this._front;
1695     }
1696 
1697     ///
1698     void popFront()
1699     {
1700         assert(!this.empty, "Cannot pop empty range.");
1701 
1702         if(this._cursor >= this._input.length)
1703         {
1704             this._empty = true;
1705             return;
1706         }
1707 
1708         if((this._input.length - this._cursor) >= ANSI_CSI.length 
1709         && this._input[this._cursor..this._cursor + ANSI_CSI.length] == ANSI_CSI)
1710             this.readSequence();
1711         else
1712             this.readText();
1713     }
1714 
1715     private void readText()
1716     {
1717         const start = this._cursor;
1718         
1719         while(this._cursor < this._input.length)
1720         {
1721             if((this._input.length - this._cursor) >= 2 && this._input[this._cursor..this._cursor+2] == ANSI_CSI)
1722                 break;
1723 
1724             this._cursor++;
1725         }
1726 
1727         this._front.isAnsiSequence = false;
1728         this._front.slice = this._input[start..this._cursor];
1729     }
1730 
1731     private void readSequence()
1732     {
1733         const start = this._cursor;
1734         this._cursor += ANSI_CSI.length; // Already validated by popFront.
1735 
1736         while(this._cursor < this._input.length 
1737            && this.isValidAnsiChar(this._input[this._cursor]))
1738            this._cursor++;
1739 
1740         if(this._cursor < this._input.length)
1741             this._cursor++; // We've hit a non-ansi character, so we increment to include it in the output.
1742 
1743         this._front.isAnsiSequence = true;
1744         this._front.slice = this._input[start..this._cursor];
1745     }
1746 
1747     private bool isValidAnsiChar(char ch)
1748     {
1749         return (
1750             (ch >= '0' && ch <= '9')
1751          || ch == ';'
1752         );
1753     }
1754 }
1755 ///
1756 @("AnsiSectionRange")
1757 unittest
1758 {
1759     assert(AnsiSectionRange.init.empty);
1760     assert("".asAnsiSections.empty);
1761 
1762     auto r = "No Ansi".asAnsiSections;
1763     assert(!r.empty);
1764     assert(!r.front.isAnsiSequence);
1765     assert(r.front.slice == "No Ansi");
1766 
1767     r = "\033[m".asAnsiSections;
1768     assert(!r.empty);
1769     assert(r.front.isAnsiSequence);
1770     assert(r.front.slice == "\033[m");
1771 
1772     r = "\033[38;2;255;128;64;1;4;48;5;2m".asAnsiSections;
1773     assert(!r.empty);
1774     assert(r.front.isAnsiSequence);
1775     assert(r.front.slice == "\033[38;2;255;128;64;1;4;48;5;2m");
1776 
1777     r = "\033[mABC\033[m".asAnsiSections;
1778     assert(r.front.isAnsiSequence);
1779     assert(r.front.slice == "\033[m", r.front.slice);
1780     r.popFront();
1781     assert(!r.empty);
1782     assert(!r.front.isAnsiSequence);
1783     assert(r.front.slice == "ABC", r.front.slice);
1784     r.popFront();
1785     assert(!r.empty);
1786     assert(r.front.isAnsiSequence);
1787     assert(r.front.slice == "\033[m");
1788     r.popFront();
1789     assert(r.empty);
1790 
1791     r = "ABC\033[mDEF".asAnsiSections;
1792     assert(!r.front.isAnsiSequence);
1793     assert(r.front.slice == "ABC");
1794     r.popFront();
1795     assert(r.front.isAnsiSequence);
1796     assert(r.front.slice == "\033[m");
1797     r.popFront();
1798     assert(!r.front.isAnsiSequence);
1799     assert(r.front.slice == "DEF");
1800     r.popFront();
1801     assert(r.empty);
1802 }
1803 
1804 /+++ PUBLIC HELPERS +++/
1805 
1806 /// Determines if `CT` is a valid RGB data type.
1807 enum isUserDefinedRgbType(CT) =
1808 (
1809     __traits(hasMember, CT, "r")
1810  && __traits(hasMember, CT, "g")
1811  && __traits(hasMember, CT, "b")
1812 );
1813 
1814 /++
1815  + Converts any suitable data type into an `AnsiColour`.
1816  +
1817  + Params:
1818  +  colour = The colour to convert.
1819  +
1820  + Returns:
1821  +  An `AnsiColour` created from the given `colour`.
1822  +
1823  + See_Also:
1824  +  `isUserDefinedRgbType`
1825  + ++/
1826 AnsiColour to(T : AnsiColour, CT)(CT colour)
1827 if(isUserDefinedRgbType!CT)
1828 {
1829     return AnsiColour(colour.r, colour.g, colour.b);
1830 }
1831 ///
1832 @("to!AnsiColour(User defined)")
1833 @safe @nogc nothrow pure
1834 unittest
1835 {
1836     static struct RGB
1837     {
1838         ubyte r;
1839         ubyte g;
1840         ubyte b;
1841     }
1842 
1843     assert(RGB(255, 128, 64).to!AnsiColour == AnsiColour(255, 128, 64));
1844 }
1845 
1846 /// ditto.
1847 AnsiColour toBg(T)(T c)
1848 {
1849     auto colour = to!AnsiColour(c);
1850     colour.isBg = IsBgColour.yes;
1851     return colour;
1852 }
1853 ///
1854 @("toBg")
1855 @safe @nogc nothrow pure
1856 unittest
1857 {
1858     static struct RGB
1859     {
1860         ubyte r;
1861         ubyte g;
1862         ubyte b;
1863     }
1864 
1865     assert(RGB(255, 128, 64).toBg == AnsiColour(255, 128, 64, IsBgColour.yes));
1866 }
1867 
1868 /++
1869  + Creates an `AnsiTextLite` from the given `text`. This function is mostly used when using
1870  + the fluent UFCS chaining pattern.
1871  +
1872  + Params:
1873  +  text = The text to use.
1874  +
1875  + Returns:
1876  +  An `AnsiTextLite` from the given `text`.
1877  + ++/
1878 @safe @nogc
1879 AnsiTextLite ansi(const(char)[] text) nothrow pure
1880 {
1881     return AnsiTextLite(text);
1882 }
1883 ///
1884 @("ansi")
1885 unittest
1886 {
1887     version(none)
1888     {
1889         import std.stdio;
1890         writeln("Hello, World!".ansi
1891                                .fg(Ansi4BitColour.red)
1892                                .bg(AnsiRgbColour(128, 128, 128))
1893                                .style(AnsiStyle.init.bold.underline)
1894         );
1895     }
1896 }
1897 
1898 /++
1899  + Constructs an `AnsiSectionRange` from the given `slice`.
1900  + ++/
1901 @safe @nogc
1902 AnsiSectionRange asAnsiSections(const(char)[] slice) nothrow
1903 {
1904     return AnsiSectionRange(slice);
1905 }
1906 
1907 /+++ PRIVATE HELPERS +++/
1908 private char[] numToStrBase10(NumT)(char[] buffer, NumT num)
1909 {
1910     if(num == 0)
1911     {
1912         if(buffer.length > 0)
1913         {
1914             buffer[0] = '0';
1915             return buffer[0..1];
1916         }
1917         else
1918             return null;
1919     }
1920 
1921     const CHARS = "0123456789";
1922 
1923     ptrdiff_t i = buffer.length;
1924     while(i > 0 && num > 0)
1925     {
1926         buffer[--i] = CHARS[num % 10];
1927         num /= 10;
1928     }
1929 
1930     return buffer[i..$];
1931 }
1932 ///
1933 @("numToStrBase10")
1934 unittest
1935 {
1936     char[2] b;
1937     assert(numToStrBase10(b, 32) == "32");
1938 }