Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/console.py: 47%

947 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1import inspect 

2import io 

3import os 

4import platform 

5import sys 

6import threading 

7import zlib 

8from abc import ABC, abstractmethod 

9from dataclasses import dataclass, field 

10from datetime import datetime 

11from functools import wraps 

12from getpass import getpass 

13from html import escape 

14from inspect import isclass 

15from itertools import islice 

16from math import ceil 

17from time import monotonic 

18from types import FrameType, ModuleType, TracebackType 

19from typing import ( 

20 IO, 

21 TYPE_CHECKING, 

22 Any, 

23 Callable, 

24 Dict, 

25 Iterable, 

26 List, 

27 Mapping, 

28 NamedTuple, 

29 Optional, 

30 TextIO, 

31 Tuple, 

32 Type, 

33 Union, 

34 cast, 

35) 

36 

37from rich._null_file import NULL_FILE 

38 

39if sys.version_info >= (3, 8): 

40 from typing import Literal, Protocol, runtime_checkable 

41else: 

42 from typing_extensions import ( 

43 Literal, 

44 Protocol, 

45 runtime_checkable, 

46 ) # pragma: no cover 

47 

48from . import errors, themes 

49from ._emoji_replace import _emoji_replace 

50from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT 

51from ._log_render import FormatTimeCallable, LogRender 

52from .align import Align, AlignMethod 

53from .color import ColorSystem, blend_rgb 

54from .control import Control 

55from .emoji import EmojiVariant 

56from .highlighter import NullHighlighter, ReprHighlighter 

57from .markup import render as render_markup 

58from .measure import Measurement, measure_renderables 

59from .pager import Pager, SystemPager 

60from .pretty import Pretty, is_expandable 

61from .protocol import rich_cast 

62from .region import Region 

63from .scope import render_scope 

64from .screen import Screen 

65from .segment import Segment 

66from .style import Style, StyleType 

67from .styled import Styled 

68from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme 

69from .text import Text, TextType 

70from .theme import Theme, ThemeStack 

71 

72if TYPE_CHECKING: 

73 from ._windows import WindowsConsoleFeatures 

74 from .live import Live 

75 from .status import Status 

76 

77JUPYTER_DEFAULT_COLUMNS = 115 

78JUPYTER_DEFAULT_LINES = 100 

79WINDOWS = platform.system() == "Windows" 

80 

81HighlighterType = Callable[[Union[str, "Text"]], "Text"] 

82JustifyMethod = Literal["default", "left", "center", "right", "full"] 

83OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"] 

84 

85 

86class NoChange: 

87 pass 

88 

89 

90NO_CHANGE = NoChange() 

91 

92try: 

93 _STDIN_FILENO = sys.__stdin__.fileno() 

94except Exception: 

95 _STDIN_FILENO = 0 

96try: 

97 _STDOUT_FILENO = sys.__stdout__.fileno() 

98except Exception: 

99 _STDOUT_FILENO = 1 

100try: 

101 _STDERR_FILENO = sys.__stderr__.fileno() 

102except Exception: 

103 _STDERR_FILENO = 2 

104 

105_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) 

106_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) 

107 

108 

109_TERM_COLORS = { 

110 "kitty": ColorSystem.EIGHT_BIT, 

111 "256color": ColorSystem.EIGHT_BIT, 

112 "16color": ColorSystem.STANDARD, 

113} 

114 

115 

116class ConsoleDimensions(NamedTuple): 

117 """Size of the terminal.""" 

118 

119 width: int 

120 """The width of the console in 'cells'.""" 

121 height: int 

122 """The height of the console in lines.""" 

123 

124 

125@dataclass 

126class ConsoleOptions: 

127 """Options for __rich_console__ method.""" 

128 

129 size: ConsoleDimensions 

130 """Size of console.""" 

131 legacy_windows: bool 

132 """legacy_windows: flag for legacy windows.""" 

133 min_width: int 

134 """Minimum width of renderable.""" 

135 max_width: int 

136 """Maximum width of renderable.""" 

137 is_terminal: bool 

138 """True if the target is a terminal, otherwise False.""" 

139 encoding: str 

140 """Encoding of terminal.""" 

141 max_height: int 

142 """Height of container (starts as terminal)""" 

143 justify: Optional[JustifyMethod] = None 

144 """Justify value override for renderable.""" 

145 overflow: Optional[OverflowMethod] = None 

146 """Overflow value override for renderable.""" 

147 no_wrap: Optional[bool] = False 

148 """Disable wrapping for text.""" 

149 highlight: Optional[bool] = None 

150 """Highlight override for render_str.""" 

151 markup: Optional[bool] = None 

152 """Enable markup when rendering strings.""" 

153 height: Optional[int] = None 

154 

155 @property 

156 def ascii_only(self) -> bool: 

157 """Check if renderables should use ascii only.""" 

158 return not self.encoding.startswith("utf") 

159 

160 def copy(self) -> "ConsoleOptions": 

161 """Return a copy of the options. 

162 

163 Returns: 

164 ConsoleOptions: a copy of self. 

165 """ 

166 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) 

167 options.__dict__ = self.__dict__.copy() 

168 return options 

169 

170 def update( 

171 self, 

172 *, 

173 width: Union[int, NoChange] = NO_CHANGE, 

174 min_width: Union[int, NoChange] = NO_CHANGE, 

175 max_width: Union[int, NoChange] = NO_CHANGE, 

176 justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE, 

177 overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE, 

178 no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE, 

179 highlight: Union[Optional[bool], NoChange] = NO_CHANGE, 

180 markup: Union[Optional[bool], NoChange] = NO_CHANGE, 

181 height: Union[Optional[int], NoChange] = NO_CHANGE, 

182 ) -> "ConsoleOptions": 

183 """Update values, return a copy.""" 

184 options = self.copy() 

185 if not isinstance(width, NoChange): 

186 options.min_width = options.max_width = max(0, width) 

187 if not isinstance(min_width, NoChange): 

188 options.min_width = min_width 

189 if not isinstance(max_width, NoChange): 

190 options.max_width = max_width 

191 if not isinstance(justify, NoChange): 

192 options.justify = justify 

193 if not isinstance(overflow, NoChange): 

194 options.overflow = overflow 

195 if not isinstance(no_wrap, NoChange): 

196 options.no_wrap = no_wrap 

197 if not isinstance(highlight, NoChange): 

198 options.highlight = highlight 

199 if not isinstance(markup, NoChange): 

200 options.markup = markup 

201 if not isinstance(height, NoChange): 

202 if height is not None: 

203 options.max_height = height 

204 options.height = None if height is None else max(0, height) 

205 return options 

206 

207 def update_width(self, width: int) -> "ConsoleOptions": 

208 """Update just the width, return a copy. 

209 

210 Args: 

211 width (int): New width (sets both min_width and max_width) 

212 

213 Returns: 

214 ~ConsoleOptions: New console options instance. 

215 """ 

216 options = self.copy() 

217 options.min_width = options.max_width = max(0, width) 

218 return options 

219 

220 def update_height(self, height: int) -> "ConsoleOptions": 

221 """Update the height, and return a copy. 

222 

223 Args: 

224 height (int): New height 

225 

226 Returns: 

227 ~ConsoleOptions: New Console options instance. 

228 """ 

229 options = self.copy() 

230 options.max_height = options.height = height 

231 return options 

232 

233 def reset_height(self) -> "ConsoleOptions": 

234 """Return a copy of the options with height set to ``None``. 

235 

236 Returns: 

237 ~ConsoleOptions: New console options instance. 

238 """ 

239 options = self.copy() 

240 options.height = None 

241 return options 

242 

243 def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": 

244 """Update the width and height, and return a copy. 

245 

246 Args: 

247 width (int): New width (sets both min_width and max_width). 

248 height (int): New height. 

249 

250 Returns: 

251 ~ConsoleOptions: New console options instance. 

252 """ 

253 options = self.copy() 

254 options.min_width = options.max_width = max(0, width) 

255 options.height = options.max_height = height 

256 return options 

257 

258 

259@runtime_checkable 

260class RichCast(Protocol): 

261 """An object that may be 'cast' to a console renderable.""" 

262 

