From 3d33f52a78968cd45d7e0383eb13d9619c8efb2a Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Wed, 4 Sep 2019 11:01:10 -0700 Subject: [PATCH 1/7] Add support for ouput styling on Windows 10 --- mypy/util.py | 75 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index 7284d0639ca2..d1cc50a04886 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -338,38 +338,61 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> None: self.show_error_codes = show_error_codes # Check if we are in a human-facing terminal on a supported platform. - if sys.platform not in ('linux', 'darwin'): + if sys.platform not in ('linux', 'darwin', 'win32'): self.dummy_term = True return force_color = int(os.getenv('MYPY_FORCE_COLOR', '0')) if not force_color and (not f_out.isatty() or not f_err.isatty()): self.dummy_term = True return - - # We in a human-facing terminal, check if it supports enough styling. - if not CURSES_ENABLED: - self.dummy_term = True - return - try: - curses.setupterm() - except curses.error: - # Most likely terminfo not found. - self.dummy_term = True - return - bold = curses.tigetstr('bold') - under = curses.tigetstr('smul') - set_color = curses.tigetstr('setaf') - self.dummy_term = not (bold and under and set_color) - if self.dummy_term: - return - - self.BOLD = bold.decode() - self.UNDER = under.decode() - self.BLUE = curses.tparm(set_color, curses.COLOR_BLUE).decode() - self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode() - self.RED = curses.tparm(set_color, curses.COLOR_RED).decode() - self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode() - self.NORMAL = curses.tigetstr('sgr0').decode() + if sys.platform == 'win32': + # Windows ANSI escape sequences are only supported on Threshold 2 and above. + if sys.getwindowsversion().build < 10586: + self.dummy_term = True + return + self.dummy_term = False + import ctypes + kernel32 = ctypes.windll.kernel32 + ENABLE_PROCESSED_OUTPUT = 0x1 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + STD_OUTPUT_HANDLE = -11 + kernel32.SetConsoleMode(kernel32.GetStdHandle(STD_OUTPUT_HANDLE), + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.BOLD = '\033[1m' + self.UNDER = '\033[4m' + self.BLUE = '\033[94m' + self.GREEN = '\033[92m' + self.RED = '\033[91m' + self.YELLOW = '\033[93m' + self.NORMAL = '\033[0m' + else: + # We in a human-facing terminal, check if it supports enough styling. + if not CURSES_ENABLED: + self.dummy_term = True + return + try: + curses.setupterm() + except curses.error: + # Most likely terminfo not found. + self.dummy_term = True + return + bold = curses.tigetstr('bold') + under = curses.tigetstr('smul') + set_color = curses.tigetstr('setaf') + self.dummy_term = not (bold and under and set_color) + if self.dummy_term: + return + + self.BOLD = bold.decode() + self.UNDER = under.decode() + self.BLUE = curses.tparm(set_color, curses.COLOR_BLUE).decode() + self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode() + self.RED = curses.tparm(set_color, curses.COLOR_RED).decode() + self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode() + self.NORMAL = curses.tigetstr('sgr0').decode() self.colors = {'red': self.RED, 'green': self.GREEN, 'blue': self.BLUE, 'yellow': self.YELLOW, 'none': ''} From 96a89a9785910a3822a95eb21ae40c36ba0e8214 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Wed, 4 Sep 2019 12:31:23 -0700 Subject: [PATCH 2/7] Fix lint --- mypy/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index 3ce2a7fb7ac8..531160c503b4 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -487,9 +487,9 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> No ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 STD_OUTPUT_HANDLE = -11 kernel32.SetConsoleMode(kernel32.GetStdHandle(STD_OUTPUT_HANDLE), - ENABLE_PROCESSED_OUTPUT - | ENABLE_WRAP_AT_EOL_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING) self.BOLD = '\033[1m' self.UNDER = '\033[4m' self.BLUE = '\033[94m' From f77d478790dfc8406820f9e76bd4af583496fa48 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Thu, 5 Sep 2019 13:08:31 -0700 Subject: [PATCH 3/7] Respond to review --- mypy/util.py | 117 +++++++++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index 531160c503b4..74f5c8bf669c 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -38,6 +38,13 @@ # error location when printing source code snippet. MINIMUM_WIDTH = 20 +# VT100 color code processing was added in Windows 10, but only the second major update, +# Threshold 2. Fortunately, everyone (even on LTSB, Long Term Support Branch) should +# have a version of Windows 10 newer than this. Note that Windows 8 and below are not +# supported, but are either going out of support, or make up only a few % of the market. +MINIMUM_WINDOWS_MAJOR_VT100 = 10 +MINIMUM_WINDOWS_BUILD_VT100 = 10586 + default_python2_interpreter = \ ['python2', 'python', '/usr/bin/python', 'C:\\Python27\\python.exe'] # type: Final @@ -475,62 +482,70 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> No self.dummy_term = True return if sys.platform == 'win32': - # Windows ANSI escape sequences are only supported on Threshold 2 and above. - if sys.getwindowsversion().build < 10586: - self.dummy_term = True - return - self.dummy_term = False - import ctypes - kernel32 = ctypes.windll.kernel32 - ENABLE_PROCESSED_OUTPUT = 0x1 - ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 - STD_OUTPUT_HANDLE = -11 - kernel32.SetConsoleMode(kernel32.GetStdHandle(STD_OUTPUT_HANDLE), - ENABLE_PROCESSED_OUTPUT - | ENABLE_WRAP_AT_EOL_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - self.BOLD = '\033[1m' - self.UNDER = '\033[4m' - self.BLUE = '\033[94m' - self.GREEN = '\033[92m' - self.RED = '\033[91m' - self.YELLOW = '\033[93m' - self.NORMAL = '\033[0m' - self.DIM = '\033[2m' + self.initialize_win_colors() else: - # We in a human-facing terminal, check if it supports enough styling. - if not CURSES_ENABLED: - self.dummy_term = True - return - try: - curses.setupterm() - except curses.error: - # Most likely terminfo not found. - self.dummy_term = True - return - bold = curses.tigetstr('bold') - under = curses.tigetstr('smul') - set_color = curses.tigetstr('setaf') - self.dummy_term = not (bold and under and set_color) - if self.dummy_term: - return - - self.NORMAL = curses.tigetstr('sgr0').decode() - self.BOLD = bold.decode() - self.UNDER = under.decode() - dim = curses.tigetstr('dim') - # TODO: more reliable way to get gray color good for both dark and light schemes. - self.DIM = dim.decode() if dim else PLAIN_ANSI_DIM - - self.BLUE = curses.tparm(set_color, curses.COLOR_BLUE).decode() - self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode() - self.RED = curses.tparm(set_color, curses.COLOR_RED).decode() - self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode() + self.initialize_unix_colors() self.colors = {'red': self.RED, 'green': self.GREEN, 'blue': self.BLUE, 'yellow': self.YELLOW, 'none': ''} + def initialize_win_colors(self) -> None: + # Windows ANSI escape sequences are only supported on Threshold 2 and above. + winver = sys.getwindowsversion() + if (winver.major < MINIMUM_WINDOWS_MAJOR_VT100 + or winver.build < MINIMUM_WINDOWS_BUILD_VT100): + self.dummy_term = True + return + self.dummy_term = False + import ctypes + kernel32 = ctypes.windll.kernel32 + ENABLE_PROCESSED_OUTPUT = 0x1 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + STD_OUTPUT_HANDLE = -11 + kernel32.SetConsoleMode(kernel32.GetStdHandle(STD_OUTPUT_HANDLE), + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.BOLD = '\033[1m' + self.UNDER = '\033[4m' + self.BLUE = '\033[94m' + self.GREEN = '\033[92m' + self.RED = '\033[91m' + self.YELLOW = '\033[93m' + self.NORMAL = '\033[0m' + self.DIM = '\033[2m' + + def initialize_unix_colors(self) -> None: + # We in a human-facing terminal, check if it supports enough styling. + if not CURSES_ENABLED: + self.dummy_term = True + return + try: + curses.setupterm() + except curses.error: + # Most likely terminfo not found. + self.dummy_term = True + return + bold = curses.tigetstr('bold') + under = curses.tigetstr('smul') + set_color = curses.tigetstr('setaf') + self.dummy_term = not (bold and under and set_color) + if self.dummy_term: + return + + self.NORMAL = curses.tigetstr('sgr0').decode() + self.BOLD = bold.decode() + self.UNDER = under.decode() + dim = curses.tigetstr('dim') + # TODO: more reliable way to get gray color good for both dark and light schemes. + self.DIM = dim.decode() if dim else PLAIN_ANSI_DIM + + self.BLUE = curses.tparm(set_color, curses.COLOR_BLUE).decode() + self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode() + self.RED = curses.tparm(set_color, curses.COLOR_RED).decode() + self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode() + def style(self, text: str, color: Literal['red', 'green', 'blue', 'yellow', 'none'], bold: bool = False, underline: bool = False, dim: bool = False) -> str: """Apply simple color and style (underlined or bold).""" From a402e4204ff253a72fafdc7a3243a76eaf4a155c Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Thu, 5 Sep 2019 13:30:43 -0700 Subject: [PATCH 4/7] Fix typecheck on Linux --- mypy/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/util.py b/mypy/util.py index 74f5c8bf669c..20ee8eb666e9 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -491,6 +491,7 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> No def initialize_win_colors(self) -> None: # Windows ANSI escape sequences are only supported on Threshold 2 and above. + assert sys.platform == 'win32' winver = sys.getwindowsversion() if (winver.major < MINIMUM_WINDOWS_MAJOR_VT100 or winver.build < MINIMUM_WINDOWS_BUILD_VT100): From 01b544ae414cacd36b3198bfa2a80a0d0628f059 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Thu, 5 Sep 2019 13:46:17 -0700 Subject: [PATCH 5/7] Fix early returns --- mypy/util.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index 20ee8eb666e9..d94db101c740 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -482,22 +482,21 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> No self.dummy_term = True return if sys.platform == 'win32': - self.initialize_win_colors() + self.dummy_term = self.initialize_win_colors() else: - self.initialize_unix_colors() - self.colors = {'red': self.RED, 'green': self.GREEN, - 'blue': self.BLUE, 'yellow': self.YELLOW, - 'none': ''} + self.dummy_term = self.initialize_unix_colors() + if not self.dummy_term: + self.colors = {'red': self.RED, 'green': self.GREEN, + 'blue': self.BLUE, 'yellow': self.YELLOW, + 'none': ''} - def initialize_win_colors(self) -> None: + def initialize_win_colors(self) -> bool: # Windows ANSI escape sequences are only supported on Threshold 2 and above. assert sys.platform == 'win32' winver = sys.getwindowsversion() if (winver.major < MINIMUM_WINDOWS_MAJOR_VT100 or winver.build < MINIMUM_WINDOWS_BUILD_VT100): - self.dummy_term = True - return - self.dummy_term = False + return True import ctypes kernel32 = ctypes.windll.kernel32 ENABLE_PROCESSED_OUTPUT = 0x1 @@ -516,24 +515,22 @@ def initialize_win_colors(self) -> None: self.YELLOW = '\033[93m' self.NORMAL = '\033[0m' self.DIM = '\033[2m' + return False - def initialize_unix_colors(self) -> None: + def initialize_unix_colors(self) -> bool: # We in a human-facing terminal, check if it supports enough styling. if not CURSES_ENABLED: - self.dummy_term = True - return + return True try: curses.setupterm() except curses.error: # Most likely terminfo not found. - self.dummy_term = True - return + return True bold = curses.tigetstr('bold') under = curses.tigetstr('smul') set_color = curses.tigetstr('setaf') - self.dummy_term = not (bold and under and set_color) - if self.dummy_term: - return + if not (bold and under and set_color): + return True self.NORMAL = curses.tigetstr('sgr0').decode() self.BOLD = bold.decode() @@ -546,6 +543,7 @@ def initialize_unix_colors(self) -> None: self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode() self.RED = curses.tparm(set_color, curses.COLOR_RED).decode() self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode() + return False def style(self, text: str, color: Literal['red', 'green', 'blue', 'yellow', 'none'], bold: bool = False, underline: bool = False, dim: bool = False) -> str: From e7f6c30fd6d867a0ccfb771487bd6ad1684e4f28 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Thu, 5 Sep 2019 13:50:30 -0700 Subject: [PATCH 6/7] Actually check for the platform --- mypy/util.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index d94db101c740..89fac033baf7 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -492,30 +492,30 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> No def initialize_win_colors(self) -> bool: # Windows ANSI escape sequences are only supported on Threshold 2 and above. - assert sys.platform == 'win32' - winver = sys.getwindowsversion() - if (winver.major < MINIMUM_WINDOWS_MAJOR_VT100 - or winver.build < MINIMUM_WINDOWS_BUILD_VT100): - return True - import ctypes - kernel32 = ctypes.windll.kernel32 - ENABLE_PROCESSED_OUTPUT = 0x1 - ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 - STD_OUTPUT_HANDLE = -11 - kernel32.SetConsoleMode(kernel32.GetStdHandle(STD_OUTPUT_HANDLE), - ENABLE_PROCESSED_OUTPUT - | ENABLE_WRAP_AT_EOL_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - self.BOLD = '\033[1m' - self.UNDER = '\033[4m' - self.BLUE = '\033[94m' - self.GREEN = '\033[92m' - self.RED = '\033[91m' - self.YELLOW = '\033[93m' - self.NORMAL = '\033[0m' - self.DIM = '\033[2m' - return False + if sys.platform == 'win32': + winver = sys.getwindowsversion() + if (winver.major < MINIMUM_WINDOWS_MAJOR_VT100 + or winver.build < MINIMUM_WINDOWS_BUILD_VT100): + return True + import ctypes + kernel32 = ctypes.windll.kernel32 + ENABLE_PROCESSED_OUTPUT = 0x1 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + STD_OUTPUT_HANDLE = -11 + kernel32.SetConsoleMode(kernel32.GetStdHandle(STD_OUTPUT_HANDLE), + ENABLE_PROCESSED_OUTPUT + | ENABLE_WRAP_AT_EOL_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.BOLD = '\033[1m' + self.UNDER = '\033[4m' + self.BLUE = '\033[94m' + self.GREEN = '\033[92m' + self.RED = '\033[91m' + self.YELLOW = '\033[93m' + self.NORMAL = '\033[0m' + self.DIM = '\033[2m' + return False def initialize_unix_colors(self) -> bool: # We in a human-facing terminal, check if it supports enough styling. From ce30a80fb368dac7e7c697a213993c140c1bae15 Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Sat, 7 Sep 2019 12:21:32 -0700 Subject: [PATCH 7/7] Respond to review again --- mypy/util.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/mypy/util.py b/mypy/util.py index 89fac033baf7..a19c887d4c63 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -482,21 +482,25 @@ def __init__(self, f_out: IO[str], f_err: IO[str], show_error_codes: bool) -> No self.dummy_term = True return if sys.platform == 'win32': - self.dummy_term = self.initialize_win_colors() + self.dummy_term = not self.initialize_win_colors() else: - self.dummy_term = self.initialize_unix_colors() + self.dummy_term = not self.initialize_unix_colors() if not self.dummy_term: self.colors = {'red': self.RED, 'green': self.GREEN, 'blue': self.BLUE, 'yellow': self.YELLOW, 'none': ''} def initialize_win_colors(self) -> bool: + """Return True if initialization was successful and we can use colors, False otherwise""" # Windows ANSI escape sequences are only supported on Threshold 2 and above. + # we check with an assert at runtime and an if check for mypy, as asserts do not + # yet narrow platform + assert sys.platform == 'win32' if sys.platform == 'win32': winver = sys.getwindowsversion() if (winver.major < MINIMUM_WINDOWS_MAJOR_VT100 or winver.build < MINIMUM_WINDOWS_BUILD_VT100): - return True + return False import ctypes kernel32 = ctypes.windll.kernel32 ENABLE_PROCESSED_OUTPUT = 0x1 @@ -515,22 +519,23 @@ def initialize_win_colors(self) -> bool: self.YELLOW = '\033[93m' self.NORMAL = '\033[0m' self.DIM = '\033[2m' - return False + return True + return False def initialize_unix_colors(self) -> bool: - # We in a human-facing terminal, check if it supports enough styling. + """Return True if initialization was successful and we can use colors, False otherwise""" if not CURSES_ENABLED: - return True + return False try: curses.setupterm() except curses.error: # Most likely terminfo not found. - return True + return False bold = curses.tigetstr('bold') under = curses.tigetstr('smul') set_color = curses.tigetstr('setaf') if not (bold and under and set_color): - return True + return False self.NORMAL = curses.tigetstr('sgr0').decode() self.BOLD = bold.decode() @@ -543,7 +548,7 @@ def initialize_unix_colors(self) -> bool: self.GREEN = curses.tparm(set_color, curses.COLOR_GREEN).decode() self.RED = curses.tparm(set_color, curses.COLOR_RED).decode() self.YELLOW = curses.tparm(set_color, curses.COLOR_YELLOW).decode() - return False + return True def style(self, text: str, color: Literal['red', 'green', 'blue', 'yellow', 'none'], bold: bool = False, underline: bool = False, dim: bool = False) -> str: