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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

992 statements  

1import inspect 

2import os 

3import sys 

4import threading 

5import zlib 

6from abc import ABC, abstractmethod 

7from dataclasses import dataclass, field 

8from datetime import datetime 

9from functools import wraps 

10from getpass import getpass 

11from html import escape 

12from inspect import isclass 

13from itertools import islice 

14from math import ceil 

15from time import monotonic 

16from types import FrameType, ModuleType, TracebackType 

17from typing import ( 

18 IO, 

19 TYPE_CHECKING, 

20 Any, 

21 Callable, 

22 Dict, 

23 Iterable, 

24 List, 

25 Mapping, 

26 NamedTuple, 

27 Optional, 

28 TextIO, 

29 Tuple, 

30 Type, 

31 Union, 

32 cast, 

33) 

34 

35from rich._null_file import NULL_FILE 

36 

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

38 from typing import Literal, Protocol, runtime_checkable 

39else: 

40 from typing_extensions import ( 

41 Literal, 

42 Protocol, 

43 runtime_checkable, 

44 ) # pragma: no cover 

45 

46from . import errors, themes 

47from ._emoji_replace import _emoji_replace 

48from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT 

49from ._fileno import get_fileno 

50from ._log_render import FormatTimeCallable, LogRender 

51from .align import Align, AlignMethod 

52from .color import ColorSystem, blend_rgb 

53from .control import Control 

54from .emoji import EmojiVariant 

55from .highlighter import NullHighlighter, ReprHighlighter 

56from .markup import render as render_markup 

57from .measure import Measurement, measure_renderables 

58from .pager import Pager, SystemPager 

59from .pretty import Pretty, is_expandable 

60from .protocol import rich_cast 

61from .region import Region 

62from .scope import render_scope 

63from .screen import Screen 

64from .segment import Segment 

65from .style import Style, StyleType 

66from .styled import Styled 

67from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme 

68from .text import Text, TextType 

69from .theme import Theme, ThemeStack 

70 

71if TYPE_CHECKING: 

72 from ._windows import WindowsConsoleFeatures 

73 from .live import Live 

74 from .status import Status 

75 

76JUPYTER_DEFAULT_COLUMNS = 115 

77JUPYTER_DEFAULT_LINES = 100 

78WINDOWS = sys.platform == "win32" 

79 

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

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

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

83 

84 

85class NoChange: 

86 pass 

87 

88 

89NO_CHANGE = NoChange() 

90 

91try: 

92 _STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr] 

93except Exception: 

94 _STDIN_FILENO = 0 

95try: 

96 _STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr] 

97except Exception: 

98 _STDOUT_FILENO = 1 

99try: 

100 _STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr] 

101except Exception: 

102 _STDERR_FILENO = 2 

103 

104_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) 

105_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) 

106 

107 

108_TERM_COLORS = { 

109 "kitty": ColorSystem.EIGHT_BIT, 

110 "256color": ColorSystem.EIGHT_BIT, 

111 "16color": ColorSystem.STANDARD, 

112} 

113 

114 

115class ConsoleDimensions(NamedTuple): 

116 """Size of the terminal.""" 

117 

118 width: int 

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

120 height: int 

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

122 

123 

124@dataclass 

125class ConsoleOptions: 

126 """Options for __rich_console__ method.""" 

127 

128 size: ConsoleDimensions 

129 """Size of console.""" 

130 legacy_windows: bool 

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

132 min_width: int 

133 """Minimum width of renderable.""" 

134 max_width: int 

135 """Maximum width of renderable.""" 

136 is_terminal: bool 

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

138 encoding: str 

139 """Encoding of terminal.""" 

140 max_height: int 

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

142 justify: Optional[JustifyMethod] = None 

143 """Justify value override for renderable.""" 

144 overflow: Optional[OverflowMethod] = None 

145 """Overflow value override for renderable.""" 

146 no_wrap: Optional[bool] = False 

147 """Disable wrapping for text.""" 

148 highlight: Optional[bool] = None 

149 """Highlight override for render_str.""" 

150 markup: Optional[bool] = None 

151 """Enable markup when rendering strings.""" 

152 height: Optional[int] = None 

153 

154 @property 

155 def ascii_only(self) -> bool: 

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

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

158 

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

160 """Return a copy of the options. 

161 

162 Returns: 

163 ConsoleOptions: a copy of self. 

164 """ 

165 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) 

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

167 return options 

168 

169 def update( 

170 self, 

171 *, 

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

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

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

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

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

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

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

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

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

181 ) -> "ConsoleOptions": 

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

183 options = self.copy() 

184 if not isinstance(width, NoChange): 

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

186 if not isinstance(min_width, NoChange): 

187 options.min_width = min_width 

188 if not isinstance(max_width, NoChange): 

189 options.max_width = max_width 

190 if not isinstance(justify, NoChange): 

191 options.justify = justify 

192 if not isinstance(overflow, NoChange): 

193 options.overflow = overflow 

194 if not isinstance(no_wrap, NoChange): 

195 options.no_wrap = no_wrap 

196 if not isinstance(highlight, NoChange): 

197 options.highlight = highlight 

198 if not isinstance(markup, NoChange): 

199 options.markup = markup 

200 if not isinstance(height, NoChange): 

201 if height is not None: 

202 options.max_height = height 

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

204 return options 

205 

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

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

208 

209 Args: 

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

211 

212 Returns: 

213 ~ConsoleOptions: New console options instance. 

214 """ 

215 options = self.copy() 

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

217 return options 

218 

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

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

221 

222 Args: 

223 height (int): New height 

224 

225 Returns: 

226 ~ConsoleOptions: New Console options instance. 

227 """ 

228 options = self.copy() 

229 options.max_height = options.height = height 

230 return options 

231 

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

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

234 

235 Returns: 

236 ~ConsoleOptions: New console options instance. 

237 """ 

238 options = self.copy() 

239 options.height = None 

240 return options 

241 

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

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

244 

245 Args: 

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

247 height (int): New height. 

248 

249 Returns: 

250 ~ConsoleOptions: New console options instance. 

251 """ 

252 options = self.copy() 

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

254 options.height = options.max_height = height 

255 return options 

256 

257 

258@runtime_checkable 

259class RichCast(Protocol): 

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

261 