263 def __rich__( 

264 self, 

265 ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover 

266 ... 

267 

268 

269@runtime_checkable 

270class ConsoleRenderable(Protocol): 

271 """An object that supports the console protocol.""" 

272 

273 def __rich_console__( 

274 self, console: "Console", options: "ConsoleOptions" 

275 ) -> "RenderResult": # pragma: no cover 

276 ... 

277 

278 

279# A type that may be rendered by Console. 

280RenderableType = Union[ConsoleRenderable, RichCast, str] 

281 

282# The result of calling a __rich_console__ method. 

283RenderResult = Iterable[Union[RenderableType, Segment]] 

284 

285_null_highlighter = NullHighlighter() 

286 

287 

288class CaptureError(Exception): 

289 """An error in the Capture context manager.""" 

290 

291 

292class NewLine: 

293 """A renderable to generate new line(s)""" 

294 

295 def __init__(self, count: int = 1) -> None: 

296 self.count = count 

297 

298 def __rich_console__( 

299 self, console: "Console", options: "ConsoleOptions" 

300 ) -> Iterable[Segment]: 

301 yield Segment("\n" * self.count) 

302 

303 

304class ScreenUpdate: 

305 """Render a list of lines at a given offset.""" 

306 

307 def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None: 

308 self._lines = lines 

309 self.x = x 

310 self.y = y 

311 

312 def __rich_console__( 

313 self, console: "Console", options: ConsoleOptions 

314 ) -> RenderResult: 

315 x = self.x 

316 move_to = Control.move_to 

317 for offset, line in enumerate(self._lines, self.y): 

318 yield move_to(x, offset) 

319 yield from line 

320 

321 

322class Capture: 

323 """Context manager to capture the result of printing to the console. 

324 See :meth:`~rich.console.Console.capture` for how to use. 

325 

326 Args: 

327 console (Console): A console instance to capture output. 

328 """ 

329 

330 def __init__(self, console: "Console") -> None: 

331 self._console = console 

332 self._result: Optional[str] = None 

333 

334 def __enter__(self) -> "Capture": 

335 self._console.begin_capture() 

336 return self 

337 

338 def __exit__( 

339 self, 

340 exc_type: Optional[Type[BaseException]], 

341 exc_val: Optional[BaseException], 

342 exc_tb: Optional[TracebackType], 

343 ) -> None: 

344 self._result = self._console.end_capture() 

345 

346 def get(self) -> str: 

347 """Get the result of the capture.""" 

348 if self._result is None: 

349 raise CaptureError( 

350 "Capture result is not available until context manager exits." 

351 ) 

352 return self._result 

353 

354 

355class ThemeContext: 

356 """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage.""" 

357 

358 def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None: 

359 self.console = console 

360 self.theme = theme 

361 self.inherit = inherit 

362 

363 def __enter__(self) -> "ThemeContext": 

364 self.console.push_theme(self.theme) 

365 return self 

366 

367 def __exit__( 

368 self, 

369 exc_type: Optional[Type[BaseException]], 

370 exc_val: Optional[BaseException], 

371 exc_tb: Optional[TracebackType], 

372 ) -> None: 

373 self.console.pop_theme() 

374 

375 

376class PagerContext: 

377 """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage.""" 

378 

379 def __init__( 

380 self, 

381 console: "Console", 

382 pager: Optional[Pager] = None, 

383 styles: bool = False, 

384 links: bool = False, 

385 ) -> None: 

386 self._console = console 

387 self.pager = SystemPager() if pager is None else pager 

388 self.styles = styles 

389 self.links = links 

390 

391 def __enter__(self) -> "PagerContext": 

392 self._console._enter_buffer() 

393 return self 

394 

395 def __exit__( 

396 self, 

397 exc_type: Optional[Type[BaseException]], 

398 exc_val: Optional[BaseException], 

399 exc_tb: Optional[TracebackType], 

400 ) -> None: 

401 if exc_type is None: 

402 with self._console._lock: 

403 buffer: List[Segment] = self._console._buffer[:] 

404 del self._console._buffer[:] 

405 segments: Iterable[Segment] = buffer 

406 if not self.styles: 

407 segments = Segment.strip_styles(segments) 

408 elif not self.links: 

409 segments = Segment.strip_links(segments) 

410 content = self._console._render_buffer(segments) 

411 self.pager.show(content) 

412 self._console._exit_buffer() 

413 

414 

415class ScreenContext: 

416 """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage.""" 

417 

418 def __init__( 

419 self, console: "Console", hide_cursor: bool, style: StyleType = "" 

420 ) -> None: 

421 self.console = console 

422 self.hide_cursor = hide_cursor 

423 self.screen = Screen(style=style) 

424 self._changed = False 

425 

426 def update( 

427 self, *renderables: RenderableType, style: Optional[StyleType] = None 

428 ) -> None: 

429 """Update the screen. 

430 

431 Args: 

432 renderable (RenderableType, optional): Optional renderable to replace current renderable, 

433 or None for no change. Defaults to None. 

434 style: (Style, optional): Replacement style, or None for no change. Defaults to None. 

435 """ 

436 if renderables: 

437 self.screen.renderable = ( 

438 Group(*renderables) if len(renderables) > 1 else renderables[0] 

439 ) 

440 if style is not None: 

441 self.screen.style = style 

442 self.console.print(self.screen, end="") 

443 

444 def __enter__(self) -> "ScreenContext": 

445 self._changed = self.console.set_alt_screen(True) 

446 if self._changed and self.hide_cursor: 

447 self.console.show_cursor(False) 

448 return self 

449 

450 def __exit__( 

451 self, 

452 exc_type: Optional[Type[BaseException]], 

453 exc_val: Optional[BaseException], 

454 exc_tb: Optional[TracebackType], 

455 ) -> None: 

456 if self._changed: 

457 self.console.set_alt_screen(False) 

458 if self.hide_cursor: 

459 self.console.show_cursor(True) 

460 

461 

462class Group: 

463 """Takes a group of renderables and returns a renderable object that renders the group. 

464 

465 Args: 

466 renderables (Iterable[RenderableType]): An iterable of renderable objects. 

467 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. 

468 """ 

469 

470 def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None: 

471 self._renderables = renderables 

472 self.fit = fit 

473 self._render: Optional[List[RenderableType]] = None 

474 

475 @property 

476 def renderables(self) -> List["RenderableType"]: 

477 if self._render is None: 

478 self._render = list(self._renderables) 

479 return self._render 

480 

481 def __rich_measure__( 

482 self, console: "Console", options: "ConsoleOptions" 

483 ) -> "Measurement": 

484 if self.fit: 

485 return measure_renderables(console, options, self.renderables) 

486 else: 

487 return Measurement(options.max_width, options.max_width) 

488 

489 def __rich_console__( 

490 self, console: "Console", options: "ConsoleOptions" 

491 ) -> RenderResult: 

492 yield from self.renderables 

493 

494 

495def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: 

496 """A decorator that turns an iterable of renderables in to a group. 

497 

498 Args: 

499 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. 

500 """ 

501 

502 def decorator( 

503 method: Callable[..., Iterable[RenderableType]] 

504 ) -> Callable[..., Group]: 

505 """Convert a method that returns an iterable of renderables in to a Group.""" 

506 

507 @wraps(method) 

508 def _replace(*args: Any, **kwargs: Any) -> Group: 

509 renderables = method(*args, **kwargs) 

510 return Group(*renderables, fit=fit) 

511 

512 return _replace 

513 

514 return decorator 

515 

516 

517def _is_jupyter() -> bool: # pragma: no cover 

518 """Check if we're running in a Jupyter notebook.""" 

519 try: 

520 get_ipython # type: ignore[name-defined] 

521 except NameError: 

522 return False 

523 ipython = get_ipython() # type: ignore[name-defined] 

524 shell = ipython.__class__.__name__ 

525 if ( 

526 "google.colab" in str(ipython.__class__) 

527 or os.getenv("DATABRICKS_RUNTIME_VERSION") 

528 or shell == "ZMQInteractiveShell" 

529 ): 

530 return True # Jupyter notebook or qtconsole 

531 elif shell == "TerminalInteractiveShell": 

532 return False # Terminal running IPython 

533 else: 

534 return False # Other type (?) 

535 

536 

537COLOR_SYSTEMS = { 

538 "standard": ColorSystem.STANDARD, 

539 "256": ColorSystem.EIGHT_BIT, 

540 "truecolor": ColorSystem.TRUECOLOR, 

541 "windows": ColorSystem.WINDOWS, 

542} 

543 

544_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()} 

545 

546 

547@dataclass 

548class ConsoleThreadLocals(threading.local): 

549 """Thread local values for Console context.""" 

550 

551 theme_stack: ThemeStack 

552 buffer: List[Segment] = field(default_factory=list) 

553 buffer_index: int = 0 

554 

555 

556class RenderHook(ABC): 

557 """Provides hooks in to the render process.""" 

558 

559 @abstractmethod 

560 def process_renderables( 

561 self, renderables: List[ConsoleRenderable] 

562 ) -> List[ConsoleRenderable]: 

563 """Called with a list of objects to render. 

564 

565 This method can return a new list of renderables, or modify and return the same list. 

566 

567 Args: 

568 renderables (List[ConsoleRenderable]): A number of renderable objects. 

569 

570 Returns: 

571 List[ConsoleRenderable]: A replacement list of renderables. 

572 """ 

573 

574 

575_windows_console_features: Optional["WindowsConsoleFeatures"] = None 

576 

577 

578def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover 

579 global _windows_console_features 

580 if _windows_console_features is not None: 

581 return _windows_console_features 

582 from ._windows import get_windows_console_features 

583 

584 _windows_console_features = get_windows_console_features() 

585 return _windows_console_features 

586 

587 

588def detect_legacy_windows() -> bool: 

589 """Detect legacy Windows.""" 

590 return WINDOWS and not get_windows_console_features().vt 

591 

592 

593class Console: 

594 """A high level console interface. 

595 

596 Args: 

597 color_system (str, optional): The color system supported by your terminal, 

598 either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect. 

599 force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None. 

600 force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None. 

601 force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None. 

602 soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False. 

603 theme (Theme, optional): An optional style theme object, or ``None`` for default theme. 

604 stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False. 

605 file (IO, optional): A file object where the console should write to. Defaults to stdout. 

606 quiet (bool, Optional): Boolean to suppress all output. Defaults to False. 

607 width (int, optional): The width of the terminal. Leave as default to auto-detect width. 

608 height (int, optional): The height of the terminal. Leave as default to auto-detect height. 

609 style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None. 

610 no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None. 

611 tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8. 

612 record (bool, optional): Boolean to enable recording of terminal output, 

613 required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False. 

614 markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True. 

615 emoji (bool, optional): Enable emoji code. Defaults to True. 

616 emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. 

617 highlight (bool, optional): Enable automatic highlighting. Defaults to True. 

618 log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True. 

619 log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True. 

620 log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ". 

621 highlighter (HighlighterType, optional): Default highlighter. 

622 legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. 

623 safe_box (bool, optional): Restrict box options that don't render on legacy Windows. 

624 get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log), 

625 or None for datetime.now. 

626 get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic. 

627 """ 

628 

629 _environ: Mapping[str, str] = os.environ 

630 

631 def __init__( 

632 self, 

633 *, 

634 color_system: Optional[ 

635 Literal["auto", "standard", "256", "truecolor", "windows"] 

636 ] = "auto", 

637 force_terminal: Optional[bool] = None, 

638 force_jupyter: Optional[bool] = None, 

639 force_interactive: Optional[bool] = None, 

640 soft_wrap: bool = False, 

641 theme: Optional[Theme] = None, 

642 stderr: bool = False, 

643 file: Optional[IO[str]] = None, 

644 quiet: bool = False, 

645 width: Optional[int] = None, 

646 height: Optional[int] = None, 

647 style: Optional[StyleType] = None, 

648 no_color: Optional[bool] = None, 

649 tab_size: int = 8, 

650 record: bool = False, 

651 markup: bool = True, 

652 emoji: bool = True, 

653 emoji_variant: Optional[EmojiVariant] = None, 

654 highlight: bool = True, 

655 log_time: bool = True, 

656 log_path: bool = True, 

657 log_time_format: Union[str, FormatTimeCallable] = "[%X]", 

658 highlighter: Optional["HighlighterType"] = ReprHighlighter(), 

659 legacy_windows: Optional[bool] = None, 

660 safe_box: bool = True, 

661 get_datetime: Optional[Callable[[], datetime]] = None, 

662 get_time: Optional[Callable[[], float]] = None, 

663 _environ: Optional[Mapping[str, str]] = None, 

664 ): 

665 # Copy of os.environ allows us to replace it for testing 

666 if _environ is not None: 

667 self._environ = _environ 

668 

669 self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter 

670 if self.is_jupyter: 

671 if width is None: 

672 jupyter_columns = self._environ.get("JUPYTER_COLUMNS") 

673 if jupyter_columns is not None and jupyter_columns.isdigit(): 

674 width = int(jupyter_columns) 

675 else: 

676 width = JUPYTER_DEFAULT_COLUMNS 

677 if height is None: 

678 jupyter_lines = self._environ.get("JUPYTER_LINES") 

679 if jupyter_lines is not None and jupyter_lines.isdigit(): 

680 height = int(jupyter_lines) 

681 else: 

682 height = JUPYTER_DEFAULT_LINES 

683 

684 self.tab_size = tab_size 

685 self.record = record 

686 self._markup = markup 

