vt100.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. """
  2. Output for vt100 terminals.
  3. A lot of thanks, regarding outputting of colors, goes to the Pygments project:
  4. (We don't rely on Pygments anymore, because many things are very custom, and
  5. everything has been highly optimized.)
  6. http://pygments.org/
  7. """
  8. from __future__ import annotations
  9. import io
  10. import os
  11. import sys
  12. from typing import Callable, Dict, Hashable, Iterable, Sequence, TextIO, Tuple
  13. from prompt_toolkit.cursor_shapes import CursorShape
  14. from prompt_toolkit.data_structures import Size
  15. from prompt_toolkit.output import Output
  16. from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs
  17. from prompt_toolkit.utils import is_dumb_terminal
  18. from .color_depth import ColorDepth
  19. from .flush_stdout import flush_stdout
  20. __all__ = [
  21. "Vt100_Output",
  22. ]
  23. FG_ANSI_COLORS = {
  24. "ansidefault": 39,
  25. # Low intensity.
  26. "ansiblack": 30,
  27. "ansired": 31,
  28. "ansigreen": 32,
  29. "ansiyellow": 33,
  30. "ansiblue": 34,
  31. "ansimagenta": 35,
  32. "ansicyan": 36,
  33. "ansigray": 37,
  34. # High intensity.
  35. "ansibrightblack": 90,
  36. "ansibrightred": 91,
  37. "ansibrightgreen": 92,
  38. "ansibrightyellow": 93,
  39. "ansibrightblue": 94,
  40. "ansibrightmagenta": 95,
  41. "ansibrightcyan": 96,
  42. "ansiwhite": 97,
  43. }
  44. BG_ANSI_COLORS = {
  45. "ansidefault": 49,
  46. # Low intensity.
  47. "ansiblack": 40,
  48. "ansired": 41,
  49. "ansigreen": 42,
  50. "ansiyellow": 43,
  51. "ansiblue": 44,
  52. "ansimagenta": 45,
  53. "ansicyan": 46,
  54. "ansigray": 47,
  55. # High intensity.
  56. "ansibrightblack": 100,
  57. "ansibrightred": 101,
  58. "ansibrightgreen": 102,
  59. "ansibrightyellow": 103,
  60. "ansibrightblue": 104,
  61. "ansibrightmagenta": 105,
  62. "ansibrightcyan": 106,
  63. "ansiwhite": 107,
  64. }
  65. ANSI_COLORS_TO_RGB = {
  66. "ansidefault": (
  67. 0x00,
  68. 0x00,
  69. 0x00,
  70. ), # Don't use, 'default' doesn't really have a value.
  71. "ansiblack": (0x00, 0x00, 0x00),
  72. "ansigray": (0xE5, 0xE5, 0xE5),
  73. "ansibrightblack": (0x7F, 0x7F, 0x7F),
  74. "ansiwhite": (0xFF, 0xFF, 0xFF),
  75. # Low intensity.
  76. "ansired": (0xCD, 0x00, 0x00),
  77. "ansigreen": (0x00, 0xCD, 0x00),
  78. "ansiyellow": (0xCD, 0xCD, 0x00),
  79. "ansiblue": (0x00, 0x00, 0xCD),
  80. "ansimagenta": (0xCD, 0x00, 0xCD),
  81. "ansicyan": (0x00, 0xCD, 0xCD),
  82. # High intensity.
  83. "ansibrightred": (0xFF, 0x00, 0x00),
  84. "ansibrightgreen": (0x00, 0xFF, 0x00),
  85. "ansibrightyellow": (0xFF, 0xFF, 0x00),
  86. "ansibrightblue": (0x00, 0x00, 0xFF),
  87. "ansibrightmagenta": (0xFF, 0x00, 0xFF),
  88. "ansibrightcyan": (0x00, 0xFF, 0xFF),
  89. }
  90. assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
  91. assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
  92. assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES)
  93. def _get_closest_ansi_color(r: int, g: int, b: int, exclude: Sequence[str] = ()) -> str:
  94. """
  95. Find closest ANSI color. Return it by name.
  96. :param r: Red (Between 0 and 255.)
  97. :param g: Green (Between 0 and 255.)
  98. :param b: Blue (Between 0 and 255.)
  99. :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.)
  100. """
  101. exclude = list(exclude)
  102. # When we have a bit of saturation, avoid the gray-like colors, otherwise,
  103. # too often the distance to the gray color is less.
  104. saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510
  105. if saturation > 30:
  106. exclude.extend(["ansilightgray", "ansidarkgray", "ansiwhite", "ansiblack"])
  107. # Take the closest color.
  108. # (Thanks to Pygments for this part.)
  109. distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff)
  110. match = "ansidefault"
  111. for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items():
  112. if name != "ansidefault" and name not in exclude:
  113. d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2
  114. if d < distance:
  115. match = name
  116. distance = d
  117. return match
  118. _ColorCodeAndName = Tuple[int, str]
  119. class _16ColorCache:
  120. """
  121. Cache which maps (r, g, b) tuples to 16 ansi colors.
  122. :param bg: Cache for background colors, instead of foreground.
  123. """
  124. def __init__(self, bg: bool = False) -> None:
  125. self.bg = bg
  126. self._cache: dict[Hashable, _ColorCodeAndName] = {}
  127. def get_code(
  128. self, value: tuple[int, int, int], exclude: Sequence[str] = ()
  129. ) -> _ColorCodeAndName:
  130. """
  131. Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for
  132. a given (r,g,b) value.
  133. """
  134. key: Hashable = (value, tuple(exclude))
  135. cache = self._cache
  136. if key not in cache:
  137. cache[key] = self._get(value, exclude)
  138. return cache[key]
  139. def _get(
  140. self, value: tuple[int, int, int], exclude: Sequence[str] = ()
  141. ) -> _ColorCodeAndName:
  142. r, g, b = value
  143. match = _get_closest_ansi_color(r, g, b, exclude=exclude)
  144. # Turn color name into code.
  145. if self.bg:
  146. code = BG_ANSI_COLORS[match]
  147. else:
  148. code = FG_ANSI_COLORS[match]
  149. return code, match
  150. class _256ColorCache(Dict[Tuple[int, int, int], int]):
  151. """
  152. Cache which maps (r, g, b) tuples to 256 colors.
  153. """
  154. def __init__(self) -> None:
  155. # Build color table.
  156. colors: list[tuple[int, int, int]] = []
  157. # colors 0..15: 16 basic colors
  158. colors.append((0x00, 0x00, 0x00)) # 0
  159. colors.append((0xCD, 0x00, 0x00)) # 1
  160. colors.append((0x00, 0xCD, 0x00)) # 2
  161. colors.append((0xCD, 0xCD, 0x00)) # 3
  162. colors.append((0x00, 0x00, 0xEE)) # 4
  163. colors.append((0xCD, 0x00, 0xCD)) # 5
  164. colors.append((0x00, 0xCD, 0xCD)) # 6
  165. colors.append((0xE5, 0xE5, 0xE5)) # 7
  166. colors.append((0x7F, 0x7F, 0x7F)) # 8
  167. colors.append((0xFF, 0x00, 0x00)) # 9
  168. colors.append((0x00, 0xFF, 0x00)) # 10
  169. colors.append((0xFF, 0xFF, 0x00)) # 11
  170. colors.append((0x5C, 0x5C, 0xFF)) # 12
  171. colors.append((0xFF, 0x00, 0xFF)) # 13
  172. colors.append((0x00, 0xFF, 0xFF)) # 14
  173. colors.append((0xFF, 0xFF, 0xFF)) # 15
  174. # colors 16..232: the 6x6x6 color cube
  175. valuerange = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF)
  176. for i in range(217):
  177. r = valuerange[(i // 36) % 6]
  178. g = valuerange[(i // 6) % 6]
  179. b = valuerange[i % 6]
  180. colors.append((r, g, b))
  181. # colors 233..253: grayscale
  182. for i in range(1, 22):
  183. v = 8 + i * 10
  184. colors.append((v, v, v))
  185. self.colors = colors
  186. def __missing__(self, value: tuple[int, int, int]) -> int:
  187. r, g, b = value
  188. # Find closest color.
  189. # (Thanks to Pygments for this!)
  190. distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff)
  191. match = 0
  192. for i, (r2, g2, b2) in enumerate(self.colors):
  193. if i >= 16: # XXX: We ignore the 16 ANSI colors when mapping RGB
  194. # to the 256 colors, because these highly depend on
  195. # the color scheme of the terminal.
  196. d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2
  197. if d < distance:
  198. match = i
  199. distance = d
  200. # Turn color name into code.
  201. self[value] = match
  202. return match
  203. _16_fg_colors = _16ColorCache(bg=False)
  204. _16_bg_colors = _16ColorCache(bg=True)
  205. _256_colors = _256ColorCache()
  206. class _EscapeCodeCache(Dict[Attrs, str]):
  207. """
  208. Cache for VT100 escape codes. It maps
  209. (fgcolor, bgcolor, bold, underline, strike, reverse) tuples to VT100
  210. escape sequences.
  211. :param true_color: When True, use 24bit colors instead of 256 colors.
  212. """
  213. def __init__(self, color_depth: ColorDepth) -> None:
  214. self.color_depth = color_depth
  215. def __missing__(self, attrs: Attrs) -> str:
  216. (
  217. fgcolor,
  218. bgcolor,
  219. bold,
  220. underline,
  221. strike,
  222. italic,
  223. blink,
  224. reverse,
  225. hidden,
  226. ) = attrs
  227. parts: list[str] = []
  228. parts.extend(self._colors_to_code(fgcolor or "", bgcolor or ""))
  229. if bold:
  230. parts.append("1")
  231. if italic:
  232. parts.append("3")
  233. if blink:
  234. parts.append("5")
  235. if underline:
  236. parts.append("4")
  237. if reverse:
  238. parts.append("7")
  239. if hidden:
  240. parts.append("8")
  241. if strike:
  242. parts.append("9")
  243. if parts:
  244. result = "\x1b[0;" + ";".join(parts) + "m"
  245. else:
  246. result = "\x1b[0m"
  247. self[attrs] = result
  248. return result
  249. def _color_name_to_rgb(self, color: str) -> tuple[int, int, int]:
  250. "Turn 'ffffff', into (0xff, 0xff, 0xff)."
  251. try:
  252. rgb = int(color, 16)
  253. except ValueError:
  254. raise
  255. else:
  256. r = (rgb >> 16) & 0xFF
  257. g = (rgb >> 8) & 0xFF
  258. b = rgb & 0xFF
  259. return r, g, b
  260. def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]:
  261. """
  262. Return a tuple with the vt100 values that represent this color.
  263. """
  264. # When requesting ANSI colors only, and both fg/bg color were converted
  265. # to ANSI, ensure that the foreground and background color are not the
  266. # same. (Unless they were explicitly defined to be the same color.)
  267. fg_ansi = ""
  268. def get(color: str, bg: bool) -> list[int]:
  269. nonlocal fg_ansi
  270. table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS
  271. if not color or self.color_depth == ColorDepth.DEPTH_1_BIT:
  272. return []
  273. # 16 ANSI colors. (Given by name.)
  274. elif color in table:
  275. return [table[color]]
  276. # RGB colors. (Defined as 'ffffff'.)
  277. else:
  278. try:
  279. rgb = self._color_name_to_rgb(color)
  280. except ValueError:
  281. return []
  282. # When only 16 colors are supported, use that.
  283. if self.color_depth == ColorDepth.DEPTH_4_BIT:
  284. if bg: # Background.
  285. if fg_color != bg_color:
  286. exclude = [fg_ansi]
  287. else:
  288. exclude = []
  289. code, name = _16_bg_colors.get_code(rgb, exclude=exclude)
  290. return [code]
  291. else: # Foreground.
  292. code, name = _16_fg_colors.get_code(rgb)
  293. fg_ansi = name
  294. return [code]
  295. # True colors. (Only when this feature is enabled.)
  296. elif self.color_depth == ColorDepth.DEPTH_24_BIT:
  297. r, g, b = rgb
  298. return [(48 if bg else 38), 2, r, g, b]
  299. # 256 RGB colors.
  300. else:
  301. return [(48 if bg else 38), 5, _256_colors[rgb]]
  302. result: list[int] = []
  303. result.extend(get(fg_color, False))
  304. result.extend(get(bg_color, True))
  305. return map(str, result)
  306. def _get_size(fileno: int) -> tuple[int, int]:
  307. """
  308. Get the size of this pseudo terminal.
  309. :param fileno: stdout.fileno()
  310. :returns: A (rows, cols) tuple.
  311. """
  312. size = os.get_terminal_size(fileno)
  313. return size.lines, size.columns
  314. class Vt100_Output(Output):
  315. """
  316. :param get_size: A callable which returns the `Size` of the output terminal.
  317. :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property.
  318. :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...)
  319. :param enable_cpr: When `True` (the default), send "cursor position
  320. request" escape sequences to the output in order to detect the cursor
  321. position. That way, we can properly determine how much space there is
  322. available for the UI (especially for drop down menus) to render. The
  323. `Renderer` will still try to figure out whether the current terminal
  324. does respond to CPR escapes. When `False`, never attempt to send CPR
  325. requests.
  326. """
  327. # For the error messages. Only display "Output is not a terminal" once per
  328. # file descriptor.
  329. _fds_not_a_terminal: set[int] = set()
  330. def __init__(
  331. self,
  332. stdout: TextIO,
  333. get_size: Callable[[], Size],
  334. term: str | None = None,
  335. default_color_depth: ColorDepth | None = None,
  336. enable_bell: bool = True,
  337. enable_cpr: bool = True,
  338. ) -> None:
  339. assert all(hasattr(stdout, a) for a in ("write", "flush"))
  340. self._buffer: list[str] = []
  341. self.stdout: TextIO = stdout
  342. self.default_color_depth = default_color_depth
  343. self._get_size = get_size
  344. self.term = term
  345. self.enable_bell = enable_bell
  346. self.enable_cpr = enable_cpr
  347. # Cache for escape codes.
  348. self._escape_code_caches: dict[ColorDepth, _EscapeCodeCache] = {
  349. ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT),
  350. ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT),
  351. ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT),
  352. ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT),
  353. }
  354. # Keep track of whether the cursor shape was ever changed.
  355. # (We don't restore the cursor shape if it was never changed - by
  356. # default, we don't change them.)
  357. self._cursor_shape_changed = False
  358. @classmethod
  359. def from_pty(
  360. cls,
  361. stdout: TextIO,
  362. term: str | None = None,
  363. default_color_depth: ColorDepth | None = None,
  364. enable_bell: bool = True,
  365. ) -> Vt100_Output:
  366. """
  367. Create an Output class from a pseudo terminal.
  368. (This will take the dimensions by reading the pseudo
  369. terminal attributes.)
  370. """
  371. fd: int | None
  372. # Normally, this requires a real TTY device, but people instantiate
  373. # this class often during unit tests as well. For convenience, we print
  374. # an error message, use standard dimensions, and go on.
  375. try:
  376. fd = stdout.fileno()
  377. except io.UnsupportedOperation:
  378. fd = None
  379. if not stdout.isatty() and (fd is None or fd not in cls._fds_not_a_terminal):
  380. msg = "Warning: Output is not a terminal (fd=%r).\n"
  381. sys.stderr.write(msg % fd)
  382. sys.stderr.flush()
  383. if fd is not None:
  384. cls._fds_not_a_terminal.add(fd)
  385. def get_size() -> Size:
  386. # If terminal (incorrectly) reports its size as 0, pick a
  387. # reasonable default. See
  388. # https://github.com/ipython/ipython/issues/10071
  389. rows, columns = (None, None)
  390. # It is possible that `stdout` is no longer a TTY device at this
  391. # point. In that case we get an `OSError` in the ioctl call in
  392. # `get_size`. See:
  393. # https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1021
  394. try:
  395. rows, columns = _get_size(stdout.fileno())
  396. except OSError:
  397. pass
  398. return Size(rows=rows or 24, columns=columns or 80)
  399. return cls(
  400. stdout,
  401. get_size,
  402. term=term,
  403. default_color_depth=default_color_depth,
  404. enable_bell=enable_bell,
  405. )
  406. def get_size(self) -> Size:
  407. return self._get_size()
  408. def fileno(self) -> int:
  409. "Return file descriptor."
  410. return self.stdout.fileno()
  411. def encoding(self) -> str:
  412. "Return encoding used for stdout."
  413. return self.stdout.encoding
  414. def write_raw(self, data: str) -> None:
  415. """
  416. Write raw data to output.
  417. """
  418. self._buffer.append(data)
  419. def write(self, data: str) -> None:
  420. """
  421. Write text to output.
  422. (Removes vt100 escape codes. -- used for safely writing text.)
  423. """
  424. self._buffer.append(data.replace("\x1b", "?"))
  425. def set_title(self, title: str) -> None:
  426. """
  427. Set terminal title.
  428. """
  429. if self.term not in (
  430. "linux",
  431. "eterm-color",
  432. ): # Not supported by the Linux console.
  433. self.write_raw(
  434. "\x1b]2;%s\x07" % title.replace("\x1b", "").replace("\x07", "")
  435. )
  436. def clear_title(self) -> None:
  437. self.set_title("")
  438. def erase_screen(self) -> None:
  439. """
  440. Erases the screen with the background color and moves the cursor to
  441. home.
  442. """
  443. self.write_raw("\x1b[2J")
  444. def enter_alternate_screen(self) -> None:
  445. self.write_raw("\x1b[?1049h\x1b[H")
  446. def quit_alternate_screen(self) -> None:
  447. self.write_raw("\x1b[?1049l")
  448. def enable_mouse_support(self) -> None:
  449. self.write_raw("\x1b[?1000h")
  450. # Enable mouse-drag support.
  451. self.write_raw("\x1b[?1003h")
  452. # Enable urxvt Mouse mode. (For terminals that understand this.)
  453. self.write_raw("\x1b[?1015h")
  454. # Also enable Xterm SGR mouse mode. (For terminals that understand this.)
  455. self.write_raw("\x1b[?1006h")
  456. # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr
  457. # extensions.
  458. def disable_mouse_support(self) -> None:
  459. self.write_raw("\x1b[?1000l")
  460. self.write_raw("\x1b[?1015l")
  461. self.write_raw("\x1b[?1006l")
  462. self.write_raw("\x1b[?1003l")
  463. def erase_end_of_line(self) -> None:
  464. """
  465. Erases from the current cursor position to the end of the current line.
  466. """
  467. self.write_raw("\x1b[K")
  468. def erase_down(self) -> None:
  469. """
  470. Erases the screen from the current line down to the bottom of the
  471. screen.
  472. """
  473. self.write_raw("\x1b[J")
  474. def reset_attributes(self) -> None:
  475. self.write_raw("\x1b[0m")
  476. def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
  477. """
  478. Create new style and output.
  479. :param attrs: `Attrs` instance.
  480. """
  481. # Get current depth.
  482. escape_code_cache = self._escape_code_caches[color_depth]
  483. # Write escape character.
  484. self.write_raw(escape_code_cache[attrs])
  485. def disable_autowrap(self) -> None:
  486. self.write_raw("\x1b[?7l")
  487. def enable_autowrap(self) -> None:
  488. self.write_raw("\x1b[?7h")
  489. def enable_bracketed_paste(self) -> None:
  490. self.write_raw("\x1b[?2004h")
  491. def disable_bracketed_paste(self) -> None:
  492. self.write_raw("\x1b[?2004l")
  493. def reset_cursor_key_mode(self) -> None:
  494. """
  495. For vt100 only.
  496. Put the terminal in cursor mode (instead of application mode).
  497. """
  498. # Put the terminal in cursor mode. (Instead of application mode.)
  499. self.write_raw("\x1b[?1l")
  500. def cursor_goto(self, row: int = 0, column: int = 0) -> None:
  501. """
  502. Move cursor position.
  503. """
  504. self.write_raw("\x1b[%i;%iH" % (row, column))
  505. def cursor_up(self, amount: int) -> None:
  506. if amount == 0:
  507. pass
  508. elif amount == 1:
  509. self.write_raw("\x1b[A")
  510. else:
  511. self.write_raw("\x1b[%iA" % amount)
  512. def cursor_down(self, amount: int) -> None:
  513. if amount == 0:
  514. pass
  515. elif amount == 1:
  516. # Note: Not the same as '\n', '\n' can cause the window content to
  517. # scroll.
  518. self.write_raw("\x1b[B")
  519. else:
  520. self.write_raw("\x1b[%iB" % amount)
  521. def cursor_forward(self, amount: int) -> None:
  522. if amount == 0:
  523. pass
  524. elif amount == 1:
  525. self.write_raw("\x1b[C")
  526. else:
  527. self.write_raw("\x1b[%iC" % amount)
  528. def cursor_backward(self, amount: int) -> None:
  529. if amount == 0:
  530. pass
  531. elif amount == 1:
  532. self.write_raw("\b") # '\x1b[D'
  533. else:
  534. self.write_raw("\x1b[%iD" % amount)
  535. def hide_cursor(self) -> None:
  536. self.write_raw("\x1b[?25l")
  537. def show_cursor(self) -> None:
  538. self.write_raw("\x1b[?12l\x1b[?25h") # Stop blinking cursor and show.
  539. def set_cursor_shape(self, cursor_shape: CursorShape) -> None:
  540. if cursor_shape == CursorShape._NEVER_CHANGE:
  541. return
  542. self._cursor_shape_changed = True
  543. self.write_raw(
  544. {
  545. CursorShape.BLOCK: "\x1b[2 q",
  546. CursorShape.BEAM: "\x1b[6 q",
  547. CursorShape.UNDERLINE: "\x1b[4 q",
  548. CursorShape.BLINKING_BLOCK: "\x1b[1 q",
  549. CursorShape.BLINKING_BEAM: "\x1b[5 q",
  550. CursorShape.BLINKING_UNDERLINE: "\x1b[3 q",
  551. }.get(cursor_shape, "")
  552. )
  553. def reset_cursor_shape(self) -> None:
  554. "Reset cursor shape."
  555. # (Only reset cursor shape, if we ever changed it.)
  556. if self._cursor_shape_changed:
  557. self._cursor_shape_changed = False
  558. # Reset cursor shape.
  559. self.write_raw("\x1b[0 q")
  560. def flush(self) -> None:
  561. """
  562. Write to output stream and flush.
  563. """
  564. if not self._buffer:
  565. return
  566. data = "".join(self._buffer)
  567. self._buffer = []
  568. flush_stdout(self.stdout, data)
  569. def ask_for_cpr(self) -> None:
  570. """
  571. Asks for a cursor position report (CPR).
  572. """
  573. self.write_raw("\x1b[6n")
  574. self.flush()
  575. @property
  576. def responds_to_cpr(self) -> bool:
  577. if not self.enable_cpr:
  578. return False
  579. # When the input is a tty, we assume that CPR is supported.
  580. # It's not when the input is piped from Pexpect.
  581. if os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1":
  582. return False
  583. if is_dumb_terminal(self.term):
  584. return False
  585. try:
  586. return self.stdout.isatty()
  587. except ValueError:
  588. return False # ValueError: I/O operation on closed file
  589. def bell(self) -> None:
  590. "Sound bell."
  591. if self.enable_bell:
  592. self.write_raw("\a")
  593. self.flush()
  594. def get_default_color_depth(self) -> ColorDepth:
  595. """
  596. Return the default color depth for a vt100 terminal, according to the
  597. our term value.
  598. We prefer 256 colors almost always, because this is what most terminals
  599. support these days, and is a good default.
  600. """
  601. if self.default_color_depth is not None:
  602. return self.default_color_depth
  603. term = self.term
  604. if term is None:
  605. return ColorDepth.DEFAULT
  606. if is_dumb_terminal(term):
  607. return ColorDepth.DEPTH_1_BIT
  608. if term in ("linux", "eterm-color"):
  609. return ColorDepth.DEPTH_4_BIT
  610. return ColorDepth.DEFAULT