From 8aecf82bfef6647315eb96e69eac3f65ce38ee70 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Mon, 22 Jun 2015 18:15:51 -0500 Subject: [PATCH] WriteFile() on WIN32 when stdout isatty (fix #824) Use WriteFile() and bypass stdio IFF stdout isatty and we're on Windows. --- jv.h | 1 + jv_print.c | 193 +++++++++++++++++++++++++++++++---------------------- main.c | 15 ++++- util.h | 2 + 4 files changed, 130 insertions(+), 81 deletions(-) diff --git a/jv.h b/jv.h index 28f6ae0..98ac5e0 100644 --- a/jv.h +++ b/jv.h @@ -167,6 +167,7 @@ enum jv_print_flags { JV_PRINT_INVALID = 16, JV_PRINT_REFCOUNT = 32, JV_PRINT_TAB = 64, + JV_PRINT_ISATTY = 128, JV_PRINT_SPACE0 = 256, JV_PRINT_SPACE1 = 512, JV_PRINT_SPACE2 = 1024, diff --git a/jv_print.c b/jv_print.c index 8615bec..0f6d624 100644 --- a/jv_print.c +++ b/jv_print.c @@ -1,9 +1,15 @@ -#include "jv.h" +#include #include #include #include -#include +#ifdef WIN32 +#include +#include +#include +#endif + +#include "jv.h" #include "jv_dtoa.h" #include "jv_unicode.h" @@ -21,72 +27,103 @@ static const char* const colours[] = COL("0;32"), COL("1;39"), COL("1;39")}; #define FIELD_COLOUR COL("34;1") -static void put_buf(const char* s, int len, FILE* fout, jv* strout) { +/* + * The Windows CRT and console are something else. In order for the + * console to get UTF-8 written to it correctly we have to bypass stdio + * completely. No amount of fflush()ing helps. If the first byte of a + * buffer being written with fwrite() is non-ASCII UTF-8 then the + * console misinterprets the byte sequence. But one must not + * WriteFile() if stdout is a file!1!! + * + * We carry knowledge of whether the FILE * is a tty everywhere we + * output to it just so we can write with WriteFile() if stdout is a + * console on WIN32. + */ + +void priv_fwrite(const char *s, size_t len, FILE *fout, int is_tty) { +#ifdef WIN32 + if (is_tty) + WriteFile((HANDLE)_get_osfhandle(fileno(fout)), s, len, NULL, NULL); + else + fwrite(s, 1, len, fout); +#else + fwrite(s, 1, len, fout); +#endif +} + +static void put_buf(const char *s, int len, FILE *fout, jv *strout, int is_tty) { if (strout) { *strout = jv_string_append_buf(*strout, s, len); } else { +#ifdef WIN32 + if (is_tty) + WriteFile((HANDLE)_get_osfhandle(fileno(fout)), s, len, NULL, NULL); + else fwrite(s, 1, len, fout); +#else + fwrite(s, 1, len, fout); +#endif } } -static void put_char(char c, FILE* fout, jv* strout) { - put_buf(&c, 1, fout, strout); +static void put_char(char c, FILE* fout, jv* strout, int T) { + put_buf(&c, 1, fout, strout, T); } -static void put_str(const char* s, FILE* fout, jv* strout) { - put_buf(s, strlen(s), fout, strout); +static void put_str(const char* s, FILE* fout, jv* strout, int T) { + put_buf(s, strlen(s), fout, strout, T); } -static void put_indent(int n, int flags, FILE* fout, jv* strout) { +static void put_indent(int n, int flags, FILE* fout, jv* strout, int T) { if (flags & JV_PRINT_TAB) { while (n--) - put_char('\t', fout, strout); + put_char('\t', fout, strout, T); } else { n *= ((flags & (JV_PRINT_SPACE0 | JV_PRINT_SPACE1 | JV_PRINT_SPACE2)) >> 8); while (n--) - put_char(' ', fout, strout); + put_char(' ', fout, strout, T); } } -static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S) { +static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S, int T) { assert(jv_get_kind(str) == JV_KIND_STRING); const char* i = jv_string_value(str); const char* end = i + jv_string_length_bytes(jv_copy(str)); const char* cstart; int c = 0; char buf[32]; - put_char('"', F, S); + put_char('"', F, S, T); while ((i = jvp_utf8_next((cstart = i), end, &c))) { assert(c != -1); int unicode_escape = 0; if (0x20 <= c && c <= 0x7E) { // printable ASCII if (c == '"' || c == '\\') { - put_char('\\', F, S); + put_char('\\', F, S, T); } - put_char(c, F, S); + put_char(c, F, S, T); } else if (c < 0x20 || c == 0x7F) { // ASCII control character switch (c) { case '\b': - put_char('\\', F, S); - put_char('b', F, S); + put_char('\\', F, S, T); + put_char('b', F, S, T); break; case '\t': - put_char('\\', F, S); - put_char('t', F, S); + put_char('\\', F, S, T); + put_char('t', F, S, T); break; case '\r': - put_char('\\', F, S); - put_char('r', F, S); + put_char('\\', F, S, T); + put_char('r', F, S, T); break; case '\n': - put_char('\\', F, S); - put_char('n', F, S); + put_char('\\', F, S, T); + put_char('n', F, S, T); break; case '\f': - put_char('\\', F, S); - put_char('f', F, S); + put_char('\\', F, S, T); + put_char('f', F, S, T); break; default: unicode_escape = 1; @@ -96,7 +133,7 @@ static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S) { if (ascii_only) { unicode_escape = 1; } else { - put_buf(cstart, i - cstart, F, S); + put_buf(cstart, i - cstart, F, S, T); } } if (unicode_escape) { @@ -108,19 +145,19 @@ static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S) { 0xD800 | ((c & 0xffc00) >> 10), 0xDC00 | (c & 0x003ff)); } - put_str(buf, F, S); + put_str(buf, F, S, T); } } assert(c != -1); - put_char('"', F, S); + put_char('"', F, S, T); } -static void put_refcnt(struct dtoa_context* C, int refcnt, FILE *F, jv* S){ +static void put_refcnt(struct dtoa_context* C, int refcnt, FILE *F, jv* S, int T){ char buf[JVP_DTOA_FMT_MAX_LEN]; - put_char(' ', F, S); - put_char('(', F, S); - put_str(jvp_dtoa_fmt(C, buf, refcnt), F, S); - put_char(')', F, S); + put_char(' ', F, S, T); + put_char('(', F, S, T); + put_str(jvp_dtoa_fmt(C, buf, refcnt), F, S, T); + put_char(')', F, S, T); } static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FILE* F, jv* S) { @@ -131,7 +168,7 @@ static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FI for (unsigned i=0; i", F, S); + put_str("", F, S, flags & JV_PRINT_ISATTY); } else { - put_str("", F, S); + put_str("", F, S, flags & JV_PRINT_ISATTY); } } else { assert(0 && "Invalid value"); } break; case JV_KIND_NULL: - put_str("null", F, S); + put_str("null", F, S, flags & JV_PRINT_ISATTY); break; case JV_KIND_FALSE: - put_str("false", F, S); + put_str("false", F, S, flags & JV_PRINT_ISATTY); break; case JV_KIND_TRUE: - put_str("true", F, S); + put_str("true", F, S, flags & JV_PRINT_ISATTY); break; case JV_KIND_NUMBER: { double d = jv_number_value(x); if (d != d) { // JSON doesn't have NaN, so we'll render it as "null" - put_str("null", F, S); + put_str("null", F, S, flags & JV_PRINT_ISATTY); } else { // Normalise infinities to something we can print in valid JSON if (d > DBL_MAX) d = DBL_MAX; if (d < -DBL_MAX) d = -DBL_MAX; - put_str(jvp_dtoa_fmt(C, buf, d), F, S); + put_str(jvp_dtoa_fmt(C, buf, d), F, S, flags & JV_PRINT_ISATTY); } break; } case JV_KIND_STRING: - jvp_dump_string(x, flags & JV_PRINT_ASCII, F, S); + jvp_dump_string(x, flags & JV_PRINT_ASCII, F, S, flags & JV_PRINT_ISATTY); if (flags & JV_PRINT_REFCOUNT) - put_refcnt(C, refcnt, F, S); + put_refcnt(C, refcnt, F, S, flags & JV_PRINT_ISATTY); break; case JV_KIND_ARRAY: { if (jv_array_length(jv_copy(x)) == 0) { - put_str("[]", F, S); + put_str("[]", F, S, flags & JV_PRINT_ISATTY); break; } - put_str("[", F, S); + put_str("[", F, S, flags & JV_PRINT_ISATTY); if (flags & JV_PRINT_PRETTY) { - put_char('\n', F, S); - put_indent(indent + 1, flags, F, S); + put_char('\n', F, S, flags & JV_PRINT_ISATTY); + put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY); } jv_array_foreach(x, i, elem) { if (i!=0) { if (flags & JV_PRINT_PRETTY) { - put_str(",\n", F, S); - put_indent(indent + 1, flags, F, S); + put_str(",\n", F, S, flags & JV_PRINT_ISATTY); + put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY); } else { - put_str(",", F, S); + put_str(",", F, S, flags & JV_PRINT_ISATTY); } } jv_dump_term(C, elem, flags, indent + 1, F, S); - if (colour) put_str(colour, F, S); + if (colour) put_str(colour, F, S, flags & JV_PRINT_ISATTY); } if (flags & JV_PRINT_PRETTY) { - put_char('\n', F, S); - put_indent(indent, flags, F, S); + put_char('\n', F, S, flags & JV_PRINT_ISATTY); + put_indent(indent, flags, F, S, flags & JV_PRINT_ISATTY); } - if (colour) put_str(colour, F, S); - put_char(']', F, S); + if (colour) put_str(colour, F, S, flags & JV_PRINT_ISATTY); + put_char(']', F, S, flags & JV_PRINT_ISATTY); if (flags & JV_PRINT_REFCOUNT) - put_refcnt(C, refcnt, F, S); + put_refcnt(C, refcnt, F, S, flags & JV_PRINT_ISATTY); break; } case JV_KIND_OBJECT: { if (jv_object_length(jv_copy(x)) == 0) { - put_str("{}", F, S); + put_str("{}", F, S, flags & JV_PRINT_ISATTY); break; } - put_char('{', F, S); + put_char('{', F, S, flags & JV_PRINT_ISATTY); if (flags & JV_PRINT_PRETTY) { - put_char('\n', F, S); - put_indent(indent + 1, flags, F, S); + put_char('\n', F, S, flags & JV_PRINT_ISATTY); + put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY); } int first = 1; int i = 0; @@ -252,40 +289,40 @@ static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FI if (!first) { if (flags & JV_PRINT_PRETTY){ - put_str(",\n", F, S); - put_indent(indent + 1, flags, F, S); + put_str(",\n", F, S, flags & JV_PRINT_ISATTY); + put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY); } else { - put_str(",", F, S); + put_str(",", F, S, flags & JV_PRINT_ISATTY); } } - if (colour) put_str(COLRESET, F, S); + if (colour) put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY); first = 0; - if (colour) put_str(FIELD_COLOUR, F, S); - jvp_dump_string(key, flags & JV_PRINT_ASCII, F, S); + if (colour) put_str(FIELD_COLOUR, F, S, flags & JV_PRINT_ISATTY); + jvp_dump_string(key, flags & JV_PRINT_ASCII, F, S, flags & JV_PRINT_ISATTY); jv_free(key); - if (colour) put_str(COLRESET, F, S); + if (colour) put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY); - if (colour) put_str(colour, F, S); - put_str((flags & JV_PRINT_PRETTY) ? ": " : ":", F, S); - if (colour) put_str(COLRESET, F, S); + if (colour) put_str(colour, F, S, flags & JV_PRINT_ISATTY); + put_str((flags & JV_PRINT_PRETTY) ? ": " : ":", F, S, flags & JV_PRINT_ISATTY); + if (colour) put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY); jv_dump_term(C, value, flags, indent + 1, F, S); - if (colour) put_str(colour, F, S); + if (colour) put_str(colour, F, S, flags & JV_PRINT_ISATTY); } if (flags & JV_PRINT_PRETTY) { - put_char('\n', F, S); - put_indent(indent, flags, F, S); + put_char('\n', F, S, flags & JV_PRINT_ISATTY); + put_indent(indent, flags, F, S, flags & JV_PRINT_ISATTY); } - if (colour) put_str(colour, F, S); - put_char('}', F, S); + if (colour) put_str(colour, F, S, flags & JV_PRINT_ISATTY); + put_char('}', F, S, flags & JV_PRINT_ISATTY); if (flags & JV_PRINT_REFCOUNT) - put_refcnt(C, refcnt, F, S); + put_refcnt(C, refcnt, F, S, flags & JV_PRINT_ISATTY); } } jv_free(x); if (colour) { - put_str(COLRESET, F, S); + put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY); } } diff --git a/main.c b/main.c index a888c30..deadc75 100644 --- a/main.c +++ b/main.c @@ -9,6 +9,8 @@ #ifdef WIN32 #include +#include +#include #include #include #include @@ -131,11 +133,11 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts) { else ret = 0; if (options & SEQ) - fwrite("\036", 1, 1, stdout); + priv_fwrite("\036", 1, stdout, dumpopts & JV_PRINT_ISATTY); jv_dump(result, dumpopts); } if (!(options & RAW_NO_LF)) - printf("\n"); + priv_fwrite("\n", 1, stdout, dumpopts & JV_PRINT_ISATTY); if (options & UNBUFFERED_OUTPUT) fflush(stdout); } @@ -175,6 +177,11 @@ int main(int argc, char* argv[]) { jv program_arguments = jv_array(); #ifdef WIN32 + SetConsoleOutputCP(CP_UTF8); + fflush(stdout); + fflush(stderr); + _setmode(fileno(stdout), _O_TEXT | _O_U8TEXT); + _setmode(fileno(stderr), _O_TEXT | _O_U8TEXT); int wargc; wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); assert(wargc == argc); @@ -414,12 +421,14 @@ int main(int argc, char* argv[]) { } } + if (isatty(fileno(stdout))) { + dumpopts |= JV_PRINT_ISATTY; #ifndef WIN32 /* Disable colour by default on Windows builds as Windows terminals tend not to display it correctly */ - if (isatty(fileno(stdout))) dumpopts |= JV_PRINT_COLOUR; #endif + } if (options & SORTED_OUTPUT) dumpopts |= JV_PRINT_SORTED; if (options & ASCII_OUTPUT) dumpopts |= JV_PRINT_ASCII; if (options & COLOUR_OUTPUT) dumpopts |= JV_PRINT_COLOUR; diff --git a/util.h b/util.h index 101a802..14eb135 100644 --- a/util.h +++ b/util.h @@ -11,6 +11,8 @@ jv expand_path(jv); jv get_home(void); jv jq_realpath(jv); +void priv_fwrite(const char *, size_t, FILE *, int); + const void *_jq_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen); -- 2.40.0