Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/output/vt100.py: 33%

321 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

1""" 

2Output for vt100 terminals. 

3 

4A lot of thanks, regarding outputting of colors, goes to the Pygments project: 

5(We don't rely on Pygments anymore, because many things are very custom, and 

6everything has been highly optimized.) 

7http://pygments.org/ 

8""" 

9from __future__ import annotations 

10 

11import io 

12import os 

13import sys 

14from typing import ( 

15 Callable, 

16 Dict, 

17 Hashable, 

18 Iterable, 

19 List, 

20 Optional, 

21 Sequence, 

22 Set, 

23 TextIO, 

24 Tuple, 

25) 

26 

27from prompt_toolkit.cursor_shapes import CursorShape 

28from prompt_toolkit.data_structures import Size 

29from prompt_toolkit.output import Output 

30from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs 

31from prompt_toolkit.utils import is_dumb_terminal 

32 

33from .color_depth import ColorDepth 

34from .flush_stdout import flush_stdout 

35 

36__all__ = [ 

37 "Vt100_Output", 

38] 

39 

40 

41FG_ANSI_COLORS = { 

42 "ansidefault": 39, 

43 # Low intensity. 

44 "ansiblack": 30, 

45 "ansired": 31, 

46 "ansigreen": 32, 

47 "ansiyellow": 33, 

48 "ansiblue": 34, 

49 "ansimagenta": 35, 

50 "ansicyan": 36, 

51 "ansigray": 37, 

52 # High intensity. 

53 "ansibrightblack": 90, 

54 "ansibrightred": 91, 

55 "ansibrightgreen": 92, 

56 "ansibrightyellow": 93, 

57 "ansibrightblue": 94, 

58 "ansibrightmagenta": 95, 

59 "ansibrightcyan": 96, 

60 "ansiwhite": 97, 

61} 

62 

63BG_ANSI_COLORS = { 

64 "ansidefault": 49, 

65 # Low intensity. 

66 "ansiblack": 40, 

67 "ansired": 41, 

68 "ansigreen": 42, 

69 "ansiyellow": 43, 

70 "ansiblue": 44, 

71 "ansimagenta": 45, 

72 "ansicyan": 46, 

73 "ansigray": 47, 

74 # High intensity. 

75 "ansibrightblack": 100, 

76 "ansibrightred": 101, 

77 "ansibrightgreen": 102, 

78 "ansibrightyellow": 103, 

79 "ansibrightblue": 104, 

80 "ansibrightmagenta": 105, 

81 "ansibrightcyan": 106, 

82 "ansiwhite": 107, 

83} 

84 

85 

86ANSI_COLORS_TO_RGB = { 

87 "ansidefault": ( 

88 0x00, 

89 0x00, 

90 0x00, 

91 ), # Don't use, 'default' doesn't really have a value. 

92 "ansiblack": (0x00, 0x00, 0x00), 

93 "ansigray": (0xE5, 0xE5, 0xE5), 

94 "ansibrightblack": (0x7F, 0x7F, 0x7F), 

95 "ansiwhite": (0xFF, 0xFF, 0xFF), 

96 # Low intensity. 

97 "ansired": (0xCD, 0x00, 0x00), 

98 "ansigreen": (0x00, 0xCD, 0x00), 

99 "ansiyellow": (0xCD, 0xCD, 0x00), 

100 "ansiblue": (0x00, 0x00, 0xCD), 

101 "ansimagenta": (0xCD, 0x00, 0xCD), 

102 "ansicyan": (0x00, 0xCD, 0xCD), 

103 # High intensity. 

104 "ansibrightred": (0xFF, 0x00, 0x00), 

105 "ansibrightgreen": (0x00, 0xFF, 0x00), 

106 "ansibrightyellow": (0xFF, 0xFF, 0x00), 

107 "ansibrightblue": (0x00, 0x00, 0xFF), 

108 "ansibrightmagenta": (0xFF, 0x00, 0xFF), 

109 "ansibrightcyan": (0x00, 0xFF, 0xFF), 

110} 

111 

112 

113assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 

114assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 

115assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) 

116 

117 

118def _get_closest_ansi_color(r: int, g: int, b: int, exclude: Sequence[str] = ()) -> str: 