262 def __rich__( 

263 self, 

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

265 ... 

266 

267 

268@runtime_checkable 

269class ConsoleRenderable(Protocol): 

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

271 

272 def __rich_console__( 

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

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

275 ... 

276 

277 

278# A type that may be rendered by Console. 

279RenderableType = Union[ConsoleRenderable, RichCast, str] 

280"""A string or any object that may be rendered by Rich.""" 

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 

715 self._file = file 

716 self.quiet = quiet 

717 self.stderr = stderr 

718 

719 if color_system is None: 

720 self._color_system = None 

721 elif color_system == "auto": 

722 self._color_system = self._detect_color_system() 

723 else: 

724 self._color_system = COLOR_SYSTEMS[color_system] 

725 

726 self._lock = threading.RLock() 

727 self._log_render = LogRender( 

728 show_time=log_time, 

729 show_path=log_path, 

730 time_format=log_time_format, 

731 ) 

732 self.highlighter: HighlighterType = highlighter or _null_highlighter 

733 self.safe_box = safe_box 

734 self.get_datetime = get_datetime or datetime.now 

735 self.get_time = get_time or monotonic 

736 self.style = style 

737 self.no_color = ( 

738 no_color 

739 if no_color is not None 

740 else self._environ.get("NO_COLOR", "") != "" 

741 ) 

742 self.is_interactive = ( 

743 (self.is_terminal and not self.is_dumb_terminal) 

744 if force_interactive is None 

745 else force_interactive 

746 ) 

747 

748 self._record_buffer_lock = threading.RLock() 

749 self._thread_locals = ConsoleThreadLocals( 

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

751 ) 

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

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

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

755 self._is_alt_screen = False 

756 

757 def __repr__(self) -> str: 

758 return f"<console width={self.width} {self._color_system!s}>" 

759 

760 @property 

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

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

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

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

765 if file is None: 

766 file = NULL_FILE 

767 return file 

768 

769 @file.setter 

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

771 """Set a new file object.""" 

772 self._file = new_file 

773 

774 @property 

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

776 """Get a thread local buffer.""" 

777 return self._thread_locals.buffer 

778 

779 @property 

780 def _buffer_index(self) -> int: 

781 """Get a thread local buffer.""" 

782 return self._thread_locals.buffer_index 

783 

784 @_buffer_index.setter 

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

786 self._thread_locals.buffer_index = value 

787 

788 @property 

789 def _theme_stack(self) -> ThemeStack: 

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

791 return self._thread_locals.theme_stack 

792 

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

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

795 if self.is_jupyter: 

796 return ColorSystem.TRUECOLOR 

797 if not self.is_terminal or self.is_dumb_terminal: 

798 return None 

799 if WINDOWS: # pragma: no cover 

800 if self.legacy_windows: # pragma: no cover 

801 return ColorSystem.WINDOWS 

802 windows_console_features = get_windows_console_features() 

803 return ( 

804 ColorSystem.TRUECOLOR 

805 if windows_console_features.truecolor 

806 else ColorSystem.EIGHT_BIT 

807 ) 

808 else: 

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

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

811 return ColorSystem.TRUECOLOR 

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

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

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

815 return color_system 

816 

817 def _enter_buffer(self) -> None: 

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

819 self._buffer_index += 1 

820 

821 def _exit_buffer(self) -> None: 

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

823 self._buffer_index -= 1 

824 self._check_buffer() 

825 

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

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

828 

829 Args: 

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

831 

832 Raises: 

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

834 """ 

835 with self._lock: 

836 if self._live is not None: 

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

838 self._live = live 

839 

840 def clear_live(self) -> None: 

841 """Clear the Live instance.""" 

842 with self._lock: 

843 self._live = None 

844 

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

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

847 

848 Args: 

849 hook (RenderHook): Render hook instance. 

850 """ 

851 with self._lock: 

852 self._render_hooks.append(hook) 

853 

854 def pop_render_hook(self) -> None: 

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

856 with self._lock: 

857 self._render_hooks.pop() 

858 

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

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

861 self._enter_buffer() 

862 return self 

863 

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

865 """Exit buffer context.""" 

866 self._exit_buffer() 

867 

868 def begin_capture(self) -> None: 

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

870 self._enter_buffer() 

871 

872 def end_capture(self) -> str: 

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

874 

875 Returns: 

876 str: Console output. 

877 """ 

878 render_result = self._render_buffer(self._buffer) 

879 del self._buffer[:] 

880 self._exit_buffer() 

881 return render_result 

882 

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

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

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

886 than calling this method directly. 

887 

888 Args: 

889 theme (Theme): A theme instance. 

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

891 """ 

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

893 

894 def pop_theme(self) -> None: 

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

896 self._theme_stack.pop_theme() 

897 

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

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

900 

901 Args: 

902 theme (Theme): Theme instance to user. 

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

904 

905 Returns: 

906 ThemeContext: [description] 

907 """ 

908 return ThemeContext(self, theme, inherit) 

909 

910 @property 

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

912 """Get color system string. 

913 

914 Returns: 

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

916 """ 

917 

918 if self._color_system is not None: 

919 return _COLOR_SYSTEMS_NAMES[self._color_system] 

920 else: 

921 return None 

922 

923 @property 

924 def encoding(self) -> str: 

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

926 

927 Returns: 

928 str: A standard encoding string. 

929 """ 

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

931 

932 @property 

933 def is_terminal(self) -> bool: 

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

935 

936 Returns: 

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

938 understanding escape sequences, otherwise False. 

939 """ 

940 # If dev has explicitly set this value, return it 

941 if self._force_terminal is not None: 

942 return self._force_terminal 

943 

944 # Fudge for Idle 

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

946 "idlelib" 

947 ): 

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

949 return False 

950 

951 if self.is_jupyter: 

952 # return False for Jupyter, which may have FORCE_COLOR set 

953 return False 

954 

955 environ = self._environ 

956 

957 tty_compatible = environ.get("TTY_COMPATIBLE", "") 

958 # 0 indicates device is not tty compatible 

959 if tty_compatible == "0": 

960 return False 

961 # 1 indicates device is tty compatible 

962 if tty_compatible == "1": 

963 return True 

964 

965 # https://force-color.org/ 

966 force_color = environ.get("FORCE_COLOR") 

967 if force_color is not None: 

968 return force_color != "" 

969 

970 # Any other value defaults to auto detect 

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

972 try: 

973 return False if isatty is None else isatty() 

974 except ValueError: 

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

976 # ValueError: I/O operation on closed file 

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

978 return False 

979 

980 @property 

981 def is_dumb_terminal(self) -> bool: 

982 """Detect dumb terminal. 

983 

984 Returns: 

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

986 

987 """ 

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

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

990 return self.is_terminal and is_dumb 

991 

992 @property 

993 def options(self) -> ConsoleOptions: 

994 """Get default console options.""" 

995 return ConsoleOptions( 

996 max_height=self.size.height, 

997 size=self.size, 

998 legacy_windows=self.legacy_windows, 

999 min_width=1, 

1000 max_width=self.width, 

1001 encoding=self.encoding, 

1002 is_terminal=self.is_terminal, 

1003 ) 

1004 

1005 @property 

1006 def size(self) -> ConsoleDimensions: 

1007 """Get the size of the console. 

1008 

1009 Returns: 

1010 ConsoleDimensions: A named tuple containing the dimensions. 

1011 """ 

1012 

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

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

1015 

1016 if self.is_dumb_terminal: 

1017 return ConsoleDimensions(80, 25) 

1018 

1019 width: Optional[int] = None 

1020 height: Optional[int] = None 

1021 

1022 streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS 

1023 for file_descriptor in streams: 

1024 try: 

1025 width, height = os.get_terminal_size(file_descriptor) 

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

1027 pass 

1028 else: 

1029 break 

1030 

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

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

1033 width = int(columns) 

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

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

1036 height = int(lines) 

1037 

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

1039 width = width or 80 

1040 height = height or 25 

1041 return ConsoleDimensions( 

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

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

1044 ) 

1045 

1046 @size.setter 

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

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

1049 

1050 Args: 

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

1052 """ 

1053 width, height = new_size 

1054 self._width = width 

1055 self._height = height 

1056 

1057 @property 

1058 def width(self) -> int: 

1059 """Get the width of the console. 

1060 

1061 Returns: 

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

1063 """ 

1064 return self.size.width 

1065 

1066 @width.setter 

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

1068 """Set width. 

1069 

1070 Args: 

1071 width (int): New width. 

1072 """ 

1073 self._width = width 

1074 

1075 @property 

1076 def height(self) -> int: 

1077 """Get the height of the console. 

1078 

1079 Returns: 

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

1081 """ 

1082 return self.size.height 

1083 

1084 @height.setter 

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

1086 """Set height. 

1087 

1088 Args: 

1089 height (int): new height. 

1090 """ 

1091 self._height = height 

1092 

1093 def bell(self) -> None: 

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

1095 self.control(Control.bell()) 

1096 

1097 def capture(self) -> Capture: 

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

1099 rather than writing it to the console. 

1100 

1101 Example: 

1102 >>> from rich.console import Console 

1103 >>> console = Console() 

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

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

1106 >>> print(capture.get()) 

1107 

1108 Returns: 

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

1110 """ 

1111 capture = Capture(self) 

1112 return capture 

1113 

1114 def pager( 

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

1116 ) -> PagerContext: 

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

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

1119 

1120 Args: 

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

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

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

1124 

1125 Example: 

1126 >>> from rich.console import Console 

1127 >>> from rich.__main__ import make_test_card 

1128 >>> console = Console() 

1129 >>> with console.pager(): 

1130 console.print(make_test_card()) 

1131 

1132 Returns: 

1133 PagerContext: A context manager. 

1134 """ 

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

1136 

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

1138 """Write new line(s). 

1139 

1140 Args: 

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

1142 """ 

1143 

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

1145 self.print(NewLine(count)) 

1146 

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

1148 """Clear the screen. 

1149 

1150 Args: 

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

1152 """ 

1153 if home: 

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

1155 else: 

1156 self.control(Control.clear()) 

1157 

1158 def status( 

1159 self, 

1160 status: RenderableType, 

1161 *, 

1162 spinner: str = "dots", 

1163 spinner_style: StyleType = "status.spinner", 

1164 speed: float = 1.0, 

1165 refresh_per_second: float = 12.5, 

1166 ) -> "Status": 

1167 """Display a status and spinner. 

1168 

1169 Args: 

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

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

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

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

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

1175 

1176 Returns: 

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

1178 """ 

1179 from .status import Status 

1180 

1181 status_renderable = Status( 

1182 status, 

1183 console=self, 

1184 spinner=spinner, 

1185 spinner_style=spinner_style, 

1186 speed=speed, 

1187 refresh_per_second=refresh_per_second, 

1188 ) 

1189 return status_renderable 

1190 

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

1192 """Show or hide the cursor. 

1193 

1194 Args: 

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

1196 """ 

1197 if self.is_terminal: 

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

1199 return True 

1200 return False 

1201 

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

1203 """Enables alternative screen mode. 

1204 

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

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

1207 that handles this for you. 

1208 

1209 Args: 

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

1211 

1212 Returns: 

1213 bool: True if the control codes were written. 

1214 

1215 """ 

1216 changed = False 

1217 if self.is_terminal and not self.legacy_windows: 

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

1219 changed = True 

1220 self._is_alt_screen = enable 

1221 return changed 

1222 

1223 @property 

1224 def is_alt_screen(self) -> bool: 

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

1226 

1227 Returns: 

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

1229 """ 

1230 return self._is_alt_screen 

1231 

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

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

1234 

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

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

1237 exits. 

1238 

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

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

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

1242 

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

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

1245 

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

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

1248 using this method being overwritten. 

1249 

1250 Args: 

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

1252 

1253 Returns: 

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

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

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

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

1258 """ 

1259 if self.is_terminal: 

1260 self.control(Control.title(title)) 

1261 return True 

1262 return False 

1263 

1264 def screen( 

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

1266 ) -> "ScreenContext": 

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

1268 

1269 Args: 

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

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

1272 

1273 Returns: 

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

1275 """ 

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

1277 

1278 def measure( 

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

1280 ) -> Measurement: 

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

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

1283 

1284 Args: 

1285 renderable (RenderableType): Any renderable or string. 

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

1287 to use default options. Defaults to None. 

1288 

1289 Returns: 

1290 Measurement: A measurement of the renderable. 

1291 """ 

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

1293 return measurement 

1294 

1295 def render( 

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

1297 ) -> Iterable[Segment]: 

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

1299 

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

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

1302 

1303 Args: 

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

1305 an object that may be converted to a string. 

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

1307 

1308 Returns: 

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

1310 """ 

1311 

1312 _options = options or self.options 

1313 if _options.max_width < 1: 

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

1315 return 

1316 render_iterable: RenderResult 

1317 

1318 renderable = rich_cast(renderable) 

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

1320 render_iterable = renderable.__rich_console__(self, _options) 

1321 elif isinstance(renderable, str): 

1322 text_renderable = self.render_str( 

1323 renderable, highlight=_options.highlight, markup=_options.markup 

1324 ) 

1325 render_iterable = text_renderable.__rich_console__(self, _options) 

1326 else: 

1327 raise errors.NotRenderableError( 

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

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

1330 ) 

1331 

1332 try: 

1333 iter_render = iter(render_iterable) 

1334 except TypeError: 

1335 raise errors.NotRenderableError( 

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

1337 ) 

1338 _Segment = Segment 

1339 _options = _options.reset_height() 

1340 for render_output in iter_render: 

1341 if isinstance(render_output, _Segment): 

1342 yield render_output 

1343 else: 

1344 yield from self.render(render_output, _options) 

1345 

1346 def render_lines( 

1347 self, 

1348 renderable: RenderableType, 

1349 options: Optional[ConsoleOptions] = None, 

1350 *, 

1351 style: Optional[Style] = None, 

1352 pad: bool = True, 

1353 new_lines: bool = False, 

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

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

1356 

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

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

1359 

1360 Args: 

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

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

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

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

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

1366 

1367 Returns: 

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

1369 """ 

1370 with self._lock: 

1371 render_options = options or self.options 

1372 _rendered = self.render(renderable, render_options) 

1373 if style: 

1374 _rendered = Segment.apply_style(_rendered, style) 

1375 

1376 render_height = render_options.height 

1377 if render_height is not None: 

1378 render_height = max(0, render_height) 

1379 

1380 lines = list( 

1381 islice( 

1382 Segment.split_and_crop_lines( 

1383 _rendered, 

1384 render_options.max_width, 

1385 include_new_lines=new_lines, 

1386 pad=pad, 

1387 style=style, 

1388 ), 

1389 None, 

1390 render_height, 

1391 ) 

1392 ) 

1393 if render_options.height is not None: 

1394 extra_lines = render_options.height - len(lines) 

1395 if extra_lines > 0: 

1396 pad_line = [ 

1397 ( 

1398 [ 

1399 Segment(" " * render_options.max_width, style), 

1400 Segment("\n"), 

1401 ] 

1402 if new_lines 

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

1404 ) 

1405 ] 

1406 lines.extend(pad_line * extra_lines) 

1407 

1408 return lines 

1409 

1410 def render_str( 

1411 self, 

1412 text: str, 

1413 *, 

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

1415 justify: Optional[JustifyMethod] = None, 

1416 overflow: Optional[OverflowMethod] = None, 

1417 emoji: Optional[bool] = None, 

1418 markup: Optional[bool] = None, 

1419 highlight: Optional[bool] = None, 

1420 highlighter: Optional[HighlighterType] = None, 

1421 ) -> "Text": 

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

1423 you print or log a string. 

1424 

1425 Args: 

1426 text (str): Text to render. 

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

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

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

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

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

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

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

1434 Returns: 

1435 ConsoleRenderable: Renderable object. 

1436 

1437 """ 

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

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

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

1441 

1442 if markup_enabled: 

1443 rich_text = render_markup( 

1444 text, 

1445 style=style, 

1446 emoji=emoji_enabled, 

1447 emoji_variant=self._emoji_variant, 

1448 ) 

1449 rich_text.justify = justify 

1450 rich_text.overflow = overflow 

1451 else: 

1452 rich_text = Text( 

1453 ( 

1454 _emoji_replace(text, default_variant=self._emoji_variant) 

1455 if emoji_enabled 

1456 else text 

1457 ), 

1458 justify=justify, 

1459 overflow=overflow, 

1460 style=style, 

1461 ) 

1462 

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

1464 if _highlighter is not None: 

1465 highlight_text = _highlighter(str(rich_text)) 

1466 highlight_text.copy_styles(rich_text) 

1467 return highlight_text 

1468 

1469 return rich_text 

1470 

1471 def get_style( 

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

1473 ) -> Style: 

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

1475 

1476 Args: 

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

1478 

1479 Returns: 

1480 Style: A Style object. 

1481 

1482 Raises: 

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

1484 

1485 """ 

1486 if isinstance(name, Style): 

1487 return name 

1488 

1489 try: 

1490 style = self._theme_stack.get(name) 

1491 if style is None: 

1492 style = Style.parse(name) 

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

1494 except errors.StyleSyntaxError as error: 

1495 if default is not None: 

1496 return self.get_style(default) 

1497 raise errors.MissingStyle( 

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

1499 ) from None 

1500 

1501 def _collect_renderables( 

1502 self, 

1503 objects: Iterable[Any], 

1504 sep: str, 

1505 end: str, 

1506 *, 

1507 justify: Optional[JustifyMethod] = None, 

1508 emoji: Optional[bool] = None, 

1509 markup: Optional[bool] = None, 

1510 highlight: Optional[bool] = None, 

1511 ) -> List[ConsoleRenderable]: 

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

1513 

1514 Args: 

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

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

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

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

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

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

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

1522 

1523 Returns: 

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

1525 """ 

1526 renderables: List[ConsoleRenderable] = [] 

1527 _append = renderables.append 

1528 text: List[Text] = [] 

1529 append_text = text.append 

1530 

1531 append = _append 

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

1533 

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

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

1536 

1537 append = align_append 

1538 

1539 _highlighter: HighlighterType = _null_highlighter 

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

1541 _highlighter = self.highlighter 

1542 

1543 def check_text() -> None: 

1544 if text: 

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

1546 append(sep_text.join(text)) 

1547 text.clear() 

1548 

1549 for renderable in objects: 

1550 renderable = rich_cast(renderable) 

1551 if isinstance(renderable, str): 

1552 append_text( 

1553 self.render_str( 

1554 renderable, 

1555 emoji=emoji, 

1556 markup=markup, 

1557 highlight=highlight, 

1558 highlighter=_highlighter, 

1559 ) 

1560 ) 

1561 elif isinstance(renderable, Text): 

1562 append_text(renderable) 

1563 elif isinstance(renderable, ConsoleRenderable): 

1564 check_text() 

1565 append(renderable) 

1566 elif is_expandable(renderable): 

1567 check_text() 

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

1569 else: 

1570 append_text(_highlighter(str(renderable))) 

1571 

1572 check_text() 

1573 

1574 if self.style is not None: 

1575 style = self.get_style(self.style) 

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

1577 

1578 return renderables 

1579 

1580 def rule( 

1581 self, 

1582 title: TextType = "", 

1583 *, 

1584 characters: str = "─", 

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

1586 align: AlignMethod = "center", 

1587 ) -> None: 

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

1589 

1590 Args: 

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

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

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

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

1595 """ 

1596 from .rule import Rule 

1597 

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

1599 self.print(rule) 

1600 

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

1602 """Insert non-printing control codes. 

1603 

1604 Args: 

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

1606 """ 

1607 if not self.is_dumb_terminal: 

1608 with self: 

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

1610 

1611 def out( 

1612 self, 

1613 *objects: Any, 

1614 sep: str = " ", 

1615 end: str = "\n", 

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

1617 highlight: Optional[bool] = None, 

1618 ) -> None: 

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

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

1621 optionally apply highlighting and a basic style. 

1622 

1623 Args: 

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

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

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

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

1628 console default. Defaults to ``None``. 

1629 """ 

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

1631 self.print( 

1632 raw_output, 

1633 style=style, 

1634 highlight=highlight, 

1635 emoji=False, 

1636 markup=False, 

1637 no_wrap=True, 

1638 overflow="ignore", 

1639 crop=False, 

1640 end=end, 

1641 ) 

1642 

1643 def print( 

1644 self, 

1645 *objects: Any, 

1646 sep: str = " ", 

1647 end: str = "\n", 

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

1649 justify: Optional[JustifyMethod] = None, 

1650 overflow: Optional[OverflowMethod] = None, 

1651 no_wrap: Optional[bool] = None, 

1652 emoji: Optional[bool] = None, 

1653 markup: Optional[bool] = None, 

1654 highlight: Optional[bool] = None, 

1655 width: Optional[int] = None, 

1656 height: Optional[int] = None, 

1657 crop: bool = True, 

1658 soft_wrap: Optional[bool] = None, 

1659 new_line_start: bool = False, 

1660 ) -> None: 

1661 """Print to the console. 

1662 

1663 Args: 

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

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

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

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

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

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

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

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

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

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

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

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

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

1677 Console default. Defaults to ``None``. 

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

1679 """ 

1680 if not objects: 

1681 objects = (NewLine(),) 

1682 

1683 if soft_wrap is None: 

1684 soft_wrap = self.soft_wrap 

1685 if soft_wrap: 

1686 if no_wrap is None: 

1687 no_wrap = True 

1688 if overflow is None: 

1689 overflow = "ignore" 

1690 crop = False 

1691 render_hooks = self._render_hooks[:] 

1692 with self: 

1693 renderables = self._collect_renderables( 

1694 objects, 

1695 sep, 

1696 end, 

1697 justify=justify, 

1698 emoji=emoji, 

1699 markup=markup, 

1700 highlight=highlight, 

1701 ) 

1702 for hook in render_hooks: 

1703 renderables = hook.process_renderables(renderables) 

1704 render_options = self.options.update( 

1705 justify=justify, 

1706 overflow=overflow, 

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

1708 height=height, 

1709 no_wrap=no_wrap, 

1710 markup=markup, 

1711 highlight=highlight, 

1712 ) 

1713 

1714 new_segments: List[Segment] = [] 

1715 extend = new_segments.extend 

1716 render = self.render 

1717 if style is None: 

1718 for renderable in renderables: 

1719 extend(render(renderable, render_options)) 

1720 else: 

1721 for renderable in renderables: 

1722 extend( 

1723 Segment.apply_style( 

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

1725 ) 

1726 ) 

1727 if new_line_start: 

1728 if ( 

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

1730 > 1 

1731 ): 

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

1733 if crop: 

1734 buffer_extend = self._buffer.extend 

1735 for line in Segment.split_and_crop_lines( 

1736 new_segments, self.width, pad=False 

1737 ): 

1738 buffer_extend(line) 

1739 else: 

1740 self._buffer.extend(new_segments) 

1741 

1742 def print_json( 

1743 self, 

1744 json: Optional[str] = None, 

1745 *, 

1746 data: Any = None, 

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

1748 highlight: bool = True, 

1749 skip_keys: bool = False, 

1750 ensure_ascii: bool = False, 

1751 check_circular: bool = True, 

1752 allow_nan: bool = True, 

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

1754 sort_keys: bool = False, 

1755 ) -> None: 

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

1757 

1758 Args: 

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

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

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

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

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

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

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

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

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

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

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

1770 """ 

1771 from rich.json import JSON 

1772 

1773 if json is None: 

1774 json_renderable = JSON.from_data( 

1775 data, 

1776 indent=indent, 

1777 highlight=highlight, 

1778 skip_keys=skip_keys, 

1779 ensure_ascii=ensure_ascii, 

1780 check_circular=check_circular, 

1781 allow_nan=allow_nan, 

1782 default=default, 

1783 sort_keys=sort_keys, 

1784 ) 

1785 else: 

1786 if not isinstance(json, str): 

1787 raise TypeError( 

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

1789 ) 

1790 json_renderable = JSON( 

1791 json, 

1792 indent=indent, 

1793 highlight=highlight, 

1794 skip_keys=skip_keys, 

1795 ensure_ascii=ensure_ascii, 

1796 check_circular=check_circular, 

1797 allow_nan=allow_nan, 

1798 default=default, 

1799 sort_keys=sort_keys, 

1800 ) 

1801 self.print(json_renderable, soft_wrap=True) 

1802 

1803 def update_screen( 

1804 self, 

1805 renderable: RenderableType, 

1806 *, 

1807 region: Optional[Region] = None, 

1808 options: Optional[ConsoleOptions] = None, 

1809 ) -> None: 

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

1811 

1812 Args: 

1813 renderable (RenderableType): A Rich renderable. 

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

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

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

1817 

1818 Raises: 

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

1820 

1821 """ 

1822 if not self.is_alt_screen: 

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

1824 render_options = options or self.options 

1825 if region is None: 

1826 x = y = 0 

1827 render_options = render_options.update_dimensions( 

1828 render_options.max_width, render_options.height or self.height 

1829 ) 

1830 else: 

1831 x, y, width, height = region 

1832 render_options = render_options.update_dimensions(width, height) 

1833 

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

1835 self.update_screen_lines(lines, x, y) 

1836 

1837 def update_screen_lines( 

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

1839 ) -> None: 

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

1841 

1842 Args: 

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

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

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

1846 

1847 Raises: 

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

1849 """ 

1850 if not self.is_alt_screen: 

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

1852 screen_update = ScreenUpdate(lines, x, y) 

1853 segments = self.render(screen_update) 

1854 self._buffer.extend(segments) 

1855 self._check_buffer() 

1856 

1857 def print_exception( 

1858 self, 

1859 *, 

1860 width: Optional[int] = 100, 

1861 extra_lines: int = 3, 

1862 theme: Optional[str] = None, 

1863 word_wrap: bool = False, 

1864 show_locals: bool = False, 

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

1866 max_frames: int = 100, 

1867 ) -> None: 

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

1869 

1870 Args: 

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

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

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

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

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

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

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

1878 """ 

1879 from .traceback import Traceback 

1880 

1881 traceback = Traceback( 

1882 width=width, 

1883 extra_lines=extra_lines, 

1884 theme=theme, 

1885 word_wrap=word_wrap, 

1886 show_locals=show_locals, 

1887 suppress=suppress, 

1888 max_frames=max_frames, 

1889 ) 

1890 self.print(traceback) 

1891 

1892 @staticmethod 

1893 def _caller_frame_info( 

1894 offset: int, 

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

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

1897 """Get caller frame information. 

1898 

1899 Args: 

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

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

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

1903 

1904 Returns: 

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

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

1907 

1908 Raises: 

1909 RuntimeError: If the stack offset is invalid. 

1910 """ 

1911 # Ignore the frame of this local helper 

1912 offset += 1 

1913 

1914 frame = currentframe() 

1915 if frame is not None: 

1916 # Use the faster currentframe where implemented 

1917 while offset and frame is not None: 

1918 frame = frame.f_back 

1919 offset -= 1 

1920 assert frame is not None 

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

1922 else: 

1923 # Fallback to the slower stack 

1924 frame_info = inspect.stack()[offset] 

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

1926 

1927 def log( 

1928 self, 

1929 *objects: Any, 

1930 sep: str = " ", 

1931 end: str = "\n", 

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

1933 justify: Optional[JustifyMethod] = None, 

1934 emoji: Optional[bool] = None, 

1935 markup: Optional[bool] = None, 

1936 highlight: Optional[bool] = None, 

1937 log_locals: bool = False, 

1938 _stack_offset: int = 1, 

1939 ) -> None: 

1940 """Log rich content to the terminal. 

1941 

1942 Args: 

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

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

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

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

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

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

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

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

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

1952 was called. Defaults to False. 

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

1954 """ 

1955 if not objects: 

1956 objects = (NewLine(),) 

1957 

1958 render_hooks = self._render_hooks[:] 

1959 

1960 with self: 

1961 renderables = self._collect_renderables( 

1962 objects, 

1963 sep, 

1964 end, 

1965 justify=justify, 

1966 emoji=emoji, 

1967 markup=markup, 

1968 highlight=highlight, 

1969 ) 

1970 if style is not None: 

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

1972 

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

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

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

1976 if log_locals: 

1977 locals_map = { 

1978 key: value 

1979 for key, value in locals.items() 

1980 if not key.startswith("__") 

1981 } 

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

1983 

1984 renderables = [ 

1985 self._log_render( 

1986 self, 

1987 renderables, 

1988 log_time=self.get_datetime(), 

1989 path=path, 

1990 line_no=line_no, 

1991 link_path=link_path, 

1992 ) 

1993 ] 

1994 for hook in render_hooks: 

1995 renderables = hook.process_renderables(renderables) 

1996 new_segments: List[Segment] = [] 

1997 extend = new_segments.extend 

1998 render = self.render 

1999 render_options = self.options 

2000 for renderable in renderables: 

2001 extend(render(renderable, render_options)) 

2002 buffer_extend = self._buffer.extend 

2003 for line in Segment.split_and_crop_lines( 

2004 new_segments, self.width, pad=False 

2005 ): 

2006 buffer_extend(line) 

2007 

2008 def on_broken_pipe(self) -> None: 

2009 """This function is called when a `BrokenPipeError` is raised. 

2010 

2011 This can occur when piping Textual output in Linux and macOS. 

2012 The default implementation is to exit the app, but you could implement 

2013 this method in a subclass to change the behavior. 

2014 

2015 See https://docs.python.org/3/library/signal.html#note-on-sigpipe for details. 

2016 """ 

2017 self.quiet = True 

2018 devnull = os.open(os.devnull, os.O_WRONLY) 

2019 os.dup2(devnull, sys.stdout.fileno()) 

2020 raise SystemExit(1) 

2021 

2022 def _check_buffer(self) -> None: 

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

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

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

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

2027 """ 

2028 if self.quiet: 

2029 del self._buffer[:] 

2030 return 

2031 

2032 try: 

2033 self._write_buffer() 

2034 except BrokenPipeError: 

2035 self.on_broken_pipe() 

2036 

2037 def _write_buffer(self) -> None: 

2038 """Write the buffer to the output file.""" 

2039 

2040 with self._lock: 

2041 if self.record and not self._buffer_index: 

2042 with self._record_buffer_lock: 

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

2044 

2045 if self._buffer_index == 0: 

2046 if self.is_jupyter: # pragma: no cover 

2047 from .jupyter import display 

2048 

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

2050 del self._buffer[:] 

2051 else: 

2052 if WINDOWS: 

2053 use_legacy_windows_render = False 

2054 if self.legacy_windows: 

2055 fileno = get_fileno(self.file) 

2056 if fileno is not None: 

2057 use_legacy_windows_render = ( 

2058 fileno in _STD_STREAMS_OUTPUT 

2059 ) 

2060 

2061 if use_legacy_windows_render: 

2062 from rich._win32_console import LegacyWindowsTerm 

2063 from rich._windows_renderer import legacy_windows_render 

2064 

2065 buffer = self._buffer[:] 

2066 if self.no_color and self._color_system: 

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

2068 

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

2070 else: 

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

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

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

2074 # https://github.com/python/cpython/issues/82052 

2075 # We need to avoid writing more than 32Kb in a single write, due to the above bug 

2076 write = self.file.write 

2077 # Worse case scenario, every character is 4 bytes of utf-8 

2078 MAX_WRITE = 32 * 1024 // 4 

2079 try: 

2080 if len(text) <= MAX_WRITE: 

2081 write(text) 

2082 else: 

2083 batch: List[str] = [] 

2084 batch_append = batch.append 

2085 size = 0 

2086 for line in text.splitlines(True): 

2087 if size + len(line) > MAX_WRITE and batch: 

2088 write("".join(batch)) 

2089 batch.clear() 

2090 size = 0 

2091 batch_append(line) 

2092 size += len(line) 

2093 if batch: 

2094 write("".join(batch)) 

2095 batch.clear() 

2096 except UnicodeEncodeError as error: 

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

2098 raise 

2099 else: 

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

2101 try: 

2102 self.file.write(text) 

2103 except UnicodeEncodeError as error: 

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

2105 raise 

2106 

2107 self.file.flush() 

2108 del self._buffer[:] 

2109 

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

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

2112 output: List[str] = [] 

2113 append = output.append 

2114 color_system = self._color_system 

2115 legacy_windows = self.legacy_windows 

2116 not_terminal = not self.is_terminal 

2117 if self.no_color and color_system: 

2118 buffer = Segment.remove_color(buffer) 

2119 for text, style, control in buffer: 

2120 if style: 

2121 append( 

2122 style.render( 

2123 text, 

2124 color_system=color_system, 

2125 legacy_windows=legacy_windows, 

2126 ) 

2127 ) 

2128 elif not (not_terminal and control): 

2129 append(text) 

2130 

2131 rendered = "".join(output) 

2132 return rendered 

2133 

2134 def input( 

2135 self, 

2136 prompt: TextType = "", 

2137 *, 

2138 markup: bool = True, 

2139 emoji: bool = True, 

2140 password: bool = False, 

2141 stream: Optional[TextIO] = None, 

2142 ) -> str: 

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

2144 

2145 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. 

2146 

2147 Args: 

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

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

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

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

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

2153 

2154 Returns: 

2155 str: Text read from stdin. 

2156 """ 

2157 if prompt: 

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

2159 if password: 

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

2161 else: 

2162 if stream: 

2163 result = stream.readline() 

2164 else: 

2165 result = input() 

2166 return result 

2167 

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

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

2170 

2171 Args: 

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

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

2174 Defaults to ``False``. 

2175 

2176 Returns: 

2177 str: String containing console contents. 

2178 

2179 """ 

2180 assert ( 

2181 self.record 

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

2183 

2184 with self._record_buffer_lock: 

2185 if styles: 

2186 text = "".join( 

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

2188 for text, style, _ in self._record_buffer 

2189 ) 

2190 else: 

2191 text = "".join( 

2192 segment.text 

2193 for segment in self._record_buffer 

2194 if not segment.control 

2195 ) 

2196 if clear: 

2197 del self._record_buffer[:] 

2198 return text 

2199 

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

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

2202 

2203 Args: 

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

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

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

2207 Defaults to ``False``. 

2208 

2209 """ 

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

2211 with open(path, "w", encoding="utf-8") as write_file: 

2212 write_file.write(text) 

2213 

2214 def export_html( 

2215 self, 

2216 *, 

2217 theme: Optional[TerminalTheme] = None, 

2218 clear: bool = True, 

2219 code_format: Optional[str] = None, 

2220 inline_styles: bool = False, 

2221 ) -> str: 

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

2223 

2224 Args: 

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

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

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

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

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

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

2231 Defaults to False. 

2232 

2233 Returns: 

2234 str: String containing console contents as HTML. 

2235 """ 

2236 assert ( 

2237 self.record 

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

2239 fragments: List[str] = [] 

2240 append = fragments.append 

2241 _theme = theme or DEFAULT_TERMINAL_THEME 

2242 stylesheet = "" 

2243 

2244 render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format 

2245 

2246 with self._record_buffer_lock: 

2247 if inline_styles: 

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

2249 Segment.simplify(self._record_buffer) 

2250 ): 

2251 text = escape(text) 

2252 if style: 

2253 rule = style.get_html_style(_theme) 

2254 if style.link: 

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

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

2257 append(text) 

2258 else: 

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

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

2261 Segment.simplify(self._record_buffer) 

2262 ): 

2263 text = escape(text) 

2264 if style: 

2265 rule = style.get_html_style(_theme) 

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

2267 if style.link: 

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

2269 else: 

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

2271 append(text) 

2272 stylesheet_rules: List[str] = [] 

2273 stylesheet_append = stylesheet_rules.append 

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

2275 if style_rule: 

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

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

2278 

2279 rendered_code = render_code_format.format( 

2280 code="".join(fragments), 

2281 stylesheet=stylesheet, 

2282 foreground=_theme.foreground_color.hex, 

2283 background=_theme.background_color.hex, 

2284 ) 

2285 if clear: 

2286 del self._record_buffer[:] 

2287 return rendered_code 

2288 

2289 def save_html( 

2290 self, 

2291 path: str, 

2292 *, 

2293 theme: Optional[TerminalTheme] = None, 

2294 clear: bool = True, 

2295 code_format: str = CONSOLE_HTML_FORMAT, 

2296 inline_styles: bool = False, 

2297 ) -> None: 

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

2299 

2300 Args: 

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

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

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

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

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

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

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

2308 Defaults to False. 

2309 

2310 """ 

2311 html = self.export_html( 

2312 theme=theme, 

2313 clear=clear, 

2314 code_format=code_format, 

2315 inline_styles=inline_styles, 

2316 ) 

2317 with open(path, "w", encoding="utf-8") as write_file: 

2318 write_file.write(html) 

2319 

2320 def export_svg( 

2321 self, 

2322 *, 

2323 title: str = "Rich", 

2324 theme: Optional[TerminalTheme] = None, 

2325 clear: bool = True, 

2326 code_format: str = CONSOLE_SVG_FORMAT, 

2327 font_aspect_ratio: float = 0.61, 

2328 unique_id: Optional[str] = None, 

2329 ) -> str: 

2330 """ 

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

2332 

2333 Args: 

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

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

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

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

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

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

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

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

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

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

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

2345 """ 

2346 

2347 from rich.cells import cell_len 

2348 

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

2350 

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

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

2353 if style in style_cache: 

2354 return style_cache[style] 

2355 css_rules = [] 

2356 color = ( 

2357 _theme.foreground_color 

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

2359 else style.color.get_truecolor(_theme) 

2360 ) 

2361 bgcolor = ( 

2362 _theme.background_color 

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

2364 else style.bgcolor.get_truecolor(_theme) 

2365 ) 

2366 if style.reverse: 

2367 color, bgcolor = bgcolor, color 

2368 if style.dim: 

2369 color = blend_rgb(color, bgcolor, 0.4) 

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

2371 if style.bold: 

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

2373 if style.italic: 

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

2375 if style.underline: 

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

2377 if style.strike: 

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

2379 

2380 css = ";".join(css_rules) 

2381 style_cache[style] = css 

2382 return css 

2383 

2384 _theme = theme or SVG_EXPORT_THEME 

2385 

2386 width = self.width 

2387 char_height = 20 

2388 char_width = char_height * font_aspect_ratio 

2389 line_height = char_height * 1.22 

2390 

2391 margin_top = 1 

2392 margin_right = 1 

2393 margin_bottom = 1 

2394 margin_left = 1 

2395 

2396 padding_top = 40 

2397 padding_right = 8 

2398 padding_bottom = 8 

2399 padding_left = 8 

2400 

2401 padding_width = padding_left + padding_right 

2402 padding_height = padding_top + padding_bottom 

2403 margin_width = margin_left + margin_right 

2404 margin_height = margin_top + margin_bottom 

2405 

2406 text_backgrounds: List[str] = [] 

2407 text_group: List[str] = [] 

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

2409 style_no = 1 

2410 

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

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

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

2414 

2415 def make_tag( 

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

2417 ) -> str: 

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

2419 

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

2421 if isinstance(value, (float)): 

2422 return format(value, "g") 

2423 return str(value) 

2424 

2425 tag_attribs = " ".join( 

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

2427 for k, v in attribs.items() 

2428 ) 

2429 return ( 

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

2431 if content 

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

2433 ) 

2434 

2435 with self._record_buffer_lock: 

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

2437 if clear: 

2438 self._record_buffer.clear() 

2439 

2440 if unique_id is None: 

2441 unique_id = "terminal-" + str( 

2442 zlib.adler32( 

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

2444 "utf-8", 

2445 "ignore", 

2446 ) 

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

2448 ) 

2449 ) 

2450 y = 0 

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

2452 x = 0 

2453 for text, style, _control in line: 

2454 style = style or Style() 

2455 rules = get_svg_style(style) 

2456 if rules not in classes: 

2457 classes[rules] = style_no 

2458 style_no += 1 

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

2460 

2461 if style.reverse: 

2462 has_background = True 

2463 background = ( 

2464 _theme.foreground_color.hex 

2465 if style.color is None 

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

2467 ) 

2468 else: 

2469 bgcolor = style.bgcolor 

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

2471 background = ( 

2472 _theme.background_color.hex 

2473 if style.bgcolor is None 

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

2475 ) 

2476 

2477 text_length = cell_len(text) 

2478 if has_background: 

2479 text_backgrounds.append( 

2480 make_tag( 

2481 "rect", 

2482 fill=background, 

2483 x=x * char_width, 

2484 y=y * line_height + 1.5, 

2485 width=char_width * text_length, 

2486 height=line_height + 0.25, 

2487 shape_rendering="crispEdges", 

2488 ) 

2489 ) 

2490 

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

2492 text_group.append( 

2493 make_tag( 

2494 "text", 

2495 escape_text(text), 

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

2497 x=x * char_width, 

2498 y=y * line_height + char_height, 

2499 textLength=char_width * len(text), 

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

2501 ) 

2502 ) 

2503 x += cell_len(text) 

2504 

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

2506 lines = "\n".join( 

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

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

2509 </clipPath>""" 

2510 for line_no, offset in enumerate(line_offsets) 

2511 ) 

2512 

2513 styles = "\n".join( 

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

2515 ) 

2516 backgrounds = "".join(text_backgrounds) 

2517 matrix = "".join(text_group) 

2518 

2519 terminal_width = ceil(width * char_width + padding_width) 

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

2521 chrome = make_tag( 

2522 "rect", 

2523 fill=_theme.background_color.hex, 

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

2525 stroke_width="1", 

2526 x=margin_left, 

2527 y=margin_top, 

2528 width=terminal_width, 

2529 height=terminal_height, 

2530 rx=8, 

2531 ) 

2532 

2533 title_color = _theme.foreground_color.hex 

2534 if title: 

2535 chrome += make_tag( 

2536 "text", 

2537 escape_text(title), 

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

2539 fill=title_color, 

2540 text_anchor="middle", 

2541 x=terminal_width // 2, 

2542 y=margin_top + char_height + 6, 

2543 ) 

2544 chrome += f""" 

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

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

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

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

2549 </g> 

2550 """ 

2551 

2552 svg = code_format.format( 

2553 unique_id=unique_id, 

2554 char_width=char_width, 

2555 char_height=char_height, 

2556 line_height=line_height, 

2557 terminal_width=char_width * width - 1, 

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

2559 width=terminal_width + margin_width, 

2560 height=terminal_height + margin_height, 

2561 terminal_x=margin_left + padding_left, 

2562 terminal_y=margin_top + padding_top, 

2563 styles=styles, 

2564 chrome=chrome, 

2565 backgrounds=backgrounds, 

2566 matrix=matrix, 

2567 lines=lines, 

2568 ) 

2569 return svg 

2570 

2571 def save_svg( 

2572 self, 

2573 path: str, 

2574 *, 

2575 title: str = "Rich", 

2576 theme: Optional[TerminalTheme] = None, 

2577 clear: bool = True, 

2578 code_format: str = CONSOLE_SVG_FORMAT, 

2579 font_aspect_ratio: float = 0.61, 

2580 unique_id: Optional[str] = None, 

2581 ) -> None: 

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

2583 

2584 Args: 

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

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

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

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

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

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

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

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

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

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

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

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

2597 """ 

2598 svg = self.export_svg( 

2599 title=title, 

2600 theme=theme, 

2601 clear=clear, 

2602 code_format=code_format, 

2603 font_aspect_ratio=font_aspect_ratio, 

2604 unique_id=unique_id, 

2605 ) 

2606 with open(path, "w", encoding="utf-8") as write_file: 

2607 write_file.write(svg) 

2608 

2609 

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

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

2612 

2613 Args: 

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

2615 

2616 Returns: 

2617 str: a hash of the given content 

2618 """ 

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

2620 

2621 

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

2623 console = Console(record=True) 

2624 

2625 console.log( 

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

2627 5, 

2628 1.3, 

2629 True, 

2630 False, 

2631 None, 

2632 { 

2633 "jsonrpc": "2.0", 

2634 "method": "subtract", 

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

2636 "id": 3, 

2637 }, 

2638 ) 

2639 

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

2641 

2642 console.print( 

2643 { 

2644 "name": None, 

2645 "empty": [], 

2646 "quiz": { 

2647 "sport": { 

2648 "answered": True, 

2649 "q1": { 

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

2651 "options": [ 

2652 "New York Bulls", 

2653 "Los Angeles Kings", 

2654 "Golden State Warriors", 

2655 "Huston Rocket", 

2656 ], 

2657 "answer": "Huston Rocket", 

2658 }, 

2659 }, 

2660 "maths": { 

2661 "answered": False, 

2662 "q1": { 

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

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

2665 "answer": 12, 

2666 }, 

2667 "q2": { 

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

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

2670 "answer": 4, 

2671 }, 

2672 }, 

2673 }, 

2674 } 

2675 )