687 self._emoji = emoji 

688 self._emoji_variant: Optional[EmojiVariant] = emoji_variant 

689 self._highlight = highlight 

690 self.legacy_windows: bool = ( 

691 (detect_legacy_windows() and not self.is_jupyter) 

692 if legacy_windows is None 

693 else legacy_windows 

694 ) 

695 

696 if width is None: 

697 columns = self._environ.get("COLUMNS") 

698 if columns is not None and columns.isdigit(): 

699 width = int(columns) - self.legacy_windows 

700 if height is None: 

701 lines = self._environ.get("LINES") 

702 if lines is not None and lines.isdigit(): 

703 height = int(lines) 

704 

705 self.soft_wrap = soft_wrap 

706 self._width = width 

707 self._height = height 

708 

709 self._color_system: Optional[ColorSystem] 

710 

711 self._force_terminal = None 

712 if force_terminal is not None: 

713 self._force_terminal = force_terminal 

714 else: 

715 # If FORCE_COLOR env var has any value at all, we force terminal. 

716 force_color = self._environ.get("FORCE_COLOR") 

717 if force_color is not None: 

718 self._force_terminal = True 

719 

720 self._file = file 

721 self.quiet = quiet 

722 self.stderr = stderr 

723 

724 if color_system is None: 

725 self._color_system = None 

726 elif color_system == "auto": 

727 self._color_system = self._detect_color_system() 

728 else: 

729 self._color_system = COLOR_SYSTEMS[color_system] 

730 

731 self._lock = threading.RLock() 

732 self._log_render = LogRender( 

733 show_time=log_time, 

734 show_path=log_path, 

735 time_format=log_time_format, 

736 ) 

737 self.highlighter: HighlighterType = highlighter or _null_highlighter 

738 self.safe_box = safe_box 

739 self.get_datetime = get_datetime or datetime.now 

740 self.get_time = get_time or monotonic 

741 self.style = style 

742 self.no_color = ( 

743 no_color if no_color is not None else "NO_COLOR" in self._environ 

744 ) 

745 self.is_interactive = ( 

746 (self.is_terminal and not self.is_dumb_terminal) 

747 if force_interactive is None 

748 else force_interactive 

749 ) 

750 

751 self._record_buffer_lock = threading.RLock() 

752 self._thread_locals = ConsoleThreadLocals( 

753 theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme) 

754 ) 

755 self._record_buffer: List[Segment] = [] 

756 self._render_hooks: List[RenderHook] = [] 

757 self._live: Optional["Live"] = None 

758 self._is_alt_screen = False 

759 

760 def __repr__(self) -> str: 

761 return f"<console width={self.width} {str(self._color_system)}>" 

762 

763 @property 

764 def file(self) -> IO[str]: 

765 """Get the file object to write to.""" 

766 file = self._file or (sys.stderr if self.stderr else sys.stdout) 

767 file = getattr(file, "rich_proxied_file", file) 

768 if file is None: 

769 file = NULL_FILE 

770 return file 

771 

772 @file.setter 

773 def file(self, new_file: IO[str]) -> None: 

774 """Set a new file object.""" 

775 self._file = new_file 

776 

777 @property 

778 def _buffer(self) -> List[Segment]: 

779 """Get a thread local buffer.""" 

780 return self._thread_locals.buffer 

781 

782 @property 

783 def _buffer_index(self) -> int: 

784 """Get a thread local buffer.""" 

785 return self._thread_locals.buffer_index 

786 

787 @_buffer_index.setter 

788 def _buffer_index(self, value: int) -> None: 

789 self._thread_locals.buffer_index = value 

790 

791 @property 

792 def _theme_stack(self) -> ThemeStack: 

793 """Get the thread local theme stack.""" 

794 return self._thread_locals.theme_stack 

795 

796 def _detect_color_system(self) -> Optional[ColorSystem]: 

797 """Detect color system from env vars.""" 

798 if self.is_jupyter: 

799 return ColorSystem.TRUECOLOR 

800 if not self.is_terminal or self.is_dumb_terminal: 

801 return None 

802 if WINDOWS: # pragma: no cover 

803 if self.legacy_windows: # pragma: no cover 

804 return ColorSystem.WINDOWS 

805 windows_console_features = get_windows_console_features() 

806 return ( 

807 ColorSystem.TRUECOLOR 

808 if windows_console_features.truecolor 

809 else ColorSystem.EIGHT_BIT 

810 ) 

811 else: 

812 color_term = self._environ.get("COLORTERM", "").strip().lower() 

813 if color_term in ("truecolor", "24bit"): 

814 return ColorSystem.TRUECOLOR 

815 term = self._environ.get("TERM", "").strip().lower() 

816 _term_name, _hyphen, colors = term.rpartition("-") 

817 color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) 

818 return color_system 

819 

820 def _enter_buffer(self) -> None: 

821 """Enter in to a buffer context, and buffer all output.""" 

822 self._buffer_index += 1 

823 

824 def _exit_buffer(self) -> None: 

825 """Leave buffer context, and render content if required.""" 

826 self._buffer_index -= 1 

827 self._check_buffer() 

828 

829 def set_live(self, live: "Live") -> None: 

830 """Set Live instance. Used by Live context manager. 

831 

832 Args: 

833 live (Live): Live instance using this Console. 

834 

835 Raises: 

836 errors.LiveError: If this Console has a Live context currently active. 

837 """ 

838 with self._lock: 

839 if self._live is not None: 

840 raise errors.LiveError("Only one live display may be active at once") 

841 self._live = live 

842 

843 def clear_live(self) -> None: 

844 """Clear the Live instance.""" 

845 with self._lock: 

846 self._live = None 

847 

848 def push_render_hook(self, hook: RenderHook) -> None: 

849 """Add a new render hook to the stack. 

850 

851 Args: 

852 hook (RenderHook): Render hook instance. 

853 """ 

854 with self._lock: 

855 self._render_hooks.append(hook) 

856 

857 def pop_render_hook(self) -> None: 

858 """Pop the last renderhook from the stack.""" 

859 with self._lock: 

860 self._render_hooks.pop() 

861 

862 def __enter__(self) -> "Console": 

863 """Own context manager to enter buffer context.""" 

864 self._enter_buffer() 

865 return self 

866 

867 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: 

868 """Exit buffer context.""" 

869 self._exit_buffer() 

870 

871 def begin_capture(self) -> None: 

872 """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output.""" 

873 self._enter_buffer() 

874 

875 def end_capture(self) -> str: 

876 """End capture mode and return captured string. 

877 

878 Returns: 

879 str: Console output. 

880 """ 

881 render_result = self._render_buffer(self._buffer) 

882 del self._buffer[:] 

883 self._exit_buffer() 

884 return render_result 

885 

886 def push_theme(self, theme: Theme, *, inherit: bool = True) -> None: 

887 """Push a new theme on to the top of the stack, replacing the styles from the previous theme. 

888 Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather 

889 than calling this method directly. 

890 

891 Args: 

892 theme (Theme): A theme instance. 

893 inherit (bool, optional): Inherit existing styles. Defaults to True. 

894 """ 

895 self._theme_stack.push_theme(theme, inherit=inherit) 

896 

897 def pop_theme(self) -> None: 

898 """Remove theme from top of stack, restoring previous theme.""" 

899 self._theme_stack.pop_theme() 

900 

901 def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext: 

902 """Use a different theme for the duration of the context manager. 

903 

904 Args: 

905 theme (Theme): Theme instance to user. 

906 inherit (bool, optional): Inherit existing console styles. Defaults to True. 

907 

908 Returns: 

909 ThemeContext: [description] 

910 """ 

911 return ThemeContext(self, theme, inherit) 

912 

913 @property 

914 def color_system(self) -> Optional[str]: 

915 """Get color system string. 

916 

917 Returns: 

918 Optional[str]: "standard", "256" or "truecolor". 

919 """ 

920 

921 if self._color_system is not None: 

922 return _COLOR_SYSTEMS_NAMES[self._color_system] 

923 else: 

924 return None 

925 

926 @property 

927 def encoding(self) -> str: 

928 """Get the encoding of the console file, e.g. ``"utf-8"``. 

929 

930 Returns: 

931 str: A standard encoding string. 

932 """ 

933 return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower() 

934 

935 @property 

936 def is_terminal(self) -> bool: 

937 """Check if the console is writing to a terminal. 

938 

939 Returns: 

940 bool: True if the console writing to a device capable of 

941 understanding terminal codes, otherwise False. 

942 """ 

943 if self._force_terminal is not None: 

944 return self._force_terminal 

945 

946 if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith( 

947 "idlelib" 

948 ): 

949 # Return False for Idle which claims to be a tty but can't handle ansi codes 

950 return False 

951 

952 isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) 

953 try: 

954 return False if isatty is None else isatty() 

955 except ValueError: 

956 # in some situation (at the end of a pytest run for example) isatty() can raise 

957 # ValueError: I/O operation on closed file 

958 # return False because we aren't in a terminal anymore 

959 return False 

960 

961 @property 

962 def is_dumb_terminal(self) -> bool: 

963 """Detect dumb terminal. 

964 

965 Returns: 

966 bool: True if writing to a dumb terminal, otherwise False. 

967 

968 """ 

969 _term = self._environ.get("TERM", "") 

970 is_dumb = _term.lower() in ("dumb", "unknown") 

971 return self.is_terminal and is_dumb 

972 

973 @property 

974 def options(self) -> ConsoleOptions: 

975 """Get default console options.""" 

976 return ConsoleOptions( 

977 max_height=self.size.height, 

978 size=self.size, 

979 legacy_windows=self.legacy_windows, 

980 min_width=1, 

981 max_width=self.width, 

982 encoding=self.encoding, 

983 is_terminal=self.is_terminal, 

984 ) 

985 

986 @property 

987 def size(self) -> ConsoleDimensions: 

988 """Get the size of the console. 

989 

990 Returns: 

991 ConsoleDimensions: A named tuple containing the dimensions. 

992 """ 

993 

994 if self._width is not None and self._height is not None: 

995 return ConsoleDimensions(self._width - self.legacy_windows, self._height) 

996 

997 if self.is_dumb_terminal: 

998 return ConsoleDimensions(80, 25) 

999 

1000 width: Optional[int] = None 

1001 height: Optional[int] = None 

1002 

1003 if WINDOWS: # pragma: no cover 

1004 try: 

1005 width, height = os.get_terminal_size() 

1006 except (AttributeError, ValueError, OSError): # Probably not a terminal 

1007 pass 

1008 else: 

1009 for file_descriptor in _STD_STREAMS: 

1010 try: 

1011 width, height = os.get_terminal_size(file_descriptor) 

1012 except (AttributeError, ValueError, OSError): 

1013 pass 

1014 else: 

1015 break 

1016 

1017 columns = self._environ.get("COLUMNS") 

