Add ANSI control code emulation for the Windows console
[git/git.git] / compat / winansi.c
1 /*
2 * Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
3 */
4
5 #include <windows.h>
6 #include "../git-compat-util.h"
7
8 /*
9 Functions to be wrapped:
10 */
11 #undef printf
12 #undef fprintf
13 #undef fputs
14 /* TODO: write */
15
16 /*
17 ANSI codes used by git: m, K
18
19 This file is git-specific. Therefore, this file does not attempt
20 to implement any codes that are not used by git.
21
22 TODO: K
23 */
24
25 static HANDLE console;
26 static WORD plain_attr;
27 static WORD attr;
28 static int negative;
29
30 static void init(void)
31 {
32 CONSOLE_SCREEN_BUFFER_INFO sbi;
33
34 static int initialized = 0;
35 if (initialized)
36 return;
37
38 console = GetStdHandle(STD_OUTPUT_HANDLE);
39 if (console == INVALID_HANDLE_VALUE)
40 console = NULL;
41
42 if (!console)
43 return;
44
45 GetConsoleScreenBufferInfo(console, &sbi);
46 attr = plain_attr = sbi.wAttributes;
47 negative = 0;
48
49 initialized = 1;
50 }
51
52
53 #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
54 #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
55
56 static void set_console_attr(void)
57 {
58 WORD attributes = attr;
59 if (negative) {
60 attributes &= ~FOREGROUND_ALL;
61 attributes &= ~BACKGROUND_ALL;
62
63 /* This could probably use a bitmask
64 instead of a series of ifs */
65 if (attr & FOREGROUND_RED)
66 attributes |= BACKGROUND_RED;
67 if (attr & FOREGROUND_GREEN)
68 attributes |= BACKGROUND_GREEN;
69 if (attr & FOREGROUND_BLUE)
70 attributes |= BACKGROUND_BLUE;
71
72 if (attr & BACKGROUND_RED)
73 attributes |= FOREGROUND_RED;
74 if (attr & BACKGROUND_GREEN)
75 attributes |= FOREGROUND_GREEN;
76 if (attr & BACKGROUND_BLUE)
77 attributes |= FOREGROUND_BLUE;
78 }
79 SetConsoleTextAttribute(console, attributes);
80 }
81
82 static const char *set_attr(const char *str)
83 {
84 const char *func;
85 size_t len = strspn(str, "0123456789;");
86 func = str + len;
87
88 switch (*func) {
89 case 'm':
90 do {
91 long val = strtol(str, (char **)&str, 10);
92 switch (val) {
93 case 0: /* reset */
94 attr = plain_attr;
95 negative = 0;
96 break;
97 case 1: /* bold */
98 attr |= FOREGROUND_INTENSITY;
99 break;
100 case 2: /* faint */
101 case 22: /* normal */
102 attr &= ~FOREGROUND_INTENSITY;
103 break;
104 case 3: /* italic */
105 /* Unsupported */
106 break;
107 case 4: /* underline */
108 case 21: /* double underline */
109 /* Wikipedia says this flag does nothing */
110 /* Furthermore, mingw doesn't define this flag
111 attr |= COMMON_LVB_UNDERSCORE; */
112 break;
113 case 24: /* no underline */
114 /* attr &= ~COMMON_LVB_UNDERSCORE; */
115 break;
116 case 5: /* slow blink */
117 case 6: /* fast blink */
118 /* We don't have blink, but we do have
119 background intensity */
120 attr |= BACKGROUND_INTENSITY;
121 break;
122 case 25: /* no blink */
123 attr &= ~BACKGROUND_INTENSITY;
124 break;
125 case 7: /* negative */
126 negative = 1;
127 break;
128 case 27: /* positive */
129 negative = 0;
130 break;
131 case 8: /* conceal */
132 case 28: /* reveal */
133 /* Unsupported */
134 break;
135 case 30: /* Black */
136 attr &= ~FOREGROUND_ALL;
137 break;
138 case 31: /* Red */
139 attr &= ~FOREGROUND_ALL;
140 attr |= FOREGROUND_RED;
141 break;
142 case 32: /* Green */
143 attr &= ~FOREGROUND_ALL;
144 attr |= FOREGROUND_GREEN;
145 break;
146 case 33: /* Yellow */
147 attr &= ~FOREGROUND_ALL;
148 attr |= FOREGROUND_RED | FOREGROUND_GREEN;
149 break;
150 case 34: /* Blue */
151 attr &= ~FOREGROUND_ALL;
152 attr |= FOREGROUND_BLUE;
153 break;
154 case 35: /* Magenta */
155 attr &= ~FOREGROUND_ALL;
156 attr |= FOREGROUND_RED | FOREGROUND_BLUE;
157 break;
158 case 36: /* Cyan */
159 attr &= ~FOREGROUND_ALL;
160 attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
161 break;
162 case 37: /* White */
163 attr |= FOREGROUND_RED |
164 FOREGROUND_GREEN |
165 FOREGROUND_BLUE;
166 break;
167 case 38: /* Unknown */
168 break;
169 case 39: /* reset */
170 attr &= ~FOREGROUND_ALL;
171 attr |= (plain_attr & FOREGROUND_ALL);
172 break;
173 case 40: /* Black */
174 attr &= ~BACKGROUND_ALL;
175 break;
176 case 41: /* Red */
177 attr &= ~BACKGROUND_ALL;
178 attr |= BACKGROUND_RED;
179 break;
180 case 42: /* Green */
181 attr &= ~BACKGROUND_ALL;
182 attr |= BACKGROUND_GREEN;
183 break;
184 case 43: /* Yellow */
185 attr &= ~BACKGROUND_ALL;
186 attr |= BACKGROUND_RED | BACKGROUND_GREEN;
187 break;
188 case 44: /* Blue */
189 attr &= ~BACKGROUND_ALL;
190 attr |= BACKGROUND_BLUE;
191 break;
192 case 45: /* Magenta */
193 attr &= ~BACKGROUND_ALL;
194 attr |= BACKGROUND_RED | BACKGROUND_BLUE;
195 break;
196 case 46: /* Cyan */
197 attr &= ~BACKGROUND_ALL;
198 attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
199 break;
200 case 47: /* White */
201 attr |= BACKGROUND_RED |
202 BACKGROUND_GREEN |
203 BACKGROUND_BLUE;
204 break;
205 case 48: /* Unknown */
206 break;
207 case 49: /* reset */
208 attr &= ~BACKGROUND_ALL;
209 attr |= (plain_attr & BACKGROUND_ALL);
210 break;
211 default:
212 /* Unsupported code */
213 break;
214 }
215 str++;
216 } while (*(str-1) == ';');
217
218 set_console_attr();
219 break;
220 case 'K':
221 /* TODO */
222 break;
223 default:
224 /* Unsupported code */
225 break;
226 }
227
228 return func + 1;
229 }
230
231 static int ansi_emulate(const char *str, FILE *stream)
232 {
233 int rv = 0;
234 const char *pos = str;
235
236 while (*pos) {
237 pos = strstr(str, "\033[");
238 if (pos) {
239 size_t len = pos - str;
240
241 if (len) {
242 size_t out_len = fwrite(str, 1, len, stream);
243 rv += out_len;
244 if (out_len < len)
245 return rv;
246 }
247
248 str = pos + 2;
249 rv += 2;
250
251 fflush(stream);
252
253 pos = set_attr(str);
254 rv += pos - str;
255 str = pos;
256 } else {
257 rv += strlen(str);
258 fputs(str, stream);
259 return rv;
260 }
261 }
262 return rv;
263 }
264
265 int winansi_fputs(const char *str, FILE *stream)
266 {
267 int rv;
268
269 if (!isatty(fileno(stream)))
270 return fputs(str, stream);
271
272 init();
273
274 if (!console)
275 return fputs(str, stream);
276
277 rv = ansi_emulate(str, stream);
278
279 if (rv >= 0)
280 return 0;
281 else
282 return EOF;
283 }
284
285 static int winansi_vfprintf(FILE *stream, const char *format, va_list list)
286 {
287 int len, rv;
288 char small_buf[256];
289 char *buf = small_buf;
290 va_list cp;
291
292 if (!isatty(fileno(stream)))
293 goto abort;
294
295 init();
296
297 if (!console)
298 goto abort;
299
300 va_copy(cp, list);
301 len = vsnprintf(small_buf, sizeof(small_buf), format, cp);
302 va_end(cp);
303
304 if (len > sizeof(small_buf) - 1) {
305 buf = malloc(len + 1);
306 if (!buf)
307 goto abort;
308
309 len = vsnprintf(buf, len + 1, format, list);
310 }
311
312 rv = ansi_emulate(buf, stream);
313
314 if (buf != small_buf)
315 free(buf);
316 return rv;
317
318 abort:
319 rv = vfprintf(stream, format, list);
320 return rv;
321 }
322
323 int winansi_fprintf(FILE *stream, const char *format, ...)
324 {
325 va_list list;
326 int rv;
327
328 va_start(list, format);
329 rv = winansi_vfprintf(stream, format, list);
330 va_end(list);
331
332 return rv;
333 }
334
335 int winansi_printf(const char *format, ...)
336 {
337 va_list list;
338 int rv;
339
340 va_start(list, format);
341 rv = winansi_vfprintf(stdout, format, list);
342 va_end(list);
343
344 return rv;
345 }