119 """ 

120 Find closest ANSI color. Return it by name. 

121 

122 :param r: Red (Between 0 and 255.) 

123 :param g: Green (Between 0 and 255.) 

124 :param b: Blue (Between 0 and 255.) 

125 :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.) 

126 """ 

127 exclude = list(exclude) 

128 

129 # When we have a bit of saturation, avoid the gray-like colors, otherwise, 

130 # too often the distance to the gray color is less. 

131 saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510 

132 

133 if saturation > 30: 

134 exclude.extend(["ansilightgray", "ansidarkgray", "ansiwhite", "ansiblack"]) 

135 

136 # Take the closest color. 

137 # (Thanks to Pygments for this part.) 

138 distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) 

139 match = "ansidefault" 

140 

141 for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): 

142 if name != "ansidefault" and name not in exclude: 

143 d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 

144 

145 if d < distance: 

146 match = name 

147 distance = d 

148 

149 return match 

150 

151 

152_ColorCodeAndName = Tuple[int, str] 

153 

154 

155class _16ColorCache: 

156 """ 

157 Cache which maps (r, g, b) tuples to 16 ansi colors. 

158 

159 :param bg: Cache for background colors, instead of foreground. 

160 """ 

161 

162 def __init__(self, bg: bool = False) -> None: 

163 self.bg = bg 

164 self._cache: dict[Hashable, _ColorCodeAndName] = {} 

165 

166 def get_code( 

167 self, value: tuple[int, int, int], exclude: Sequence[str] = () 

168 ) -> _ColorCodeAndName: 

169 """ 

170 Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for 

171 a given (r,g,b) value. 

172 """ 

173 key: Hashable = (value, tuple(exclude)) 

174 cache = self._cache 

175 

176 if key not in cache: 

177 cache[key] = self._get(value, exclude) 

178 

179 return cache[key] 

180 

181 def _get( 

182 self, value: tuple[int, int, int], exclude: Sequence[str] = () 

183 ) -> _ColorCodeAndName: 

184 r, g, b = value 

185 match = _get_closest_ansi_color(r, g, b, exclude=exclude) 

186 

187 # Turn color name into code. 

188 if self.bg: 

189 code = BG_ANSI_COLORS[match] 

190 else: 

191 code = FG_ANSI_COLORS[match] 

192 

193 return code, match 

194 

195 

196class _256ColorCache(Dict[Tuple[int, int, int], int]): 

197 """ 

198 Cache which maps (r, g, b) tuples to 256 colors. 

199 """ 

200 

201 def __init__(self) -> None: 

202 # Build color table. 

203 colors: list[tuple[int, int, int]] = [] 

204 

205 # colors 0..15: 16 basic colors 

206 colors.append((0x00, 0x00, 0x00)) # 0 

207 colors.append((0xCD, 0x00, 0x00)) # 1 

208 colors.append((0x00, 0xCD, 0x00)) # 2 

209 colors.append((0xCD, 0xCD, 0x00)) # 3 

210 colors.append((0x00, 0x00, 0xEE)) # 4 

211 colors.append((0xCD, 0x00, 0xCD)) # 5 

212 colors.append((0x00, 0xCD, 0xCD)) # 6 

213 colors.append((0xE5, 0xE5, 0xE5)) # 7 

214 colors.append((0x7F, 0x7F, 0x7F)) # 8 

215 colors.append((0xFF, 0x00, 0x00)) # 9 

216 colors.append((0x00, 0xFF, 0x00)) # 10 

217 colors.append((0xFF, 0xFF, 0x00)) # 11 

218 colors.append((0x5C, 0x5C, 0xFF)) # 12 

219 colors.append((0xFF, 0x00, 0xFF)) # 13 

220 colors.append((0x00, 0xFF, 0xFF)) # 14 

221 colors.append((0xFF, 0xFF, 0xFF)) # 15 

222 

223 # colors 16..232: the 6x6x6 color cube 

224 valuerange = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF) 

225 

226 for i in range(217): 