1018 if columns is not None and columns.isdigit(): 

1019 width = int(columns) 

1020 lines = self._environ.get("LINES") 

1021 if lines is not None and lines.isdigit(): 

1022 height = int(lines) 

1023 

1024 # get_terminal_size can report 0, 0 if run from pseudo-terminal 

1025 width = width or 80 

1026 height = height or 25 

1027 return ConsoleDimensions( 

1028 width - self.legacy_windows if self._width is None else self._width, 

1029 height if self._height is None else self._height, 

1030 ) 

1031 

1032 @size.setter 

1033 def size(self, new_size: Tuple[int, int]) -> None: 

1034 """Set a new size for the terminal. 

1035 

1036 Args: 

1037 new_size (Tuple[int, int]): New width and height. 

1038 """ 

1039 width, height = new_size 

1040 self._width = width 

1041 self._height = height 

1042 

1043 @property 

1044 def width(self) -> int: 

1045 """Get the width of the console. 

1046 

1047 Returns: 

1048 int: The width (in characters) of the console. 

1049 """ 

1050 return self.size.width 

1051 

1052 @width.setter 

1053 def width(self, width: int) -> None: 

1054 """Set width. 

1055 

1056 Args: 

1057 width (int): New width. 

1058 """ 

1059 self._width = width 

1060 

1061 @property 

1062 def height(self) -> int: 

1063 """Get the height of the console. 

1064 

1065 Returns: 

1066 int: The height (in lines) of the console. 

1067 """ 

1068 return self.size.height 

1069 

1070 @height.setter 

1071 def height(self, height: int) -> None: 

1072 """Set height. 

1073 

1074 Args: 

1075 height (int): new height. 

1076 """ 

1077 self._height = height 

1078 

1079 def bell(self) -> None: 

1080 """Play a 'bell' sound (if supported by the terminal).""" 

1081 self.control(Control.bell()) 

1082 

1083 def capture(self) -> Capture: 

1084 """A context manager to *capture* the result of print() or log() in a string, 

1085 rather than writing it to the console. 

1086 

1087 Example: 

1088 >>> from rich.console import Console 

1089 >>> console = Console() 

1090 >>> with console.capture() as capture: 

1091 ... console.print("[bold magenta]Hello World[/]") 

1092 >>> print(capture.get()) 

1093 

1094 Returns: 

1095 Capture: Context manager with disables writing to the terminal. 

1096 """ 

1097 capture = Capture(self) 

1098 return capture 

1099 

1100 def pager( 

1101 self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False 

1102 ) -> PagerContext: 

1103 """A context manager to display anything printed within a "pager". The pager application 

1104 is defined by the system and will typically support at least pressing a key to scroll. 

1105 

1106 Args: 

1107 pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. 

1108 styles (bool, optional): Show styles in pager. Defaults to False. 

1109 links (bool, optional): Show links in pager. Defaults to False. 

1110 

1111 Example: 

1112 >>> from rich.console import Console 

1113 >>> from rich.__main__ import make_test_card 

1114 >>> console = Console() 

1115 >>> with console.pager(): 

1116 console.print(make_test_card()) 

1117 

1118 Returns: 

1119 PagerContext: A context manager. 

1120 """ 

1121 return PagerContext(self, pager=pager, styles=styles, links=links) 

1122 

1123 def line(self, count: int = 1) -> None: 

1124 """Write new line(s). 

1125 

1126 Args: 

1127 count (int, optional): Number of new lines. Defaults to 1. 

1128 """ 

1129 

1130 assert count >= 0, "count must be >= 0" 

1131 self.print(NewLine(count)) 

1132 

1133 def clear(self, home: bool = True) -> None: 

1134 """Clear the screen. 

1135 

1136 Args: 

1137 home (bool, optional): Also move the cursor to 'home' position. Defaults to True. 

1138 """ 

1139 if home: 

1140 self.control(Control.clear(), Control.home()) 

1141 else: 

1142 self.control(Control.clear()) 

1143 

1144 def status( 

1145 self, 

1146 status: RenderableType, 

1147 *, 

1148 spinner: str = "dots", 

1149 spinner_style: str = "status.spinner", 

1150 speed: float = 1.0, 

1151 refresh_per_second: float = 12.5, 

1152 ) -> "Status": 

1153 """Display a status and spinner. 

1154 

1155 Args: 

1156 status (RenderableType): A status renderable (str or Text typically). 

1157 spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". 

1158 spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". 

1159 speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. 

1160 refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. 

1161 

1162 Returns: 

1163 Status: A Status object that may be used as a context manager. 

1164 """ 

1165 from .status import Status 

1166 

1167 status_renderable = Status( 

1168 status, 

1169 console=self, 

1170 spinner=spinner, 

1171 spinner_style=spinner_style, 

1172 speed=speed, 

1173 refresh_per_second=refresh_per_second, 

1174 ) 

1175 return status_renderable 

1176 

1177 def show_cursor(self, show: bool = True) -> bool: 

1178 """Show or hide the cursor. 

1179 

1180 Args: 

1181 show (bool, optional): Set visibility of the cursor. 

1182 """ 

1183 if self.is_terminal: 

1184 self.control(Control.show_cursor(show)) 

1185 return True 

1186 return False 

1187 

1188 def set_alt_screen(self, enable: bool = True) -> bool: 

1189 """Enables alternative screen mode. 

1190 

1191 Note, if you enable this mode, you should ensure that is disabled before 

1192 the application exits. See :meth:`~rich.Console.screen` for a context manager 

1193 that handles this for you. 

1194 

1195 Args: 

1196 enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True. 

1197 

1198 Returns: 

1199 bool: True if the control codes were written. 

1200 

1201 """ 

1202 changed = False 

1203 if self.is_terminal and not self.legacy_windows: 

1204 self.control(Control.alt_screen(enable)) 

1205 changed = True 

1206 self._is_alt_screen = enable 

1207 return changed 

1208 

1209 @property 

1210 def is_alt_screen(self) -> bool: 

1211 """Check if the alt screen was enabled. 

1212 

1213 Returns: 

1214 bool: True if the alt screen was enabled, otherwise False. 

1215 """ 

1216 return self._is_alt_screen 

1217 

1218 def set_window_title(self, title: str) -> bool: 

1219 """Set the title of the console terminal window. 

1220 

1221 Warning: There is no means within Rich of "resetting" the window title to its 

1222 previous value, meaning the title you set will persist even after your application 

1223 exits. 

1224 

1225 ``fish`` shell resets the window title before and after each command by default, 

1226 negating this issue. Windows Terminal and command prompt will also reset the title for you. 

1227 Most other shells and terminals, however, do not do this. 

1228 

1229 Some terminals may require configuration changes before you can set the title. 

1230 Some terminals may not support setting the title at all. 

1231 

1232 Other software (including the terminal itself, the shell, custom prompts, plugins, etc.) 

1233 may also set the terminal window title. This could result in whatever value you write 

1234 using this method being overwritten. 

1235 

1236 Args: 

1237 title (str): The new title of the terminal window. 

1238 

1239 Returns: 

1240 bool: True if the control code to change the terminal title was 

1241 written, otherwise False. Note that a return value of True 

1242 does not guarantee that the window title has actually changed, 

1243 since the feature may be unsupported/disabled in some terminals. 

1244 """ 

1245 if self.is_terminal: 

1246 self.control(Control.title(title)) 

1247 return True 

1248 return False 

1249 

1250 def screen( 

1251 self, hide_cursor: bool = True, style: Optional[StyleType] = None 

1252 ) -> "ScreenContext": 

1253 """Context manager to enable and disable 'alternative screen' mode. 

1254 

1255 Args: 

1256 hide_cursor (bool, optional): Also hide the cursor. Defaults to False. 

1257 style (Style, optional): Optional style for screen. Defaults to None. 

1258 

1259 Returns: 

1260 ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit. 

1261 """ 

1262 return ScreenContext(self, hide_cursor=hide_cursor, style=style or "") 

1263 

1264 def measure( 

1265 self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None 

1266 ) -> Measurement: 

1267 """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains 

1268 information regarding the number of characters required to print the renderable. 

1269 

1270 Args: 

1271 renderable (RenderableType): Any renderable or string. 

1272 options (Optional[ConsoleOptions], optional): Options to use when measuring, or None 

1273 to use default options. Defaults to None. 

1274 

1275 Returns: 

1276 Measurement: A measurement of the renderable. 

1277 """ 

1278 measurement = Measurement.get(self, options or self.options, renderable) 

1279 return measurement 

1280 

1281 def render( 

1282 self, renderable: RenderableType, options: Optional[ConsoleOptions] = None 

1283 ) -> Iterable[Segment]: 

1284 """Render an object in to an iterable of `Segment` instances. 

1285 

1286 This method contains the logic for rendering objects with the console protocol. 

1287 You are unlikely to need to use it directly, unless you are extending the library. 

1288 

1289 Args: 

1290 renderable (RenderableType): An object supporting the console protocol, or 

1291 an object that may be converted to a string. 

1292 options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None. 

1293 

1294 Returns: 

1295 Iterable[Segment]: An iterable of segments that may be rendered. 

1296 """ 

1297 

1298 _options = options or self.options 

1299 if _options.max_width < 1: 

1300 # No space to render anything. This prevents potential recursion errors. 

1301 return 

1302 render_iterable: RenderResult 

1303 

1304 renderable = rich_cast(renderable) 

1305 if hasattr(renderable, "__rich_console__") and not isclass(renderable): 

1306 render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr] 

1307 elif isinstance(renderable, str): 

1308 text_renderable = self.render_str( 

1309 renderable, highlight=_options.highlight, markup=_options.markup 

1310 ) 

1311 render_iterable = text_renderable.__rich_console__(self, _options) 

1312 else: 

1313 raise errors.NotRenderableError( 

1314 f"Unable to render {renderable!r}; " 

1315 "A str, Segment or object with __rich_console__ method is required" 

1316 ) 

1317 

1318 try: 

1319 iter_render = iter(render_iterable) 

1320 except TypeError: 

1321 raise errors.NotRenderableError( 

1322 f"object {render_iterable!r} is not renderable" 

1323 ) 

1324 _Segment = Segment 

1325 _options = _options.reset_height() 

1326 for render_output in iter_render: 

1327 if isinstance(render_output, _Segment): 

1328 yield render_output 

1329 else: 

1330 yield from self.render(render_output, _options) 

1331 

1332 def render_lines( 

1333 self, 

1334 renderable: RenderableType, 

1335 options: Optional[ConsoleOptions] = None, 

1336 *, 

1337 style: Optional[Style] = None, 

1338 pad: bool = True, 

1339 new_lines: bool = False, 

1340 ) -> List[List[Segment]]: 

