1 # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
6 from .ansi
import AnsiFore
, AnsiBack
, AnsiStyle
, Style
, BEL
7 from .winterm
import enable_vt_processing
, WinTerm
, WinColor
, WinStyle
8 from .win32
import windll
, winapi_test
12 if windll
is not None:
16 class StreamWrapper(object):
18 Wraps a stream (such as stdout), acting as a transparent proxy for all
19 attribute access apart from method 'write()', which is delegated to our
22 def __init__(self
, wrapped
, converter
):
23 # double-underscore everything to prevent clashes with names of
24 # attributes on the wrapped stream object.
25 self
.__wrapped
= wrapped
26 self
.__convertor
= converter
28 def __getattr__(self
, name
):
29 return getattr(self
.__wrapped
, name
)
31 def __enter__(self
, *args
, **kwargs
):
32 # special method lookup bypasses __getattr__/__getattribute__, see
33 # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
34 # thus, contextlib magic methods are not proxied via __getattr__
35 return self
.__wrapped
.__enter
__(*args
, **kwargs
)
37 def __exit__(self
, *args
, **kwargs
):
38 return self
.__wrapped
.__exit
__(*args
, **kwargs
)
40 def __setstate__(self
, state
):
43 def __getstate__(self
):
46 def write(self
, text
):
47 self
.__convertor
.write(text
)
50 stream
= self
.__wrapped
51 if 'PYCHARM_HOSTED' in os
.environ
:
52 if stream
is not None and (stream
is sys
.__stdout
__ or stream
is sys
.__stderr
__):
55 stream_isatty
= stream
.isatty
56 except AttributeError:
59 return stream_isatty()
63 stream
= self
.__wrapped
66 # AttributeError in the case that the stream doesn't support being closed
67 # ValueError for the case that the stream has already been detached when atexit runs
68 except (AttributeError, ValueError):
72 class AnsiToWin32(object):
74 Implements a 'write()' method which, on Windows, will strip ANSI character
75 sequences from the text, and if outputting to a tty, will convert them into
78 ANSI_CSI_RE
= re
.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
79 ANSI_OSC_RE
= re
.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command
81 def __init__(self
, wrapped
, convert
=None, strip
=None, autoreset
=False):
82 # The wrapped stream (normally sys.stdout or sys.stderr)
83 self
.wrapped
= wrapped
85 # should we reset colors to defaults after every .write()
86 self
.autoreset
= autoreset
88 # create the proxy wrapping our output stream
89 self
.stream
= StreamWrapper(wrapped
, self
)
91 on_windows
= os
.name
== 'nt'
92 # We test if the WinAPI works, because even if we are on Windows
93 # we may be using a terminal that doesn't support the WinAPI
94 # (e.g. Cygwin Terminal). In this case it's up to the terminal
95 # to support the ANSI codes.
96 conversion_supported
= on_windows
and winapi_test()
101 system_has_native_ansi
= not on_windows
or enable_vt_processing(fd
)
102 have_tty
= not self
.stream
.closed
and self
.stream
.isatty()
103 need_conversion
= conversion_supported
and not system_has_native_ansi
105 # should we strip ANSI sequences from our output?
107 strip
= need_conversion
or not have_tty
110 # should we should convert ANSI sequences into win32 calls?
112 convert
= need_conversion
and have_tty
113 self
.convert
= convert
115 # dict of ansi codes to win32 functions and parameters
116 self
.win32_calls
= self
.get_win32_calls()
118 # are we wrapping stderr?
119 self
.on_stderr
= self
.wrapped
is sys
.stderr
121 def should_wrap(self
):
123 True if this class is actually needed. If false, then the output
124 stream will not be affected, nor will win32 calls be issued, so
125 wrapping stdout is not actually required. This will generally be
126 False on non-Windows platforms, unless optional functionality like
127 autoreset has been requested using kwargs to init()
129 return self
.convert
or self
.strip
or self
.autoreset
131 def get_win32_calls(self
):
132 if self
.convert
and winterm
:
134 AnsiStyle
.RESET_ALL
: (winterm
.reset_all
, ),
135 AnsiStyle
.BRIGHT
: (winterm
.style
, WinStyle
.BRIGHT
),
136 AnsiStyle
.DIM
: (winterm
.style
, WinStyle
.NORMAL
),
137 AnsiStyle
.NORMAL
: (winterm
.style
, WinStyle
.NORMAL
),
138 AnsiFore
.BLACK
: (winterm
.fore
, WinColor
.BLACK
),
139 AnsiFore
.RED
: (winterm
.fore
, WinColor
.RED
),
140 AnsiFore
.GREEN
: (winterm
.fore
, WinColor
.GREEN
),
141 AnsiFore
.YELLOW
: (winterm
.fore
, WinColor
.YELLOW
),
142 AnsiFore
.BLUE
: (winterm
.fore
, WinColor
.BLUE
),
143 AnsiFore
.MAGENTA
: (winterm
.fore
, WinColor
.MAGENTA
),
144 AnsiFore
.CYAN
: (winterm
.fore
, WinColor
.CYAN
),
145 AnsiFore
.WHITE
: (winterm
.fore
, WinColor
.GREY
),
146 AnsiFore
.RESET
: (winterm
.fore
, ),
147 AnsiFore
.LIGHTBLACK_EX
: (winterm
.fore
, WinColor
.BLACK
, True),
148 AnsiFore
.LIGHTRED_EX
: (winterm
.fore
, WinColor
.RED
, True),
149 AnsiFore
.LIGHTGREEN_EX
: (winterm
.fore
, WinColor
.GREEN
, True),
150 AnsiFore
.LIGHTYELLOW_EX
: (winterm
.fore
, WinColor
.YELLOW
, True),
151 AnsiFore
.LIGHTBLUE_EX
: (winterm
.fore
, WinColor
.BLUE
, True),
152 AnsiFore
.LIGHTMAGENTA_EX
: (winterm
.fore
, WinColor
.MAGENTA
, True),
153 AnsiFore
.LIGHTCYAN_EX
: (winterm
.fore
, WinColor
.CYAN
, True),
154 AnsiFore
.LIGHTWHITE_EX
: (winterm
.fore
, WinColor
.GREY
, True),
155 AnsiBack
.BLACK
: (winterm
.back
, WinColor
.BLACK
),
156 AnsiBack
.RED
: (winterm
.back
, WinColor
.RED
),
157 AnsiBack
.GREEN
: (winterm
.back
, WinColor
.GREEN
),
158 AnsiBack
.YELLOW
: (winterm
.back
, WinColor
.YELLOW
),
159 AnsiBack
.BLUE
: (winterm
.back
, WinColor
.BLUE
),
160 AnsiBack
.MAGENTA
: (winterm
.back
, WinColor
.MAGENTA
),
161 AnsiBack
.CYAN
: (winterm
.back
, WinColor
.CYAN
),
162 AnsiBack
.WHITE
: (winterm
.back
, WinColor
.GREY
),
163 AnsiBack
.RESET
: (winterm
.back
, ),
164 AnsiBack
.LIGHTBLACK_EX
: (winterm
.back
, WinColor
.BLACK
, True),
165 AnsiBack
.LIGHTRED_EX
: (winterm
.back
, WinColor
.RED
, True),
166 AnsiBack
.LIGHTGREEN_EX
: (winterm
.back
, WinColor
.GREEN
, True),
167 AnsiBack
.LIGHTYELLOW_EX
: (winterm
.back
, WinColor
.YELLOW
, True),
168 AnsiBack
.LIGHTBLUE_EX
: (winterm
.back
, WinColor
.BLUE
, True),
169 AnsiBack
.LIGHTMAGENTA_EX
: (winterm
.back
, WinColor
.MAGENTA
, True),
170 AnsiBack
.LIGHTCYAN_EX
: (winterm
.back
, WinColor
.CYAN
, True),
171 AnsiBack
.LIGHTWHITE_EX
: (winterm
.back
, WinColor
.GREY
, True),
175 def write(self
, text
):
176 if self
.strip
or self
.convert
:
177 self
.write_and_convert(text
)
179 self
.wrapped
.write(text
)
187 self
.call_win32('m', (0,))
188 elif not self
.strip
and not self
.stream
.closed
:
189 self
.wrapped
.write(Style
.RESET_ALL
)
192 def write_and_convert(self
, text
):
194 Write the given text to our wrapped stream, stripping any ANSI
195 sequences from the text, and optionally converting them into win32
199 text
= self
.convert_osc(text
)
200 for match
in self
.ANSI_CSI_RE
.finditer(text
):
201 start
, end
= match
.span()
202 self
.write_plain_text(text
, cursor
, start
)
203 self
.convert_ansi(*match
.groups())
205 self
.write_plain_text(text
, cursor
, len(text
))
208 def write_plain_text(self
, text
, start
, end
):
210 self
.wrapped
.write(text
[start
:end
])
214 def convert_ansi(self
, paramstring
, command
):
216 params
= self
.extract_params(command
, paramstring
)
217 self
.call_win32(command
, params
)
220 def extract_params(self
, command
, paramstring
):
222 params
= tuple(int(p
) if len(p
) != 0 else 1 for p
in paramstring
.split(';'))
223 while len(params
) < 2:
225 params
= params
+ (1,)
227 params
= tuple(int(p
) for p
in paramstring
.split(';') if len(p
) != 0)
232 elif command
in 'ABCD':
238 def call_win32(self
, command
, params
):
241 if param
in self
.win32_calls
:
242 func_args
= self
.win32_calls
[param
]
245 kwargs
= dict(on_stderr
=self
.on_stderr
)
246 func(*args
, **kwargs
)
248 winterm
.erase_screen(params
[0], on_stderr
=self
.on_stderr
)
250 winterm
.erase_line(params
[0], on_stderr
=self
.on_stderr
)
251 elif command
in 'Hf': # cursor position - absolute
252 winterm
.set_cursor_position(params
, on_stderr
=self
.on_stderr
)
253 elif command
in 'ABCD': # cursor position - relative
255 # A - up, B - down, C - forward, D - back
256 x
, y
= {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}
[command
]
257 winterm
.cursor_adjust(x
, y
, on_stderr
=self
.on_stderr
)
260 def convert_osc(self
, text
):
261 for match
in self
.ANSI_OSC_RE
.finditer(text
):
262 start
, end
= match
.span()
263 text
= text
[:start
] + text
[end
:]
264 paramstring
, command
= match
.groups()
266 if paramstring
.count(";") == 1:
267 params
= paramstring
.split(";")
268 # 0 - change title and icon (we will only change title)
269 # 1 - change icon (we don't support this)
271 if params
[0] in '02':
272 winterm
.set_title(params
[1])