codekingpro/portable-devtools
114k
1import inspect2import os3import platform4import sys5import threading6import zlib7from abc import ABC, abstractmethod8from dataclasses import dataclass, field9from datetime import datetime10from functools import wraps11from getpass import getpass12from html import escape13from inspect import isclass14from itertools import islice15from math import ceil16from time import monotonic17from types import FrameType, ModuleType, TracebackType18from typing import (19 IO,20 TYPE_CHECKING,21 Any,22 Callable,23 Dict,24 Iterable,25 List,26 Mapping,27 NamedTuple,28 Optional,29 TextIO,30 Tuple,31 Type,32 Union,33 cast,34)35 36from rich._null_file import NULL_FILE37 38if sys.version_info >= (3, 8):39 from typing import Literal, Protocol, runtime_checkable40else:41 from typing_extensions import (42 Literal,43 Protocol,44 runtime_checkable,45 ) # pragma: no cover46 47from . import errors, themes48from ._emoji_replace import _emoji_replace49from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT50from ._fileno import get_fileno51from ._log_render import FormatTimeCallable, LogRender52from .align import Align, AlignMethod53from .color import ColorSystem, blend_rgb54from .control import Control55from .emoji import EmojiVariant56from .highlighter import NullHighlighter, ReprHighlighter57from .markup import render as render_markup58from .measure import Measurement, measure_renderables59from .pager import Pager, SystemPager60from .pretty import Pretty, is_expandable61from .protocol import rich_cast62from .region import Region63from .scope import render_scope64from .screen import Screen65from .segment import Segment66from .style import Style, StyleType67from .styled import Styled68from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme69from .text import Text, TextType70from .theme import Theme, ThemeStack71 72if TYPE_CHECKING:73 from ._windows import WindowsConsoleFeatures74 from .live import Live75 from .status import Status76 77JUPYTER_DEFAULT_COLUMNS = 11578JUPYTER_DEFAULT_LINES = 10079WINDOWS = platform.system() == "Windows"80 81HighlighterType = Callable[[Union[str, "Text"]], "Text"]82JustifyMethod = Literal["default", "left", "center", "right", "full"]83OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"]84 85 86class NoChange:87 pass88 89 90NO_CHANGE = NoChange()91 92try:93 _STDIN_FILENO = sys.__stdin__.fileno()94except Exception:95 _STDIN_FILENO = 096try:97 _STDOUT_FILENO = sys.__stdout__.fileno()98except Exception:99 _STDOUT_FILENO = 1100try:101 _STDERR_FILENO = sys.__stderr__.fileno()102except Exception:103 _STDERR_FILENO = 2104 105_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)106_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)107 108 109_TERM_COLORS = {110 "kitty": ColorSystem.EIGHT_BIT,111 "256color": ColorSystem.EIGHT_BIT,112 "16color": ColorSystem.STANDARD,113}114 115 116class ConsoleDimensions(NamedTuple):117 """Size of the terminal."""118 119 width: int120 """The width of the console in 'cells'."""121 height: int122 """The height of the console in lines."""123 124 125@dataclass126class ConsoleOptions:127 """Options for __rich_console__ method."""128 129 size: ConsoleDimensions130 """Size of console."""131 legacy_windows: bool132 """legacy_windows: flag for legacy windows."""133 min_width: int134 """Minimum width of renderable."""135 max_width: int136 """Maximum width of renderable."""137 is_terminal: bool138 """True if the target is a terminal, otherwise False."""139 encoding: str140 """Encoding of terminal."""141 max_height: int142 """Height of container (starts as terminal)"""143 justify: Optional[JustifyMethod] = None144 """Justify value override for renderable."""145 overflow: Optional[OverflowMethod] = None146 """Overflow value override for renderable."""147 no_wrap: Optional[bool] = False148 """Disable wrapping for text."""149 highlight: Optional[bool] = None150 """Highlight override for render_str."""151 markup: Optional[bool] = None152 """Enable markup when rendering strings."""153 height: Optional[int] = None154 155 @property156 def ascii_only(self) -> bool:157 """Check if renderables should use ascii only."""158 return not self.encoding.startswith("utf")159 160 def copy(self) -> "ConsoleOptions":161 """Return a copy of the options.162 163 Returns:164 ConsoleOptions: a copy of self.165 """166 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions)167 options.__dict__ = self.__dict__.copy()168 return options169 170 def update(171 self,172 *,173 width: Union[int, NoChange] = NO_CHANGE,174 min_width: Union[int, NoChange] = NO_CHANGE,175 max_width: Union[int, NoChange] = NO_CHANGE,176 justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE,177 overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,178 no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,179 highlight: Union[Optional[bool], NoChange] = NO_CHANGE,180 markup: Union[Optional[bool], NoChange] = NO_CHANGE,181 height: Union[Optional[int], NoChange] = NO_CHANGE,182 ) -> "ConsoleOptions":183 """Update values, return a copy."""184 options = self.copy()185 if not isinstance(width, NoChange):186 options.min_width = options.max_width = max(0, width)187 if not isinstance(min_width, NoChange):188 options.min_width = min_width189 if not isinstance(max_width, NoChange):190 options.max_width = max_width191 if not isinstance(justify, NoChange):192 options.justify = justify193 if not isinstance(overflow, NoChange):194 options.overflow = overflow195 if not isinstance(no_wrap, NoChange):196 options.no_wrap = no_wrap197 if not isinstance(highlight, NoChange):198 options.highlight = highlight199 if not isinstance(markup, NoChange):200 options.markup = markup201 if not isinstance(height, NoChange):202 if height is not None:203 options.max_height = height204 options.height = None if height is None else max(0, height)205 return options206 207 def update_width(self, width: int) -> "ConsoleOptions":208 """Update just the width, return a copy.209 210 Args:211 width (int): New width (sets both min_width and max_width)212 213 Returns:214 ~ConsoleOptions: New console options instance.215 """216 options = self.copy()217 options.min_width = options.max_width = max(0, width)218 return options219 220 def update_height(self, height: int) -> "ConsoleOptions":221 """Update the height, and return a copy.222 223 Args:224 height (int): New height225 226 Returns:227 ~ConsoleOptions: New Console options instance.228 """229 options = self.copy()230 options.max_height = options.height = height231 return options232 233 def reset_height(self) -> "ConsoleOptions":234 """Return a copy of the options with height set to ``None``.235 236 Returns:237 ~ConsoleOptions: New console options instance.238 """239 options = self.copy()240 options.height = None241 return options242 243 def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":244 """Update the width and height, and return a copy.245 246 Args:247 width (int): New width (sets both min_width and max_width).248 height (int): New height.249 250 Returns:251 ~ConsoleOptions: New console options instance.252 """253 options = self.copy()254 options.min_width = options.max_width = max(0, width)255 options.height = options.max_height = height256 return options257 258 259@runtime_checkable260class RichCast(Protocol):261 """An object that may be 'cast' to a console renderable."""262 263 def __rich__(264 self,265 ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover266 ...267 268 269@runtime_checkable270class ConsoleRenderable(Protocol):271 """An object that supports the console protocol."""272 273 def __rich_console__(274 self, console: "Console", options: "ConsoleOptions"275 ) -> "RenderResult": # pragma: no cover276 ...277 278 279# A type that may be rendered by Console.280RenderableType = Union[ConsoleRenderable, RichCast, str]281"""A string or any object that may be rendered by Rich."""282 283# The result of calling a __rich_console__ method.284RenderResult = Iterable[Union[RenderableType, Segment]]285 286_null_highlighter = NullHighlighter()287 288 289class CaptureError(Exception):290 """An error in the Capture context manager."""291 292 293class NewLine:294 """A renderable to generate new line(s)"""295 296 def __init__(self, count: int = 1) -> None:297 self.count = count298 299 def __rich_console__(300 self, console: "Console", options: "ConsoleOptions"301 ) -> Iterable[Segment]:302 yield Segment("\n" * self.count)303 304 305class ScreenUpdate:306 """Render a list of lines at a given offset."""307 308 def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None:309 self._lines = lines310 self.x = x311 self.y = y312 313 def __rich_console__(314 self, console: "Console", options: ConsoleOptions315 ) -> RenderResult:316 x = self.x317 move_to = Control.move_to318 for offset, line in enumerate(self._lines, self.y):319 yield move_to(x, offset)320 yield from line321 322 323class Capture:324 """Context manager to capture the result of printing to the console.325 See :meth:`~rich.console.Console.capture` for how to use.326 327 Args:328 console (Console): A console instance to capture output.329 """330 331 def __init__(self, console: "Console") -> None:332 self._console = console333 self._result: Optional[str] = None334 335 def __enter__(self) -> "Capture":336 self._console.begin_capture()337 return self338 339 def __exit__(340 self,341 exc_type: Optional[Type[BaseException]],342 exc_val: Optional[BaseException],343 exc_tb: Optional[TracebackType],344 ) -> None:345 self._result = self._console.end_capture()346 347 def get(self) -> str:348 """Get the result of the capture."""349 if self._result is None:350 raise CaptureError(351 "Capture result is not available until context manager exits."352 )353 return self._result354 355 356class ThemeContext:357 """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage."""358 359 def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None:360 self.console = console361 self.theme = theme362 self.inherit = inherit363 364 def __enter__(self) -> "ThemeContext":365 self.console.push_theme(self.theme)366 return self367 368 def __exit__(369 self,370 exc_type: Optional[Type[BaseException]],371 exc_val: Optional[BaseException],372 exc_tb: Optional[TracebackType],373 ) -> None:374 self.console.pop_theme()375 376 377class PagerContext:378 """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage."""379 380 def __init__(381 self,382 console: "Console",383 pager: Optional[Pager] = None,384 styles: bool = False,385 links: bool = False,386 ) -> None:387 self._console = console388 self.pager = SystemPager() if pager is None else pager389 self.styles = styles390 self.links = links391 392 def __enter__(self) -> "PagerContext":393 self._console._enter_buffer()394 return self395 396 def __exit__(397 self,398 exc_type: Optional[Type[BaseException]],399 exc_val: Optional[BaseException],400 exc_tb: Optional[TracebackType],401 ) -> None:402 if exc_type is None:403 with self._console._lock:404 buffer: List[Segment] = self._console._buffer[:]405 del self._console._buffer[:]406 segments: Iterable[Segment] = buffer407 if not self.styles:408 segments = Segment.strip_styles(segments)409 elif not self.links:410 segments = Segment.strip_links(segments)411 content = self._console._render_buffer(segments)412 self.pager.show(content)413 self._console._exit_buffer()414 415 416class ScreenContext:417 """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage."""418 419 def __init__(420 self, console: "Console", hide_cursor: bool, style: StyleType = ""421 ) -> None:422 self.console = console423 self.hide_cursor = hide_cursor424 self.screen = Screen(style=style)425 self._changed = False426 427 def update(428 self, *renderables: RenderableType, style: Optional[StyleType] = None429 ) -> None:430 """Update the screen.431 432 Args:433 renderable (RenderableType, optional): Optional renderable to replace current renderable,434 or None for no change. Defaults to None.435 style: (Style, optional): Replacement style, or None for no change. Defaults to None.436 """437 if renderables:438 self.screen.renderable = (439 Group(*renderables) if len(renderables) > 1 else renderables[0]440 )441 if style is not None:442 self.screen.style = style443 self.console.print(self.screen, end="")444 445 def __enter__(self) -> "ScreenContext":446 self._changed = self.console.set_alt_screen(True)447 if self._changed and self.hide_cursor:448 self.console.show_cursor(False)449 return self450 451 def __exit__(452 self,453 exc_type: Optional[Type[BaseException]],454 exc_val: Optional[BaseException],455 exc_tb: Optional[TracebackType],456 ) -> None:457 if self._changed:458 self.console.set_alt_screen(False)459 if self.hide_cursor:460 self.console.show_cursor(True)461 462 463class Group:464 """Takes a group of renderables and returns a renderable object that renders the group.465 466 Args:467 renderables (Iterable[RenderableType]): An iterable of renderable objects.468 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.469 """470 471 def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None:472 self._renderables = renderables473 self.fit = fit474 self._render: Optional[List[RenderableType]] = None475 476 @property477 def renderables(self) -> List["RenderableType"]:478 if self._render is None:479 self._render = list(self._renderables)480 return self._render481 482 def __rich_measure__(483 self, console: "Console", options: "ConsoleOptions"484 ) -> "Measurement":485 if self.fit:486 return measure_renderables(console, options, self.renderables)487 else:488 return Measurement(options.max_width, options.max_width)489 490 def __rich_console__(491 self, console: "Console", options: "ConsoleOptions"492 ) -> RenderResult:493 yield from self.renderables494 495 496def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:497 """A decorator that turns an iterable of renderables in to a group.498 499 Args:500 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.501 """502 503 def decorator(504 method: Callable[..., Iterable[RenderableType]]505 ) -> Callable[..., Group]:506 """Convert a method that returns an iterable of renderables in to a Group."""507 508 @wraps(method)509 def _replace(*args: Any, **kwargs: Any) -> Group:510 renderables = method(*args, **kwargs)511 return Group(*renderables, fit=fit)512 513 return _replace514 515 return decorator516 517 518def _is_jupyter() -> bool: # pragma: no cover519 """Check if we're running in a Jupyter notebook."""520 try:521 get_ipython # type: ignore[name-defined]522 except NameError:523 return False524 ipython = get_ipython() # type: ignore[name-defined]525 shell = ipython.__class__.__name__526 if (527 "google.colab" in str(ipython.__class__)528 or os.getenv("DATABRICKS_RUNTIME_VERSION")529 or shell == "ZMQInteractiveShell"530 ):531 return True # Jupyter notebook or qtconsole532 elif shell == "TerminalInteractiveShell":533 return False # Terminal running IPython534 else:535 return False # Other type (?)536 537 538COLOR_SYSTEMS = {539 "standard": ColorSystem.STANDARD,540 "256": ColorSystem.EIGHT_BIT,541 "truecolor": ColorSystem.TRUECOLOR,542 "windows": ColorSystem.WINDOWS,543}544 545_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()}546 547 548@dataclass549class ConsoleThreadLocals(threading.local):550 """Thread local values for Console context."""551 552 theme_stack: ThemeStack553 buffer: List[Segment] = field(default_factory=list)554 buffer_index: int = 0555 556 557class RenderHook(ABC):558 """Provides hooks in to the render process."""559 560 @abstractmethod561 def process_renderables(562 self, renderables: List[ConsoleRenderable]563 ) -> List[ConsoleRenderable]:564 """Called with a list of objects to render.565 566 This method can return a new list of renderables, or modify and return the same list.567 568 Args:569 renderables (List[ConsoleRenderable]): A number of renderable objects.570 571 Returns:572 List[ConsoleRenderable]: A replacement list of renderables.573 """574 575 576_windows_console_features: Optional["WindowsConsoleFeatures"] = None577 578 579def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover580 global _windows_console_features581 if _windows_console_features is not None:582 return _windows_console_features583 from ._windows import get_windows_console_features584 585 _windows_console_features = get_windows_console_features()586 return _windows_console_features587 588 589def detect_legacy_windows() -> bool:590 """Detect legacy Windows."""591 return WINDOWS and not get_windows_console_features().vt592 593 594class Console:595 """A high level console interface.596 597 Args:598 color_system (str, optional): The color system supported by your terminal,599 either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.600 force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None.601 force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None.602 force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None.603 soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False.604 theme (Theme, optional): An optional style theme object, or ``None`` for default theme.605 stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False.606 file (IO, optional): A file object where the console should write to. Defaults to stdout.607 quiet (bool, Optional): Boolean to suppress all output. Defaults to False.608 width (int, optional): The width of the terminal. Leave as default to auto-detect width.609 height (int, optional): The height of the terminal. Leave as default to auto-detect height.610 style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None.611 no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None.612 tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8.613 record (bool, optional): Boolean to enable recording of terminal output,614 required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False.615 markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True.616 emoji (bool, optional): Enable emoji code. Defaults to True.617 emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.618 highlight (bool, optional): Enable automatic highlighting. Defaults to True.619 log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True.620 log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True.621 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] ".622 highlighter (HighlighterType, optional): Default highlighter.623 legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``.624 safe_box (bool, optional): Restrict box options that don't render on legacy Windows.625 get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log),626 or None for datetime.now.627 get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic.628 """629 630 _environ: Mapping[str, str] = os.environ631 632 def __init__(633 self,634 *,635 color_system: Optional[636 Literal["auto", "standard", "256", "truecolor", "windows"]637 ] = "auto",638 force_terminal: Optional[bool] = None,639 force_jupyter: Optional[bool] = None,640 force_interactive: Optional[bool] = None,641 soft_wrap: bool = False,642 theme: Optional[Theme] = None,643 stderr: bool = False,644 file: Optional[IO[str]] = None,645 quiet: bool = False,646 width: Optional[int] = None,647 height: Optional[int] = None,648 style: Optional[StyleType] = None,649 no_color: Optional[bool] = None,650 tab_size: int = 8,651 record: bool = False,652 markup: bool = True,653 emoji: bool = True,654 emoji_variant: Optional[EmojiVariant] = None,655 highlight: bool = True,656 log_time: bool = True,657 log_path: bool = True,658 log_time_format: Union[str, FormatTimeCallable] = "[%X]",659 highlighter: Optional["HighlighterType"] = ReprHighlighter(),660 legacy_windows: Optional[bool] = None,661 safe_box: bool = True,662 get_datetime: Optional[Callable[[], datetime]] = None,663 get_time: Optional[Callable[[], float]] = None,664 _environ: Optional[Mapping[str, str]] = None,665 ):666 # Copy of os.environ allows us to replace it for testing667 if _environ is not None:668 self._environ = _environ669 670 self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter671 if self.is_jupyter:672 if width is None:673 jupyter_columns = self._environ.get("JUPYTER_COLUMNS")674 if jupyter_columns is not None and jupyter_columns.isdigit():675 width = int(jupyter_columns)676 else:677 width = JUPYTER_DEFAULT_COLUMNS678 if height is None:679 jupyter_lines = self._environ.get("JUPYTER_LINES")680 if jupyter_lines is not None and jupyter_lines.isdigit():681 height = int(jupyter_lines)682 else:683 height = JUPYTER_DEFAULT_LINES684 685 self.tab_size = tab_size686 self.record = record687 self._markup = markup688 self._emoji = emoji689 self._emoji_variant: Optional[EmojiVariant] = emoji_variant690 self._highlight = highlight691 self.legacy_windows: bool = (692 (detect_legacy_windows() and not self.is_jupyter)693 if legacy_windows is None694 else legacy_windows695 )696 697 if width is None:698 columns = self._environ.get("COLUMNS")699 if columns is not None and columns.isdigit():700 width = int(columns) - self.legacy_windows701 if height is None:702 lines = self._environ.get("LINES")703 if lines is not None and lines.isdigit():704 height = int(lines)705 706 self.soft_wrap = soft_wrap707 self._width = width708 self._height = height709 710 self._color_system: Optional[ColorSystem]711 712 self._force_terminal = None713 if force_terminal is not None:714 self._force_terminal = force_terminal715 716 self._file = file717 self.quiet = quiet718 self.stderr = stderr719 720 if color_system is None:721 self._color_system = None722 elif color_system == "auto":723 self._color_system = self._detect_color_system()724 else:725 self._color_system = COLOR_SYSTEMS[color_system]726 727 self._lock = threading.RLock()728 self._log_render = LogRender(729 show_time=log_time,730 show_path=log_path,731 time_format=log_time_format,732 )733 self.highlighter: HighlighterType = highlighter or _null_highlighter734 self.safe_box = safe_box735 self.get_datetime = get_datetime or datetime.now736 self.get_time = get_time or monotonic737 self.style = style738 self.no_color = (739 no_color if no_color is not None else "NO_COLOR" in self._environ740 )741 self.is_interactive = (742 (self.is_terminal and not self.is_dumb_terminal)743 if force_interactive is None744 else force_interactive745 )746 747 self._record_buffer_lock = threading.RLock()748 self._thread_locals = ConsoleThreadLocals(749 theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme)750 )751 self._record_buffer: List[Segment] = []752 self._render_hooks: List[RenderHook] = []753 self._live: Optional["Live"] = None754 self._is_alt_screen = False755 756 def __repr__(self) -> str:757 return f"<console width={self.width} {self._color_system!s}>"758 759 @property760 def file(self) -> IO[str]:761 """Get the file object to write to."""762 file = self._file or (sys.stderr if self.stderr else sys.stdout)763 file = getattr(file, "rich_proxied_file", file)764 if file is None:765 file = NULL_FILE766 return file767 768 @file.setter769 def file(self, new_file: IO[str]) -> None:770 """Set a new file object."""771 self._file = new_file772 773 @property774 def _buffer(self) -> List[Segment]:775 """Get a thread local buffer."""776 return self._thread_locals.buffer777 778 @property779 def _buffer_index(self) -> int:780 """Get a thread local buffer."""781 return self._thread_locals.buffer_index782 783 @_buffer_index.setter784 def _buffer_index(self, value: int) -> None:785 self._thread_locals.buffer_index = value786 787 @property788 def _theme_stack(self) -> ThemeStack:789 """Get the thread local theme stack."""790 return self._thread_locals.theme_stack791 792 def _detect_color_system(self) -> Optional[ColorSystem]:793 """Detect color system from env vars."""794 if self.is_jupyter:795 return ColorSystem.TRUECOLOR796 if not self.is_terminal or self.is_dumb_terminal:797 return None798 if WINDOWS: # pragma: no cover799 if self.legacy_windows: # pragma: no cover800 return ColorSystem.WINDOWS801 windows_console_features = get_windows_console_features()802 return (803 ColorSystem.TRUECOLOR804 if windows_console_features.truecolor805 else ColorSystem.EIGHT_BIT806 )807 else:808 color_term = self._environ.get("COLORTERM", "").strip().lower()809 if color_term in ("truecolor", "24bit"):810 return ColorSystem.TRUECOLOR811 term = self._environ.get("TERM", "").strip().lower()812 _term_name, _hyphen, colors = term.rpartition("-")813 color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD)814 return color_system815 816 def _enter_buffer(self) -> None:817 """Enter in to a buffer context, and buffer all output."""818 self._buffer_index += 1819 820 def _exit_buffer(self) -> None:821 """Leave buffer context, and render content if required."""822 self._buffer_index -= 1823 self._check_buffer()824 825 def set_live(self, live: "Live") -> None:826 """Set Live instance. Used by Live context manager.827 828 Args:829 live (Live): Live instance using this Console.830 831 Raises:832 errors.LiveError: If this Console has a Live context currently active.833 """834 with self._lock:835 if self._live is not None:836 raise errors.LiveError("Only one live display may be active at once")837 self._live = live838 839 def clear_live(self) -> None:840 """Clear the Live instance."""841 with self._lock:842 self._live = None843 844 def push_render_hook(self, hook: RenderHook) -> None:845 """Add a new render hook to the stack.846 847 Args:848 hook (RenderHook): Render hook instance.849 """850 with self._lock:851 self._render_hooks.append(hook)852 853 def pop_render_hook(self) -> None:854 """Pop the last renderhook from the stack."""855 with self._lock:856 self._render_hooks.pop()857 858 def __enter__(self) -> "Console":859 """Own context manager to enter buffer context."""860 self._enter_buffer()861 return self862 863 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:864 """Exit buffer context."""865 self._exit_buffer()866 867 def begin_capture(self) -> None:868 """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output."""869 self._enter_buffer()870 871 def end_capture(self) -> str:872 """End capture mode and return captured string.873 874 Returns:875 str: Console output.876 """877 render_result = self._render_buffer(self._buffer)878 del self._buffer[:]879 self._exit_buffer()880 return render_result881 882 def push_theme(self, theme: Theme, *, inherit: bool = True) -> None:883 """Push a new theme on to the top of the stack, replacing the styles from the previous theme.884 Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather885 than calling this method directly.886 887 Args:888 theme (Theme): A theme instance.889 inherit (bool, optional): Inherit existing styles. Defaults to True.890 """891 self._theme_stack.push_theme(theme, inherit=inherit)892 893 def pop_theme(self) -> None:894 """Remove theme from top of stack, restoring previous theme."""895 self._theme_stack.pop_theme()896 897 def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext:898 """Use a different theme for the duration of the context manager.899 900 Args:901 theme (Theme): Theme instance to user.902 inherit (bool, optional): Inherit existing console styles. Defaults to True.903 904 Returns:905 ThemeContext: [description]906 """907 return ThemeContext(self, theme, inherit)908 909 @property910 def color_system(self) -> Optional[str]:911 """Get color system string.912 913 Returns:914 Optional[str]: "standard", "256" or "truecolor".915 """916 917 if self._color_system is not None:918 return _COLOR_SYSTEMS_NAMES[self._color_system]919 else:920 return None921 922 @property923 def encoding(self) -> str:924 """Get the encoding of the console file, e.g. ``"utf-8"``.925 926 Returns:927 str: A standard encoding string.928 """929 return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower()930 931 @property932 def is_terminal(self) -> bool:933 """Check if the console is writing to a terminal.934 935 Returns:936 bool: True if the console writing to a device capable of937 understanding terminal codes, otherwise False.938 """939 if self._force_terminal is not None:940 return self._force_terminal941 942 if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith(943 "idlelib"944 ):945 # Return False for Idle which claims to be a tty but can't handle ansi codes946 return False947 948 if self.is_jupyter:949 # return False for Jupyter, which may have FORCE_COLOR set950 return False951 952 # If FORCE_COLOR env var has any value at all, we assume a terminal.953 force_color = self._environ.get("FORCE_COLOR")954 if force_color is not None:955 self._force_terminal = True956 return True957 958 isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)959 try:960 return False if isatty is None else isatty()961 except ValueError:962 # in some situation (at the end of a pytest run for example) isatty() can raise963 # ValueError: I/O operation on closed file964 # return False because we aren't in a terminal anymore965 return False966 967 @property968 def is_dumb_terminal(self) -> bool:969 """Detect dumb terminal.970 971 Returns:972 bool: True if writing to a dumb terminal, otherwise False.973 974 """975 _term = self._environ.get("TERM", "")976 is_dumb = _term.lower() in ("dumb", "unknown")977 return self.is_terminal and is_dumb978 979 @property980 def options(self) -> ConsoleOptions:981 """Get default console options."""982 return ConsoleOptions(983 max_height=self.size.height,984 size=self.size,985 legacy_windows=self.legacy_windows,986 min_width=1,987 max_width=self.width,988 encoding=self.encoding,989 is_terminal=self.is_terminal,990 )991 992 @property993 def size(self) -> ConsoleDimensions:994 """Get the size of the console.995 996 Returns:997 ConsoleDimensions: A named tuple containing the dimensions.998 """999 1000 if self._width is not None and self._height is not None:1001 return ConsoleDimensions(self._width - self.legacy_windows, self._height)1002 1003 if self.is_dumb_terminal:1004 return ConsoleDimensions(80, 25)1005 1006 width: Optional[int] = None1007 height: Optional[int] = None1008 1009 if WINDOWS: # pragma: no cover1010 try:1011 width, height = os.get_terminal_size()1012 except (AttributeError, ValueError, OSError): # Probably not a terminal1013 pass1014 else:1015 for file_descriptor in _STD_STREAMS:1016 try:1017 width, height = os.get_terminal_size(file_descriptor)1018 except (AttributeError, ValueError, OSError):1019 pass1020 else:1021 break1022 1023 columns = self._environ.get("COLUMNS")1024 if columns is not None and columns.isdigit():1025 width = int(columns)1026 lines = self._environ.get("LINES")1027 if lines is not None and lines.isdigit():1028 height = int(lines)1029 1030 # get_terminal_size can report 0, 0 if run from pseudo-terminal1031 width = width or 801032 height = height or 251033 return ConsoleDimensions(1034 width - self.legacy_windows if self._width is None else self._width,1035 height if self._height is None else self._height,1036 )1037 1038 @size.setter1039 def size(self, new_size: Tuple[int, int]) -> None:1040 """Set a new size for the terminal.1041 1042 Args:1043 new_size (Tuple[int, int]): New width and height.1044 """1045 width, height = new_size1046 self._width = width1047 self._height = height1048 1049 @property1050 def width(self) -> int:1051 """Get the width of the console.1052 1053 Returns:1054 int: The width (in characters) of the console.1055 """1056 return self.size.width1057 1058 @width.setter1059 def width(self, width: int) -> None:1060 """Set width.1061 1062 Args:1063 width (int): New width.1064 """1065 self._width = width1066 1067 @property1068 def height(self) -> int:1069 """Get the height of the console.1070 1071 Returns:1072 int: The height (in lines) of the console.1073 """1074 return self.size.height1075 1076 @height.setter1077 def height(self, height: int) -> None:1078 """Set height.1079 1080 Args:1081 height (int): new height.1082 """1083 self._height = height1084 1085 def bell(self) -> None:1086 """Play a 'bell' sound (if supported by the terminal)."""1087 self.control(Control.bell())1088 1089 def capture(self) -> Capture:1090 """A context manager to *capture* the result of print() or log() in a string,1091 rather than writing it to the console.1092 1093 Example:1094 >>> from rich.console import Console1095 >>> console = Console()1096 >>> with console.capture() as capture:1097 ... console.print("[bold magenta]Hello World[/]")1098 >>> print(capture.get())1099 1100 Returns:1101 Capture: Context manager with disables writing to the terminal.1102 """1103 capture = Capture(self)1104 return capture1105 1106 def pager(1107 self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False1108 ) -> PagerContext:1109 """A context manager to display anything printed within a "pager". The pager application1110 is defined by the system and will typically support at least pressing a key to scroll.1111 1112 Args:1113 pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None.1114 styles (bool, optional): Show styles in pager. Defaults to False.1115 links (bool, optional): Show links in pager. Defaults to False.1116 1117 Example:1118 >>> from rich.console import Console1119 >>> from rich.__main__ import make_test_card1120 >>> console = Console()1121 >>> with console.pager():1122 console.print(make_test_card())1123 1124 Returns:1125 PagerContext: A context manager.1126 """1127 return PagerContext(self, pager=pager, styles=styles, links=links)1128 1129 def line(self, count: int = 1) -> None:1130 """Write new line(s).1131 1132 Args:1133 count (int, optional): Number of new lines. Defaults to 1.1134 """1135 1136 assert count >= 0, "count must be >= 0"1137 self.print(NewLine(count))1138 1139 def clear(self, home: bool = True) -> None:1140 """Clear the screen.1141 1142 Args:1143 home (bool, optional): Also move the cursor to 'home' position. Defaults to True.1144 """1145 if home:1146 self.control(Control.clear(), Control.home())1147 else:1148 self.control(Control.clear())1149 1150 def status(1151 self,1152 status: RenderableType,1153 *,1154 spinner: str = "dots",1155 spinner_style: StyleType = "status.spinner",1156 speed: float = 1.0,1157 refresh_per_second: float = 12.5,1158 ) -> "Status":1159 """Display a status and spinner.1160 1161 Args:1162 status (RenderableType): A status renderable (str or Text typically).1163 spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".1164 spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".1165 speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.1166 refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.1167 1168 Returns:1169 Status: A Status object that may be used as a context manager.1170 """1171 from .status import Status1172 1173 status_renderable = Status(1174 status,1175 console=self,1176 spinner=spinner,1177 spinner_style=spinner_style,1178 speed=speed,1179 refresh_per_second=refresh_per_second,1180 )1181 return status_renderable1182 1183 def show_cursor(self, show: bool = True) -> bool:1184 """Show or hide the cursor.1185 1186 Args:1187 show (bool, optional): Set visibility of the cursor.1188 """1189 if self.is_terminal:1190 self.control(Control.show_cursor(show))1191 return True1192 return False1193 1194 def set_alt_screen(self, enable: bool = True) -> bool:1195 """Enables alternative screen mode.1196 1197 Note, if you enable this mode, you should ensure that is disabled before1198 the application exits. See :meth:`~rich.Console.screen` for a context manager1199 that handles this for you.1200 