1341 """Render objects in to a list of lines. 

1342 

1343 The output of render_lines is useful when further formatting of rendered console text 

1344 is required, such as the Panel class which draws a border around any renderable object. 

1345 

1346 Args: 

1347 renderable (RenderableType): Any object renderable in the console. 

1348 options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``. 

1349 style (Style, optional): Optional style to apply to renderables. Defaults to ``None``. 

1350 pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``. 

1351 new_lines (bool, optional): Include "\n" characters at end of lines. 

1352 

1353 Returns: 

1354 List[List[Segment]]: A list of lines, where a line is a list of Segment objects. 

1355 """ 

1356 with self._lock: 

1357 render_options = options or self.options 

1358 _rendered = self.render(renderable, render_options) 

1359 if style: 

1360 _rendered = Segment.apply_style(_rendered, style) 

1361 

1362 render_height = render_options.height 

1363 if render_height is not None: 

1364 render_height = max(0, render_height) 

1365 

1366 lines = list( 

1367 islice( 

1368 Segment.split_and_crop_lines( 

1369 _rendered, 

1370 render_options.max_width, 

1371 include_new_lines=new_lines, 

1372 pad=pad, 

1373 style=style, 

1374 ), 

1375 None, 

1376 render_height, 

1377 ) 

1378 ) 

1379 if render_options.height is not None: 

1380 extra_lines = render_options.height - len(lines) 

1381 if extra_lines > 0: 

1382 pad_line = [ 

1383 [Segment(" " * render_options.max_width, style), Segment("\n")] 

1384 if new_lines 

1385 else [Segment(" " * render_options.max_width, style)] 

1386 ] 

1387 lines.extend(pad_line * extra_lines) 

1388 

1389 return lines 

1390 

1391 def render_str( 

1392 self, 

1393 text: str, 

1394 *, 

1395 style: Union[str, Style] = "", 

1396 justify: Optional[JustifyMethod] = None, 

1397 overflow: Optional[OverflowMethod] = None, 

1398 emoji: Optional[bool] = None, 

1399 markup: Optional[bool] = None, 

1400 highlight: Optional[bool] = None, 

1401 highlighter: Optional[HighlighterType] = None, 

1402 ) -> "Text": 

1403 """Convert a string to a Text instance. This is called automatically if 

1404 you print or log a string. 

1405 

1406 Args: 

1407 text (str): Text to render. 

1408 style (Union[str, Style], optional): Style to apply to rendered text. 

1409 justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``. 

1410 overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``. 

1411 emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default. 

1412 markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default. 

1413 highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default. 

1414 highlighter (HighlighterType, optional): Optional highlighter to apply. 

1415 Returns: 

1416 ConsoleRenderable: Renderable object. 

1417 

1418 """ 

1419 emoji_enabled = emoji or (emoji is None and self._emoji) 

1420 markup_enabled = markup or (markup is None and self._markup) 

1421 highlight_enabled = highlight or (highlight is None and self._highlight) 

1422 

1423 if markup_enabled: 

1424 rich_text = render_markup( 

1425 text, 

1426 style=style, 

1427 emoji=emoji_enabled, 

1428 emoji_variant=self._emoji_variant, 

1429 ) 

1430 rich_text.justify = justify 

1431 rich_text.overflow = overflow 

1432 else: 

1433 rich_text = Text( 

1434 _emoji_replace(text, default_variant=self._emoji_variant) 

1435 if emoji_enabled 

1436 else text, 

1437 justify=justify, 

1438 overflow=overflow, 

1439 style=style, 

1440 ) 

1441 

1442 _highlighter = (highlighter or self.highlighter) if highlight_enabled else None 

1443 if _highlighter is not None: 

1444 highlight_text = _highlighter(str(rich_text)) 

1445 highlight_text.copy_styles(rich_text) 

1446 return highlight_text 

1447 

1448 return rich_text 

1449 

1450 def get_style( 

1451 self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None 

1452 ) -> Style: 

1453 """Get a Style instance by its theme name or parse a definition. 

1454 

1455 Args: 

1456 name (str): The name of a style or a style definition. 

1457 

1458 Returns: 

1459 Style: A Style object. 

1460 

1461 Raises: 

1462 MissingStyle: If no style could be parsed from name. 

1463 

1464 """ 

1465 if isinstance(name, Style): 

1466 return name 

1467 

1468 try: 

1469 style = self._theme_stack.get(name) 

1470 if style is None: 

1471 style = Style.parse(name) 

1472 return style.copy() if style.link else style 

1473 except errors.StyleSyntaxError as error: 

1474 if default is not None: 

1475 return self.get_style(default) 

1476 raise errors.MissingStyle( 

1477 f"Failed to get style {name!r}; {error}" 

1478 ) from None 

1479 

1480 def _collect_renderables( 

1481 self, 

1482 objects: Iterable[Any], 

1483 sep: str, 

1484 end: str, 

1485 *, 

1486 justify: Optional[JustifyMethod] = None, 

1487 emoji: Optional[bool] = None, 

1488 markup: Optional[bool] = None, 

1489 highlight: Optional[bool] = None, 

1490 ) -> List[ConsoleRenderable]: 

1491 """Combine a number of renderables and text into one renderable. 

1492 

1493 Args: 

1494 objects (Iterable[Any]): Anything that Rich can render. 

1495 sep (str): String to write between print data. 

1496 end (str): String to write at end of print data. 

1497 justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. 

1498 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. 

1499 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. 

1500 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. 

1501 

1502 Returns: 

1503 List[ConsoleRenderable]: A list of things to render. 

1504 """ 

1505 renderables: List[ConsoleRenderable] = [] 

1506 _append = renderables.append 

1507 text: List[Text] = [] 

1508 append_text = text.append 

1509 

1510 append = _append 

1511 if justify in ("left", "center", "right"): 

1512 

1513 def align_append(renderable: RenderableType) -> None: 

1514 _append(Align(renderable, cast(AlignMethod, justify))) 

1515 

1516 append = align_append 

1517 

1518 _highlighter: HighlighterType = _null_highlighter 

1519 if highlight or (highlight is None and self._highlight): 

1520 _highlighter = self.highlighter 

1521 

1522 def check_text() -> None: 

1523 if text: 

1524 sep_text = Text(sep, justify=justify, end=end) 

1525 append(sep_text.join(text)) 

1526 del text[:] 

1527 

1528 for renderable in objects: 

1529 renderable = rich_cast(renderable) 

1530 if isinstance(renderable, str): 

1531 append_text( 

1532 self.render_str( 

1533 renderable, emoji=emoji, markup=markup, highlighter=_highlighter 

1534 ) 

1535 ) 

1536 elif isinstance(renderable, Text): 

1537 append_text(renderable) 

1538 elif isinstance(renderable, ConsoleRenderable): 

1539 check_text() 

1540 append(renderable) 

1541 elif is_expandable(renderable): 

1542 check_text() 

1543 append(Pretty(renderable, highlighter=_highlighter)) 

1544 else: 

1545 append_text(_highlighter(str(renderable))) 

1546 

1547 check_text() 

1548 

1549 if self.style is not None: 

1550 style = self.get_style(self.style) 

1551 renderables = [Styled(renderable, style) for renderable in renderables] 

1552 

1553 return renderables 

1554 

1555 def rule( 

1556 self, 

1557 title: TextType = "", 

1558 *, 

1559 characters: str = "─", 

1560 style: Union[str, Style] = "rule.line", 

1561 align: AlignMethod = "center", 

1562 ) -> None: 

1563 """Draw a line with optional centered title. 

1564 

1565 Args: 

1566 title (str, optional): Text to render over the rule. Defaults to "". 

1567 characters (str, optional): Character(s) to form the line. Defaults to "─". 

1568 style (str, optional): Style of line. Defaults to "rule.line". 

1569 align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". 

1570 """ 

1571 from .rule import Rule 

1572 

1573 rule = Rule(title=title, characters=characters, style=style, align=align) 

1574 self.print(rule) 

1575 

1576 def control(self, *control: Control) -> None: 

1577 """Insert non-printing control codes. 

1578 

1579 Args: 

1580 control_codes (str): Control codes, such as those that may move the cursor. 

1581 """ 

1582 if not self.is_dumb_terminal: 

1583 with self: 

1584 self._buffer.extend(_control.segment for _control in control) 

1585 

1586 def out( 

1587 self, 

1588 *objects: Any, 

1589 sep: str = " ", 

1590 end: str = "\n", 

1591 style: Optional[Union[str, Style]] = None, 

1592 highlight: Optional[bool] = None, 

1593 ) -> None: 

1594 """Output to the terminal. This is a low-level way of writing to the terminal which unlike 

1595 :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will 

1596 optionally apply highlighting and a basic style. 

1597 

1598 Args: 

1599 sep (str, optional): String to write between print data. Defaults to " ". 

1600 end (str, optional): String to write at end of print data. Defaults to "\\\\n". 

1601 style (Union[str, Style], optional): A style to apply to output. Defaults to None. 

1602 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use 

1603 console default. Defaults to ``None``. 

1604 """ 

1605 raw_output: str = sep.join(str(_object) for _object in objects) 

1606 self.print( 

1607 raw_output, 

1608 style=style, 

1609 highlight=highlight, 

1610 emoji=False, 

1611 markup=False, 

1612 no_wrap=True, 

1613 overflow="ignore", 

1614 crop=False, 

1615 end=end, 

1616 ) 

1617 

1618 def print( 

1619 self, 

1620 *objects: Any, 

1621 sep: str = " ", 

1622 end: str = "\n", 

1623 style: Optional[Union[str, Style]] = None, 

1624 justify: Optional[JustifyMethod] = None, 

1625 overflow: Optional[OverflowMethod] = None, 

1626 no_wrap: Optional[bool] = None, 

1627 emoji: Optional[bool] = None, 

1628 markup: Optional[bool] = None, 

1629 highlight: Optional[bool] = None, 

1630 width: Optional[int] = None, 

1631 height: Optional[int] = None, 

1632 crop: bool = True, 

1633 soft_wrap: Optional[bool] = None, 

1634 new_line_start: bool = False, 

1635 ) -> None: 

1636 """Print to the console. 

1637 

1638 Args: 

1639 objects (positional args): Objects to log to the terminal. 

1640 sep (str, optional): String to write between print data. Defaults to " ". 

1641 end (str, optional): String to write at end of print data. Defaults to "\\\\n". 

1642 style (Union[str, Style], optional): A style to apply to output. Defaults to None. 

1643 justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. 

1644 overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. 

1645 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None. 

1646 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``. 

1647 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``. 

1648 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``. 

1649 width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``. 

1650 crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True. 

1651 soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for 

1652 Console default. Defaults to ``None``. 

1653 new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``. 

1654 """ 

1655 if not objects: 

1656 objects = (NewLine(),) 

1657 

1658 if soft_wrap is None: 

1659 soft_wrap = self.soft_wrap 

1660 if soft_wrap: 

