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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

330 statements  

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""" 

9 

10from __future__ import annotations 

11 

12import io 

13import os 

14import sys 

15from collections.abc import Callable, Hashable, Iterable, Sequence 

16from typing import TextIO 

17 

18from prompt_toolkit.cursor_shapes import CursorShape 

19from prompt_toolkit.data_structures import Size 

20from prompt_toolkit.output import Output 

21from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs 

22from prompt_toolkit.utils import is_dumb_terminal 

23 

24from .color_depth import ColorDepth 

25from .flush_stdout import flush_stdout 

26 

27__all__ = [ 

28 "Vt100_Output", 

29] 

30 

31 

32FG_ANSI_COLORS = { 

33 "ansidefault": 39, 

34 # Low intensity. 

35 "ansiblack": 30, 

36 "ansired": 31, 

37 "ansigreen": 32, 

38 "ansiyellow": 33, 

39 "ansiblue": 34, 

40 "ansimagenta": 35, 

41 "ansicyan": 36, 

42 "ansigray": 37, 

43 # High intensity. 

44 "ansibrightblack": 90, 

45 "ansibrightred": 91, 

46 "ansibrightgreen": 92, 

47 "ansibrightyellow": 93, 

48 "ansibrightblue": 94, 

49 "ansibrightmagenta": 95, 

50 "ansibrightcyan": 96, 

51 "ansiwhite": 97, 

52} 

53 

54BG_ANSI_COLORS = { 

55 "ansidefault": 49, 

56 # Low intensity. 

57 "ansiblack": 40, 

58 "ansired": 41, 

59 "ansigreen": 42, 

60 "ansiyellow": 43, 

61 "ansiblue": 44, 

62 "ansimagenta": 45, 

63 "ansicyan": 46, 

64 "ansigray": 47, 

65 # High intensity. 

66 "ansibrightblack": 100, 

67 "ansibrightred": 101, 

68 "ansibrightgreen": 102, 

69 "ansibrightyellow": 103, 

70 "ansibrightblue": 104, 

71 "ansibrightmagenta": 105, 

72 "ansibrightcyan": 106, 

73 "ansiwhite": 107, 

74} 

75 

76 

77ANSI_COLORS_TO_RGB = { 

78 "ansidefault": ( 

79 0x00, 

80 0x00, 

81 0x00, 

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

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

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

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

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

87 # Low intensity. 

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

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

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

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

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

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

94 # High intensity. 

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

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

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

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

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

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

101} 

102 

103 

104assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 

105assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 

106assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) 

107 

108 

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

110 """ 

111 Find closest ANSI color. Return it by name. 

112 

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

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

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

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

117 """ 

118 exclude = list(exclude) 

119 

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

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

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

123 

124 if saturation > 30: 

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

126 

127 # Take the closest color. 

128 # (Thanks to Pygments for this part.) 

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

130 match = "ansidefault" 

131 

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

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

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

135 

136 if d < distance: 

137 match = name 

138 distance = d 

139 

140 return match 

141 

142 

143_ColorCodeAndName = tuple[int, str] 

144 

145 

146class _16ColorCache: 

147 """ 

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

149 

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

151 """ 

152 

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

154 self.bg = bg 

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

156 

157 def get_code( 

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

159 ) -> _ColorCodeAndName: 

160 """ 

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

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

163 """ 

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

165 cache = self._cache 

166 

167 if key not in cache: 

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

169 

170 return cache[key] 

171 

172 def _get( 

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

174 ) -> _ColorCodeAndName: 

175 r, g, b = value 

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

177 

178 # Turn color name into code. 

179 if self.bg: 

180 code = BG_ANSI_COLORS[match] 

181 else: 

182 code = FG_ANSI_COLORS[match] 

183 

184 return code, match 

185 

186 

187class _256ColorCache(dict[tuple[int, int, int], int]): 

188 """ 

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

190 """ 

191 

192 def __init__(self) -> None: 

193 # Build color table. 

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

195 

196 # colors 0..15: 16 basic colors 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

213 

214 # colors 16..231: the 6x6x6 color cube 

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

216 

217 for i in range(216): 

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

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

220 b = valuerange[i % 6] 

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

222 

223 # colors 232..255: grayscale 

224 for i in range(24): 

225 v = 8 + i * 10 

226 colors.append((v, v, v)) 

227 

228 self.colors = colors 

229 

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

231 r, g, b = value 

232 

233 # Find closest color. 

234 # (Thanks to Pygments for this!) 

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

236 match = 0 

237 

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

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

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

241 # the color scheme of the terminal. 

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

243 

244 if d < distance: 

245 match = i 

246 distance = d 

247 

248 # Turn color name into code. 

249 self[value] = match 

250 return match 

251 

252 

253_16_fg_colors = _16ColorCache(bg=False) 

254_16_bg_colors = _16ColorCache(bg=True) 

255_256_colors = _256ColorCache() 

256 

257 

258class _EscapeCodeCache(dict[Attrs, str]): 

259 """ 

260 Cache for VT100 escape codes. It maps 

