Coverage for yuio / term.py: 74%

305 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-04 10:05 +0000

1# Yuio project, MIT license. 

2# 

3# https://github.com/taminomara/yuio/ 

4# 

5# You're free to copy this file to your project and edit it for your needs, 

6# just keep this copyright line please :3 

7 

8""" 

9Querying terminal info and working with ANSI terminals. 

10 

11This is a low-level module upon which :mod:`yuio.io` builds 

12its higher-level abstraction. 

13 

14 

15Detecting terminal capabilities 

16------------------------------- 

17 

18Terminal capabilities are stored in a :class:`Term` object. 

19 

20Usually, you don't need to query terminal capabilities yourself, 

21as you can use Yuio's global configuration from :mod:`yuio.io` 

22(see :func:`yuio.io.get_term`). 

23 

24However, you can get a :class:`Term` object by using :func:`get_term_from_stream`: 

25 

26.. autofunction:: get_term_from_stream 

27 

28.. autoclass:: Term 

29 :members: 

30 

31.. autoclass:: TerminalTheme 

32 :members: 

33 

34.. autoclass:: Lightness 

35 :members: 

36 

37.. autoclass:: InteractiveSupport 

38 :members: 

39 

40 

41Utilities 

42--------- 

43 

44.. autofunction:: detect_ci 

45 

46.. autofunction:: detect_ci_color_support 

47 

48""" 

49 

50from __future__ import annotations 

51 

52import contextlib 

53import dataclasses 

54import enum 

55import locale 

56import os 

57import re 

58import shutil 

59import sys 

60from dataclasses import dataclass 

61 

62import yuio 

63import yuio.color 

64from yuio import _typing as _t 

65from yuio.color import ColorSupport 

66 

67__all__ = [ 

68 "ColorSupport", 

69 "InteractiveSupport", 

70 "Lightness", 

71 "Term", 

72 "TerminalTheme", 

73 "detect_ci", 

74 "detect_ci_color_support", 

75 "get_term_from_stream", 

76] 

77 

78T = _t.TypeVar("T") 

79 

80 

81class Lightness(enum.Enum): 

82 """ 

83 Overall color theme of a terminal. 

84 

85 Can help with deciding which colors to use when printing output. 

86 

87 """ 

88 

89 UNKNOWN = enum.auto() 

90 """ 

91 We couldn't determine terminal background, or it wasn't dark 

92 or bright enough to fall in one category or another. 

93 

94 """ 

95 

96 DARK = enum.auto() 

97 """ 

98 Terminal background is dark. 

99 

100 """ 

101 

102 LIGHT = enum.auto() 

103 """ 

104 Terminal background is light. 

105 

106 """ 

107 

108 

109class InteractiveSupport(enum.IntEnum): 

110 """ 

111 Terminal's capability for rendering interactive widgets. 

112 

113 """ 

114 

115 NONE = 0 

116 """ 

117 Terminal can't render anything interactive. 

118 

119 """ 

120 

121 MOVE_CURSOR = 1 

122 """ 

123 Terminal can move cursor and erase lines. 

124 

125 """ 

126 

127 FULL = 2 

128 """ 

129 Terminal can process queries, enter ``CBREAK`` mode, etc. 

130 

131 """ 

132 

133 

134@dataclass(frozen=True, slots=True) 

135class TerminalTheme: 

136 """ 

137 Colors and theme of the attached terminal. 

138 

139 """ 

140 

141 background: yuio.color.ColorValue 

142 """ 

143 Background color of a terminal. 

144 

145 """ 

146 

147 foreground: yuio.color.ColorValue 

148 """ 

149 Foreground color of a terminal. 

150 

151 """ 

152 

153 black: yuio.color.ColorValue 

154 """ 

155 Color value for the default "black" color. 

156 

157 """ 

158 

159 red: yuio.color.ColorValue 

160 """ 

161 Color value for the default "red" color. 

162 

163 """ 

164 

165 green: yuio.color.ColorValue 

166 """ 

167 Color value for the default "green" color. 

168 

169 """ 

170 

171 yellow: yuio.color.ColorValue 

172 """ 

173 Color value for the default "yellow" color. 

174 

175 """ 

176 

177 blue: yuio.color.ColorValue 

178 """ 

179 Color value for the default "blue" color. 

180 

181 """ 

182 

183 magenta: yuio.color.ColorValue 

184 """ 

185 Color value for the default "magenta" color. 

186 

187 """ 

188 

