git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] Add ANSI control code emulation for the Windows console

From
Johannes Sixt <johannes.sixt@telecom.at>
Date
Jul 18, 2008, 07:34 UTC
Message-ID
<1216366485-12201-4-git-send-email-johannes.sixt@telecom.at>
In-Reply-To
<1216366485-12201-3-git-send-email-johannes.sixt@telecom.at>
From: Peter Harris <git@peter.is-a-geek.org>

This adds only the minimum necessary to keep git pull/merge's diffstat from wrapping. Notably absent is support for the K (erase) operation, and support for POSIX write.

Signed-off-by: Peter Harris <git@peter.is-a-geek.org>
Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at>
---
 Makefile         |    2 +-
 compat/mingw.h   |   11 ++
 compat/winansi.c |  345 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 357 insertions(+), 1 deletions(-)
 create mode 100644 compat/winansi.c
diff --git a/Makefile b/Makefile
index 78e08d3..2c6cc9d 100644
--- a/Makefile
+++ b/Makefile
@@ -740,7 +740,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
 	COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat
 	COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
 	COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
-	COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o
+	COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o compat/winansi.o
 	EXTLIBS += -lws2_32
 	X = .exe
 	template_dir = ../share/git-core/templates/
diff --git a/compat/mingw.h b/compat/mingw.h
index 6bc049a..5f11114 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -194,6 +194,17 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler);
 #define signal mingw_signal
 
 /*
+ * ANSI emulation wrappers
+ */
+
+int winansi_fputs(const char *str, FILE *stream);
+int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2)));
+int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3)));
+#define fputs winansi_fputs
+#define printf(...) winansi_printf(__VA_ARGS__)
+#define fprintf(...) winansi_fprintf(__VA_ARGS__)
+
+/*
  * git specific compatibility
  */
 
