Signed-off-by: Peter Harris <g...@peter.is-a-geek.org>
---
I got tired of seeing control codes appear on my console. Rather than
turn off colours, I decided to fix them instead.
Note that this patch does not catch \033[K at the end of lines when
fetching from a remote. This patch only wraps ANSI C printf/fputs, not
POSIX open/write (yet).
I'm not sure if Cygwin wants to define WIN_ANSI=YesPlease too.
Comments welcome.
Makefile | 6 +
compat/winansi.c | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++++
git-compat-util.h | 7 ++
3 files changed, 322 insertions(+), 0 deletions(-)
create mode 100644 compat/winansi.c
diff --git a/Makefile b/Makefile
index 5e57a58..62034e7 100644
--- a/Makefile
+++ b/Makefile
@@ -547,6 +547,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_R_TO_GCC_LINKER = YesPlease
INTERNAL_QSORT = YesPlease
NO_EXTRA_PROGRAMS = YesPlease
+ WIN_ANSI = YesPlease
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o
@@ -781,6 +782,11 @@ ifdef THREADED_DELTA_SEARCH
EXTLIBS += -lpthread
endif
+ifdef WIN_ANSI
+ COMPAT_CFLAGS += -DWIN_ANSI
+ COMPAT_OBJS += compat/winansi.o
+endif
+
ifeq ($(TCLTK_PATH),)
NO_TCLTK=NoThanks
endif
diff --git a/compat/winansi.c b/compat/winansi.c
new file mode 100644
index 0000000..86c3fd2
--- /dev/null
+++ b/compat/winansi.c
@@ -0,0 +1,309 @@
+#include <windows.h>
+#include "../git-compat-util.h"
+
+/*
+ Functions to be wrapped:
+*/
+#undef printf
+#undef fputs
+
+/*
+ ANSI codes to implement: m, 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 output_len = fwrite(str, 1, len, stream);
+ rv += output_len;
+ if (output_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 git_fputs(const char *str, FILE *stream)
+{
+ int rv;
+
+ init();
+
+ if (!console)
+ return fputs(str, stream);
+
+ if (!isatty(fileno(stream)))
+ return fputs(str, stream);
+
+ rv = ansi_emulate(str, stream);
+
+ if (rv >= 0)
+ return 0;
+ else
+ return EOF;
+}
+
+int git_printf(const char *format, ...)
+{
+ va_list list;
+
+ char small_buf[256];
+ char *buf = small_buf;
+ int len, rv;
+
+ init();
+
+ if (!console)
+ goto abort;
+
+ if (!isatty(fileno(stdout)))
+ goto abort;
+
+ va_start(list, format);
+ len = vsnprintf(small_buf, sizeof(small_buf), format, list);
+ va_end(list);
+
+ if (len > sizeof(small_buf) - 1) {
+ buf = malloc(len + 1);
+ if (!buf)
+ goto abort;
+
+ va_start(list, format);
+ len = vsnprintf(buf, len + 1, format, list);
+ va_end(list);
+ }
+
+ rv = ansi_emulate(buf, stdout);
+
+ if (buf != small_buf)
+ free(buf);
+ return rv;
+
+abort:
+ va_start(list, format);
+ rv = vprintf(format, list);
+ va_end(list);
+ return rv;
+}
diff --git a/git-compat-util.h b/git-compat-util.h
index e0fe165..218d6b6 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -661,4 +661,11 @@ void git_qsort(void *base, size_t nmemb, size_t size,
#define qsort git_qsort
#endif
+#ifdef WIN_ANSI
+extern int git_fputs(const char *str, FILE *stream);
+extern int git_printf(const char *format, ...) __attribute__((format (printf, 1, 2)));
+#define fputs git_fputs
+#define printf(...) git_printf(__VA_ARGS__)
+#endif
+
#endif
--
1.5.3.7
On Sun, 2 Mar 2008, Peter Harris wrote:
> Note that this does not interfere with rxvt's control code handling,
> since rxvt fails the isatty() test, so this will pass the control codes
> through unmodified.
>
> I got tired of seeing control codes appear on my console. Rather than
> turn off colours, I decided to fix them instead.
Nice! Thank you. I Cc'ed Hannes, since he is most likely interested in
this, too.
Ciao,
Dscho
If output is going to $PAGER, isatty() returns false, so the patch
just passes the control codes on unmodified.
My pager doesn't support ANSI control codes. That's okay, though,
because the default git configuration does not generate control codes
when isatty() returns false, and I get control-code-free (and also
colourless) output in my pager.
Peter Harris
Yes, it's interesting. But I wonder if this also works if output goes to
$PAGER.
-- Hannes
I wonder if this can be a separate git command and make it default
pager for Windows environment? I'm thinking about its use outside
C-based commands, perharps shell scripts or perl scripts..
--
Duy
I suppose you could turn it into "git color-cat", or, since it would
be entirely unrelated at that point, just "color-cat".
I would be very much against it being made the default pager, though,
since it doesn't page at all. Worse, I wrote that code to catch the
un$PAGERed output of git (git pull, git merge). So removing this from
the git core and putting it into a pager would defeat the purpose.
On a somewhat related note, the latest release of 'less' claims to
support some sort of "color escape sequences". I haven't been able to
make it work yet, though.
Peter Harris
Um.. I was not aware of that. Thanks for the info.
--
Duy
On Tue, 4 Mar 2008, Peter Harris wrote:
> On a somewhat related note, the latest release of 'less' claims to
> support some sort of "color escape sequences". I haven't been able to
> make it work yet, though.
I thought "less" worked already? The problem -- as you stated before --
is with the output that is not piped through a pager, right?
Ciao,
Dscho
Right. Any pager works now, in the sense that color.diff=auto will not
output control sequences when git is writing to a pipe.
"less version 418" claims to support control codes, and could in
theory work with color.diff=always. Too bad it's GPL3, or I'd look at
it in more detail to see if there was any code worth borrowing.
Peter Harris
Yes. Very strange.
It looks like msys-based apps get color if they're running under an
msys shell. It looks like git is compiled as a mingw application, not
an msys application. Given that perl, less, and other parts of the
msysGit distribution are msys-based, does it make sense to link git
with msys?
Peter Harris
Ah, that is the explanation.
No, Git should not be compiled as an MSys application. MSys only provides
a very rudimentary POSIXish environment, and it is really only meant for
the official MSys programs. (We slightly abused it, to get git-svn to
run.)
Ciao,
Dscho
On Tue, 4 Mar 2008, Johannes Sixt wrote:
> On Tuesday 04 March 2008 17:05, Johannes Schindelin wrote:
> > Hi,
> >
> > On Tue, 4 Mar 2008, Peter Harris wrote:
> > > On a somewhat related note, the latest release of 'less' claims to
> > > support some sort of "color escape sequences". I haven't been able to
> > > make it work yet, though.
> >
> > I thought "less" worked already?
>
> Not so. less on CMD does not show colors. :-(
Something does not compute here. I just tried, on Windows XP, in an
msysGit window (created by /share/msysGit/add-shortcut.tcl):
$ git log --color
and guess what: I saw colour! Is that something _you_ did? I thought it
was a by-product of using less?
Ciao,
Dscho
We're using cmd, you're using sh.
> I thought it was a by-product of using less?
I suspect it's a by-product of using sh.
Peter Harris
I see. But then it is funny that non-paged text is not coloured, no?
Ciao,
Dscho
Not so. less on CMD does not show colors. :-(
-- Hannes
That makes sense.
For reference, the ANSI color code is in the file
MSYS-1.0.11-20071204-src\source\winsup\cygwin\fhandler_console.cc
...and msys is GPL2, so maybe we can borrow some of that code. Hmm.
Peter Harris