227 r = valuerange[(i // 36) % 6] 

228 g = valuerange[(i // 6) % 6] 

229 b = valuerange[i % 6] 

230 colors.append((r, g, b)) 

231 

232 # colors 233..253: grayscale 

233 for i in range(1, 22): 

234 v = 8 + i * 10 

235 colors.append((v, v, v)) 

236 

237 self.colors = colors 

238 

239 def __missing__(self, value: tuple[int, int, int]) -> int: 

240 r, g, b = value 

241 

242 # Find closest color. 

243 # (Thanks to Pygments for this!) 

244 distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) 

245 match = 0 

246 

247 for i, (r2, g2, b2) in enumerate(self.colors): 

248 if i >= 16: # XXX: We ignore the 16 ANSI colors when mapping RGB 

249 # to the 256 colors, because these highly depend on 

250 # the color scheme of the terminal. 

251 d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 

252 

253 if d < distance: 

254 match = i 

255 distance = d 

256 

257 # Turn color name into code. 

258 self[value] = match 

259 return match 

260 

261 

262_16_fg_colors = _16ColorCache(bg=False) 

263_16_bg_colors = _16ColorCache(bg=True) 

264_256_colors = _256ColorCache() 

265 

266 

267class _EscapeCodeCache(Dict[Attrs, str]): 

268 """ 

269 Cache for VT100 escape codes. It maps 

270 (fgcolor, bgcolor, bold, underline, strike, reverse) tuples to VT100 

271 escape sequences. 

272 

273 :param true_color: When True, use 24bit colors instead of 256 colors. 

274 """ 

275 

276 def __init__(self, color_depth: ColorDepth) -> None: 

277 self.color_depth = color_depth 

278 

279 def __missing__(self, attrs: Attrs) -> str: 

280 ( 

281 fgcolor, 

282 bgcolor, 

283 bold, 

284 underline, 

285 strike, 

286 italic, 

287 blink, 

288 reverse, 

289 hidden, 

290 ) = attrs 

291 parts: list[str] = [] 

292 

293 parts.extend(self._colors_to_code(fgcolor or "", bgcolor or "")) 

294 

295 if bold: 

296 parts.append("1") 

297 if italic: 

298 parts.append("3") 

299 if blink: 

300 parts.append("5") 

301 if underline: 

302 parts.append("4") 

303 if reverse: 

304 parts.append("7") 

305 if hidden: 

306 parts.append("8") 

307 if strike: 

308 parts.append("9") 

309 

310 if parts: 

311 result = "\x1b[0;" + ";".join(parts) + "m" 

312 else: 

313 result = "\x1b[0m" 

314 

315 self[attrs] = result 

316 return result 

317 

318 def _color_name_to_rgb(self, color: str) -> tuple[int, int, int]: 

319 "Turn 'ffffff', into (0xff, 0xff, 0xff)." 

320 try: 

321 rgb = int(color, 16) 

322 except ValueError: 

323 raise 

324 else: 

325 r = (rgb >> 16) & 0xFF 

326 g = (rgb >> 8) & 0xFF 

327 b = rgb & 0xFF 

328 return r, g, b 

329 

330 def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]: 

331 """ 

332 Return a tuple with the vt100 values that represent this color. 

333 """ 

334 # When requesting ANSI colors only, and both fg/bg color were converted 

335 # to ANSI, ensure that the foreground and background color are not the 

336 # same. (Unless they were explicitly defined to be the same color.) 

337 fg_ansi = "" 

338 

339 def get(color: str, bg: bool) -> list[int]: 

340 nonlocal fg_ansi 

341 

342 table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS 

343 

344 if not color or self.color_depth == ColorDepth.DEPTH_1_BIT: 

345 return [] 

346 

347 # 16 ANSI colors. (Given by name.) 

348 elif color in table: 

349 return [table[color]] 

350 

351 # RGB colors. (Defined as 'ffffff'.) 

352 else: 

353 try: 

354 rgb = self._color_name_to_rgb(color) 

355 except ValueError: 

356 return [] 

357 

358 # When only 16 colors are supported, use that. 

359 if self.color_depth == ColorDepth.DEPTH_4_BIT: 

360 if bg: # Background. 

361 if fg_color != bg_color: 

362 exclude = [fg_ansi] 

363 else: 

364 exclude = [] 

365 code, name = _16_bg_colors.get_code(rgb, exclude=exclude) 

366 return [code] 

367 else: # Foreground. 

368 code, name = _16_fg_colors.get_code(rgb) 

369 fg_ansi = name 

370 return [code] 

371 

372 # True colors. (Only when this feature is enabled.) 

373 elif self.color_depth == ColorDepth.DEPTH_24_BIT: 

374 r, g, b = rgb 

375 return [(48 if bg else 38), 2, r, g, b] 

376 

377 # 256 RGB colors. 

378 else: 

379 return [(48 if bg else 38), 5, _256_colors[rgb]] 

380 

381 result: list[int] = [] 

382 result.extend(get(fg_color, False)) 

383 result.extend(get(bg_color, True)) 

384 

385 return map(str, result) 

386 

387 

388def _get_size(fileno: int) -> tuple[int, int]: 

389 """ 

390 Get the size of this pseudo terminal. 

391 

392 :param fileno: stdout.fileno() 

393 :returns: A (rows, cols) tuple. 

394 """ 

395 size = os.get_terminal_size(fileno) 

396 return size.lines, size.columns 

397 

398 

399class Vt100_Output(Output): 

400 """ 

401 :param get_size: A callable which returns the `Size` of the output terminal. 

402 :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property. 

403 :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...) 

404 :param enable_cpr: When `True` (the default), send "cursor position 

405 request" escape sequences to the output in order to detect the cursor 

406 position. That way, we can properly determine how much space there is 

407 available for the UI (especially for drop down menus) to render. The 

408 `Renderer` will still try to figure out whether the current terminal 

409 does respond to CPR escapes. When `False`, never attempt to send CPR 

410 requests. 

411 """ 

412 

413 # For the error messages. Only display "Output is not a terminal" once per 

414 # file descriptor. 

415 _fds_not_a_terminal: set[int] = set() 

416 

417 def __init__( 

418 self, 

419 stdout: TextIO, 

420 get_size: Callable[[], Size], 

421 term: str | None = None, 

422 default_color_depth: ColorDepth | None = None, 

423 enable_bell: bool = True, 

424 enable_cpr: bool = True, 

425 ) -> None: 

426 assert all(hasattr(stdout, a) for a in ("write", "flush")) 

427 

428 self._buffer: list[str] = [] 

429 self.stdout: TextIO = stdout 

430 self.default_color_depth = default_color_depth 

431 self._get_size = get_size 

432 self.term = term 

433 self.enable_bell = enable_bell 

434 self.enable_cpr = enable_cpr 

435 

436 # Cache for escape codes. 

437 self._escape_code_caches: dict[ColorDepth, _EscapeCodeCache] = { 

438 ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT), 

439 ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT), 

440 ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT), 