261 (fgcolor, bgcolor, bold, underline, strike, italic, blink, reverse, hidden, dim) tuples to VT100 

262 escape sequences. 

263 

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

265 """ 

266 

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

268 self.color_depth = color_depth 

269 

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

271 ( 

272 fgcolor, 

273 bgcolor, 

274 bold, 

275 underline, 

276 strike, 

277 italic, 

278 blink, 

279 reverse, 

280 hidden, 

281 dim, 

282 ) = attrs 

283 parts: list[str] = [] 

284 

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

286 

287 if bold: 

288 parts.append("1") 

289 if dim: 

290 parts.append("2") 

291 if italic: 

292 parts.append("3") 

293 if blink: 

294 parts.append("5") 

295 if underline: 

296 parts.append("4") 

297 if reverse: 

298 parts.append("7") 

299 if hidden: 

300 parts.append("8") 

301 if strike: 

302 parts.append("9") 

303 

304 if parts: 

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

306 else: 

307 result = "\x1b[0m" 

308 

309 self[attrs] = result 

310 return result 

311 

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

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

314 try: 

315 rgb = int(color, 16) 

316 except ValueError: 

317 raise 

318 else: 

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

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

321 b = rgb & 0xFF 

322 return r, g, b 

323 

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

325 """ 

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

327 """ 

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

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

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

331 fg_ansi = "" 

332 

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

334 nonlocal fg_ansi 

335 

336 table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS 

337 

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

339 return [] 

340 

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

342 elif color in table: 

343 return [table[color]] 

344 

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

346 else: 

347 try: 

348 rgb = self._color_name_to_rgb(color) 

349 except ValueError: 

350 return [] 

351 

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

353 if self.color_depth == ColorDepth.DEPTH_4_BIT: 

354 if bg: # Background. 

355 if fg_color != bg_color: 

356 exclude = [fg_ansi] 

357 else: 

358 exclude = [] 

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

360 return [code] 

361 else: # Foreground. 

362 code, name = _16_fg_colors.get_code(rgb) 

363 fg_ansi = name 

364 return [code] 

365 

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

367 elif self.color_depth == ColorDepth.DEPTH_24_BIT: 

368 r, g, b = rgb 

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

370 

371 # 256 RGB colors. 

372 else: 

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

374 

375 result: list[int] = [] 

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

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

378 

379 return map(str, result) 

380 

381 

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

383 """ 

384 Get the size of this pseudo terminal. 

385 

386 :param fileno: stdout.fileno() 

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

388 """ 

389 size = os.get_terminal_size(fileno) 

390 return size.lines, size.columns 

391 

392 

393class Vt100_Output(Output): 

394 """ 

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

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

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

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

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

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

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

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

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

404 requests. 

405 """ 

406 

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

408 # file descriptor. 

409 _fds_not_a_terminal: set[int] = set() 

410 

411 def __init__( 

412 self, 

413 stdout: TextIO, 

414 get_size: Callable[[], Size], 

415 term: str | None = None, 

416 default_color_depth: ColorDepth | None = None, 

417 enable_bell: bool = True, 

418 enable_cpr: bool = True, 

419 ) -> None: 

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

421 

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

423 self.stdout: TextIO = stdout 

424 self.default_color_depth = default_color_depth 

425 self._get_size = get_size 

426 self.term = term 

427 self.enable_bell = enable_bell 

428 self.enable_cpr = enable_cpr 

429 

430 # Cache for escape codes. 

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

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

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

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

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

436 } 

437 

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

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

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

441 self._cursor_shape_changed = False 

442 

443 # Don't hide/show the cursor when this was already done. 

444 # (`None` means that we don't know whether the cursor is visible or 

445 # not.) 

446 self._cursor_visible: bool | None = None 

447 

448 @classmethod 

449 def from_pty( 

450 cls, 

451 stdout: TextIO, 

452 term: str | None = None, 

453 default_color_depth: ColorDepth | None = None, 

454 enable_bell: bool = True, 

455 ) -> Vt100_Output: 

456 """ 

457 Create an Output class from a pseudo terminal. 

458 (This will take the dimensions by reading the pseudo 

459 terminal attributes.) 

460 """ 

461 fd: int | None 

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

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

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

465 try: 

466 fd = stdout.fileno() 

467 except io.UnsupportedOperation: 

468 fd = None 

469 

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

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

472 sys.stderr.write(msg % fd) 

473 sys.stderr.flush() 

474 if fd is not None: 

475 cls._fds_not_a_terminal.add(fd) 

476 

477 def get_size() -> Size: 

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

479 # reasonable default. See 

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

481 rows, columns = (None, None) 

482 

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

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

485 # `get_size`. See: 

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

487 try: 

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

489 except OSError: 

490 pass 

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

492 

493 return cls( 

494 stdout, 

495 get_size, 

496 term=term, 

497 default_color_depth=default_color_depth, 

498 enable_bell=enable_bell, 

499 ) 

500 

501 def get_size(self) -> Size: 

502 return self._get_size() 

503 

504 def fileno(self) -> int: 

505 "Return file descriptor." 

506 return self.stdout.fileno() 

507 

508 def encoding(self) -> str: 

509 "Return encoding used for stdout." 

510 return self.stdout.encoding 

511 

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

513 """ 