1661 if no_wrap is None: 

1662 no_wrap = True 

1663 if overflow is None: 

1664 overflow = "ignore" 

1665 crop = False 

1666 render_hooks = self._render_hooks[:] 

1667 with self: 

1668 renderables = self._collect_renderables( 

1669 objects, 

1670 sep, 

1671 end, 

1672 justify=justify, 

1673 emoji=emoji, 

1674 markup=markup, 

1675 highlight=highlight, 

1676 ) 

1677 for hook in render_hooks: 

1678 renderables = hook.process_renderables(renderables) 

1679 render_options = self.options.update( 

1680 justify=justify, 

1681 overflow=overflow, 

1682 width=min(width, self.width) if width is not None else NO_CHANGE, 

1683 height=height, 

1684 no_wrap=no_wrap, 

1685 markup=markup, 

1686 highlight=highlight, 

1687 ) 

1688 

1689 new_segments: List[Segment] = [] 

1690 extend = new_segments.extend 

1691 render = self.render 

1692 if style is None: 

1693 for renderable in renderables: 

1694 extend(render(renderable, render_options)) 

1695 else: 

1696 for renderable in renderables: 

1697 extend( 

1698 Segment.apply_style( 

1699 render(renderable, render_options), self.get_style(style) 

1700 ) 

1701 ) 

1702 if new_line_start: 

1703 if ( 

1704 len("".join(segment.text for segment in new_segments).splitlines()) 

1705 > 1 

1706 ): 

1707 new_segments.insert(0, Segment.line()) 

1708 if crop: 

1709 buffer_extend = self._buffer.extend 

1710 for line in Segment.split_and_crop_lines( 

1711 new_segments, self.width, pad=False 

1712 ): 

1713 buffer_extend(line) 

1714 else: 

1715 self._buffer.extend(new_segments) 

1716 

1717 def print_json( 

1718 self, 

1719 json: Optional[str] = None, 

1720 *, 

1721 data: Any = None, 

1722 indent: Union[None, int, str] = 2, 

1723 highlight: bool = True, 

1724 skip_keys: bool = False, 

1725 ensure_ascii: bool = False, 

1726 check_circular: bool = True, 

1727 allow_nan: bool = True, 

1728 default: Optional[Callable[[Any], Any]] = None, 

1729 sort_keys: bool = False, 

1730 ) -> None: 

1731 """Pretty prints JSON. Output will be valid JSON. 

1732 

1733 Args: 

1734 json (Optional[str]): A string containing JSON. 

1735 data (Any): If json is not supplied, then encode this data. 

1736 indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. 

1737 highlight (bool, optional): Enable highlighting of output: Defaults to True. 

1738 skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. 

1739 ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. 

1740 check_circular (bool, optional): Check for circular references. Defaults to True. 

1741 allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. 

1742 default (Callable, optional): A callable that converts values that can not be encoded 

1743 in to something that can be JSON encoded. Defaults to None. 

1744 sort_keys (bool, optional): Sort dictionary keys. Defaults to False. 

1745 """ 

1746 from rich.json import JSON 

1747 

1748 if json is None: 

1749 json_renderable = JSON.from_data( 

1750 data, 

1751 indent=indent, 

1752 highlight=highlight, 

1753 skip_keys=skip_keys, 

1754 ensure_ascii=ensure_ascii, 

1755 check_circular=check_circular, 

1756 allow_nan=allow_nan, 

1757 default=default, 

1758 sort_keys=sort_keys, 

1759 ) 

1760 else: 

1761 if not isinstance(json, str): 

1762 raise TypeError( 

1763 f"json must be str. Did you mean print_json(data={json!r}) ?" 

1764 ) 

1765 json_renderable = JSON( 

1766 json, 

1767 indent=indent, 

1768 highlight=highlight, 

1769 skip_keys=skip_keys, 

1770 ensure_ascii=ensure_ascii, 

1771 check_circular=check_circular, 

1772 allow_nan=allow_nan, 

1773 default=default, 

1774 sort_keys=sort_keys, 

1775 ) 

1776 self.print(json_renderable, soft_wrap=True) 

1777 

1778 def update_screen( 

1779 self, 

1780 renderable: RenderableType, 

1781 *, 

1782 region: Optional[Region] = None, 

1783 options: Optional[ConsoleOptions] = None, 

1784 ) -> None: 

1785 """Update the screen at a given offset. 

1786 

1787 Args: 

1788 renderable (RenderableType): A Rich renderable. 

1789 region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None. 

1790 x (int, optional): x offset. Defaults to 0. 

1791 y (int, optional): y offset. Defaults to 0. 

1792 

1793 Raises: 

1794 errors.NoAltScreen: If the Console isn't in alt screen mode. 

1795 

1796 """ 

1797 if not self.is_alt_screen: 

1798 raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") 

1799 render_options = options or self.options 

1800 if region is None: 

1801 x = y = 0 

1802 render_options = render_options.update_dimensions( 

1803 render_options.max_width, render_options.height or self.height 

1804 ) 

1805 else: 

1806 x, y, width, height = region 

1807 render_options = render_options.update_dimensions(width, height) 

1808 

1809 lines = self.render_lines(renderable, options=render_options) 

1810 self.update_screen_lines(lines, x, y) 

1811 

1812 def update_screen_lines( 

1813 self, lines: List[List[Segment]], x: int = 0, y: int = 0 

1814 ) -> None: 

1815 """Update lines of the screen at a given offset. 

1816 

1817 Args: 

1818 lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`). 

1819 x (int, optional): x offset (column no). Defaults to 0. 

1820 y (int, optional): y offset (column no). Defaults to 0. 

1821 

1822 Raises: 

1823 errors.NoAltScreen: If the Console isn't in alt screen mode. 

1824 """ 

1825 if not self.is_alt_screen: 

1826 raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") 

1827 screen_update = ScreenUpdate(lines, x, y) 

1828 segments = self.render(screen_update) 

1829 self._buffer.extend(segments) 

1830 self._check_buffer() 

1831 

1832 def print_exception( 

1833 self, 

1834 *, 

1835 width: Optional[int] = 100, 

1836 extra_lines: int = 3, 

1837 theme: Optional[str] = None, 

1838 word_wrap: bool = False, 

1839 show_locals: bool = False, 

1840 suppress: Iterable[Union[str, ModuleType]] = (), 

1841 max_frames: int = 100, 

1842 ) -> None: 

1843 """Prints a rich render of the last exception and traceback. 

1844 

1845 Args: 

1846 width (Optional[int], optional): Number of characters used to render code. Defaults to 100. 

1847 extra_lines (int, optional): Additional lines of code to render. Defaults to 3. 

1848 theme (str, optional): Override pygments theme used in traceback 

1849 word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. 

1850 show_locals (bool, optional): Enable display of local variables. Defaults to False. 

1851 suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. 

1852 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. 

1853 """ 

1854 from .traceback import Traceback 

1855 

1856 traceback = Traceback( 

1857 width=width, 

1858 extra_lines=extra_lines, 

1859 theme=theme, 

1860 word_wrap=word_wrap, 

1861 show_locals=show_locals, 

1862 suppress=suppress, 

1863 max_frames=max_frames, 

1864 ) 

1865 self.print(traceback) 

1866 

1867 @staticmethod 

1868 def _caller_frame_info( 

1869 offset: int, 

1870 currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe, 

1871 ) -> Tuple[str, int, Dict[str, Any]]: 

1872 """Get caller frame information. 

1873 

1874 Args: 

1875 offset (int): the caller offset within the current frame stack. 

1876 currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to 

1877 retrieve the current frame. Defaults to ``inspect.currentframe``. 

1878 

1879 Returns: 

1880 Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and 

1881 the dictionary of local variables associated with the caller frame. 

1882 

1883 Raises: 

1884 RuntimeError: If the stack offset is invalid. 

1885 """ 

1886 # Ignore the frame of this local helper 

1887 offset += 1 

1888 

1889 frame = currentframe() 

1890 if frame is not None: 

1891 # Use the faster currentframe where implemented 

1892 while offset and frame is not None: 

1893 frame = frame.f_back 

1894 offset -= 1 

1895 assert frame is not None 

1896 return frame.f_code.co_filename, frame.f_lineno, frame.f_locals 

1897 else: 

1898 # Fallback to the slower stack 

1899 frame_info = inspect.stack()[offset] 

1900 return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals 

1901 

1902 def log( 

1903 self, 

1904 *objects: Any, 

1905 sep: str = " ", 

1906 end: str = "\n", 

1907 style: Optional[Union[str, Style]] = None, 

1908 justify: Optional[JustifyMethod] = None, 

1909 emoji: Optional[bool] = None, 

1910 markup: Optional[bool] = None, 

1911 highlight: Optional[bool] = None, 

1912 log_locals: bool = False, 

1913 _stack_offset: int = 1, 

1914 ) -> None: 

1915 """Log rich content to the terminal. 

1916 

1917 Args: 

1918 objects (positional args): Objects to log to the terminal. 

1919 sep (str, optional): String to write between print data. Defaults to " ". 

1920 end (str, optional): String to write at end of print data. Defaults to "\\\\n". 

1921 style (Union[str, Style], optional): A style to apply to output. Defaults to None. 

1922 justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. 

1923 overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. 

1924 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None. 

1925 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None. 

1926 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None. 

1927 log_locals (bool, optional): Boolean to enable logging of locals where ``log()`` 

1928 was called. Defaults to False. 

1929 _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1. 

1930 """ 

1931 if not objects: 

1932 objects = (NewLine(),) 

1933 

1934 render_hooks = self._render_hooks[:] 

1935 

1936 with self: 

1937 renderables = self._collect_renderables( 

1938 objects, 

1939 sep, 

1940 end, 

1941 justify=justify, 

1942 emoji=emoji, 

1943 markup=markup, 

1944 highlight=highlight, 

1945 ) 

1946 if style is not None: 

1947 renderables = [Styled(renderable, style) for renderable in renderables] 

1948 

1949 filename, line_no, locals = self._caller_frame_info(_stack_offset) 

1950 link_path = None if filename.startswith("<") else os.path.abspath(filename) 

1951 path = filename.rpartition(os.sep)[-1] 

1952 if log_locals: 

1953 locals_map = { 

1954 key: value 

1955 for key, value in locals.items() 

1956 if not key.startswith("__") 

1957 } 

1958 renderables.append(render_scope(locals_map, title="[i]locals")) 

1959 

1960 renderables = [ 

1961 self._log_render( 

1962 self, 

1963 renderables, 

1964 log_time=self.get_datetime(), 

1965 path=path, 

1966 line_no=line_no, 

1967 link_path=link_path, 

1968 ) 

1969 ] 

1970 for hook in render_hooks: 

1971 renderables = hook.process_renderables(renderables) 

1972 new_segments: List[Segment] = [] 

