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

329 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 typing import Callable, Dict, Hashable, Iterable, Sequence, TextIO, Tuple 

16 

17from prompt_toolkit.cursor_shapes import CursorShape 

18from prompt_toolkit.data_structures import Size 

19from prompt_toolkit.output import Output 

20from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs 

21from prompt_toolkit.utils import is_dumb_terminal 

22 

23from .color_depth import ColorDepth 

24from .flush_stdout import flush_stdout 

25 

26__all__ = [ 

27 "Vt100_Output", 

28] 

29 

30 

31FG_ANSI_COLORS = { 

32 "ansidefault": 39, 

33 # Low intensity. 

34 "ansiblack": 30, 

35 "ansired": 31, 

36 "ansigreen": 32, 

37 "ansiyellow": 33, 

38 "ansiblue": 34, 

39 "ansimagenta": 35, 

40 "ansicyan": 36, 

41 "ansigray": 37, 

42 # High intensity. 

43 "ansibrightblack": 90, 

44 "ansibrightred": 91, 

45 "ansibrightgreen": 92, 

46 "ansibrightyellow": 93, 

47 "ansibrightblue": 94, 

48 "ansibrightmagenta": 95, 

49 "ansibrightcyan": 96, 

50 "ansiwhite": 97, 

51} 

52 

53BG_ANSI_COLORS = { 

54 "ansidefault": 49, 

55 # Low intensity. 

56 "ansiblack": 40, 

57 "ansired": 41, 

58 "ansigreen": 42, 

59 "ansiyellow": 43, 

60 "ansiblue": 44, 

61 "ansimagenta": 45, 

62 "ansicyan": 46, 

63 "ansigray": 47, 

64 # High intensity. 

65 "ansibrightblack": 100, 

66 "ansibrightred": 101, 

67 "ansibrightgreen": 102, 

68 "ansibrightyellow": 103, 

69 "ansibrightblue": 104, 

70 "ansibrightmagenta": 105, 

71 "ansibrightcyan": 106, 

72 "ansiwhite": 107, 

73} 

74 

75 

76ANSI_COLORS_TO_RGB = { 

77 "ansidefault": ( 

78 0x00, 

79 0x00, 

80 0x00, 

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

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

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

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

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

86 # Low intensity. 

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

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

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

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

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

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

93 # High intensity. 

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

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

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

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

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

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

100} 

101 

102 

103assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 

104assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 

105assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) 

106 

107 

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

109 """ 

110 Find closest ANSI color. Return it by name. 

111 

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

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

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

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

116 """ 

117 exclude = list(exclude) 

118 

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

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

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

122 

123 if saturation > 30: 

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

125 

126 # Take the closest color. 

127 # (Thanks to Pygments for this part.) 

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

129 match = "ansidefault" 

130 

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

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

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

134 

135 if d < distance: 

136 match = name 

137 distance = d 

138 

139 return match 

140 

141 

142_ColorCodeAndName = Tuple[int, str] 

143 

144 

145class _16ColorCache: 

146 """ 

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

148 

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

150 """ 

151 

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

153 self.bg = bg 

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

155 

156 def get_code( 

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

158 ) -> _ColorCodeAndName: 

159 """ 

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

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

162 """ 

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

164 cache = self._cache 

165 

166 if key not in cache: 

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

168 

169 return cache[key] 

170 

171 def _get( 

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

173 ) -> _ColorCodeAndName: 

174 r, g, b = value 

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

176 

177 # Turn color name into code. 

178 if self.bg: 

179 code = BG_ANSI_COLORS[match] 

180 else: 

181 code = FG_ANSI_COLORS[match] 

182 

183 return code, match 

184 

185 

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

187 """ 

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

189 """ 

190 

191 def __init__(self) -> None: 

192 # Build color table. 

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

194 

195 # colors 0..15: 16 basic colors 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

212 

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

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

215 

216 for i in range(217): 

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

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

219 b = valuerange[i % 6] 

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

221 

222 # colors 233..253: grayscale 

223 for i in range(1, 22): 

224 v = 8 + i * 10 

225 colors.append((v, v, v)) 

226 

227 self.colors = colors 

228 

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

230 r, g, b = value 

231 

232 # Find closest color. 

233 # (Thanks to Pygments for this!) 

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

235 match = 0 

236 

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

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

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

240 # the color scheme of the terminal. 

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

242 

243 if d < distance: 

244 match = i 

245 distance = d 

246 

247 # Turn color name into code. 

248 self[value] = match 

249 return match 

250 

251 

252_16_fg_colors = _16ColorCache(bg=False) 

253_16_bg_colors = _16ColorCache(bg=True) 

254_256_colors = _256ColorCache() 

255 

256 

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

258 """ 

259 Cache for VT100 escape codes. It maps 

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

261 escape sequences. 

262 

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

264 """ 

