1 /**
2  * Authors: ponce
3  * Date: July 28, 2014
4  * License: Licensed under the MIT license. See LICENSE for more information
5  * Version: 1.0.2
6  */
7 module colorize.winterm;
8 
9 version(Windows)
10 {
11   import core.sys.windows.windows;
12 
13   // Patch for DMD 2.065 compatibility
14   static if( __VERSION__ < 2066 ) private enum nogc = 1;
15 
16   // This is a state machine to enable terminal colors on Windows.
17   // Parses and interpret ANSI/VT100 Terminal Control Escape Sequences.
18   // Only supports colour sequences, will output char incorrectly on invalid input.
19   struct WinTermEmulation
20   {
21   public:
22     @nogc void initialize() nothrow
23     {
24       // saves console attributes
25       _console = GetStdHandle(STD_OUTPUT_HANDLE);
26       _savedInitialColor = (0 != GetConsoleScreenBufferInfo(_console, &consoleInfo));
27       _state = State.initial;
28     }
29 
30     @nogc ~this() nothrow
31     {
32       // Restore initial text attributes on release
33       if (_savedInitialColor)
34       {
35         SetConsoleTextAttribute(_console, consoleInfo.wAttributes);
36         _savedInitialColor = false;
37       }
38     }
39 
40     enum CharAction
41     {
42       write,
43       drop,
44       flush
45     }
46 
47     // Eat one character and update color state accordingly.
48     // Returns what to do with the fed character.
49     @nogc CharAction feed(dchar d) nothrow
50     {
51       final switch(_state) with (State)
52       {
53         case initial:
54           if (d == '\x1B')
55           {
56             _state = escaped;
57             return CharAction.flush;
58           }
59           break;
60 
61         case escaped:
62           if (d == '[')
63           {
64             _state = readingAttribute;
65             _parsedAttr = 0;
66             return CharAction.drop;
67           }
68           break;
69 
70 
71         case readingAttribute:
72           if (d >= '0' && d <= '9')
73           {
74             _parsedAttr = _parsedAttr * 10 + (d - '0');
75             return CharAction.drop;
76           }
77           else if (d == ';')
78           {
79             executeAttribute(_parsedAttr);
80             _parsedAttr = 0;
81             return CharAction.drop;
82           }
83           else if (d == 'm')
84           {
85             executeAttribute(_parsedAttr);
86             _state = State.initial;
87             return CharAction.drop;
88           }
89           break;
90       }
91       return CharAction.write;
92     }
93 
94   private:
95     HANDLE _console;
96     bool _savedInitialColor;
97     CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
98     State _state;
99     WORD _currentAttr;
100     int _parsedAttr;
101 
102     enum State
103     {
104       initial,
105       escaped,
106       readingAttribute
107     }
108 
109     @nogc void setForegroundColor(WORD fgFlags) nothrow
110     {
111       _currentAttr = _currentAttr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
112       _currentAttr = _currentAttr | fgFlags;
113       SetConsoleTextAttribute(_console, _currentAttr);
114     }
115 
116     @nogc void setBackgroundColor(WORD bgFlags) nothrow
117     {
118       _currentAttr = _currentAttr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
119       _currentAttr = _currentAttr | bgFlags;
120       SetConsoleTextAttribute(_console, _currentAttr);
121     }
122 
123     @nogc void executeAttribute(int attr) nothrow
124     {
125       switch (attr)
126       {
127         case 0:
128           // reset all attributes
129           SetConsoleTextAttribute(_console, consoleInfo.wAttributes);
130           break;
131 
132         default:
133           if ( (30 <= attr && attr <= 37) || (90 <= attr && attr <= 97) )
134           {
135             WORD color = 0;
136             if (90 <= attr && attr <= 97)
137             {
138               color = FOREGROUND_INTENSITY;
139               attr -= 60;
140             }
141             attr -= 30;
142             color |= (attr & 1 ? FOREGROUND_RED : 0) | (attr & 2 ? FOREGROUND_GREEN : 0) | (attr & 4 ? FOREGROUND_BLUE : 0);
143             setForegroundColor(color);
144           }
145 
146           if ( (40 <= attr && attr <= 47) || (100 <= attr && attr <= 107) )
147           {
148             WORD color = 0;
149             if (100 <= attr && attr <= 107)
150             {
151               color = BACKGROUND_INTENSITY;
152               attr -= 60;
153             }
154             attr -= 40;
155             color |= (attr & 1 ? BACKGROUND_RED : 0) | (attr & 2 ? BACKGROUND_GREEN : 0) | (attr & 4 ? BACKGROUND_BLUE : 0);
156             setBackgroundColor(color);
157           }
158       }
159     }
160   }
161 }