441 ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT), 

442 } 

443 

444 # Keep track of whether the cursor shape was ever changed. 

445 # (We don't restore the cursor shape if it was never changed - by 

446 # default, we don't change them.) 

447 self._cursor_shape_changed = False 

448 

449 @classmethod 

450 def from_pty( 

451 cls, 

452 stdout: TextIO, 

453 term: str | None = None, 

454 default_color_depth: ColorDepth | None = None, 

455 enable_bell: bool = True, 

456 ) -> Vt100_Output: 

457 """ 

458 Create an Output class from a pseudo terminal. 

459 (This will take the dimensions by reading the pseudo 

460 terminal attributes.) 

461 """ 

462 fd: int | None 

463 # Normally, this requires a real TTY device, but people instantiate 

464 # this class often during unit tests as well. For convenience, we print 

465 # an error message, use standard dimensions, and go on. 

466 try: 

467 fd = stdout.fileno() 

468 except io.UnsupportedOperation: 

469 fd = None 

470 

471 if not stdout.isatty() and (fd is None or fd not in cls._fds_not_a_terminal): 

472 msg = "Warning: Output is not a terminal (fd=%r).\n" 

473 sys.stderr.write(msg % fd) 

474 sys.stderr.flush() 

475 if fd is not None: 

476 cls._fds_not_a_terminal.add(fd) 

477 

478 def get_size() -> Size: 

479 # If terminal (incorrectly) reports its size as 0, pick a 

480 # reasonable default. See 

481 # https://github.com/ipython/ipython/issues/10071 

482 rows, columns = (None, None) 

483 

484 # It is possible that `stdout` is no longer a TTY device at this 

485 # point. In that case we get an `OSError` in the ioctl call in 

486 # `get_size`. See: 

487 # https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1021 

488 try: 

489 rows, columns = _get_size(stdout.fileno()) 

490 except OSError: 

491 pass 

492 return Size(rows=rows or 24, columns=columns or 80) 

493 

494 return cls( 

495 stdout, 

496 get_size, 

497 term=term, 

498 default_color_depth=default_color_depth, 

499 enable_bell=enable_bell, 

500 ) 

