win32_output.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. from __future__ import unicode_literals
  2. from ctypes import windll, byref, ArgumentError, c_char, c_long, c_ulong, c_uint, pointer
  3. from ctypes.wintypes import DWORD, HANDLE
  4. from prompt_toolkit.renderer import Output
  5. from prompt_toolkit.styles import ANSI_COLOR_NAMES
  6. from prompt_toolkit.win32_types import CONSOLE_SCREEN_BUFFER_INFO, STD_OUTPUT_HANDLE, STD_INPUT_HANDLE, COORD, SMALL_RECT
  7. import os
  8. import six
  9. __all__ = (
  10. 'Win32Output',
  11. )
  12. def _coord_byval(coord):
  13. """
  14. Turns a COORD object into a c_long.
  15. This will cause it to be passed by value instead of by reference. (That is what I think at least.)
  16. When runing ``ptipython`` is run (only with IPython), we often got the following error::
  17. Error in 'SetConsoleCursorPosition'.
  18. ArgumentError("argument 2: <class 'TypeError'>: wrong type",)
  19. argument 2: <class 'TypeError'>: wrong type
  20. It was solved by turning ``COORD`` parameters into a ``c_long`` like this.
  21. More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
  22. """
  23. return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF)
  24. #: If True: write the output of the renderer also to the following file. This
  25. #: is very useful for debugging. (e.g.: to see that we don't write more bytes
  26. #: than required.)
  27. _DEBUG_RENDER_OUTPUT = False
  28. _DEBUG_RENDER_OUTPUT_FILENAME = r'prompt-toolkit-windows-output.log'
  29. class NoConsoleScreenBufferError(Exception):
  30. """
  31. Raised when the application is not running inside a Windows Console, but
  32. the user tries to instantiate Win32Output.
  33. """
  34. def __init__(self):
  35. # Are we running in 'xterm' on Windows, like git-bash for instance?
  36. xterm = 'xterm' in os.environ.get('TERM', '')
  37. if xterm:
  38. message = ('Found %s, while expecting a Windows console. '
  39. 'Maybe try to run this program using "winpty" '
  40. 'or run it in cmd.exe instead. Or otherwise, '
  41. 'in case of Cygwin, use the Python executable '
  42. 'that is compiled for Cygwin.' % os.environ['TERM'])
  43. else:
  44. message = 'No Windows console found. Are you running cmd.exe?'
  45. super(NoConsoleScreenBufferError, self).__init__(message)
  46. class Win32Output(Output):
  47. """
  48. I/O abstraction for rendering to Windows consoles.
  49. (cmd.exe and similar.)
  50. """
  51. def __init__(self, stdout, use_complete_width=False):
  52. self.use_complete_width = use_complete_width
  53. self._buffer = []
  54. self.stdout = stdout
  55. self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))
  56. self._in_alternate_screen = False
  57. self.color_lookup_table = ColorLookupTable()
  58. # Remember the default console colors.
  59. info = self.get_win32_screen_buffer_info()
  60. self.default_attrs = info.wAttributes if info else 15
  61. if _DEBUG_RENDER_OUTPUT:
  62. self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, 'ab')
  63. def fileno(self):
  64. " Return file descriptor. "
  65. return self.stdout.fileno()
  66. def encoding(self):
  67. " Return encoding used for stdout. "
  68. return self.stdout.encoding
  69. def write(self, data):
  70. self._buffer.append(data)
  71. def write_raw(self, data):
  72. " For win32, there is no difference between write and write_raw. "
  73. self.write(data)
  74. def get_size(self):
  75. from prompt_toolkit.layout.screen import Size
  76. info = self.get_win32_screen_buffer_info()
  77. # We take the width of the *visible* region as the size. Not the width
  78. # of the complete screen buffer. (Unless use_complete_width has been
  79. # set.)
  80. if self.use_complete_width:
  81. width = info.dwSize.X
  82. else:
  83. width = info.srWindow.Right - info.srWindow.Left
  84. height = info.srWindow.Bottom - info.srWindow.Top + 1
  85. # We avoid the right margin, windows will wrap otherwise.
  86. maxwidth = info.dwSize.X - 1
  87. width = min(maxwidth, width)
  88. # Create `Size` object.
  89. return Size(rows=height, columns=width)
  90. def _winapi(self, func, *a, **kw):
  91. """
  92. Flush and call win API function.
  93. """
  94. self.flush()
  95. if _DEBUG_RENDER_OUTPUT:
  96. self.LOG.write(('%r' % func.__name__).encode('utf-8') + b'\n')
  97. self.LOG.write(b' ' + ', '.join(['%r' % i for i in a]).encode('utf-8') + b'\n')
  98. self.LOG.write(b' ' + ', '.join(['%r' % type(i) for i in a]).encode('utf-8') + b'\n')
  99. self.LOG.flush()
  100. try:
  101. return func(*a, **kw)
  102. except ArgumentError as e:
  103. if _DEBUG_RENDER_OUTPUT:
  104. self.LOG.write((' Error in %r %r %s\n' % (func.__name__, e, e)).encode('utf-8'))
  105. def get_win32_screen_buffer_info(self):
  106. """
  107. Return Screen buffer info.
  108. """
  109. # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through
  110. # `self._winapi`. Doing so causes Python to crash on certain 64bit
  111. # Python versions. (Reproduced with 64bit Python 2.7.6, on Windows
  112. # 10). It is not clear why. Possibly, it has to do with passing
  113. # these objects as an argument, or through *args.
  114. # The Python documentation contains the following - possibly related - warning:
  115. # ctypes does not support passing unions or structures with
  116. # bit-fields to functions by value. While this may work on 32-bit
  117. # x86, it's not guaranteed by the library to work in the general
  118. # case. Unions and structures with bit-fields should always be
  119. # passed to functions by pointer.
  120. # Also see:
  121. # - https://github.com/ipython/ipython/issues/10070
  122. # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406
  123. # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86
  124. self.flush()
  125. sbinfo = CONSOLE_SCREEN_BUFFER_INFO()
  126. success = windll.kernel32.GetConsoleScreenBufferInfo(self.hconsole, byref(sbinfo))
  127. # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo,
  128. # self.hconsole, byref(sbinfo))
  129. if success:
  130. return sbinfo
  131. else:
  132. raise NoConsoleScreenBufferError
  133. def set_title(self, title):
  134. """
  135. Set terminal title.
  136. """
  137. assert isinstance(title, six.text_type)
  138. self._winapi(windll.kernel32.SetConsoleTitleW, title)
  139. def clear_title(self):
  140. self._winapi(windll.kernel32.SetConsoleTitleW, '')
  141. def erase_screen(self):
  142. start = COORD(0, 0)
  143. sbinfo = self.get_win32_screen_buffer_info()
  144. length = sbinfo.dwSize.X * sbinfo.dwSize.Y
  145. self.cursor_goto(row=0, column=0)
  146. self._erase(start, length)
  147. def erase_down(self):
  148. sbinfo = self.get_win32_screen_buffer_info()
  149. size = sbinfo.dwSize
  150. start = sbinfo.dwCursorPosition
  151. length = ((size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y))
  152. self._erase(start, length)
  153. def erase_end_of_line(self):
  154. """
  155. """
  156. sbinfo = self.get_win32_screen_buffer_info()
  157. start = sbinfo.dwCursorPosition
  158. length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X
  159. self._erase(start, length)
  160. def _erase(self, start, length):
  161. chars_written = c_ulong()
  162. self._winapi(windll.kernel32.FillConsoleOutputCharacterA,
  163. self.hconsole, c_char(b' '), DWORD(length), _coord_byval(start),
  164. byref(chars_written))
  165. # Reset attributes.
  166. sbinfo = self.get_win32_screen_buffer_info()
  167. self._winapi(windll.kernel32.FillConsoleOutputAttribute,
  168. self.hconsole, sbinfo.wAttributes, length, _coord_byval(start),
  169. byref(chars_written))
  170. def reset_attributes(self):
  171. " Reset the console foreground/background color. "
  172. self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole,
  173. self.default_attrs)
  174. def set_attributes(self, attrs):
  175. fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs
  176. # Start from the default attributes.
  177. attrs = self.default_attrs
  178. # Override the last four bits: foreground color.
  179. if fgcolor is not None:
  180. attrs = attrs & ~0xf
  181. attrs |= self.color_lookup_table.lookup_fg_color(fgcolor)
  182. # Override the next four bits: background color.
  183. if bgcolor is not None:
  184. attrs = attrs & ~0xf0
  185. attrs |= self.color_lookup_table.lookup_bg_color(bgcolor)
  186. # Reverse: swap these four bits groups.
  187. if reverse:
  188. attrs = (attrs & ~0xff) | ((attrs & 0xf) << 4) | ((attrs & 0xf0) >> 4)
  189. self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, attrs)
  190. def disable_autowrap(self):
  191. # Not supported by Windows.
  192. pass
  193. def enable_autowrap(self):
  194. # Not supported by Windows.
  195. pass
  196. def cursor_goto(self, row=0, column=0):
  197. pos = COORD(x=column, y=row)
  198. self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos))
  199. def cursor_up(self, amount):
  200. sr = self.get_win32_screen_buffer_info().dwCursorPosition
  201. pos = COORD(sr.X, sr.Y - amount)
  202. self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos))
  203. def cursor_down(self, amount):
  204. self.cursor_up(-amount)
  205. def cursor_forward(self, amount):
  206. sr = self.get_win32_screen_buffer_info().dwCursorPosition
  207. # assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount)
  208. pos = COORD(max(0, sr.X + amount), sr.Y)
  209. self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos))
  210. def cursor_backward(self, amount):
  211. self.cursor_forward(-amount)
  212. def flush(self):
  213. """
  214. Write to output stream and flush.
  215. """
  216. if not self._buffer:
  217. # Only flush stdout buffer. (It could be that Python still has
  218. # something in its buffer. -- We want to be sure to print that in
  219. # the correct color.)
  220. self.stdout.flush()
  221. return
  222. data = ''.join(self._buffer)
  223. if _DEBUG_RENDER_OUTPUT:
  224. self.LOG.write(('%r' % data).encode('utf-8') + b'\n')
  225. self.LOG.flush()
  226. # Print characters one by one. This appears to be the best soluton
  227. # in oder to avoid traces of vertical lines when the completion
  228. # menu disappears.
  229. for b in data:
  230. written = DWORD()
  231. retval = windll.kernel32.WriteConsoleW(self.hconsole, b, 1, byref(written), None)
  232. assert retval != 0
  233. self._buffer = []
  234. def get_rows_below_cursor_position(self):
  235. info = self.get_win32_screen_buffer_info()
  236. return info.srWindow.Bottom - info.dwCursorPosition.Y + 1
  237. def scroll_buffer_to_prompt(self):
  238. """
  239. To be called before drawing the prompt. This should scroll the console
  240. to left, with the cursor at the bottom (if possible).
  241. """
  242. # Get current window size
  243. info = self.get_win32_screen_buffer_info()
  244. sr = info.srWindow
  245. cursor_pos = info.dwCursorPosition
  246. result = SMALL_RECT()
  247. # Scroll to the left.
  248. result.Left = 0
  249. result.Right = sr.Right - sr.Left
  250. # Scroll vertical
  251. win_height = sr.Bottom - sr.Top
  252. if 0 < sr.Bottom - cursor_pos.Y < win_height - 1:
  253. # no vertical scroll if cursor already on the screen
  254. result.Bottom = sr.Bottom
  255. else:
  256. result.Bottom = max(win_height, cursor_pos.Y)
  257. result.Top = result.Bottom - win_height
  258. # Scroll API
  259. self._winapi(windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result))
  260. def enter_alternate_screen(self):
  261. """
  262. Go to alternate screen buffer.
  263. """
  264. if not self._in_alternate_screen:
  265. GENERIC_READ = 0x80000000
  266. GENERIC_WRITE = 0x40000000
  267. # Create a new console buffer and activate that one.
  268. handle = HANDLE(self._winapi(windll.kernel32.CreateConsoleScreenBuffer, GENERIC_READ|GENERIC_WRITE,
  269. DWORD(0), None, DWORD(1), None))
  270. self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle)
  271. self.hconsole = handle
  272. self._in_alternate_screen = True
  273. def quit_alternate_screen(self):
  274. """
  275. Make stdout again the active buffer.
  276. """
  277. if self._in_alternate_screen:
  278. stdout = HANDLE(self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE))
  279. self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout)
  280. self._winapi(windll.kernel32.CloseHandle, self.hconsole)
  281. self.hconsole = stdout
  282. self._in_alternate_screen = False
  283. def enable_mouse_support(self):
  284. ENABLE_MOUSE_INPUT = 0x10
  285. handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
  286. original_mode = DWORD()
  287. self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode))
  288. self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value | ENABLE_MOUSE_INPUT)
  289. def disable_mouse_support(self):
  290. ENABLE_MOUSE_INPUT = 0x10
  291. handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
  292. original_mode = DWORD()
  293. self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode))
  294. self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value & ~ ENABLE_MOUSE_INPUT)
  295. def hide_cursor(self):
  296. pass
  297. def show_cursor(self):
  298. pass
  299. @classmethod
  300. def win32_refresh_window(cls):
  301. """
  302. Call win32 API to refresh the whole Window.
  303. This is sometimes necessary when the application paints background
  304. for completion menus. When the menu disappears, it leaves traces due
  305. to a bug in the Windows Console. Sending a repaint request solves it.
  306. """
  307. # Get console handle
  308. handle = HANDLE(windll.kernel32.GetConsoleWindow())
  309. RDW_INVALIDATE = 0x0001
  310. windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE))
  311. class FOREGROUND_COLOR:
  312. BLACK = 0x0000
  313. BLUE = 0x0001
  314. GREEN = 0x0002
  315. CYAN = 0x0003
  316. RED = 0x0004
  317. MAGENTA = 0x0005
  318. YELLOW = 0x0006
  319. GRAY = 0x0007
  320. INTENSITY = 0x0008 # Foreground color is intensified.
  321. class BACKROUND_COLOR:
  322. BLACK = 0x0000
  323. BLUE = 0x0010
  324. GREEN = 0x0020
  325. CYAN = 0x0030
  326. RED = 0x0040
  327. MAGENTA = 0x0050
  328. YELLOW = 0x0060
  329. GRAY = 0x0070
  330. INTENSITY = 0x0080 # Background color is intensified.
  331. def _create_ansi_color_dict(color_cls):
  332. " Create a table that maps the 16 named ansi colors to their Windows code. "
  333. return {
  334. 'ansidefault': color_cls.BLACK,
  335. 'ansiblack': color_cls.BLACK,
  336. 'ansidarkgray': color_cls.BLACK | color_cls.INTENSITY,
  337. 'ansilightgray': color_cls.GRAY,
  338. 'ansiwhite': color_cls.GRAY | color_cls.INTENSITY,
  339. # Low intensity.
  340. 'ansidarkred': color_cls.RED,
  341. 'ansidarkgreen': color_cls.GREEN,
  342. 'ansibrown': color_cls.YELLOW,
  343. 'ansidarkblue': color_cls.BLUE,
  344. 'ansipurple': color_cls.MAGENTA,
  345. 'ansiteal': color_cls.CYAN,
  346. # High intensity.
  347. 'ansired': color_cls.RED | color_cls.INTENSITY,
  348. 'ansigreen': color_cls.GREEN | color_cls.INTENSITY,
  349. 'ansiyellow': color_cls.YELLOW | color_cls.INTENSITY,
  350. 'ansiblue': color_cls.BLUE | color_cls.INTENSITY,
  351. 'ansifuchsia': color_cls.MAGENTA | color_cls.INTENSITY,
  352. 'ansiturquoise': color_cls.CYAN | color_cls.INTENSITY,
  353. }
  354. FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR)
  355. BG_ANSI_COLORS = _create_ansi_color_dict(BACKROUND_COLOR)
  356. assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
  357. assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
  358. class ColorLookupTable(object):
  359. """
  360. Inspired by pygments/formatters/terminal256.py
  361. """
  362. def __init__(self):
  363. self._win32_colors = self._build_color_table()
  364. self.best_match = {} # Cache
  365. @staticmethod
  366. def _build_color_table():
  367. """
  368. Build an RGB-to-256 color conversion table
  369. """
  370. FG = FOREGROUND_COLOR
  371. BG = BACKROUND_COLOR
  372. return [
  373. (0x00, 0x00, 0x00, FG.BLACK, BG.BLACK),
  374. (0x00, 0x00, 0xaa, FG.BLUE, BG.BLUE),
  375. (0x00, 0xaa, 0x00, FG.GREEN, BG.GREEN),
  376. (0x00, 0xaa, 0xaa, FG.CYAN, BG.CYAN),
  377. (0xaa, 0x00, 0x00, FG.RED, BG.RED),
  378. (0xaa, 0x00, 0xaa, FG.MAGENTA, BG.MAGENTA),
  379. (0xaa, 0xaa, 0x00, FG.YELLOW, BG.YELLOW),
  380. (0x88, 0x88, 0x88, FG.GRAY, BG.GRAY),
  381. (0x44, 0x44, 0xff, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY),
  382. (0x44, 0xff, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY),
  383. (0x44, 0xff, 0xff, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY),
  384. (0xff, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY),
  385. (0xff, 0x44, 0xff, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY),
  386. (0xff, 0xff, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY),
  387. (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY),
  388. (0xff, 0xff, 0xff, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY),
  389. ]
  390. def _closest_color(self, r, g, b):
  391. distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff)
  392. fg_match = 0
  393. bg_match = 0
  394. for r_, g_, b_, fg_, bg_ in self._win32_colors:
  395. rd = r - r_
  396. gd = g - g_
  397. bd = b - b_
  398. d = rd * rd + gd * gd + bd * bd
  399. if d < distance:
  400. fg_match = fg_
  401. bg_match = bg_
  402. distance = d
  403. return fg_match, bg_match
  404. def _color_indexes(self, color):
  405. indexes = self.best_match.get(color, None)
  406. if indexes is None:
  407. try:
  408. rgb = int(str(color), 16)
  409. except ValueError:
  410. rgb = 0
  411. r = (rgb >> 16) & 0xff
  412. g = (rgb >> 8) & 0xff
  413. b = rgb & 0xff
  414. indexes = self._closest_color(r, g, b)
  415. self.best_match[color] = indexes
  416. return indexes
  417. def lookup_fg_color(self, fg_color):
  418. """
  419. Return the color for use in the
  420. `windll.kernel32.SetConsoleTextAttribute` API call.
  421. :param fg_color: Foreground as text. E.g. 'ffffff' or 'red'
  422. """
  423. # Foreground.
  424. if fg_color in FG_ANSI_COLORS:
  425. return FG_ANSI_COLORS[fg_color]
  426. else:
  427. return self._color_indexes(fg_color)[0]
  428. def lookup_bg_color(self, bg_color):
  429. """
  430. Return the color for use in the
  431. `windll.kernel32.SetConsoleTextAttribute` API call.
  432. :param bg_color: Background as text. E.g. 'ffffff' or 'red'
  433. """
  434. # Background.
  435. if bg_color in BG_ANSI_COLORS:
  436. return BG_ANSI_COLORS[bg_color]
  437. else:
  438. return self._color_indexes(bg_color)[1]