189 cyan: yuio.color.ColorValue 

190 """ 

191 Color value for the default "cyan" color. 

192 

193 """ 

194 

195 white: yuio.color.ColorValue 

196 """ 

197 Color value for the default "white" color. 

198 

199 """ 

200 

201 lightness: Lightness 

202 """ 

203 Overall color theme of a terminal, i.e. dark or light. 

204 

205 """ 

206 

207 

208@dataclass(frozen=True, slots=True) 

209class Term: 

210 """ 

211 This class contains all info about what kinds of things the terminal 

212 supports. If available, it will also have info about terminal's theme, 

213 i.e. dark or light background, etc. 

214 

215 """ 

216 

217 ostream: _t.TextIO 

218 """ 

219 Terminal's output stream. 

220 

221 """ 

222 

223 istream: _t.TextIO 

224 """ 

225 Terminal's input stream. 

226 

227 """ 

228 

229 ostream_is_tty: bool = dataclasses.field(default=False, kw_only=True) 

230 """ 

231 Output stream is connected to a TTY device, meaning that is goes to a user. 

232 

233 Output stream being a TTY doesn't necessarily mean that it's interactive. 

234 For example, the process can run in background, but still be attached 

235 to a terminal. 

236 

237 Use :attr:`~Term.interactive_support` to check for interactivity level. 

238 

239 """ 

240 

241 istream_is_tty: bool = dataclasses.field(default=False, kw_only=True) 

242 """ 

243 Input stream is connected to a TTY device, meaning that is goes to a user. 

244 

245 """ 

246 

247 color_support: ColorSupport = dataclasses.field( 

248 default=ColorSupport.NONE, kw_only=True 

249 ) 

250 """ 

251 Terminal's capability for coloring output. 

252 

253 """ 

254 

255 interactive_support: InteractiveSupport = dataclasses.field( 

256 default=InteractiveSupport.NONE, kw_only=True 

257 ) 

258 """ 

259 Terminal's capability for rendering interactive widgets. 

260 

261 """ 

262 

263 terminal_theme: TerminalTheme | None = dataclasses.field(default=None, kw_only=True) 

264 """ 

265 Terminal's default foreground, background, and text colors. 

266 

267 """ 

268 

269 @property 

270 def supports_colors(self) -> bool: 

271 """ 

272 Return :data:`True` if terminal supports simple 8-bit color codes. 

273 

274 """ 

275 

276 return self.color_support >= ColorSupport.ANSI 

277 

278 @property 

279 def supports_colors_256(self) -> bool: 

280 """ 

281 Return :data:`True` if terminal supports 256-encoded colors. 

282 

283 """ 

284 

285 return self.color_support >= ColorSupport.ANSI_256 

286 

287 @property 

288 def supports_colors_true(self) -> bool: 

289 """ 

290 Return :data:`True` if terminal supports true colors. 

291 

292 """ 

293 

294 return self.color_support >= ColorSupport.ANSI_TRUE 

295 

296 @property 

297 def can_move_cursor(self) -> bool: 

298 """ 

299 Return :data:`True` if terminal can move cursor and erase lines. 

300 

301 """ 

302 

303 return ( 

304 self.supports_colors 

305 and self.interactive_support >= InteractiveSupport.MOVE_CURSOR 

306 ) 

307 

308 @property 

309 def can_query_terminal(self) -> bool: 

310 """ 

311 Return :data:`True` if terminal can process queries, enter ``CBREAK`` mode, etc. 

312 

313 This is an alias to :attr:`~Term.is_fully_interactive`. 

314 

315 """ 

316 

317 return self.is_fully_interactive 

318 

319 @property 

320 def is_fully_interactive(self) -> bool: 

321 """ 

322 Return :data:`True` if we're in a fully interactive environment. 

323 

324 """ 

325 

326 return ( 

327 self.supports_colors and self.interactive_support >= InteractiveSupport.FULL 

328 ) 

329 

330 @property 

331 def is_unicode(self) -> bool: 

332 encoding = ( 

333 getattr(self.ostream, "encoding", None) or locale.getpreferredencoding() 

334 ) 

335 return "utf" in encoding or "unicode" in encoding 

336 

337 

338_CI_ENV_VARS = [ 

339 "TRAVIS", 

340 "CIRCLECI", 

341 "APPVEYOR", 

342 "GITLAB_CI", 

343 "BUILDKITE", 

344 "DRONE", 

345 "TEAMCITY_VERSION", 

346 "GITHUB_ACTIONS", 

347] 