501 

502 def get_size(self) -> Size: 

503 return self._get_size() 

504 

505 def fileno(self) -> int: 

506 "Return file descriptor." 

507 return self.stdout.fileno() 

508 

509 def encoding(self) -> str: 

510 "Return encoding used for stdout." 

511 return self.stdout.encoding 

512 

513 def write_raw(self, data: str) -> None: 

514 """ 

515 Write raw data to output. 

516 """ 

517 self._buffer.append(data) 

518 

519 def write(self, data: str) -> None: 

520 """ 

521 Write text to output. 

522 (Removes vt100 escape codes. -- used for safely writing text.) 

523 """ 

524 self._buffer.append(data.replace("\x1b", "?")) 

525 

526 def set_title(self, title: str) -> None: 

527 """ 

528 Set terminal title. 

529 """ 

530 if self.term not in ( 

531 "linux", 

532 "eterm-color", 

533 ): # Not supported by the Linux console. 

534 self.write_raw( 

535 "\x1b]2;%s\x07" % title.replace("\x1b", "").replace("\x07", "") 

536 ) 

537 

538 def clear_title(self) -> None: 

539 self.set_title("") 

540 

541 def erase_screen(self) -> None: 

542 """ 

543 Erases the screen with the background colour and moves the cursor to 

544 home. 

545 """ 

546 self.write_raw("\x1b[2J") 

547 

548 def enter_alternate_screen(self) -> None: 

549 self.write_raw("\x1b[?1049h\x1b[H") 

550 

551 def quit_alternate_screen(self) -> None: 

552 self.write_raw("\x1b[?1049l") 

553 

554 def enable_mouse_support(self) -> None: 

555 self.write_raw("\x1b[?1000h") 

556 

557 # Enable mouse-drag support. 

558 self.write_raw("\x1b[?1003h") 

559 

560 # Enable urxvt Mouse mode. (For terminals that understand this.) 

561 self.write_raw("\x1b[?1015h") 

562 

563 # Also enable Xterm SGR mouse mode. (For terminals that understand this.) 

564 self.write_raw("\x1b[?1006h") 

565 

566 # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr 

567 # extensions. 

568 

569 def disable_mouse_support(self) -> None: 

570 self.write_raw("\x1b[?1000l") 

571 self.write_raw("\x1b[?1015l") 

572 self.write_raw("\x1b[?1006l") 

573 self.write_raw("\x1b[?1003l") 

574 

575 def erase_end_of_line(self) -> None: 

576 """ 

577 Erases from the current cursor position to the end of the current line. 

578 """ 

579 self.write_raw("\x1b[K") 

580 

581 def erase_down(self) -> None: 

582 """ 

583 Erases the screen from the current line down to the bottom of the 

584 screen. 

585 """ 

586 self.write_raw("\x1b[J") 

587 

588 def reset_attributes(self) -> None: 

589 self.write_raw("\x1b[0m") 

590 

591 def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None: 

592 """ 

593 Create new style and output. 

594 

595 :param attrs: `Attrs` instance. 

596 """ 

597 # Get current depth. 

598 escape_code_cache = self._escape_code_caches[color_depth] 

599 

600 # Write escape character. 

601 self.write_raw(escape_code_cache[attrs]) 

602 

603 def disable_autowrap(self) -> None: 

604 self.write_raw("\x1b[?7l") 

605 

606 def enable_autowrap(self) -> None: 

607 self.write_raw("\x1b[?7h") 

608 

609 def enable_bracketed_paste(self) -> None: 

610 self.write_raw("\x1b[?2004h") 

611 

612 def disable_bracketed_paste(self) -> None: 

613 self.write_raw("\x1b[?2004l") 

614 

615 def reset_cursor_key_mode(self) -> None: 

616 """ 

617 For vt100 only. 

618 Put the terminal in cursor mode (instead of application mode). 

619 """ 

620 # Put the terminal in cursor mode. (Instead of application mode.) 

621 self.write_raw("\x1b[?1l") 

622 

623 def cursor_goto(self, row: int = 0, column: int = 0) -> None: 

624 """ 

625 Move cursor position. 

626 """ 

627 self.write_raw("\x1b[%i;%iH" % (row, column)) 

628 

629 def cursor_up(self, amount: int) -> None: 