diff --git a/compat/winansi.c b/compat/winansi.c
new file mode 100644
index 0000000..e2d96df
--- /dev/null
+++ b/compat/winansi.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
+ */
+
+#include <windows.h>
+#include "../git-compat-util.h"
+
+/*
+ Functions to be wrapped:
+*/
+#undef printf
+#undef fprintf
+#undef fputs
+/* TODO: write */
+
+/*
+ ANSI codes used by git: m, K
+
+ This file is git-specific. Therefore, this file does not attempt
+ to implement any codes that are not used by git.
+
+ TODO: K
+*/
+
+static HANDLE console;
+static WORD plain_attr;
+static WORD attr;
+static int negative;
+
+static void init(void)
+{
+	CONSOLE_SCREEN_BUFFER_INFO sbi;
+
+	static int initialized = 0;
+	if (initialized)
+		return;
+
+	console = GetStdHandle(STD_OUTPUT_HANDLE);
+	if (console == INVALID_HANDLE_VALUE)
+		console = NULL;
+
+	if (!console)
+		return;
+
+	GetConsoleScreenBufferInfo(console, &sbi);
+	attr = plain_attr = sbi.wAttributes;
+	negative = 0;
+
+	initialized = 1;
+}
+
+
+#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
+#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
+
+static void set_console_attr(void)
+{
+	WORD attributes = attr;
+	if (negative) {
+		attributes &= ~FOREGROUND_ALL;
+		attributes &= ~BACKGROUND_ALL;
+
+		/* This could probably use a bitmask
+		   instead of a series of ifs */
+		if (attr & FOREGROUND_RED)
+			attributes |= BACKGROUND_RED;
+		if (attr & FOREGROUND_GREEN)
+			attributes |= BACKGROUND_GREEN;
+		if (attr & FOREGROUND_BLUE)
+			attributes |= BACKGROUND_BLUE;
+
+		if (attr & BACKGROUND_RED)
+			attributes |= FOREGROUND_RED;
+		if (attr & BACKGROUND_GREEN)
+			attributes |= FOREGROUND_GREEN;
+		if (attr & BACKGROUND_BLUE)
+			attributes |= FOREGROUND_BLUE;
+	}
+	SetConsoleTextAttribute(console, attributes);
+}
+
+static const char *set_attr(const char *str)
+{
+	const char *func;
+	size_t len = strspn(str, "0123456789;");
+	func = str + len;
+
+	switch (*func) {
+	case 'm':
+		do {
+			long val = strtol(str, (char **)&str, 10);
+			switch (val) {
+			case 0: /* reset */
+				attr = plain_attr;
+				negative = 0;
+				break;
+			case 1: /* bold */
+				attr |= FOREGROUND_INTENSITY;
+				break;
+			case 2:  /* faint */
+			case 22: /* normal */
+				attr &= ~FOREGROUND_INTENSITY;
+				break;
+			case 3:  /* italic */
+				/* Unsupported */
+				break;
+			case 4:  /* underline */
+			case 21: /* double underline */
+				/* Wikipedia says this flag does nothing */
+				/* Furthermore, mingw doesn't define this flag
+				attr |= COMMON_LVB_UNDERSCORE; */
+				break;
+			case 24: /* no underline */
+				/* attr &= ~COMMON_LVB_UNDERSCORE; */
+				break;
+			case 5:  /* slow blink */
+			case 6:  /* fast blink */
+				/* We don't have blink, but we do have
+				   background intensity */
+				attr |= BACKGROUND_INTENSITY;
+				break;
+			case 25: /* no blink */
+				attr &= ~BACKGROUND_INTENSITY;
+				break;
+			case 7:  /* negative */
+				negative = 1;
+				break;
+			case 27: /* positive */
+				negative = 0;
+				break;
+			case 8:  /* conceal */
+			case 28: /* reveal */
+				/* Unsupported */
+				break;
+			case 30: /* Black */
+				attr &= ~FOREGROUND_ALL;
+				break;
+			case 31: /* Red */
+				attr &= ~FOREGROUND_ALL;
+				attr |= FOREGROUND_RED;
+				break;
+			case 32: /* Green */
+				attr &= ~FOREGROUND_ALL;
+				attr |= FOREGROUND_GREEN;
+				break;
+			case 33: /* Yellow */
+				attr &= ~FOREGROUND_ALL;
+				attr |= FOREGROUND_RED | FOREGROUND_GREEN;
+				break;
+			case 34: /* Blue */
+				attr &= ~FOREGROUND_ALL;
+				attr |= FOREGROUND_BLUE;
+				break;
+			case 35: /* Magenta */
+				attr &= ~FOREGROUND_ALL;
+				attr |= FOREGROUND_RED | FOREGROUND_BLUE;
+				break;
+			case 36: /* Cyan */
+				attr &= ~FOREGROUND_ALL;
+				attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
+				break;
+			case 37: /* White */
+				attr |= FOREGROUND_RED |
+					FOREGROUND_GREEN |
+					FOREGROUND_BLUE;
+				break;
+			case 38: /* Unknown */
+				break;
+			case 39: /* reset */
+				attr &= ~FOREGROUND_ALL;
+				attr |= (plain_attr & FOREGROUND_ALL);
+				break;
+			case 40: /* Black */
+				attr &= ~BACKGROUND_ALL;
+				break;
+			case 41: /* Red */
+				attr &= ~BACKGROUND_ALL;
+				attr |= BACKGROUND_RED;
+				break;
+			case 42: /* Green */
+				attr &= ~BACKGROUND_ALL;
+				attr |= BACKGROUND_GREEN;
+				break;
+			case 43: /* Yellow */
+				attr &= ~BACKGROUND_ALL;
+				attr |= BACKGROUND_RED | BACKGROUND_GREEN;
+				break;
+			case 44: /* Blue */
+				attr &= ~BACKGROUND_ALL;
+				attr |= BACKGROUND_BLUE;
+				break;
+			case 45: /* Magenta */
+				attr &= ~BACKGROUND_ALL;
+				attr |= BACKGROUND_RED | BACKGROUND_BLUE;
+				break;
+			case 46: /* Cyan */
+				attr &= ~BACKGROUND_ALL;
+				attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
+				break;
+			case 47: /* White */
+				attr |= BACKGROUND_RED |
+					BACKGROUND_GREEN |
+					BACKGROUND_BLUE;
+				break;
+			case 48: /* Unknown */
+				break;
+			case 49: /* reset */
+				attr &= ~BACKGROUND_ALL;
+				attr |= (plain_attr & BACKGROUND_ALL);
+				break;
+			default:
+				/* Unsupported code */
+				break;
+			}
+			str++;
+		} while (*(str-1) == ';');
+
+		set_console_attr();
+		break;
+	case 'K':
+		/* TODO */
+		break;
+	default:
+		/* Unsupported code */
+		break;
+	}
+
+	return func + 1;
+}
+
+static int ansi_emulate(const char *str, FILE *stream)
+{
+	int rv = 0;
+	const char *pos = str;
+
+	while (*pos) {
+		pos = strstr(str, "\033[");
+		if (pos) {
+			size_t len = pos - str;
+
+			if (len) {
+				size_t out_len = fwrite(str, 1, len, stream);
+				rv += out_len;
+				if (out_len < len)
+					return rv;
+			}
+
+			str = pos + 2;
+			rv += 2;
+
+			fflush(stream);
+
+			pos = set_attr(str);
+			rv += pos - str;
+			str = pos;
+		} else {
+			rv += strlen(str);
+			fputs(str, stream);
+			return rv;
+		}
+	}
+	return rv;
+}
+
+int winansi_fputs(const char *str, FILE *stream)
+{
+	int rv;
+
+	if (!isatty(fileno(stream)))
+		return fputs(str, stream);
+
+	init();
+
+	if (!console)
+		return fputs(str, stream);
+
+	rv = ansi_emulate(str, stream);
+
+	if (rv >= 0)
+		return 0;
+	else
+		return EOF;
+}
+
+static int winansi_vfprintf(FILE *stream, const char *format, va_list list)
+{
+	int len, rv;
+	char small_buf[256];
+	char *buf = small_buf;
+	va_list cp;
+
+	if (!isatty(fileno(stream)))
+		goto abort;
+
+	init();
+
+	if (!console)
+		goto abort;
+
+	va_copy(cp, list);
+	len = vsnprintf(small_buf, sizeof(small_buf), format, cp);
+	va_end(cp);
+
+	if (len > sizeof(small_buf) - 1) {
+		buf = malloc(len + 1);
+		if (!buf)
+			goto abort;
+
+		len = vsnprintf(buf, len + 1, format, list);
+	}
+
+	rv = ansi_emulate(buf, stream);
+
+	if (buf != small_buf)
+		free(buf);
+	return rv;
+
+abort:
+	rv = vfprintf(stream, format, list);
+	return rv;
+}
+
+int winansi_fprintf(FILE *stream, const char *format, ...)
+{
+	va_list list;
+	int rv;
+
+	va_start(list, format);
+	rv = winansi_vfprintf(stream, format, list);
+	va_end(list);
+
+	return rv;
+}
+
+int winansi_printf(const char *format, ...)
+{
+	va_list list;
+	int rv;
+
+	va_start(list, format);
+	rv = winansi_vfprintf(stdout, format, list);
+	va_end(list);
+
+	return rv;
+}
-- 
1.5.6.1.275.g0a3e0f
Previous: Johannes SixtNext: Johannes Sixt
Message 4 of 16 in “Windows related patches”
  1. Windows related patchesJohannes Sixt, Jul 18, 2008
  2. Teach lookup_prog not to select directoriesJohannes Sixt, Jul 18, 2008
  3. builtin-clone: Use is_dir_sep() instead of '/'Johannes Sixt, Jul 18, 2008
  4. Add ANSI control code emulation for the Windows consoleJohannes Sixt, Jul 18, 2008
  5. Windows: set gitexecdir = $(bindir)Johannes Sixt, Jul 18, 2008
  6. Junio C HamanoJul 19, 2008
  7. Johannes SixtJul 19, 2008
  8. Steffen ProhaskaJul 19, 2008
  9. Junio C HamanoJul 19, 2008
  10. Johannes SixtJul 19, 2008
  11. Junio C HamanoJul 19, 2008
  12. Johannes SixtJul 19, 2008
  13. Johannes SchindelinJul 19, 2008
  14. Johannes SixtJul 19, 2008
  15. Daniel BarkalowJul 19, 2008
  16. Kristian HøgsbergJul 21, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.