348 

349 

350def get_term_from_stream( 

351 ostream: _t.TextIO, istream: _t.TextIO, /, *, query_terminal_theme: bool = True 

352) -> Term: 

353 """ 

354 Query info about a terminal attached to the given stream. 

355 

356 :param ostream: 

357 output stream. 

358 :param istream: 

359 input stream. 

360 :param query_terminal_theme: 

361 By default, this function queries background, foreground, and text colors 

362 of the terminal if ``ostream`` and ``istream`` are connected to a TTY. 

363 

364 Set this parameter to :data:`False` to disable querying. 

365 

366 """ 

367 

368 if "__YUIO_FORCE_FULL_TERM_SUPPORT" in os.environ: # pragma: no cover 

369 # For building docs in github 

370 return Term( 

371 ostream=ostream, 

372 istream=istream, 

373 ostream_is_tty=True, 

374 istream_is_tty=True, 

375 color_support=ColorSupport.ANSI_TRUE, 

376 interactive_support=InteractiveSupport.FULL, 

377 ) 

378 

379 has_interactive_output = _is_interactive_output(ostream) 

380 has_interactive_input = _is_interactive_input(istream) 

381 

382 # Note: we don't rely on argparse to parse out flags and send them to us 

383 # because these functions can be called before parsing arguments. 

384 if ( 

385 "--no-color" in sys.argv 

386 or "--no-colors" in sys.argv 

387 or "--force-no-color" in sys.argv 

388 or "--force-no-colors" in sys.argv 

389 or "NO_COLOR" in os.environ 

390 or "FORCE_NO_COLOR" in os.environ 

391 or "FORCE_NO_COLORS" in os.environ 

392 ): 

393 return Term( 

394 ostream, 

395 istream, 

396 ostream_is_tty=has_interactive_output, 

397 istream_is_tty=has_interactive_input, 

398 ) 

399 

400 term = os.environ.get("TERM", "").lower() 

401 colorterm = os.environ.get("COLORTERM", "").lower() 

402 is_foreground = _is_foreground(ostream) and _is_foreground(istream) 

403 in_ci = detect_ci() 

404 color_support = ColorSupport.NONE 

405 if ( 

406 "--force-color" in sys.argv 

407 or "--force-colors" in sys.argv 

408 or "FORCE_COLOR" in os.environ 

409 or "FORCE_COLORS" in os.environ 

410 ): 

411 color_support = ColorSupport.ANSI 

412 if has_interactive_output: 

413 if in_ci: 

414 color_support = detect_ci_color_support() 

415 elif os.name == "nt": 

416 if _enable_vt_processing(ostream, istream): 

417 color_support = ColorSupport.ANSI_TRUE 

418 elif colorterm in ("truecolor", "24bit") or term == "xterm-kitty": 

419 color_support = ColorSupport.ANSI_TRUE 

420 elif colorterm in ("yes", "true") or "256color" in term or term == "screen": 

421 if ( 

422 os.name == "posix" 

423 and term == "xterm-256color" 

424 and shutil.which("wslinfo") 

425 ): 

426 color_support = ColorSupport.ANSI_TRUE 

427 else: 

428 color_support = ColorSupport.ANSI_256 

429 elif "linux" in term or "color" in term or "ansi" in term or "xterm" in term: 

430 color_support = ColorSupport.ANSI 

431 

432 interactive_support = InteractiveSupport.NONE 

433 theme = None 

434 if is_foreground and color_support >= ColorSupport.ANSI and not in_ci: 

435 if has_interactive_output and has_interactive_input: 

436 interactive_support = InteractiveSupport.FULL 

437 if ( 

438 query_terminal_theme 

439 and color_support >= ColorSupport.ANSI_256 

440 and "YUIO_DISABLE_OSC_QUERIES" not in os.environ 

441 ): 

442 theme = _get_standard_colors(ostream, istream) 

443 else: 

444 interactive_support = InteractiveSupport.MOVE_CURSOR 

445 

446 return Term( 

447 ostream=ostream, 

448 istream=istream, 

449 ostream_is_tty=has_interactive_output, 

450 istream_is_tty=has_interactive_input, 

451 color_support=color_support, 

452 interactive_support=interactive_support, 

453 terminal_theme=theme, 

454 ) 

455 

456 

457def detect_ci() -> bool: 

458 """ 

459 Scan environment variables to detect if we're in a known CI environment. 

460 

461 """ 

462 