514 Write raw data to output. 

515 """ 

516 self._buffer.append(data) 

517 

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

519 """ 

520 Write text to output. 

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

522 """ 

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

524 

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

526 """ 

527 Set terminal title. 

528 """ 

529 if self.term not in ( 

530 "linux", 

531 "eterm-color", 

532 ): # Not supported by the Linux console. 

533 self.write_raw( 

534 "\x1b]2;{}\x07".format(title.replace("\x1b", "").replace("\x07", "")) 

535 ) 

536 

537 def clear_title(self) -> None: 

538 self.set_title("") 

539 

540 def erase_screen(self) -> None: 

541 """ 

542 Erases the screen with the background color and moves the cursor to 

543 home. 

544 """ 

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

546 

547 def enter_alternate_screen(self) -> None: 

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

549 

550 def quit_alternate_screen(self) -> None: 

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

552 

553 def enable_mouse_support(self) -> None: 

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

555 

556 # Enable mouse-drag support. 

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

558 

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

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

561 

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

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

564 

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

566 # extensions. 

567 

568 def disable_mouse_support(self) -> None: 

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

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

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

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

573 

574 def erase_end_of_line(self) -> None: 

575 """ 

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

577 """ 

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

579 

580 def erase_down(self) -> None: 

581 """ 

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

583 screen. 

584 """ 

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

586 

587 def reset_attributes(self) -> None: 

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

589 

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

591 """ 

592 Create new style and output. 

593 

594 :param attrs: `Attrs` instance. 

595 """ 

596 # Get current depth. 

597 escape_code_cache = self._escape_code_caches[color_depth] 

598 

599 # Write escape character. 

600 self.write_raw(escape_code_cache[attrs]) 

601 

602 def disable_autowrap(self) -> None: 

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

604 

605 def enable_autowrap(self) -> None: 

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

607 

608 def enable_bracketed_paste(self) -> None: 

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

610 

611 def disable_bracketed_paste(self) -> None: 

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

613 

614 def reset_cursor_key_mode(self) -> None: 

615 """ 

616 For vt100 only. 

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

618 """ 

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

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

621 

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

623 """ 

624 Move cursor position. 

625 """ 

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

627 

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

629 if amount == 0: 

630 pass 

631 elif amount == 1: 

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

633 else: 

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

635 

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

637 if amount == 0: 

638 pass 

639 elif amount == 1: 

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

641 # scroll. 

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

643 else: 

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

645 

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

647 if amount == 0: 

648 pass 

649 elif amount == 1: 

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

651 else: 

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

653 

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

655 if amount == 0: 

656 pass 

657 elif amount == 1: 

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

659 else: 

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

661 

662 def hide_cursor(self) -> None: 

663 if self._cursor_visible in (True, None): 

664 self._cursor_visible = False 

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

666 

667 def show_cursor(self) -> None: 

668 if self._cursor_visible in (False, None): 

669 self._cursor_visible = True 

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

671 

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

673 if cursor_shape == CursorShape._NEVER_CHANGE: 

674 return 

675 

676 self._cursor_shape_changed = True 

677 self.write_raw( 

678 { 

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

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

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

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

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

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

685 }.get(cursor_shape, "") 

686 ) 

687 

688 def reset_cursor_shape(self) -> None: 

689 "Reset cursor shape." 

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

691 if self._cursor_shape_changed: 

692 self._cursor_shape_changed = False 

693 

694 # Reset cursor shape. 

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

696 

697 def flush(self) -> None: 

698 """ 

699 Write to output stream and flush. 

700 """ 

701 if not self._buffer: 

702 return 

703 

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

705 self._buffer = [] 

706 

707 flush_stdout(self.stdout, data) 

708 

709 def ask_for_cpr(self) -> None: 

710 """ 

711 Asks for a cursor position report (CPR). 

712 """ 

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

714 self.flush() 

715 

716 @property 

717 def responds_to_cpr(self) -> bool: 

718 if not self.enable_cpr: 

719 return False 

720 

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

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

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

724 return False 

725 

726 if is_dumb_terminal(self.term): 

727 return False 

728 try: 

729 return self.stdout.isatty() 

730 except ValueError: 

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

732 

733 def bell(self) -> None: 

734 "Sound bell." 

735 if self.enable_bell: 

736 self.write_raw("\a") 

737 self.flush() 

738 

739 def get_default_color_depth(self) -> ColorDepth: 

740 """ 

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

742 our term value. 

743 

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

745 support these days, and is a good default. 

746 """ 

747 if self.default_color_depth is not None: 

748 return self.default_color_depth 

749 

750 term = self.term 

751 

752 if term is None: 

753 return ColorDepth.DEFAULT 

754 

755 if is_dumb_terminal(term): 

756 return ColorDepth.DEPTH_1_BIT 

757 

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

759 return ColorDepth.DEPTH_4_BIT 

760 

761 return ColorDepth.DEFAULT