265 

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

267 self.color_depth = color_depth 

268 

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

270 ( 

271 fgcolor, 

272 bgcolor, 

273 bold, 

274 underline, 

275 strike, 

276 italic, 

277 blink, 

278 reverse, 

279 hidden, 

280 dim, 

281 ) = attrs 

282 parts: list[str] = [] 

283 

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

285 

286 if bold: 

287 parts.append("1") 

288 if dim: 

289 parts.append("2") 

290 if italic: 

291 parts.append("3") 

292 if blink: 

293 parts.append("5") 

294 if underline: 

295 parts.append("4") 

296 if reverse: 

297 parts.append("7") 

298 if hidden: 

299 parts.append("8") 

300 if strike: 

301 parts.append("9") 

302 

303 if parts: 

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

305 else: 

306 result = "\x1b[0m" 

307 

308 self[attrs] = result 

309 return result 

310 

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

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

313 try: 

314 rgb = int(color, 16) 

315 except ValueError: 

316 raise 

317 else: 

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

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

320 b = rgb & 0xFF 

321 return r, g, b 

322 

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

324 """ 

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

326 """ 

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

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

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

330 fg_ansi = "" 

331 

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

333 nonlocal fg_ansi 

334 

335 table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS 

336 

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

338 return [] 

339 

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

341 elif color in table: 

342 return [table[color]] 

343 

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

345 else: 

346 try: 

347 rgb = self._color_name_to_rgb(color) 

348 except ValueError: 

349 return [] 

350 

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

352 if self.color_depth == ColorDepth.DEPTH_4_BIT: 

353 if bg: # Background. 

354 if fg_color != bg_color: 

355 exclude = [fg_ansi] 

356 else: 

357 exclude = [] 

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

359 return [code] 

360 else: # Foreground. 

361 code, name = _16_fg_colors.get_code(rgb) 

362 fg_ansi = name 

363 return [code] 

364 

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

366 elif self.color_depth == ColorDepth.DEPTH_24_BIT: 

367 r, g, b = rgb 

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

369 

370 # 256 RGB colors. 

371 else: 

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

373 

374 result: list[int] = [] 

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

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

377 

378 return map(str, result) 

379 

380 

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

382 """ 

383 Get the size of this pseudo terminal. 

384 

385 :param fileno: stdout.fileno() 

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

387 """ 

388 size = os.get_terminal_size(fileno) 

389 return size.lines, size.columns 

390 

391 

392class Vt100_Output(Output): 

393 """ 

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

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

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

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

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

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

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

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

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

403 requests. 

404 """ 

405 

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

407 # file descriptor. 

408 _fds_not_a_terminal: set[int] = set() 

409 

410 def __init__( 

411 self, 

412 stdout: TextIO, 

413 get_size: Callable[[], Size], 

414 term: str | None = None, 

415 default_color_depth: ColorDepth | None = None, 

416 enable_bell: bool = True, 

417 enable_cpr: bool = True, 

418 ) -> None: 

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

420 

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

422 self.stdout: TextIO = stdout 

423 self.default_color_depth = default_color_depth 

424 self._get_size = get_size 

425 self.term = term 

426 self.enable_bell = enable_bell 

427 self.enable_cpr = enable_cpr 

428 

429 # Cache for escape codes. 

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

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

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

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

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

435 } 

436 

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

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

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

440 self._cursor_shape_changed = False 

441 

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

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

444 # not.) 

445 self._cursor_visible: bool | None = None 

446 

447 @classmethod 

448 def from_pty( 

449 cls, 

450 stdout: TextIO, 

451 term: str | None = None, 

452 default_color_depth: ColorDepth | None = None, 

453 enable_bell: bool = True, 

454 ) -> Vt100_Output: 

455 """ 

456 Create an Output class from a pseudo terminal. 

457 (This will take the dimensions by reading the pseudo 

458 terminal attributes.) 

459 """ 

460 fd: int | None 

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

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

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

464 try: 

465 fd = stdout.fileno() 

466 except io.UnsupportedOperation: 

467 fd = None 

468 

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

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

471 sys.stderr.write(msg % fd) 

472 sys.stderr.flush() 

473 if fd is not None: 

474 cls._fds_not_a_terminal.add(fd) 

475 

476 def get_size() -> Size: 

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

478 # reasonable default. See 

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

480 rows, columns = (None, None) 

481 

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

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

484 # `get_size`. See: 

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

486 try: 

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

488 except OSError: 

489 pass 

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

491 

492 return cls( 

493 stdout, 

494 get_size, 

495 term=term, 

496 default_color_depth=default_color_depth, 

497 enable_bell=enable_bell, 

498 ) 

499 

500 def get_size(self) -> Size: 

501 return self._get_size() 

502 

503 def fileno(self) -> int: 

504 "Return file descriptor." 

505 return self.stdout.fileno() 

