From 82aa43407ca3f4096ec2be2a13869fb584775952 Mon Sep 17 00:00:00 2001 From: nobu Date: Mon, 4 Jun 2012 06:39:40 +0000 Subject: [PATCH] win32: VT100 escape * win32/win32.c (constat_apply): apply VT100 functions. [ruby-core:44958] [Feature #6418] * win32/win32.c (constat_parse): parse some VT100 escape sequence. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@35907 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 7 + bootstraptest/runner.rb | 1 - lib/test/unit.rb | 1 - sample/test.rb | 1 - win32/win32.c | 378 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 379 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 12f0abd77b..c5a59dbe8a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Mon Jun 4 15:39:33 2012 Nobuyoshi Nakada + + * win32/win32.c (constat_apply): apply VT100 functions. + [ruby-core:44958] [Feature #6418] + + * win32/win32.c (constat_parse): parse some VT100 escape sequence. + Mon Jun 4 14:06:12 2012 NAKAMURA Usaku * process.c (rb_exec_err): should preserve errno. diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 62e63eb3f4..6612d4b840 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -120,7 +120,6 @@ End case @color when nil @color = @tty && /dumb/ !~ ENV["TERM"] - @color &= /mswin|mingw/ !~ RUBY_PLATFORM when true @tty = true end diff --git a/lib/test/unit.rb b/lib/test/unit.rb index c64d9b488d..33bc93add1 100644 --- a/lib/test/unit.rb +++ b/lib/test/unit.rb @@ -672,7 +672,6 @@ module Test color = true when :auto, nil color = @options[:job_status] == :replace && /dumb/ !~ ENV["TERM"] - color &&= /mswin|mingw/ !~ RUBY_PLATFORM else color = false end diff --git a/sample/test.rb b/sample/test.rb index 3e73be93b6..b32a16430a 100755 --- a/sample/test.rb +++ b/sample/test.rb @@ -15,7 +15,6 @@ PROGRESS.instance_eval do @rotator = %w[- \\ | /] @bs = "\b" * @rotator[0].size @tty = STDERR.tty? && /dumb/ !~ ENV["TERM"] - @tty &&= /mswin|mingw/ !~ RUBY_PLATFORM case @color when nil @color = @tty diff --git a/win32/win32.c b/win32/win32.c index 34c62fb1a0..d7166b646b 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -629,9 +629,39 @@ rtc_error_handler(int e, const char *src, int line, const char *exe, const char static CRITICAL_SECTION select_mutex; static int NtSocketsInitialized = 0; static st_table *socklist = NULL; +static st_table *conlist = NULL; static char *envarea; static char *uenvarea; +/* License: Ruby's */ +struct constat { + struct { + int state, seq[16]; + WORD attr; + COORD saved; + } vt100; +}; +enum {constat_init = -2, constat_esc = -1, constat_seq = 0}; + +/* License: Ruby's */ +static int +free_conlist(st_data_t key, st_data_t val, st_data_t arg) +{ + xfree((struct constat *)val); + return ST_DELETE; +} + +/* License: Ruby's */ +static void +constat_delete(HANDLE h) +{ + if (conlist) { + st_data_t key = (st_data_t)h, val; + st_delete(conlist, &key, &val); + xfree((struct constat *)val); + } +} + /* License: Ruby's */ static void exit_handler(void) @@ -645,6 +675,11 @@ exit_handler(void) DeleteCriticalSection(&select_mutex); NtSocketsInitialized = 0; } + if (conlist) { + st_foreach(conlist, free_conlist, 0); + st_free_table(conlist); + conlist = NULL; + } if (envarea) { FreeEnvironmentStrings(envarea); envarea = NULL; @@ -5540,6 +5575,322 @@ rb_w32_pipe(int fds[2]) return 0; } +/* License: Ruby's */ +static struct constat * +constat_handle(HANDLE h) +{ + st_data_t data; + struct constat *p; + if (!conlist) { + conlist = st_init_numtable(); + } + if (st_lookup(conlist, (st_data_t)h, &data)) { + p = (struct constat *)data; + } + else { + CONSOLE_SCREEN_BUFFER_INFO csbi; + p = ALLOC(struct constat); + p->vt100.state = constat_init; + p->vt100.attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; + p->vt100.saved.X = p->vt100.saved.Y = 0; + if (GetConsoleScreenBufferInfo(h, &csbi)) { + p->vt100.attr = csbi.wAttributes; + } + st_insert(conlist, (st_data_t)h, (st_data_t)p); + } + return p; +} + +/* License: Ruby's */ +static void +constat_reset(HANDLE h) +{ + st_data_t data; + struct constat *p; + if (!conlist) return; + if (!st_lookup(conlist, (st_data_t)h, &data)) return; + p = (struct constat *)data; + p->vt100.state = constat_init; +} + +/* License: Ruby's */ +static DWORD +constat_attr(int count, const int *seq, DWORD attr, DWORD default_attr) +{ +#define FOREGROUND_MASK (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED) +#define BACKGROUND_MASK (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED) + DWORD bold = attr & (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY); + int rev = 0; + + if (!count) return attr; + while (count-- > 0) { + switch (*seq++) { + case 0: + attr = default_attr; + rev = 0; + bold = 0; + break; + case 1: + bold |= rev ? BACKGROUND_INTENSITY : FOREGROUND_INTENSITY; + break; + case 4: +#ifndef COMMON_LVB_UNDERSCORE +#define COMMON_LVB_UNDERSCORE 0x8000 +#endif + attr |= COMMON_LVB_UNDERSCORE; + break; + case 7: + rev = 1; + break; + + case 30: + attr &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + break; + case 17: + case 31: + attr = attr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN) | FOREGROUND_RED; + break; + case 18: + case 32: + attr = attr & ~(FOREGROUND_BLUE | FOREGROUND_RED) | FOREGROUND_GREEN; + break; + case 19: + case 33: + attr = attr & ~FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; + break; + case 20: + case 34: + attr = attr & ~(FOREGROUND_GREEN | FOREGROUND_RED) | FOREGROUND_BLUE; + break; + case 21: + case 35: + attr = attr & ~FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED; + break; + case 22: + case 36: + attr = attr & ~FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN; + break; + case 23: + case 37: + attr |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; + break; + + case 40: + attr &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED); + case 41: + attr = attr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN) | BACKGROUND_RED; + break; + case 42: + attr = attr & ~(BACKGROUND_BLUE | BACKGROUND_RED) | BACKGROUND_GREEN; + break; + case 43: + attr = attr & ~BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED; + break; + case 44: + attr = attr & ~(BACKGROUND_GREEN | BACKGROUND_RED) | BACKGROUND_BLUE; + break; + case 45: + attr = attr & ~BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED; + break; + case 46: + attr = attr & ~BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN; + break; + case 47: + attr |= BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED; + break; + } + } + if (rev) { + attr = attr & ~(FOREGROUND_MASK | BACKGROUND_MASK) | + ((attr & FOREGROUND_MASK) << 4) | + ((attr & BACKGROUND_MASK) >> 4); + } + return attr | bold; +} + +/* License: Ruby's */ +static void +constat_apply(HANDLE handle, struct constat *s, WCHAR w) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + const int *seq = s->vt100.seq; + int count = s->vt100.state; + int arg1 = 1; + COORD pos; + DWORD written; + + if (!GetConsoleScreenBufferInfo(handle, &csbi)) return; + if (count > 0 && seq[0] > 0) arg1 = seq[0]; + switch (w) { + case L'm': + SetConsoleTextAttribute(handle, constat_attr(count, seq, csbi.wAttributes, s->vt100.attr)); + break; + case L'F': + csbi.dwCursorPosition.X = 0; + case L'A': + csbi.dwCursorPosition.Y -= arg1; + if (csbi.dwCursorPosition.Y < 0) + csbi.dwCursorPosition.Y = 0; + SetConsoleCursorPosition(handle, csbi.dwCursorPosition); + break; + case L'E': + csbi.dwCursorPosition.X = 0; + case L'B': + case L'e': + csbi.dwCursorPosition.Y += arg1; + if (csbi.dwCursorPosition.Y >= csbi.dwSize.Y) + csbi.dwCursorPosition.Y = csbi.dwSize.Y; + SetConsoleCursorPosition(handle, csbi.dwCursorPosition); + break; + case L'C': + csbi.dwCursorPosition.X += arg1; + if (csbi.dwCursorPosition.X >= csbi.dwSize.X) + csbi.dwCursorPosition.X = csbi.dwSize.X; + SetConsoleCursorPosition(handle, csbi.dwCursorPosition); + break; + case L'D': + csbi.dwCursorPosition.X -= arg1; + if (csbi.dwCursorPosition.X < 0) + csbi.dwCursorPosition.X = 0; + SetConsoleCursorPosition(handle, csbi.dwCursorPosition); + break; + case L'G': + case L'`': + csbi.dwCursorPosition.X = (arg1 > csbi.dwSize.X ? csbi.dwSize.X : arg1) - 1; + SetConsoleCursorPosition(handle, csbi.dwCursorPosition); + break; + case L'd': + csbi.dwCursorPosition.Y = (arg1 > csbi.dwSize.Y ? csbi.dwSize.Y : arg1) - 1; + SetConsoleCursorPosition(handle, csbi.dwCursorPosition); + break; + case L'H': + case L'f': + pos.Y = (arg1 > csbi.dwSize.Y ? csbi.dwSize.Y : arg1) - 1; + if (count < 2 || (arg1 = seq[1]) <= 0) arg1 = 1; + pos.X = (arg1 > csbi.dwSize.X ? csbi.dwSize.X : arg1) - 1; + SetConsoleCursorPosition(handle, pos); + break; + case L'J': + switch (arg1) { + case 0: /* erase after cursor */ + FillConsoleOutputCharacterW(handle, L' ', + csbi.dwSize.X * (csbi.dwSize.Y - csbi.dwCursorPosition.Y) - csbi.dwCursorPosition.X, + csbi.dwCursorPosition, &written); + break; + case 1: /* erase before cursor */ + pos.X = 0; + pos.Y = csbi.dwCursorPosition.Y; + FillConsoleOutputCharacterW(handle, L' ', + csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X, + pos, &written); + break; + case 2: /* erase entire line */ + pos.X = 0; + pos.Y = 0; + FillConsoleOutputCharacterW(handle, L' ', csbi.dwSize.X * csbi.dwSize.Y, pos, &written); + break; + } + break; + case L'K': + switch (arg1) { + case 0: /* erase after cursor */ + FillConsoleOutputCharacterW(handle, L' ', csbi.dwSize.X - csbi.dwCursorPosition.X, csbi.dwCursorPosition, &written); + break; + case 1: /* erase before cursor */ + pos.X = 0; + pos.Y = csbi.dwCursorPosition.Y; + FillConsoleOutputCharacterW(handle, L' ', csbi.dwCursorPosition.X, pos, &written); + break; + case 2: /* erase entire line */ + pos.X = 0; + pos.Y = csbi.dwCursorPosition.Y; + FillConsoleOutputCharacterW(handle, L' ', csbi.dwSize.X, pos, &written); + break; + } + break; + case L's': + s->vt100.saved = csbi.dwCursorPosition; + break; + case L'u': + SetConsoleCursorPosition(handle, s->vt100.saved); + break; + case L'h': + if (count >= 2 && seq[0] == -1 && seq[1] == 25) { + CONSOLE_CURSOR_INFO cci; + GetConsoleCursorInfo(handle, &cci); + cci.bVisible = TRUE; + SetConsoleCursorInfo(handle, &cci); + } + break; + case L'l': + if (count >= 2 && seq[0] == -1 && seq[1] == 25) { + CONSOLE_CURSOR_INFO cci; + GetConsoleCursorInfo(handle, &cci); + cci.bVisible = FALSE; + SetConsoleCursorInfo(handle, &cci); + } + break; + } +} + +/* License: Ruby's */ +static long +constat_parse(HANDLE h, struct constat *s, const WCHAR **ptrp, long *lenp) +{ + const WCHAR *ptr = *ptrp; + long rest, len = *lenp; + while (len-- > 0) { + WCHAR wc = *ptr++; + if (wc == 0x1b) { + rest = *lenp - len - 1; + s->vt100.state = constat_esc; + } + else if (s->vt100.state == constat_esc && wc == L'[') { + rest = *lenp - len - 1; + if (rest > 0) --rest; + s->vt100.state = constat_seq; + s->vt100.seq[0] = 0; + } + else if (s->vt100.state >= constat_seq) { + if (wc >= L'0' && wc <= L'9') { + if (s->vt100.state < (int)numberof(s->vt100.seq)) { + int *seq = &s->vt100.seq[s->vt100.state]; + *seq = (*seq * 10) + (wc - L'0'); + } + } + else if (s->vt100.state == constat_seq && s->vt100.seq[0] == 0 && wc == L'?') { + s->vt100.seq[s->vt100.state++] = -1; + } + else { + do { + if (++s->vt100.state < (int)numberof(s->vt100.seq)) { + s->vt100.seq[s->vt100.state] = 0; + } + else { + s->vt100.state = (int)numberof(s->vt100.seq); + } + } while (0); + if (wc != L';') { + constat_apply(h, s, wc); + s->vt100.state = constat_init; + } + } + rest = 0; + } + else { + continue; + } + *ptrp = ptr; + *lenp = len; + return rest; + } + len = *lenp; + *ptrp = ptr; + *lenp = 0; + return len; +} + + /* License: Ruby's */ int rb_w32_close(int fd) @@ -5553,6 +5904,7 @@ rb_w32_close(int fd) } _set_osfhnd(fd, (SOCKET)INVALID_HANDLE_VALUE); socklist_delete(&sock, NULL); + constat_delete((HANDLE)sock); _close(fd); errno = save_errno; if (closesocket(sock) == SOCKET_ERROR) { @@ -5608,6 +5960,7 @@ rb_w32_read(int fd, void *buf, size_t size) retry: /* get rid of console reading bug */ if (isconsole) { + constat_reset((HANDLE)_osfhnd(fd)); if (start) len = 1; else { @@ -5860,6 +6213,10 @@ rb_w32_write_console(uintptr_t strarg, int fd) HANDLE handle; DWORD dwMode, reslen; VALUE str = strarg; + rb_encoding *utf16 = rb_enc_find("UTF-16LE"); + const WCHAR *ptr, *next; + struct constat *s; + long len; if (disable) return -1L; handle = (HANDLE)_osfhnd(fd); @@ -5867,14 +6224,23 @@ rb_w32_write_console(uintptr_t strarg, int fd) !rb_econv_has_convpath_p(rb_enc_name(rb_enc_get(str)), "UTF-16LE")) return -1L; - str = rb_str_encode(str, rb_enc_from_encoding(rb_enc_find("UTF-16LE")), + str = rb_str_encode(str, rb_enc_from_encoding(utf16), ECONV_INVALID_REPLACE|ECONV_UNDEF_REPLACE, Qnil); - if (!WriteConsoleW(handle, (LPWSTR)RSTRING_PTR(str), RSTRING_LEN(str)/2, - &reslen, NULL)) { - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - disable = TRUE; - return -1L; + ptr = (const WCHAR *)RSTRING_PTR(str); + len = RSTRING_LEN(str) / sizeof(WCHAR); + s = constat_handle(handle); + while (len > 0) { + long curlen = constat_parse(handle, s, (next = ptr, &next), &len); + if (curlen > 0) { + if (!WriteConsoleW(handle, ptr, curlen, &reslen, NULL)) { + if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) + disable = TRUE; + return -1L; + } + } + ptr = next; } + RB_GC_GUARD(str); return (long)reslen; }