1973 extend = new_segments.extend 

1974 render = self.render 

1975 render_options = self.options 

1976 for renderable in renderables: 

1977 extend(render(renderable, render_options)) 

1978 buffer_extend = self._buffer.extend 

1979 for line in Segment.split_and_crop_lines( 

1980 new_segments, self.width, pad=False 

1981 ): 

1982 buffer_extend(line) 

1983 

1984 def _check_buffer(self) -> None: 

1985 """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) 

1986 Rendering is supported on Windows, Unix and Jupyter environments. For 

1987 legacy Windows consoles, the win32 API is called directly. 

1988 This method will also record what it renders if recording is enabled via Console.record. 

1989 """ 

1990 if self.quiet: 

1991 del self._buffer[:] 

1992 return 

1993 with self._lock: 

1994 if self.record: 

1995 with self._record_buffer_lock: 

1996 self._record_buffer.extend(self._buffer[:]) 

1997 

1998 if self._buffer_index == 0: 

1999 

2000 if self.is_jupyter: # pragma: no cover 

2001 from .jupyter import display 

2002 

2003 display(self._buffer, self._render_buffer(self._buffer[:])) 

2004 del self._buffer[:] 

2005 else: 

2006 if WINDOWS: 

2007 use_legacy_windows_render = False 

2008 if self.legacy_windows: 

2009 try: 

2010 use_legacy_windows_render = ( 

2011 self.file.fileno() in _STD_STREAMS_OUTPUT 

2012 ) 

2013 except (ValueError, io.UnsupportedOperation): 

2014 pass 

2015 

2016 if use_legacy_windows_render: 

2017 from rich._win32_console import LegacyWindowsTerm 

2018 from rich._windows_renderer import legacy_windows_render 

2019 

2020 buffer = self._buffer[:] 

2021 if self.no_color and self._color_system: 

2022 buffer = list(Segment.remove_color(buffer)) 

2023 

2024 legacy_windows_render(buffer, LegacyWindowsTerm(self.file)) 

2025 else: 

2026 # Either a non-std stream on legacy Windows, or modern Windows. 

2027 text = self._render_buffer(self._buffer[:]) 

2028 # https://bugs.python.org/issue37871 

2029 write = self.file.write 

2030 for line in text.splitlines(True): 

2031 try: 

2032 write(line) 

2033 except UnicodeEncodeError as error: 

2034 error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" 

2035 raise 

2036 else: 

2037 text = self._render_buffer(self._buffer[:]) 

2038 try: 

2039 self.file.write(text) 

2040 except UnicodeEncodeError as error: 

2041 error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" 

2042 raise 

2043 

2044 self.file.flush() 

2045 del self._buffer[:] 

2046 

2047 def _render_buffer(self, buffer: Iterable[Segment]) -> str: 

2048 """Render buffered output, and clear buffer.""" 

2049 output: List[str] = [] 

2050 append = output.append 

2051 color_system = self._color_system 

2052 legacy_windows = self.legacy_windows 

2053 not_terminal = not self.is_terminal 

2054 if self.no_color and color_system: 

2055 buffer = Segment.remove_color(buffer) 

2056 for text, style, control in buffer: 

2057 if style: 

2058 append( 

2059 style.render( 

2060 text, 

2061 color_system=color_system, 

2062 legacy_windows=legacy_windows, 

2063 ) 

2064 ) 

2065 elif not (not_terminal and control): 

2066 append(text) 

2067 

2068 rendered = "".join(output) 

2069 return rendered 

2070 

2071 def input( 

2072 self, 

2073 prompt: TextType = "", 

2074 *, 

2075 markup: bool = True, 

2076 emoji: bool = True, 

2077 password: bool = False, 

2078 stream: Optional[TextIO] = None, 

2079 ) -> str: 

2080 """Displays a prompt and waits for input from the user. The prompt may contain color / style. 

2081 

2082 It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. 

2083 

2084 Args: 

2085 prompt (Union[str, Text]): Text to render in the prompt. 

2086 markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. 

2087 emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True. 

2088 password: (bool, optional): Hide typed text. Defaults to False. 

2089 stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None. 

2090 

2091 Returns: 

2092 str: Text read from stdin. 

2093 """ 

2094 if prompt: 

2095 self.print(prompt, markup=markup, emoji=emoji, end="") 

2096 if password: 

2097 result = getpass("", stream=stream) 

2098 else: 

2099 if stream: 

2100 result = stream.readline() 

2101 else: 

2102 result = input() 

2103 return result 

2104 

2105 def export_text(self, *, clear: bool = True, styles: bool = False) -> str: 

2106 """Generate text from console contents (requires record=True argument in constructor). 

2107 

2108 Args: 

2109 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2110 styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text. 

2111 Defaults to ``False``. 

2112 

2113 Returns: 

2114 str: String containing console contents. 

2115 

2116 """ 

2117 assert ( 

2118 self.record 

2119 ), "To export console contents set record=True in the constructor or instance" 

2120 

2121 with self._record_buffer_lock: 

2122 if styles: 

2123 text = "".join( 

2124 (style.render(text) if style else text) 

2125 for text, style, _ in self._record_buffer 

2126 ) 

2127 else: 

2128 text = "".join( 

2129 segment.text 

2130 for segment in self._record_buffer 

2131 if not segment.control 

2132 ) 

2133 if clear: 

2134 del self._record_buffer[:] 

2135 return text 

2136 

2137 def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None: 

2138 """Generate text from console and save to a given location (requires record=True argument in constructor). 

2139 

2140 Args: 

2141 path (str): Path to write text files. 

2142 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2143 styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text. 

2144 Defaults to ``False``. 

2145 

2146 """ 

2147 text = self.export_text(clear=clear, styles=styles) 

2148 with open(path, "wt", encoding="utf-8") as write_file: 

2149 write_file.write(text) 

2150 

2151 def export_html( 

2152 self, 

2153 *, 

2154 theme: Optional[TerminalTheme] = None, 

2155 clear: bool = True, 

2156 code_format: Optional[str] = None, 

2157 inline_styles: bool = False, 

2158 ) -> str: 

2159 """Generate HTML from console contents (requires record=True argument in constructor). 

2160 

2161 Args: 

2162 theme (TerminalTheme, optional): TerminalTheme object containing console colors. 

2163 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2164 code_format (str, optional): Format string to render HTML. In addition to '{foreground}', 

2165 '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. 

2166 inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files 

2167 larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. 

2168 Defaults to False. 

2169 

2170 Returns: 

2171 str: String containing console contents as HTML. 

2172 """ 

2173 assert ( 

2174 self.record 

2175 ), "To export console contents set record=True in the constructor or instance" 

2176 fragments: List[str] = [] 

2177 append = fragments.append 

2178 _theme = theme or DEFAULT_TERMINAL_THEME 

2179 stylesheet = "" 

2180 

2181 render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format 

2182 

2183 with self._record_buffer_lock: 

2184 if inline_styles: 

2185 for text, style, _ in Segment.filter_control( 

2186 Segment.simplify(self._record_buffer) 

2187 ): 

2188 text = escape(text) 

2189 if style: 

2190 rule = style.get_html_style(_theme) 

2191 if style.link: 

2192 text = f'<a href="{style.link}">{text}</a>' 

2193 text = f'<span style="{rule}">{text}</span>' if rule else text 

2194 append(text) 

2195 else: 

2196 styles: Dict[str, int] = {} 

2197 for text, style, _ in Segment.filter_control( 

2198 Segment.simplify(self._record_buffer) 

2199 ): 

2200 text = escape(text) 

2201 if style: 

2202 rule = style.get_html_style(_theme) 

2203 style_number = styles.setdefault(rule, len(styles) + 1) 

2204 if style.link: 

2205 text = f'<a class="r{style_number}" href="{style.link}">{text}</a>' 

2206 else: 

2207 text = f'<span class="r{style_number}">{text}</span>' 

2208 append(text) 

2209 stylesheet_rules: List[str] = [] 

2210 stylesheet_append = stylesheet_rules.append 

2211 for style_rule, style_number in styles.items(): 

2212 if style_rule: 

2213 stylesheet_append(f".r{style_number} {{{style_rule}}}") 

2214 stylesheet = "\n".join(stylesheet_rules) 

2215 

2216 rendered_code = render_code_format.format( 

2217 code="".join(fragments), 

2218 stylesheet=stylesheet, 

2219 foreground=_theme.foreground_color.hex, 

2220 background=_theme.background_color.hex, 

2221 ) 

2222 if clear: 

2223 del self._record_buffer[:] 

2224 return rendered_code 

2225 

2226 def save_html( 

2227 self, 

2228 path: str, 

2229 *, 

2230 theme: Optional[TerminalTheme] = None, 

2231 clear: bool = True, 

2232 code_format: str = CONSOLE_HTML_FORMAT, 

2233 inline_styles: bool = False, 

2234 ) -> None: 

2235 """Generate HTML from console contents and write to a file (requires record=True argument in constructor). 

2236 

2237 Args: 

2238 path (str): Path to write html file. 

2239 theme (TerminalTheme, optional): TerminalTheme object containing console colors. 

2240 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2241 code_format (str, optional): Format string to render HTML. In addition to '{foreground}', 

2242 '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. 

2243 inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files 

2244 larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. 

2245 Defaults to False. 

2246 

2247 """ 

2248 html = self.export_html( 

2249 theme=theme, 

2250 clear=clear, 

2251 code_format=code_format, 

2252 inline_styles=inline_styles, 

2253 ) 

2254 with open(path, "wt", encoding="utf-8") as write_file: 

2255 write_file.write(html) 

2256 

2257 def export_svg( 

2258 self, 

2259 *, 

2260 title: str = "Rich", 

2261 theme: Optional[TerminalTheme] = None, 

2262 clear: bool = True, 

2263 code_format: str = CONSOLE_SVG_FORMAT, 

2264 font_aspect_ratio: float = 0.61, 

2265 unique_id: Optional[str] = None, 

2266 ) -> str: 

2267 """ 

2268 Generate an SVG from the console contents (requires record=True in Console constructor). 

2269 

2270 Args: 

2271 title (str, optional): The title of the tab in the output image 

2272 theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal 

2273 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` 

2274 code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables 

2275 into the string in order to form the final SVG output. The default template used and the variables 

2276 injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. 

2277 font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` 

2278 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). 

2279 If you aren't specifying a different font inside ``code_format``, you probably don't need this. 

2280 unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node 

2281 ids). If not set, this defaults to a computed value based on the recorded content. 

2282 """ 

2283 

2284 from rich.cells import cell_len 

2285 

2286 style_cache: Dict[Style, str] = {} 

2287 

2288 def get_svg_style(style: Style) -> str: 

2289 """Convert a Style to CSS rules for SVG.""" 

2290 if style in style_cache: 

2291 return style_cache[style] 

