Detect console streams more reliably on Windows
[git/dscho.git] / compat / winansi.c
blobc4be401a6eaffe6180ffb1c4ef7d44a24f61d374
1 /*
2 * Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
3 */
5 #include "../git-compat-util.h"
6 #include <malloc.h>
8 /*
9 Functions to be wrapped:
11 #undef printf
12 #undef fprintf
13 #undef fputs
14 #undef vfprintf
15 /* TODO: write */
18 ANSI codes used by git: m, K
20 This file is git-specific. Therefore, this file does not attempt
21 to implement any codes that are not used by git.
24 static HANDLE console;
25 static WORD plain_attr;
26 static WORD attr;
27 static int negative;
28 static FILE *last_stream = NULL;
30 static int is_console(FILE *stream)
32 CONSOLE_SCREEN_BUFFER_INFO sbi;
33 HANDLE hcon;
35 static int initialized = 0;
37 /* use cached value if stream hasn't changed */
38 if (stream == last_stream)
39 return console != NULL;
41 last_stream = stream;
42 console = NULL;
44 /* get OS handle of the stream */
45 hcon = (HANDLE) _get_osfhandle(_fileno(stream));
46 if (hcon == INVALID_HANDLE_VALUE)
47 return 0;
49 /* check if its a handle to a console output screen buffer */
50 if (!GetConsoleScreenBufferInfo(hcon, &sbi))
51 return 0;
53 if (!initialized) {
54 attr = plain_attr = sbi.wAttributes;
55 negative = 0;
56 initialized = 1;
59 console = hcon;
60 return 1;
63 static int write_console(const char *str, size_t len)
65 /* convert utf-8 to utf-16, write directly to console */
66 int wlen = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0);
67 wchar_t *wbuf = (wchar_t *) alloca(wlen * sizeof(wchar_t));
68 MultiByteToWideChar(CP_UTF8, 0, str, len, wbuf, wlen);
70 WriteConsoleW(console, wbuf, wlen, NULL, NULL);
72 /* return original (utf-8 encoded) length */
73 return len;
76 #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
77 #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
79 static void set_console_attr(void)
81 WORD attributes = attr;
82 if (negative) {
83 attributes &= ~FOREGROUND_ALL;
84 attributes &= ~BACKGROUND_ALL;
86 /* This could probably use a bitmask
87 instead of a series of ifs */
88 if (attr & FOREGROUND_RED)
89 attributes |= BACKGROUND_RED;
90 if (attr & FOREGROUND_GREEN)
91 attributes |= BACKGROUND_GREEN;
92 if (attr & FOREGROUND_BLUE)
93 attributes |= BACKGROUND_BLUE;
95 if (attr & BACKGROUND_RED)
96 attributes |= FOREGROUND_RED;
97 if (attr & BACKGROUND_GREEN)
98 attributes |= FOREGROUND_GREEN;
99 if (attr & BACKGROUND_BLUE)
100 attributes |= FOREGROUND_BLUE;
102 SetConsoleTextAttribute(console, attributes);
105 static void erase_in_line(void)
107 CONSOLE_SCREEN_BUFFER_INFO sbi;
108 DWORD dummy; /* Needed for Windows 7 (or Vista) regression */
110 if (!console)
111 return;
113 GetConsoleScreenBufferInfo(console, &sbi);
114 FillConsoleOutputCharacterA(console, ' ',
115 sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
116 &dummy);
120 static const char *set_attr(const char *str)
122 const char *func;
123 size_t len = strspn(str, "0123456789;");
124 func = str + len;
126 switch (*func) {
127 case 'm':
128 do {
129 long val = strtol(str, (char **)&str, 10);
130 switch (val) {
131 case 0: /* reset */
132 attr = plain_attr;
133 negative = 0;
134 break;
135 case 1: /* bold */
136 attr |= FOREGROUND_INTENSITY;
137 break;
138 case 2: /* faint */
139 case 22: /* normal */
140 attr &= ~FOREGROUND_INTENSITY;
141 break;
142 case 3: /* italic */
143 /* Unsupported */
144 break;
145 case 4: /* underline */
146 case 21: /* double underline */
147 /* Wikipedia says this flag does nothing */
148 /* Furthermore, mingw doesn't define this flag
149 attr |= COMMON_LVB_UNDERSCORE; */
150 break;
151 case 24: /* no underline */
152 /* attr &= ~COMMON_LVB_UNDERSCORE; */
153 break;
154 case 5: /* slow blink */
155 case 6: /* fast blink */
156 /* We don't have blink, but we do have
157 background intensity */
158 attr |= BACKGROUND_INTENSITY;
159 break;
160 case 25: /* no blink */
161 attr &= ~BACKGROUND_INTENSITY;
162 break;
163 case 7: /* negative */
164 negative = 1;
165 break;
166 case 27: /* positive */
167 negative = 0;
168 break;
169 case 8: /* conceal */
170 case 28: /* reveal */
171 /* Unsupported */
172 break;
173 case 30: /* Black */
174 attr &= ~FOREGROUND_ALL;
175 break;
176 case 31: /* Red */
177 attr &= ~FOREGROUND_ALL;
178 attr |= FOREGROUND_RED;
179 break;
180 case 32: /* Green */
181 attr &= ~FOREGROUND_ALL;
182 attr |= FOREGROUND_GREEN;
183 break;
184 case 33: /* Yellow */
185 attr &= ~FOREGROUND_ALL;
186 attr |= FOREGROUND_RED | FOREGROUND_GREEN;
187 break;
188 case 34: /* Blue */
189 attr &= ~FOREGROUND_ALL;
190 attr |= FOREGROUND_BLUE;
191 break;
192 case 35: /* Magenta */
193 attr &= ~FOREGROUND_ALL;
194 attr |= FOREGROUND_RED | FOREGROUND_BLUE;
195 break;
196 case 36: /* Cyan */
197 attr &= ~FOREGROUND_ALL;
198 attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
199 break;
200 case 37: /* White */
201 attr |= FOREGROUND_RED |
202 FOREGROUND_GREEN |
203 FOREGROUND_BLUE;
204 break;
205 case 38: /* Unknown */
206 break;
207 case 39: /* reset */
208 attr &= ~FOREGROUND_ALL;
209 attr |= (plain_attr & FOREGROUND_ALL);
210 break;
211 case 40: /* Black */
212 attr &= ~BACKGROUND_ALL;
213 break;
214 case 41: /* Red */
215 attr &= ~BACKGROUND_ALL;
216 attr |= BACKGROUND_RED;
217 break;
218 case 42: /* Green */
219 attr &= ~BACKGROUND_ALL;
220 attr |= BACKGROUND_GREEN;
221 break;
222 case 43: /* Yellow */
223 attr &= ~BACKGROUND_ALL;
224 attr |= BACKGROUND_RED | BACKGROUND_GREEN;
225 break;
226 case 44: /* Blue */
227 attr &= ~BACKGROUND_ALL;
228 attr |= BACKGROUND_BLUE;
229 break;
230 case 45: /* Magenta */
231 attr &= ~BACKGROUND_ALL;
232 attr |= BACKGROUND_RED | BACKGROUND_BLUE;
233 break;
234 case 46: /* Cyan */
235 attr &= ~BACKGROUND_ALL;
236 attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
237 break;
238 case 47: /* White */
239 attr |= BACKGROUND_RED |
240 BACKGROUND_GREEN |
241 BACKGROUND_BLUE;
242 break;
243 case 48: /* Unknown */
244 break;
245 case 49: /* reset */
246 attr &= ~BACKGROUND_ALL;
247 attr |= (plain_attr & BACKGROUND_ALL);
248 break;
249 default:
250 /* Unsupported code */
251 break;
253 str++;
254 } while (*(str-1) == ';');
256 set_console_attr();
257 break;
258 case 'K':
259 erase_in_line();
260 break;
261 default:
262 /* Unsupported code */
263 break;
266 return func + 1;
269 static int ansi_emulate(const char *str, FILE *stream)
271 int rv = 0;
272 const char *pos = str;
274 fflush(stream);
276 while (*pos) {
277 pos = strstr(str, "\033[");
278 if (pos) {
279 size_t len = pos - str;
281 if (len) {
282 size_t out_len = write_console(str, len);
283 rv += out_len;
284 if (out_len < len)
285 return rv;
288 str = pos + 2;
289 rv += 2;
291 pos = set_attr(str);
292 rv += pos - str;
293 str = pos;
294 } else {
295 size_t len = strlen(str);
296 rv += write_console(str, len);
297 return rv;
300 return rv;
303 int winansi_fputs(const char *str, FILE *stream)
305 int rv;
307 if (!is_console(stream))
308 return fputs(str, stream);
310 rv = ansi_emulate(str, stream);
312 if (rv >= 0)
313 return 0;
314 else
315 return EOF;
318 int winansi_vfprintf(FILE *stream, const char *format, va_list list)
320 int len, rv;
321 char small_buf[256];
322 char *buf = small_buf;
323 va_list cp;
325 if (!is_console(stream))
326 goto abort;
328 va_copy(cp, list);
329 len = vsnprintf(small_buf, sizeof(small_buf), format, cp);
330 va_end(cp);
332 if (len > sizeof(small_buf) - 1) {
333 buf = malloc(len + 1);
334 if (!buf)
335 goto abort;
337 len = vsnprintf(buf, len + 1, format, list);
340 rv = ansi_emulate(buf, stream);
342 if (buf != small_buf)
343 free(buf);
344 return rv;
346 abort:
347 rv = vfprintf(stream, format, list);
348 return rv;
351 int winansi_fprintf(FILE *stream, const char *format, ...)
353 va_list list;
354 int rv;
356 va_start(list, format);
357 rv = winansi_vfprintf(stream, format, list);
358 va_end(list);
360 return rv;
363 int winansi_printf(const char *format, ...)
365 va_list list;
366 int rv;
368 va_start(list, format);
369 rv = winansi_vfprintf(stdout, format, list);
370 va_end(list);
372 return rv;