codekingpro/portable-devtools
114k
1import os2import sys3import threading4from abc import ABC, abstractmethod5from dataclasses import dataclass, field6from datetime import datetime7from functools import wraps8from itertools import islice9from math import ceil10from os import PathLike11from time import monotonic12from types import FrameType, ModuleType, TracebackType13from typing import (14 IO,15 TYPE_CHECKING,16 Any,17 Callable,18 Dict,19 Iterable,20 List,21 Literal,22 Mapping,23 NamedTuple,24 Optional,25 Protocol,26 TextIO,27 Tuple,28 Type,29 Union,30 cast,31 runtime_checkable,32)33 34from rich._null_file import NULL_FILE35 36from . import errors, themes37from ._emoji_replace import _emoji_replace38from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT39from ._fileno import get_fileno40from ._log_render import FormatTimeCallable, LogRender41from .align import Align, AlignMethod42from .color import ColorSystem, blend_rgb43from .control import Control44from .emoji import EmojiVariant45from .highlighter import NullHighlighter, ReprHighlighter46from .markup import render as render_markup47from .measure import Measurement, measure_renderables48from .pager import Pager, SystemPager49from .protocol import rich_cast50from .region import Region51from .screen import Screen52from .segment import Segment53from .style import Style, StyleType54from .styled import Styled55from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme56from .text import Text, TextType57from .theme import Theme, ThemeStack58 59if TYPE_CHECKING:60 from ._windows import WindowsConsoleFeatures61 from .live import Live62 from .status import Status63 64JUPYTER_DEFAULT_COLUMNS = 11565JUPYTER_DEFAULT_LINES = 10066WINDOWS = sys.platform == "win32"67 68HighlighterType = Callable[[Union[str, "Text"]], "Text"]69JustifyMethod = Literal["default", "left", "center", "right", "full"]70OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"]71 72 73class NoChange:74 pass75 76 77NO_CHANGE = NoChange()78 79try:80 _STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr]81except Exception:82 _STDIN_FILENO = 083try:84 _STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr]85except Exception:86 _STDOUT_FILENO = 187try:88 _STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr]89except Exception:90 _STDERR_FILENO = 291 92_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)93_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)94 95 96_TERM_COLORS = {97 "kitty": ColorSystem.EIGHT_BIT,98 "256color": ColorSystem.EIGHT_BIT,99 "16color": ColorSystem.STANDARD,100}101 102 103class ConsoleDimensions(NamedTuple):104 """Size of the terminal."""105 106 width: int107 """The width of the console in 'cells'."""108 height: int109 """The height of the console in lines."""110 111 112@dataclass113class ConsoleOptions:114 """Options for __rich_console__ method."""115 116 size: ConsoleDimensions117 """Size of console."""118 legacy_windows: bool119 """legacy_windows: flag for legacy windows."""120 min_width: int121 """Minimum width of renderable."""122 max_width: int123 """Maximum width of renderable."""124 is_terminal: bool125 """True if the target is a terminal, otherwise False."""126 encoding: str127 """Encoding of terminal."""128 max_height: int129 """Height of container (starts as terminal)"""130 justify: Optional[JustifyMethod] = None131 """Justify value override for renderable."""132 overflow: Optional[OverflowMethod] = None133 """Overflow value override for renderable."""134 no_wrap: Optional[bool] = False135 """Disable wrapping for text."""136 highlight: Optional[bool] = None137 """Highlight override for render_str."""138 markup: Optional[bool] = None139 """Enable markup when rendering strings."""140 height: Optional[int] = None141 142 @property143 def ascii_only(self) -> bool:144 """Check if renderables should use ascii only."""145 return not self.encoding.startswith("utf")146 147 def copy(self) -> "ConsoleOptions":148 """Return a copy of the options.149 150 Returns:151 ConsoleOptions: a copy of self.152 """153 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions)154 options.__dict__ = self.__dict__.copy()155 return options156 157 def update(158 self,159 *,160 width: Union[int, NoChange] = NO_CHANGE,161 min_width: Union[int, NoChange] = NO_CHANGE,162 max_width: Union[int, NoChange] = NO_CHANGE,163 justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE,164 overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,165 no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,166 highlight: Union[Optional[bool], NoChange] = NO_CHANGE,167 markup: Union[Optional[bool], NoChange] = NO_CHANGE,168 height: Union[Optional[int], NoChange] = NO_CHANGE,169 ) -> "ConsoleOptions":170 """Update values, return a copy."""171 options = self.copy()172 if not isinstance(width, NoChange):173 options.min_width = options.max_width = max(0, width)174 if not isinstance(min_width, NoChange):175 options.min_width = min_width176 if not isinstance(max_width, NoChange):177 options.max_width = max_width178 if not isinstance(justify, NoChange):179 options.justify = justify180 if not isinstance(overflow, NoChange):181 options.overflow = overflow182 if not isinstance(no_wrap, NoChange):183 options.no_wrap = no_wrap184 if not isinstance(highlight, NoChange):185 options.highlight = highlight186 if not isinstance(markup, NoChange):187 options.markup = markup188 if not isinstance(height, NoChange):189 if height is not None:190 options.max_height = height191 options.height = None if height is None else max(0, height)192 return options193 194 def update_width(self, width: int) -> "ConsoleOptions":195 """Update just the width, return a copy.196 197 Args:198 width (int): New width (sets both min_width and max_width)199 200 Returns:201 ~ConsoleOptions: New console options instance.202 """203 options = self.copy()204 options.min_width = options.max_width = max(0, width)205 return options206 207 def update_height(self, height: int) -> "ConsoleOptions":208 """Update the height, and return a copy.209 210 Args:211 height (int): New height212 213 Returns:214 ~ConsoleOptions: New Console options instance.215 """216 options = self.copy()217 options.max_height = options.height = height218 return options219 220 def reset_height(self) -> "ConsoleOptions":221 """Return a copy of the options with height set to ``None``.222 223 Returns:224 ~ConsoleOptions: New console options instance.225 """226 options = self.copy()227 options.height = None228 return options229 230 def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":231 """Update the width and height, and return a copy.232 233 Args:234 width (int): New width (sets both min_width and max_width).235 height (int): New height.236 237 Returns:238 ~ConsoleOptions: New console options instance.239 """240 options = self.copy()241 options.min_width = options.max_width = max(0, width)242 options.height = options.max_height = height243 return options244 245 246@runtime_checkable247class RichCast(Protocol):248 """An object that may be 'cast' to a console renderable."""249 250 def __rich__(251 self,252 ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover253 ...254 255 256@runtime_checkable257class ConsoleRenderable(Protocol):258 """An object that supports the console protocol."""259 260 def __rich_console__(261 self, console: "Console", options: "ConsoleOptions"262 ) -> "RenderResult": # pragma: no cover263 ...264 265 266# A type that may be rendered by Console.267RenderableType = Union[ConsoleRenderable, RichCast, str]268"""A string or any object that may be rendered by Rich."""269 270# The result of calling a __rich_console__ method.271RenderResult = Iterable[Union[RenderableType, Segment]]272 273_null_highlighter = NullHighlighter()274 275 276class CaptureError(Exception):277 """An error in the Capture context manager."""278 279 280class NewLine:281 """A renderable to generate new line(s)"""282 283 def __init__(self, count: int = 1) -> None:284 self.count = count285 286 def __rich_console__(287 self, console: "Console", options: "ConsoleOptions"288 ) -> Iterable[Segment]:289 yield Segment("\n" * self.count)290 291 292class ScreenUpdate:293 """Render a list of lines at a given offset."""294 295 def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None:296 self._lines = lines297 self.x = x298 self.y = y299 300 def __rich_console__(301 self, console: "Console", options: ConsoleOptions302 ) -> RenderResult:303 x = self.x304 move_to = Control.move_to305 for offset, line in enumerate(self._lines, self.y):306 yield move_to(x, offset)307 yield from line308 309 310class Capture:311 """Context manager to capture the result of printing to the console.312 See :meth:`~rich.console.Console.capture` for how to use.313 314 Args:315 console (Console): A console instance to capture output.316 """317 318 def __init__(self, console: "Console") -> None:319 self._console = console320 self._result: Optional[str] = None321 322 def __enter__(self) -> "Capture":323 self._console.begin_capture()324 return self325 326 def __exit__(327 self,328 exc_type: Optional[Type[BaseException]],329 exc_val: Optional[BaseException],330 exc_tb: Optional[TracebackType],331 ) -> None:332 self._result = self._console.end_capture()333 334 def get(self) -> str:335 """Get the result of the capture."""336 if self._result is None:337 raise CaptureError(338 "Capture result is not available until context manager exits."339 )340 return self._result341 342 343class ThemeContext:344 """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage."""345 346 def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None:347 self.console = console348 self.theme = theme349 self.inherit = inherit350 351 def __enter__(self) -> "ThemeContext":352 self.console.push_theme(self.theme)353 return self354 355 def __exit__(356 self,357 exc_type: Optional[Type[BaseException]],358 exc_val: Optional[BaseException],359 exc_tb: Optional[TracebackType],360 ) -> None:361 self.console.pop_theme()362 363 364class PagerContext:365 """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage."""366 367 def __init__(368 self,369 console: "Console",370 pager: Optional[Pager] = None,371 styles: bool = False,372 links: bool = False,373 ) -> None:374 self._console = console375 self.pager = SystemPager() if pager is None else pager376 self.styles = styles377 self.links = links378 379 def __enter__(self) -> "PagerContext":380 self._console._enter_buffer()381 return self382 383 def __exit__(384 self,385 exc_type: Optional[Type[BaseException]],386 exc_val: Optional[BaseException],387 exc_tb: Optional[TracebackType],388 ) -> None:389 if exc_type is None:390 with self._console._lock:391 buffer: List[Segment] = self._console._buffer[:]392 del self._console._buffer[:]393 segments: Iterable[Segment] = buffer394 if not self.styles:395 segments = Segment.strip_styles(segments)396 elif not self.links:397 segments = Segment.strip_links(segments)398 content = self._console._render_buffer(segments)399 self.pager.show(content)400 self._console._exit_buffer()401 402 403class ScreenContext:404 """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage."""405 406 def __init__(407 self, console: "Console", hide_cursor: bool, style: StyleType = ""408 ) -> None:409 self.console = console410 self.hide_cursor = hide_cursor411 self.screen = Screen(style=style)412 self._changed = False413 414 def update(415 self, *renderables: RenderableType, style: Optional[StyleType] = None416 ) -> None:417 """Update the screen.418 419 Args:420 renderable (RenderableType, optional): Optional renderable to replace current renderable,421 or None for no change. Defaults to None.422 style: (Style, optional): Replacement style, or None for no change. Defaults to None.423 """424 if renderables:425 self.screen.renderable = (426 Group(*renderables) if len(renderables) > 1 else renderables[0]427 )428 if style is not None:429 self.screen.style = style430 self.console.print(self.screen, end="")431 432 def __enter__(self) -> "ScreenContext":433 self._changed = self.console.set_alt_screen(True)434 if self._changed and self.hide_cursor:435 self.console.show_cursor(False)436 return self437 438 def __exit__(439 self,440 exc_type: Optional[Type[BaseException]],441 exc_val: Optional[BaseException],442 exc_tb: Optional[TracebackType],443 ) -> None:444 if self._changed:445 self.console.set_alt_screen(False)446 if self.hide_cursor:447 self.console.show_cursor(True)448 449 450class Group:451 """Takes a group of renderables and returns a renderable object that renders the group.452 453 Args:454 renderables (Iterable[RenderableType]): An iterable of renderable objects.455 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.456 """457 458 def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None:459 self._renderables = renderables460 self.fit = fit461 self._render: Optional[List[RenderableType]] = None462 463 @property464 def renderables(self) -> List["RenderableType"]:465 if self._render is None:466 self._render = list(self._renderables)467 return self._render468 469 def __rich_measure__(470 self, console: "Console", options: "ConsoleOptions"471 ) -> "Measurement":472 if self.fit:473 return measure_renderables(console, options, self.renderables)474 else:475 return Measurement(options.max_width, options.max_width)476 477 def __rich_console__(478 self, console: "Console", options: "ConsoleOptions"479 ) -> RenderResult:480 yield from self.renderables481 482 483def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:484 """A decorator that turns an iterable of renderables in to a group.485 486 Args:487 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.488 """489 490 def decorator(491 method: Callable[..., Iterable[RenderableType]],492 ) -> Callable[..., Group]:493 """Convert a method that returns an iterable of renderables in to a Group."""494 495 @wraps(method)496 def _replace(*args: Any, **kwargs: Any) -> Group:497 renderables = method(*args, **kwargs)498 return Group(*renderables, fit=fit)499 500 return _replace501 502 return decorator503 504 505def _is_jupyter() -> bool: # pragma: no cover506 """Check if we're running in a Jupyter notebook."""507 try:508 get_ipython # type: ignore[name-defined]509 except NameError:510 return False511 ipython = get_ipython() # type: ignore[name-defined]512 shell = ipython.__class__.__name__513 if (514 "google.colab" in str(ipython.__class__)515 or os.getenv("DATABRICKS_RUNTIME_VERSION")516 or shell == "ZMQInteractiveShell"517 ):518 return True # Jupyter notebook or qtconsole519 elif shell == "TerminalInteractiveShell":520 return False # Terminal running IPython521 else:522 return False # Other type (?)523 524 525COLOR_SYSTEMS = {526 "standard": ColorSystem.STANDARD,527 "256": ColorSystem.EIGHT_BIT,528 "truecolor": ColorSystem.TRUECOLOR,529 "windows": ColorSystem.WINDOWS,530}531 532_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()}533 534 535@dataclass536class ConsoleThreadLocals(threading.local):537 """Thread local values for Console context."""538 539 theme_stack: ThemeStack540 buffer: List[Segment] = field(default_factory=list)541 buffer_index: int = 0542 543 544class RenderHook(ABC):545 """Provides hooks in to the render process."""546 547 @abstractmethod548 def process_renderables(549 self, renderables: List[ConsoleRenderable]550 ) -> List[ConsoleRenderable]:551 """Called with a list of objects to render.552 553 This method can return a new list of renderables, or modify and return the same list.554 555 Args:556 renderables (List[ConsoleRenderable]): A number of renderable objects.557 558 Returns:559 List[ConsoleRenderable]: A replacement list of renderables.560 """561 562 563_windows_console_features: Optional["WindowsConsoleFeatures"] = None564 565 566def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover567 global _windows_console_features568 if _windows_console_features is not None:569 return _windows_console_features570 from ._windows import get_windows_console_features571 572 _windows_console_features = get_windows_console_features()573 return _windows_console_features574 575 576def detect_legacy_windows() -> bool:577 """Detect legacy Windows."""578 return WINDOWS and not get_windows_console_features().vt579 580 581class Console:582 """A high level console interface.583 584 Args:585 color_system (str, optional): The color system supported by your terminal,586 either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.587 force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None.588 force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None.589 force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None.590 soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False.591 theme (Theme, optional): An optional style theme object, or ``None`` for default theme.592 stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False.593 file (IO, optional): A file object where the console should write to. Defaults to stdout.594 quiet (bool, Optional): Boolean to suppress all output. Defaults to False.595 width (int, optional): The width of the terminal. Leave as default to auto-detect width.596 height (int, optional): The height of the terminal. Leave as default to auto-detect height.597 style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None.598 no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None.599 tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8.600 record (bool, optional): Boolean to enable recording of terminal output,601 required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False.602 markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True.603 emoji (bool, optional): Enable emoji code. Defaults to True.604 emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.605 highlight (bool, optional): Enable automatic highlighting. Defaults to True.606 log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True.607 log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True.608 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] ".609 highlighter (HighlighterType, optional): Default highlighter.610 legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``.611 safe_box (bool, optional): Restrict box options that don't render on legacy Windows.612 get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log),613 or None for datetime.now.614 get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic.615 """616 617 _environ: Mapping[str, str] = os.environ618 619 def __init__(620 self,621 *,622 color_system: Optional[623 Literal["auto", "standard", "256", "truecolor", "windows"]624 ] = "auto",625 force_terminal: Optional[bool] = None,626 force_jupyter: Optional[bool] = None,627 force_interactive: Optional[bool] = None,628 soft_wrap: bool = False,629 theme: Optional[Theme] = None,630 stderr: bool = False,631 file: Optional[IO[str]] = None,632 quiet: bool = False,633 width: Optional[int] = None,634 height: Optional[int] = None,635 style: Optional[StyleType] = None,636 no_color: Optional[bool] = None,637 tab_size: int = 8,638 record: bool = False,639 markup: bool = True,640 emoji: bool = True,641 emoji_variant: Optional[EmojiVariant] = None,642 highlight: bool = True,643 log_time: bool = True,644 log_path: bool = True,645 log_time_format: Union[str, FormatTimeCallable] = "[%X]",646 highlighter: Optional["HighlighterType"] = ReprHighlighter(),647 legacy_windows: Optional[bool] = None,648 safe_box: bool = True,649 get_datetime: Optional[Callable[[], datetime]] = None,650 get_time: Optional[Callable[[], float]] = None,651 _environ: Optional[Mapping[str, str]] = None,652 ):653 # Copy of os.environ allows us to replace it for testing654 if _environ is not None:655 self._environ = _environ656 657 self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter658 if self.is_jupyter:659 if width is None:660 jupyter_columns = self._environ.get("JUPYTER_COLUMNS")661 if jupyter_columns is not None and jupyter_columns.isdigit():662 width = int(jupyter_columns)663 else:664 width = JUPYTER_DEFAULT_COLUMNS665 if height is None:666 jupyter_lines = self._environ.get("JUPYTER_LINES")667 if jupyter_lines is not None and jupyter_lines.isdigit():668 height = int(jupyter_lines)669 else:670 height = JUPYTER_DEFAULT_LINES671 672 self.tab_size = tab_size673 self.record = record674 self._markup = markup675 self._emoji = emoji676 self._emoji_variant: Optional[EmojiVariant] = emoji_variant677 self._highlight = highlight678 self.legacy_windows: bool = (679 (detect_legacy_windows() and not self.is_jupyter)680 if legacy_windows is None681 else legacy_windows682 )683 684 if width is None:685 columns = self._environ.get("COLUMNS")686 if columns is not None and columns.isdigit():687 width = int(columns) - self.legacy_windows688 if height is None:689 lines = self._environ.get("LINES")690 if lines is not None and lines.isdigit():691 height = int(lines)692 693 self.soft_wrap = soft_wrap694 self._width = width695 self._height = height696 697 self._color_system: Optional[ColorSystem]698 699 self._force_terminal = None700 if force_terminal is not None:701 self._force_terminal = force_terminal702 703 self._file = file704 self.quiet = quiet705 self.stderr = stderr706 707 if color_system is None:708 self._color_system = None709 elif color_system == "auto":710 self._color_system = self._detect_color_system()711 else:712 self._color_system = COLOR_SYSTEMS[color_system]713 714 self._lock = threading.RLock()715 self._log_render = LogRender(716 show_time=log_time,717 show_path=log_path,718 time_format=log_time_format,719 )720 self.highlighter: HighlighterType = highlighter or _null_highlighter721 self.safe_box = safe_box722 self.get_datetime = get_datetime or datetime.now723 self.get_time = get_time or monotonic724 self.style = style725 self.no_color = (726 no_color727 if no_color is not None728 else self._environ.get("NO_COLOR", "") != ""729 )730 if force_interactive is None:731 tty_interactive = self._environ.get("TTY_INTERACTIVE", None)732 if tty_interactive is not None:733 if tty_interactive == "0":734 force_interactive = False735 elif tty_interactive == "1":736 force_interactive = True737 738 self.is_interactive = (739 (self.is_terminal and not self.is_dumb_terminal)740 if force_interactive is None741 else force_interactive742 )743 744 self._record_buffer_lock = threading.RLock()745 self._thread_locals = ConsoleThreadLocals(746 theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme)747 )748 self._record_buffer: List[Segment] = []749 self._render_hooks: List[RenderHook] = []750 self._live_stack: List[Live] = []751 self._is_alt_screen = False752 753 def __repr__(self) -> str:754 return f"<console width={self.width} {self._color_system!s}>"755 756 @property757 def file(self) -> IO[str]:758 """Get the file object to write to."""759 file = self._file or (sys.stderr if self.stderr else sys.stdout)760 file = getattr(file, "rich_proxied_file", file)761 if file is None:762 file = NULL_FILE763 return file764 765 @file.setter766 def file(self, new_file: IO[str]) -> None:767 """Set a new file object."""768 self._file = new_file769 770 @property771 def _buffer(self) -> List[Segment]:772 """Get a thread local buffer."""773 return self._thread_locals.buffer774 775 @property776 def _buffer_index(self) -> int:777 """Get a thread local buffer."""778 return self._thread_locals.buffer_index779 780 @_buffer_index.setter781 def _buffer_index(self, value: int) -> None:782 self._thread_locals.buffer_index = value783 784 @property785 def _theme_stack(self) -> ThemeStack:786 """Get the thread local theme stack."""787 return self._thread_locals.theme_stack788 789 def _detect_color_system(self) -> Optional[ColorSystem]:790 """Detect color system from env vars."""791 if self.is_jupyter:792 return ColorSystem.TRUECOLOR793 if not self.is_terminal or self.is_dumb_terminal:794 return None795 if WINDOWS: # pragma: no cover796 if self.legacy_windows: # pragma: no cover797 return ColorSystem.WINDOWS798 windows_console_features = get_windows_console_features()799 return (800 ColorSystem.TRUECOLOR801 if windows_console_features.truecolor802 else ColorSystem.EIGHT_BIT803 )804 else:805 color_term = self._environ.get("COLORTERM", "").strip().lower()806 if color_term in ("truecolor", "24bit"):807 return ColorSystem.TRUECOLOR808 term = self._environ.get("TERM", "").strip().lower()809 _term_name, _hyphen, colors = term.rpartition("-")810 color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD)811 return color_system812 813 def _enter_buffer(self) -> None:814 """Enter in to a buffer context, and buffer all output."""815 self._buffer_index += 1816 817 def _exit_buffer(self) -> None:818 """Leave buffer context, and render content if required."""819 self._buffer_index -= 1820 self._check_buffer()821 822 def set_live(self, live: "Live") -> bool:823 """Set Live instance. Used by Live context manager (no need to call directly).824 825 Args:826 live (Live): Live instance using this Console.827 828 Returns:829 Boolean that indicates if the live is the topmost of the stack.830 831 Raises:832 errors.LiveError: If this Console has a Live context currently active.833 """834 with self._lock:835 self._live_stack.append(live)836 return len(self._live_stack) == 1837 838 def clear_live(self) -> None:839 """Clear the Live instance. Used by the Live context manager (no need to call directly)."""840 with self._lock:841 self._live_stack.pop()842 843 def push_render_hook(self, hook: RenderHook) -> None:844 """Add a new render hook to the stack.845 846 Args:847 hook (RenderHook): Render hook instance.848 """849 with self._lock:850 self._render_hooks.append(hook)851 852 def pop_render_hook(self) -> None:853 """Pop the last renderhook from the stack."""854 with self._lock:855 self._render_hooks.pop()856 857 def __enter__(self) -> "Console":858 """Own context manager to enter buffer context."""859 self._enter_buffer()860 return self861 862 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:863 """Exit buffer context."""864 self._exit_buffer()865 866 def begin_capture(self) -> None:867 """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output."""868 self._enter_buffer()869 870 def end_capture(self) -> str:871 """End capture mode and return captured string.872 873 Returns:874 str: Console output.875 """876 render_result = self._render_buffer(self._buffer)877 del self._buffer[:]878 self._exit_buffer()879 return render_result880 881 def push_theme(self, theme: Theme, *, inherit: bool = True) -> None:882 """Push a new theme on to the top of the stack, replacing the styles from the previous theme.883 Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather884 than calling this method directly.885 886 Args:887 theme (Theme): A theme instance.888 inherit (bool, optional): Inherit existing styles. Defaults to True.889 """890 self._theme_stack.push_theme(theme, inherit=inherit)891 892 def pop_theme(self) -> None:893 """Remove theme from top of stack, restoring previous theme."""894 self._theme_stack.pop_theme()895 896 def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext:897 """Use a different theme for the duration of the context manager.898 899 Args:900 theme (Theme): Theme instance to user.901 inherit (bool, optional): Inherit existing console styles. Defaults to True.902 903 Returns:904 ThemeContext: [description]905 """906 return ThemeContext(self, theme, inherit)907 908 @property909 def color_system(self) -> Optional[str]:910 """Get color system string.911 912 Returns:913 Optional[str]: "standard", "256" or "truecolor".914 """915 916 if self._color_system is not None:917 return _COLOR_SYSTEMS_NAMES[self._color_system]918 else:919 return None920 921 @property922 def encoding(self) -> str:923 """Get the encoding of the console file, e.g. ``"utf-8"``.924 925 Returns:926 str: A standard encoding string.927 """928 return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower()929 930 @property931 def is_terminal(self) -> bool:932 """Check if the console is writing to a terminal.933 934 Returns:935 bool: True if the console writing to a device capable of936 understanding escape sequences, otherwise False.937 """938 # If dev has explicitly set this value, return it939 if self._force_terminal is not None:940 return self._force_terminal941 942 # Fudge for Idle943 if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith(944 "idlelib"945 ):946 # Return False for Idle which claims to be a tty but can't handle ansi codes947 return False948 949 if self.is_jupyter:950 # return False for Jupyter, which may have FORCE_COLOR set951 return False952 953 environ = self._environ954 955 tty_compatible = environ.get("TTY_COMPATIBLE", "")956 # 0 indicates device is not tty compatible957 if tty_compatible == "0":958 return False959 # 1 indicates device is tty compatible960 if tty_compatible == "1":961 return True962 963 # https://force-color.org/964 force_color = environ.get("FORCE_COLOR")965 if force_color is not None:966 return force_color != ""967 968 # Any other value defaults to auto detect969 isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)970 try:971 return False if isatty is None else isatty()972 except ValueError:973 # in some situation (at the end of a pytest run for example) isatty() can raise974 # ValueError: I/O operation on closed file975 # return False because we aren't in a terminal anymore976 return False977 978 @property979 def is_dumb_terminal(self) -> bool:980 """Detect dumb terminal.981 982 Returns:983 bool: True if writing to a dumb terminal, otherwise False.984 985 """986 _term = self._environ.get("TERM", "")987 is_dumb = _term.lower() in ("dumb", "unknown")988 return self.is_terminal and is_dumb989 990 @property991 def options(self) -> ConsoleOptions:992 """Get default console options."""993 size = self.size994 return ConsoleOptions(995 max_height=size.height,996 size=size,997 legacy_windows=self.legacy_windows,998 min_width=1,999 max_width=size.width,1000 encoding=self.encoding,1001 is_terminal=self.is_terminal,1002 )1003 1004 @property1005 def size(self) -> ConsoleDimensions:1006 """Get the size of the console.1007 1008 Returns:1009 ConsoleDimensions: A named tuple containing the dimensions.1010 """1011 1012 if self._width is not None and self._height is not None:1013 return ConsoleDimensions(self._width - self.legacy_windows, self._height)1014 1015 if self.is_dumb_terminal:1016 return ConsoleDimensions(80, 25)1017 1018 width: Optional[int] = None1019 height: Optional[int] = None1020 1021 streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS1022 for file_descriptor in streams:1023 try:1024 width, height = os.get_terminal_size(file_descriptor)1025 except (AttributeError, ValueError, OSError): # Probably not a terminal1026 pass1027 else:1028 break1029 1030 columns = self._environ.get("COLUMNS")1031 if columns is not None and columns.isdigit():1032 width = int(columns)1033 lines = self._environ.get("LINES")1034 if lines is not None and lines.isdigit():1035 height = int(lines)1036 1037 # get_terminal_size can report 0, 0 if run from pseudo-terminal1038 width = width or 801039 height = height or 251040 return ConsoleDimensions(1041 width - self.legacy_windows if self._width is None else self._width,1042 height if self._height is None else self._height,1043 )1044 1045 @size.setter1046 def size(self, new_size: Tuple[int, int]) -> None:1047 """Set a new size for the terminal.1048 1049 Args:1050 new_size (Tuple[int, int]): New width and height.1051 """1052 width, height = new_size1053 self._width = width1054 self._height = height1055 1056 @property1057 def width(self) -> int:1058 """Get the width of the console.1059 1060 Returns:1061 int: The width (in characters) of the console.1062 """1063 return self.size.width1064 1065 @width.setter1066 def width(self, width: int) -> None:1067 """Set width.1068 1069 Args:1070 width (int): New width.1071 """1072 self._width = width1073 1074 @property1075 def height(self) -> int:1076 """Get the height of the console.1077 1078 Returns:1079 int: The height (in lines) of the console.1080 """1081 return self.size.height1082 1083 @height.setter1084 def height(self, height: int) -> None:1085 """Set height.1086 1087 Args:1088 height (int): new height.1089 """1090 self._height = height1091 1092 def bell(self) -> None:1093 """Play a 'bell' sound (if supported by the terminal)."""1094 self.control(Control.bell())1095 1096 def capture(self) -> Capture:1097 """A context manager to *capture* the result of print() or log() in a string,1098 rather than writing it to the console.1099 1100 Example:1101 >>> from rich.console import Console1102 >>> console = Console()1103 >>> with console.capture() as capture:1104 ... console.print("[bold magenta]Hello World[/]")1105 >>> print(capture.get())1106 1107 Returns:1108 Capture: Context manager with disables writing to the terminal.1109 """1110 capture = Capture(self)1111 return capture1112 1113 def pager(1114 self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False1115 ) -> PagerContext:1116 """A context manager to display anything printed within a "pager". The pager application1117 is defined by the system and will typically support at least pressing a key to scroll.1118 1119 Args:1120 pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None.1121 styles (bool, optional): Show styles in pager. Defaults to False.1122 links (bool, optional): Show links in pager. Defaults to False.1123 1124 Example:1125 >>> from rich.console import Console1126 >>> from rich.__main__ import make_test_card1127 >>> console = Console()1128 >>> with console.pager():1129 console.print(make_test_card())1130 1131 Returns:1132 PagerContext: A context manager.1133 """1134 return PagerContext(self, pager=pager, styles=styles, links=links)1135 1136 def line(self, count: int = 1) -> None:1137 """Write new line(s).1138 1139 Args:1140 count (int, optional): Number of new lines. Defaults to 1.1141 """1142 1143 assert count >= 0, "count must be >= 0"1144 self.print(NewLine(count))1145 1146 def clear(self, home: bool = True) -> None:1147 """Clear the screen.1148 1149 Args:1150 home (bool, optional): Also move the cursor to 'home' position. Defaults to True.1151 """1152 if home:1153 self.control(Control.clear(), Control.home())1154 else:1155 self.control(Control.clear())1156 1157 def status(1158 self,1159 status: RenderableType,1160 *,1161 spinner: str = "dots",1162 spinner_style: StyleType = "status.spinner",1163 speed: float = 1.0,1164 refresh_per_second: float = 12.5,1165 ) -> "Status":1166 """Display a status and spinner.1167 1168 Args:1169 status (RenderableType): A status renderable (str or Text typically).1170 spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".1171 spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".1172 speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.1173 refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.1174 1175 Returns:1176 Status: A Status object that may be used as a context manager.1177 """1178 from .status import Status1179 1180 status_renderable = Status(1181 status,1182 console=self,1183 spinner=spinner,1184 spinner_style=spinner_style,1185 speed=speed,1186 refresh_per_second=refresh_per_second,1187 )1188 return status_renderable1189 1190 def show_cursor(self, show: bool = True) -> bool:1191 """Show or hide the cursor.1192 1193 Args:1194 show (bool, optional): Set visibility of the cursor.1195 """1196 if self.is_terminal:1197 self.control(Control.show_cursor(show))1198 return True1199 return False1200 