630 if amount == 0: 

631 pass 

632 elif amount == 1: 

633 self.write_raw("\x1b[A") 

634 else: 

635 self.write_raw("\x1b[%iA" % amount) 

636 

637 def cursor_down(self, amount: int) -> None: 

638 if amount == 0: 

639 pass 

640 elif amount == 1: 

641 # Note: Not the same as '\n', '\n' can cause the window content to 

642 # scroll. 

643 self.write_raw("\x1b[B") 

644 else: 

645 self.write_raw("\x1b[%iB" % amount) 

646 

647 def cursor_forward(self, amount: int) -> None: 

648 if amount == 0: 

649 pass 

650 elif amount == 1: 

651 self.write_raw("\x1b[C") 

652 else: 

653 self.write_raw("\x1b[%iC" % amount) 

654 

655 def cursor_backward(self, amount: int) -> None: 

656 if amount == 0: 

657 pass 

658 elif amount == 1: 

659 self.write_raw("\b") # '\x1b[D' 

660 else: 

661 self.write_raw("\x1b[%iD" % amount) 

662 

663 def hide_cursor(self) -> None: 

664 self.write_raw("\x1b[?25l") 

665 

666 def show_cursor(self) -> None: 

667 self.write_raw("\x1b[?12l\x1b[?25h") # Stop blinking cursor and show. 

668 

669 def set_cursor_shape(self, cursor_shape: CursorShape) -> None: 

670 if cursor_shape == CursorShape._NEVER_CHANGE: 

671 return 

672 

673 self._cursor_shape_changed = True 

674 self.write_raw( 

675 { 

676 CursorShape.BLOCK: "\x1b[2 q", 

677 CursorShape.BEAM: "\x1b[6 q", 

678 CursorShape.UNDERLINE: "\x1b[4 q", 

679 CursorShape.BLINKING_BLOCK: "\x1b[1 q", 

680 CursorShape.BLINKING_BEAM: "\x1b[5 q", 

681 CursorShape.BLINKING_UNDERLINE: "\x1b[3 q", 

682 }.get(cursor_shape, "") 

683 ) 

684 

685 def reset_cursor_shape(self) -> None: 

686 "Reset cursor shape." 

687 # (Only reset cursor shape, if we ever changed it.) 

688 if self._cursor_shape_changed: 

689 self._cursor_shape_changed = False 

690 

691 # Reset cursor shape. 

692 self.write_raw("\x1b[0 q") 

693 

694 def flush(self) -> None: 

695 """ 

696 Write to output stream and flush. 

697 """ 

698 if not self._buffer: 

699 return 

700 

701 data = "".join(self._buffer) 

702 self._buffer = [] 

703 

704 flush_stdout(self.stdout, data) 

705 

706 def ask_for_cpr(self) -> None: 

707 """ 

708 Asks for a cursor position report (CPR). 

709 """ 

710 self.write_raw("\x1b[6n") 

711 self.flush() 

712 

713 @property 

714 def responds_to_cpr(self) -> bool: 

715 if not self.enable_cpr: 

716 return False 

717 

718 # When the input is a tty, we assume that CPR is supported. 

719 # It's not when the input is piped from Pexpect. 

720 if os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1": 

721 return False 

722 

723 if is_dumb_terminal(self.term): 

724 return False 

725 try: 

726 return self.stdout.isatty() 

727 except ValueError: 

728 return False # ValueError: I/O operation on closed file 

729 

730 def bell(self) -> None: 

731 "Sound bell." 

732 if self.enable_bell: 

733 self.write_raw("\a") 

734 self.flush() 

735 

736 def get_default_color_depth(self) -> ColorDepth: 

737 """ 

738 Return the default color depth for a vt100 terminal, according to the 

739 our term value. 

740 

741 We prefer 256 colors almost always, because this is what most terminals 

742 support these days, and is a good default. 

743 """ 

744 if self.default_color_depth is not None: 

745 return self.default_color_depth 

746 

747 term = self.term 

748 

749 if term is None: 

750 return ColorDepth.DEFAULT 

751 

752 if is_dumb_terminal(term): 

753 return ColorDepth.DEPTH_1_BIT 

754 

755 if term in ("linux", "eterm-color"): 

756 return ColorDepth.DEPTH_4_BIT 

757 

758 return ColorDepth.DEFAULT