463 return "CI" in os.environ or any(ci in os.environ for ci in _CI_ENV_VARS) 

464 

465 

466def detect_ci_color_support() -> ColorSupport: 

467 """ 

468 Scan environment variables to detect an appropriate level of color support 

469 of a CI environment. 

470 

471 If we're not in CI, return :attr:`ColorSupport.NONE`. 

472 

473 """ 

474 

475 if "GITHUB_ACTIONS" in os.environ: 

476 return ColorSupport.ANSI_TRUE 

477 elif any(ci in os.environ for ci in _CI_ENV_VARS): 

478 return ColorSupport.ANSI 

479 else: 

480 return ColorSupport.NONE 

481 

482 

483def _get_standard_colors( 

484 ostream: _t.TextIO, istream: _t.TextIO 

485) -> TerminalTheme | None: 

486 try: 

487 query = "\x1b]10;?\x1b\\\x1b]11;?\x1b\\" + "".join( 

488 [f"\x1b]4;{i};?\x1b\\" for i in range(8)] 

489 ) 

490 response = _query_term(ostream, istream, query) 

491 if not response: 

492 return None 

493 

494 # Deal with foreground color. 

495 

496 match = re.match( 

497 r"^\x1b]10;rgb:([0-9a-f]{2,4})/([0-9a-f]{2,4})/([0-9a-f]{2,4})(?:\x1b\\|\a)", 

498 response, 

499 re.IGNORECASE, 

500 ) 

501 if match is None: # pragma: no cover 

502 return None 

503 