2292 css_rules = [] 

2293 color = ( 

2294 _theme.foreground_color 

2295 if (style.color is None or style.color.is_default) 

2296 else style.color.get_truecolor(_theme) 

2297 ) 

2298 bgcolor = ( 

2299 _theme.background_color 

2300 if (style.bgcolor is None or style.bgcolor.is_default) 

2301 else style.bgcolor.get_truecolor(_theme) 

2302 ) 

2303 if style.reverse: 

2304 color, bgcolor = bgcolor, color 

2305 if style.dim: 

2306 color = blend_rgb(color, bgcolor, 0.4) 

2307 css_rules.append(f"fill: {color.hex}") 

2308 if style.bold: 

2309 css_rules.append("font-weight: bold") 

2310 if style.italic: 

2311 css_rules.append("font-style: italic;") 

2312 if style.underline: 

2313 css_rules.append("text-decoration: underline;") 

2314 if style.strike: 

2315 css_rules.append("text-decoration: line-through;") 

2316 

2317 css = ";".join(css_rules) 

2318 style_cache[style] = css 

2319 return css 

2320 

2321 _theme = theme or SVG_EXPORT_THEME 

2322 

2323 width = self.width 

2324 char_height = 20 

2325 char_width = char_height * font_aspect_ratio 

2326 line_height = char_height * 1.22 

2327 

2328 margin_top = 1 

2329 margin_right = 1 

2330 margin_bottom = 1 

2331 margin_left = 1 

2332 

2333 padding_top = 40 

2334 padding_right = 8 

2335 padding_bottom = 8 

2336 padding_left = 8 

2337 

2338 padding_width = padding_left + padding_right 

2339 padding_height = padding_top + padding_bottom 

2340 margin_width = margin_left + margin_right 

2341 margin_height = margin_top + margin_bottom 

2342 

2343 text_backgrounds: List[str] = [] 

2344 text_group: List[str] = [] 

2345 classes: Dict[str, int] = {} 

2346 style_no = 1 

2347 

2348 def escape_text(text: str) -> str: 

2349 """HTML escape text and replace spaces with nbsp.""" 

2350 return escape(text).replace(" ", "&#160;") 

2351 

2352 def make_tag( 

2353 name: str, content: Optional[str] = None, **attribs: object 

2354 ) -> str: 

2355 """Make a tag from name, content, and attributes.""" 

2356 

2357 def stringify(value: object) -> str: 

2358 if isinstance(value, (float)): 

2359 return format(value, "g") 

2360 return str(value) 

2361 

2362 tag_attribs = " ".join( 

2363 f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"' 

2364 for k, v in attribs.items() 

2365 ) 

2366 return ( 

2367 f"<{name} {tag_attribs}>{content}</{name}>" 

2368 if content 

2369 else f"<{name} {tag_attribs}/>" 

2370 ) 

2371 

2372 with self._record_buffer_lock: 

2373 segments = list(Segment.filter_control(self._record_buffer)) 

2374 if clear: 

2375 self._record_buffer.clear() 

2376 

2377 if unique_id is None: 

2378 unique_id = "terminal-" + str( 

2379 zlib.adler32( 

2380 ("".join(repr(segment) for segment in segments)).encode( 

2381 "utf-8", 

2382 "ignore", 

2383 ) 

2384 + title.encode("utf-8", "ignore") 

2385 ) 

2386 ) 

2387 y = 0 

2388 for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)): 

2389 x = 0 

2390 for text, style, _control in line: 

2391 style = style or Style() 

2392 rules = get_svg_style(style) 

2393 if rules not in classes: 

2394 classes[rules] = style_no 

2395 style_no += 1 

2396 class_name = f"r{classes[rules]}" 

2397 

2398 if style.reverse: 

2399 has_background = True 

2400 background = ( 

2401 _theme.foreground_color.hex 

2402 if style.color is None 

2403 else style.color.get_truecolor(_theme).hex 

2404 ) 

2405 else: 

2406 bgcolor = style.bgcolor 

2407 has_background = bgcolor is not None and not bgcolor.is_default 

2408 background = ( 

2409 _theme.background_color.hex 

2410 if style.bgcolor is None 

2411 else style.bgcolor.get_truecolor(_theme).hex 

2412 ) 

2413 

2414 text_length = cell_len(text) 

2415 if has_background: 

2416 text_backgrounds.append( 

2417 make_tag( 

2418 "rect", 

2419 fill=background, 

2420 x=x * char_width, 

2421 y=y * line_height + 1.5, 

2422 width=char_width * text_length, 

2423 height=line_height + 0.25, 

2424 shape_rendering="crispEdges", 

2425 ) 

2426 ) 

2427 

2428 if text != " " * len(text): 

2429 text_group.append( 

2430 make_tag( 

2431 "text", 

2432 escape_text(text), 

2433 _class=f"{unique_id}-{class_name}", 

2434 x=x * char_width, 

2435 y=y * line_height + char_height, 

2436 textLength=char_width * len(text), 

2437 clip_path=f"url(#{unique_id}-line-{y})", 

2438 ) 

2439 ) 

2440 x += cell_len(text) 

2441 

2442 line_offsets = [line_no * line_height + 1.5 for line_no in range(y)] 

2443 lines = "\n".join( 

2444 f"""<clipPath id="{unique_id}-line-{line_no}"> 

2445 {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)} 

2446 </clipPath>""" 

2447 for line_no, offset in enumerate(line_offsets) 

2448 ) 

2449 

2450 styles = "\n".join( 

2451 f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items() 

2452 ) 

2453 backgrounds = "".join(text_backgrounds) 

2454 matrix = "".join(text_group) 

2455 

2456 terminal_width = ceil(width * char_width + padding_width) 

2457 terminal_height = (y + 1) * line_height + padding_height 

2458 chrome = make_tag( 

2459 "rect", 

2460 fill=_theme.background_color.hex, 

2461 stroke="rgba(255,255,255,0.35)", 

2462 stroke_width="1", 

2463 x=margin_left, 

2464 y=margin_top, 

2465 width=terminal_width, 

2466 height=terminal_height, 

2467 rx=8, 

2468 ) 

2469 

2470 title_color = _theme.foreground_color.hex 

2471 if title: 

2472 chrome += make_tag( 

2473 "text", 

2474 escape_text(title), 

2475 _class=f"{unique_id}-title", 

2476 fill=title_color, 

2477 text_anchor="middle", 

2478 x=terminal_width // 2, 

2479 y=margin_top + char_height + 6, 

2480 ) 

2481 chrome += f""" 

2482 <g transform="translate(26,22)"> 

2483 <circle cx="0" cy="0" r="7" fill="#ff5f57"/> 

2484 <circle cx="22" cy="0" r="7" fill="#febc2e"/> 

2485 <circle cx="44" cy="0" r="7" fill="#28c840"/> 

2486 </g> 

2487 """ 

2488 

2489 svg = code_format.format( 

2490 unique_id=unique_id, 

2491 char_width=char_width, 

2492 char_height=char_height, 

2493 line_height=line_height, 

2494 terminal_width=char_width * width - 1, 

2495 terminal_height=(y + 1) * line_height - 1, 

2496 width=terminal_width + margin_width, 

2497 height=terminal_height + margin_height, 

2498 terminal_x=margin_left + padding_left, 

2499 terminal_y=margin_top + padding_top, 

2500 styles=styles, 

2501 chrome=chrome, 

2502 backgrounds=backgrounds, 

2503 matrix=matrix, 

2504 lines=lines, 

2505 ) 

2506 return svg 

2507 

2508 def save_svg( 

2509 self, 

2510 path: str, 

2511 *, 

2512 title: str = "Rich", 

2513 theme: Optional[TerminalTheme] = None, 

2514 clear: bool = True, 

2515 code_format: str = CONSOLE_SVG_FORMAT, 

2516 font_aspect_ratio: float = 0.61, 

2517 unique_id: Optional[str] = None, 

2518 ) -> None: 

2519 """Generate an SVG file from the console contents (requires record=True in Console constructor). 

2520 

2521 Args: 

2522 path (str): The path to write the SVG to. 

2523 title (str, optional): The title of the tab in the output image 

2524 theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal 

2525 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` 

2526 code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables 

2527 into the string in order to form the final SVG output. The default template used and the variables 

2528 injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. 

2529 font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` 

2530 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). 

2531 If you aren't specifying a different font inside ``code_format``, you probably don't need this. 

2532 unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node 

2533 ids). If not set, this defaults to a computed value based on the recorded content. 

2534 """ 

2535 svg = self.export_svg( 

2536 title=title, 

2537 theme=theme, 

2538 clear=clear, 

2539 code_format=code_format, 

2540 font_aspect_ratio=font_aspect_ratio, 

2541 unique_id=unique_id, 

2542 ) 

2543 with open(path, "wt", encoding="utf-8") as write_file: 

2544 write_file.write(svg) 

2545 

2546 

2547def _svg_hash(svg_main_code: str) -> str: 

2548 """Returns a unique hash for the given SVG main code. 

2549 

2550 Args: 

2551 svg_main_code (str): The content we're going to inject in the SVG envelope. 

2552 

2553 Returns: 

2554 str: a hash of the given content 

2555 """ 

2556 return str(zlib.adler32(svg_main_code.encode())) 

2557 

2558 

2559if __name__ == "__main__": # pragma: no cover 

2560 console = Console(record=True) 

2561 

2562 console.log( 

2563 "JSONRPC [i]request[/i]", 

2564 5, 

2565 1.3, 

2566 True, 

2567 False, 

2568 None, 

2569 { 

2570 "jsonrpc": "2.0", 

2571 "method": "subtract", 

2572 "params": {"minuend": 42, "subtrahend": 23}, 

2573 "id": 3, 

2574 }, 

2575 ) 

2576 

2577 console.log("Hello, World!", "{'a': 1}", repr(console)) 

2578 

2579 console.print( 

2580 { 

2581 "name": None, 

2582 "empty": [], 

2583 "quiz": { 

2584 "sport": { 

2585 "answered": True, 

2586 "q1": { 

2587 "question": "Which one is correct team name in NBA?", 

2588 "options": [ 

2589 "New York Bulls", 

2590 "Los Angeles Kings", 

2591 "Golden State Warriors", 

2592 "Huston Rocket", 

2593 ], 

2594 "answer": "Huston Rocket", 

2595 }, 

2596 }, 

2597 "maths": { 

2598 "answered": False, 

2599 "q1": { 

2600 "question": "5 + 7 = ?", 

2601 "options": [10, 11, 12, 13], 

2602 "answer": 12, 

2603 }, 

2604 "q2": { 

2605 "question": "12 - 8 = ?", 

2606 "options": [1, 2, 3, 4], 

2607 "answer": 4, 

2608 }, 

2609 }, 

2610 }, 

2611 } 

2612 )