506 

507 def encoding(self) -> str: 

508 "Return encoding used for stdout." 

509 return self.stdout.encoding 

510 

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

512 """ 

513 Write raw data to output. 

514 """ 

515 self._buffer.append(data) 

516 

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

518 """ 

519 Write text to output. 

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

521 """ 

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

523 

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

525 """ 

526 Set terminal title. 

527 """ 

528 if self.term not in ( 

529 "linux", 

530 "eterm-color", 

531 ): # Not supported by the Linux console. 

532 self.write_raw( 

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

534 ) 

535 

536 def clear_title(self) -> None: 

537 self.set_title("") 

538 

539 def erase_screen(self) -> None: 

540 """ 

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

542 home. 

543 """ 

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

545 

546 def enter_alternate_screen(self) -> None: 

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

548 

549 def quit_alternate_screen(self) -> None: 

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

551 

552 def enable_mouse_support(self) -> None: 

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

554 

555 # Enable mouse-drag support. 

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

557 

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

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

560 

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

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

563 

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

565 # extensions. 

566 

567 def disable_mouse_support(self) -> None: 

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

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

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

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

572 

573 def erase_end_of_line(self) -> None: 

574 """ 

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

576 """ 

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

578 

579 def erase_down(self) -> None: 

580 """ 

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

582 screen. 

583 """ 

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

585 

586 def reset_attributes(self) -> None: 

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

588 

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

590 """ 

591 Create new style and output. 

592 

593 :param attrs: `Attrs` instance. 

594 """ 

595 # Get current depth. 

596 escape_code_cache = self._escape_code_caches[color_depth] 

597 

598 # Write escape character. 

599 self.write_raw(escape_code_cache[attrs]) 

600 

601 def disable_autowrap(self) -> None: 

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

603 

604 def enable_autowrap(self) -> None: 

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

606 

607 def enable_bracketed_paste(self) -> None: 

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

609 

610 def disable_bracketed_paste(self) -> None: 

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

612 

613 def reset_cursor_key_mode(self) -> None: 

614 """ 

615 For vt100 only. 

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

617 """ 

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

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

620 

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

622 """ 

623 Move cursor position. 

624 """ 

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

626 

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

628 if amount == 0: 

629 pass 

630 elif amount == 1: 

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

632 else: 

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

634 

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

636 if amount == 0: 

637 pass 

638 elif amount == 1: 

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

640 # scroll. 

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

642 else: 

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

644 

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

646 if amount == 0: 

647 pass 

648 elif amount == 1: 

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

650 else: 

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

652 

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

654 if amount == 0: 

655 pass 

656 elif amount == 1: 

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

658 else: 

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

660 

661 def hide_cursor(self) -> None: 

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

663 self._cursor_visible = False 

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

665 

666 def show_cursor(self) -> None: 

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

668 self._cursor_visible = True 

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

670 

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

672 if cursor_shape == CursorShape._NEVER_CHANGE: 

673 return 

674 

675 self._cursor_shape_changed = True 

676 self.write_raw( 

677 { 

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

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

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

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

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

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

684 }.get(cursor_shape, "") 

685 ) 

686 

687 def reset_cursor_shape(self) -> None: 

688 "Reset cursor shape." 

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

690 if self._cursor_shape_changed: 

691 self._cursor_shape_changed = False 

692 

693 # Reset cursor shape. 

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

695 

696 def flush(self) -> None: 

697 """ 

698 Write to output stream and flush. 

699 """ 

700 if not self._buffer: 

701 return 

702 

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

704 self._buffer = [] 

705 

706 flush_stdout(self.stdout, data) 

707 

708 def ask_for_cpr(self) -> None: 

709 """ 

710 Asks for a cursor position report (CPR). 

711 """ 

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

713 self.flush() 

714 

715 @property 

716 def responds_to_cpr(self) -> bool: 

717 if not self.enable_cpr: 

718 return False 

719 

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

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

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

723 return False 

724 

725 if is_dumb_terminal(self.term): 

726 return False 

727 try: 

728 return self.stdout.isatty() 

729 except ValueError: 

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

731 

732 def bell(self) -> None: 

733 "Sound bell." 

734 if self.enable_bell: 

735 self.write_raw("\a") 

736 self.flush() 

737 

738 def get_default_color_depth(self) -> ColorDepth: 

739 """ 

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

741 our term value. 

742 

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

744 support these days, and is a good default. 

745 """ 

746 if self.default_color_depth is not None: 

747 return self.default_color_depth 

748 

749 term = self.term 

750 

751 if term is None: 

752 return ColorDepth.DEFAULT 

753 

754 if is_dumb_terminal(term): 

755 return ColorDepth.DEPTH_1_BIT 

756 

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

758 return ColorDepth.DEPTH_4_BIT 

759 

760 return ColorDepth.DEFAULT