504 r, g, b = (int(v, 16) // 16 ** (len(v) - 2) for v in match.groups()) 

505 foreground = yuio.color.ColorValue.from_rgb(r, g, b) 

506 

507 response = response[match.end() :] 

508 

509 # Deal with background color. 

510 

511 match = re.match( 

512 r"^\x1b]11;rgb:([0-9a-f]{2,4})/([0-9a-f]{2,4})/([0-9a-f]{2,4})(?:\x1b\\|\a)", 

513 response, 

514 re.IGNORECASE, 

515 ) 

516 if match is None: # pragma: no cover 

517 return None 

518 

519 r, g, b = (int(v, 16) // 16 ** (len(v) - 2) for v in match.groups()) 

520 background = yuio.color.ColorValue.from_rgb(r, g, b) 

521 luma = (0.2627 * r + 0.6780 * g + 0.0593 * b) / 256 

522 

523 if luma <= 0.2: 

524 lightness = Lightness.DARK 

525 elif luma >= 0.85: 

526 lightness = Lightness.LIGHT 

527 else: 

528 lightness = Lightness.UNKNOWN 

529 

530 response = response[match.end() :] 

531 

532 # Deal with other colors 

533 

534 colors = {} 

535 

536 while response: 

537 match = re.match( 

538 r"^\x1b]4;(\d+);rgb:([0-9a-f]{2,4})/([0-9a-f]{2,4})/([0-9a-f]{2,4})(?:\x1b\\|\a)", 

539 response, 

540 re.IGNORECASE, 

541 ) 

542 if match is None: # pragma: no cover 

543 return None 

544 

545 c = int(match.group(1)) 

546 r, g, b = (int(v, 16) // 16 ** (len(v) - 2) for v in match.groups()[1:]) 

547 colors[c] = yuio.color.ColorValue.from_rgb(r, g, b) 

548 

549 response = response[match.end() :] 

550 

551 if set(colors.keys()) != {0, 1, 2, 3, 4, 5, 6, 7}: # pragma: no cover 

552 return None 

553 

554 # return colors 

555 return TerminalTheme( 

556 background=background, 

557 foreground=foreground, 

558 black=colors[0], 

559 red=colors[1], 

560 green=colors[2], 

561 yellow=colors[3], 

562 blue=colors[4], 

563 magenta=colors[5], 

564 cyan=colors[6], 

565 white=colors[7], 

566 lightness=lightness, 

567 ) 

568 

569 except Exception: # pragma: no cover 

570 return None 

571 

572 

573def _query_term(ostream: _t.TextIO, istream: _t.TextIO, query: str) -> str | None: 

574 try: 

575 with _enter_raw_mode(ostream, istream): 

576 # Lock the keyboard. 

577 ostream.write("\x1b[2h") 

578 ostream.flush() 

579 _flush_input_buffer(ostream, istream) 

580 

581 # It is important that we unlock keyboard before exiting `cbreak`, 

582 # hence the nested `try`. 

583 try: 

584 # Append a DA1 query, as virtually all terminals support it. 

585 ostream.write(query + "\x1b[c") 

586 ostream.flush() 

587 

588 buf = _read_keycode(ostream, istream) 

589 if not buf.startswith("\x1b"): 

590 yuio._logger.warning("_query_term invalid response: %r", buf) 

591 return None 

592 

593 # Read till we find a DA1 response. 

594 while not re.search(r"\x1b\[\?.*?c", buf): 

595 buf += _read_keycode(ostream, istream) 

596 

597 return buf[: buf.index("\x1b[?")] 

598 finally: 

599 _flush_input_buffer(ostream, istream) 

600 

601 # Release the keyboard. 

602 ostream.write("\x1b[2i") 

603 ostream.flush() 

604 except Exception: # pragma: no cover 

605 yuio._logger.warning("_query_term error", exc_info=True) 

606 return None 

607 

608 

609def _is_tty(stream: _t.TextIO | None) -> bool: 

610 try: 

611 return stream is not None and stream.isatty() 

612 except Exception: # pragma: no cover 

613 return False 

614 

615 

616if os.name == "posix": 

617 

618 def _is_foreground(stream: _t.TextIO | None) -> bool: 

619 try: 

620 return stream is not None and os.getpgrp() == os.tcgetpgrp(stream.fileno()) 

621 except Exception: # pragma: no cover 

622 return False 

623 

624elif os.name == "nt": 

625 

626 def _is_foreground(stream: _t.TextIO | None) -> bool: 

627 return True 

628 

629else: 

630 

631 def _is_foreground(stream: _t.TextIO | None) -> bool: 

632 return False 

633 

634 

635def _is_interactive_input(stream: _t.TextIO | None) -> bool: 

636 try: 

637 return stream is not None and _is_tty(stream) and stream.readable() 

638 except Exception: # pragma: no cover 

639 return False 

640 

641 

642def _is_interactive_output(stream: _t.TextIO | None) -> bool: 

643 try: 

644 return stream is not None and _is_tty(stream) and stream.writable() 

645 except Exception: # pragma: no cover 

646 return False 

647 

648 

649# Platform-specific code for working with terminals. 

650if os.name == "posix": 

651 import select 

652 import termios 

653 import tty 

654 

655 @contextlib.contextmanager 

656 def _enter_raw_mode( 

657 ostream: _t.TextIO, 

658 istream: _t.TextIO, 

659 bracketed_paste: bool = False, 

660 modify_keyboard: bool = False, 

661 ): 

662 prev_mode = termios.tcgetattr(istream) 

663 tty.setcbreak(istream, termios.TCSAFLUSH) 

664 

665 prologue = [] 

666 if bracketed_paste: 

667 prologue.append("\x1b[?2004h") 

668 if modify_keyboard: 

669 prologue.append("\033[>4;2m") 

670 if prologue: 

671 ostream.write("".join(prologue)) 

672 ostream.flush() 

673 

674 try: 

675 yield 

676 finally: 

677 epilogue = [] 

678 if bracketed_paste: 

679 epilogue.append("\x1b[?2004l") 

680 if modify_keyboard: 

681 epilogue.append("\033[>4m") 

682 if epilogue: 

683 ostream.write("".join(epilogue)) 

684 ostream.flush() 

685 termios.tcsetattr(istream, termios.TCSAFLUSH, prev_mode) 

686 

687 def _read_keycode(ostream: _t.TextIO, istream: _t.TextIO) -> str: 

688 key = os.read(istream.fileno(), 128) 

689 while bool(select.select([istream], [], [], 0)[0]): 

690 key += os.read(istream.fileno(), 128) 

691 

692 return key.decode(istream.encoding, errors="replace") 

693 

694 def _flush_input_buffer(ostream: _t.TextIO, istream: _t.TextIO): 

695 pass 

696 

697 def _enable_vt_processing(ostream: _t.TextIO, istream: _t.TextIO) -> bool: 

698 return False # This is a windows functionality 

699 

700elif os.name == "nt": 

701 import ctypes 

702 import ctypes.wintypes 

703 import msvcrt 

704 

705 _FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer 

706 _FlushConsoleInputBuffer.argtypes = [ctypes.wintypes.HANDLE] 

707 _FlushConsoleInputBuffer.restype = ctypes.wintypes.BOOL 

708 

709 _GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode 

710 _GetConsoleMode.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.LPDWORD] 

711 _GetConsoleMode.restype = ctypes.wintypes.BOOL 

712 

713 _SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode 

714 _SetConsoleMode.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD] 

715 _SetConsoleMode.restype = ctypes.wintypes.BOOL 

716 

717 _ReadConsoleW = ctypes.windll.kernel32.ReadConsoleW 

718 _ReadConsoleW.argtypes = [ 

719 ctypes.wintypes.HANDLE, 

720 ctypes.wintypes.LPVOID, 

721 ctypes.wintypes.DWORD, 

722 ctypes.wintypes.LPDWORD, 

723 ctypes.wintypes.LPVOID, 

724 ] 

725 _ReadConsoleW.restype = ctypes.wintypes.BOOL 

726 

727 _ENABLE_PROCESSED_OUTPUT = 0x0001 

728 _ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 

729 _ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 

730 _ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 

731 

732 if sys.__stdin__ is not None: # TODO: don't rely on sys.__stdin__? 

733 _ISTREAM_HANDLE = msvcrt.get_osfhandle(sys.__stdin__.fileno()) 

734 else: 

735 _ISTREAM_HANDLE = None 

736 

737 @contextlib.contextmanager 

738 def _enter_raw_mode( 

739 ostream: _t.TextIO, 

740 istream: _t.TextIO, 

741 bracketed_paste: bool = False, 

742 modify_keyboard: bool = False, 

743 ): 

744 assert _ISTREAM_HANDLE is not None 

745 

746 mode = ctypes.wintypes.DWORD() 

747 success = _GetConsoleMode(_ISTREAM_HANDLE, ctypes.byref(mode)) 

748 if not success: 

749 raise ctypes.WinError() 

750 success = _SetConsoleMode(_ISTREAM_HANDLE, _ENABLE_VIRTUAL_TERMINAL_INPUT) 

751 if not success: 

752 raise ctypes.WinError() 

753 

754 try: 

755 yield 

756 finally: 

757 success = _SetConsoleMode(_ISTREAM_HANDLE, mode) 

758 if not success: 

759 raise ctypes.WinError() 

760 

761 def _read_keycode(ostream: _t.TextIO, istream: _t.TextIO) -> str: 

762 assert _ISTREAM_HANDLE is not None 

763 

764 CHAR16 = ctypes.wintypes.WCHAR * 16 

765 

766 n_read = ctypes.wintypes.DWORD() 

767 buffer = CHAR16() 

768 

769 success = _ReadConsoleW( 

770 _ISTREAM_HANDLE, 

771 ctypes.byref(buffer), 

772 16, 

773 ctypes.byref(n_read), 

774 0, 

775 ) 

776 if not success: 

777 raise ctypes.WinError() 

778 

779 return buffer.value 

780 

781 def _flush_input_buffer(ostream: _t.TextIO, istream: _t.TextIO): 

782 assert _ISTREAM_HANDLE is not None 

783 

784 success = _FlushConsoleInputBuffer(_ISTREAM_HANDLE) 

785 if not success: 

786 raise ctypes.WinError() 

787 

788 def _enable_vt_processing(ostream: _t.TextIO, istream: _t.TextIO) -> bool: 

789 try: 

790 version = sys.getwindowsversion() 

791 if version.major < 10 or version.build < 14931: 

792 return False 

793 

794 handle = msvcrt.get_osfhandle(ostream.fileno()) 

795 return bool( 

796 _SetConsoleMode( 

797 handle, 

798 _ENABLE_PROCESSED_OUTPUT 

799 | _ENABLE_WRAP_AT_EOL_OUTPUT 

800 | _ENABLE_VIRTUAL_TERMINAL_PROCESSING, 

801 ) 

802 ) 

803 except Exception: # pragma: no cover 

804 return False 

805 

806else: 

807 

808 @contextlib.contextmanager 

809 def _enter_raw_mode( 

810 ostream: _t.TextIO, 

811 istream: _t.TextIO, 

812 bracketed_paste: bool = False, 

813 modify_keyboard: bool = False, 

814 ): 

815 raise OSError("not supported") 

816 yield 

817 

818 def _read_keycode(ostream: _t.TextIO, istream: _t.TextIO) -> str: 

819 raise OSError("not supported") 

820 

821 def _flush_input_buffer(ostream: _t.TextIO, istream: _t.TextIO): 

822 raise OSError("not supported") 

823 

824 def _enable_vt_processing(ostream: _t.TextIO, istream: _t.TextIO) -> bool: 

825 raise OSError("not supported")