Spaces:
Sleeping
Sleeping
| """ | |
| Renders the command line on the console. | |
| (Redraws parts of the input line that were changed.) | |
| """ | |
| from __future__ import annotations | |
| from asyncio import FIRST_COMPLETED, Future, ensure_future, sleep, wait | |
| from collections import deque | |
| from enum import Enum | |
| from typing import TYPE_CHECKING, Any, Callable, Dict, Hashable | |
| from prompt_toolkit.application.current import get_app | |
| from prompt_toolkit.cursor_shapes import CursorShape | |
| from prompt_toolkit.data_structures import Point, Size | |
| from prompt_toolkit.filters import FilterOrBool, to_filter | |
| from prompt_toolkit.formatted_text import AnyFormattedText, to_formatted_text | |
| from prompt_toolkit.layout.mouse_handlers import MouseHandlers | |
| from prompt_toolkit.layout.screen import Char, Screen, WritePosition | |
| from prompt_toolkit.output import ColorDepth, Output | |
| from prompt_toolkit.styles import ( | |
| Attrs, | |
| BaseStyle, | |
| DummyStyleTransformation, | |
| StyleTransformation, | |
| ) | |
| if TYPE_CHECKING: | |
| from prompt_toolkit.application import Application | |
| from prompt_toolkit.layout.layout import Layout | |
| __all__ = [ | |
| "Renderer", | |
| "print_formatted_text", | |
| ] | |
| def _output_screen_diff( | |
| app: Application[Any], | |
| output: Output, | |
| screen: Screen, | |
| current_pos: Point, | |
| color_depth: ColorDepth, | |
| previous_screen: Screen | None, | |
| last_style: str | None, | |
| is_done: bool, # XXX: drop is_done | |
| full_screen: bool, | |
| attrs_for_style_string: _StyleStringToAttrsCache, | |
| style_string_has_style: _StyleStringHasStyleCache, | |
| size: Size, | |
| previous_width: int, | |
| ) -> tuple[Point, str | None]: | |
| """ | |
| Render the diff between this screen and the previous screen. | |
| This takes two `Screen` instances. The one that represents the output like | |
| it was during the last rendering and one that represents the current | |
| output raster. Looking at these two `Screen` instances, this function will | |
| render the difference by calling the appropriate methods of the `Output` | |
| object that only paint the changes to the terminal. | |
| This is some performance-critical code which is heavily optimized. | |
| Don't change things without profiling first. | |
| :param current_pos: Current cursor position. | |
| :param last_style: The style string, used for drawing the last drawn | |
| character. (Color/attributes.) | |
| :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance. | |
| :param width: The width of the terminal. | |
| :param previous_width: The width of the terminal during the last rendering. | |
| """ | |
| width, height = size.columns, size.rows | |
| #: Variable for capturing the output. | |
| write = output.write | |
| write_raw = output.write_raw | |
| # Create locals for the most used output methods. | |
| # (Save expensive attribute lookups.) | |
| _output_set_attributes = output.set_attributes | |
| _output_reset_attributes = output.reset_attributes | |
| _output_cursor_forward = output.cursor_forward | |
| _output_cursor_up = output.cursor_up | |
| _output_cursor_backward = output.cursor_backward | |
| # Hide cursor before rendering. (Avoid flickering.) | |
| output.hide_cursor() | |
| def reset_attributes() -> None: | |
| "Wrapper around Output.reset_attributes." | |
| nonlocal last_style | |
| _output_reset_attributes() | |
| last_style = None # Forget last char after resetting attributes. | |
| def move_cursor(new: Point) -> Point: | |
| "Move cursor to this `new` point. Returns the given Point." | |
| current_x, current_y = current_pos.x, current_pos.y | |
| if new.y > current_y: | |
| # Use newlines instead of CURSOR_DOWN, because this might add new lines. | |
| # CURSOR_DOWN will never create new lines at the bottom. | |
| # Also reset attributes, otherwise the newline could draw a | |
| # background color. | |
| reset_attributes() | |
| write("\r\n" * (new.y - current_y)) | |
| current_x = 0 | |
| _output_cursor_forward(new.x) | |
| return new | |
| elif new.y < current_y: | |
| _output_cursor_up(current_y - new.y) | |
| if current_x >= width - 1: | |
| write("\r") | |
| _output_cursor_forward(new.x) | |
| elif new.x < current_x or current_x >= width - 1: | |
| _output_cursor_backward(current_x - new.x) | |
| elif new.x > current_x: | |
| _output_cursor_forward(new.x - current_x) | |
| return new | |
| def output_char(char: Char) -> None: | |
| """ | |
| Write the output of this character. | |
| """ | |
| nonlocal last_style | |
| # If the last printed character has the same style, don't output the | |
| # style again. | |
| if last_style == char.style: | |
| write(char.char) | |
| else: | |
| # Look up `Attr` for this style string. Only set attributes if different. | |
| # (Two style strings can still have the same formatting.) | |
| # Note that an empty style string can have formatting that needs to | |
| # be applied, because of style transformations. | |
| new_attrs = attrs_for_style_string[char.style] | |
| if not last_style or new_attrs != attrs_for_style_string[last_style]: | |
| _output_set_attributes(new_attrs, color_depth) | |
| write(char.char) | |
| last_style = char.style | |
| def get_max_column_index(row: dict[int, Char]) -> int: | |
| """ | |
| Return max used column index, ignoring whitespace (without style) at | |
| the end of the line. This is important for people that copy/paste | |
| terminal output. | |
| There are two reasons we are sometimes seeing whitespace at the end: | |
| - `BufferControl` adds a trailing space to each line, because it's a | |
| possible cursor position, so that the line wrapping won't change if | |
| the cursor position moves around. | |
| - The `Window` adds a style class to the current line for highlighting | |
| (cursor-line). | |
| """ | |
| numbers = ( | |
| index | |
| for index, cell in row.items() | |
| if cell.char != " " or style_string_has_style[cell.style] | |
| ) | |
| return max(numbers, default=0) | |
| # Render for the first time: reset styling. | |
| if not previous_screen: | |
| reset_attributes() | |
| # Disable autowrap. (When entering a the alternate screen, or anytime when | |
| # we have a prompt. - In the case of a REPL, like IPython, people can have | |
| # background threads, and it's hard for debugging if their output is not | |
| # wrapped.) | |
| if not previous_screen or not full_screen: | |
| output.disable_autowrap() | |
| # When the previous screen has a different size, redraw everything anyway. | |
| # Also when we are done. (We might take up less rows, so clearing is important.) | |
| if ( | |
| is_done or not previous_screen or previous_width != width | |
| ): # XXX: also consider height?? | |
| current_pos = move_cursor(Point(x=0, y=0)) | |
| reset_attributes() | |
| output.erase_down() | |
| previous_screen = Screen() | |
| # Get height of the screen. | |
| # (height changes as we loop over data_buffer, so remember the current value.) | |
| # (Also make sure to clip the height to the size of the output.) | |
| current_height = min(screen.height, height) | |
| # Loop over the rows. | |
| row_count = min(max(screen.height, previous_screen.height), height) | |
| for y in range(row_count): | |
| new_row = screen.data_buffer[y] | |
| previous_row = previous_screen.data_buffer[y] | |
| zero_width_escapes_row = screen.zero_width_escapes[y] | |
| new_max_line_len = min(width - 1, get_max_column_index(new_row)) | |
| previous_max_line_len = min(width - 1, get_max_column_index(previous_row)) | |
| # Loop over the columns. | |
| c = 0 # Column counter. | |
| while c <= new_max_line_len: | |
| new_char = new_row[c] | |
| old_char = previous_row[c] | |
| char_width = new_char.width or 1 | |
| # When the old and new character at this position are different, | |
| # draw the output. (Because of the performance, we don't call | |
| # `Char.__ne__`, but inline the same expression.) | |
| if new_char.char != old_char.char or new_char.style != old_char.style: | |
| current_pos = move_cursor(Point(x=c, y=y)) | |
| # Send injected escape sequences to output. | |
| if c in zero_width_escapes_row: | |
| write_raw(zero_width_escapes_row[c]) | |
| output_char(new_char) | |
| current_pos = Point(x=current_pos.x + char_width, y=current_pos.y) | |
| c += char_width | |
| # If the new line is shorter, trim it. | |
| if previous_screen and new_max_line_len < previous_max_line_len: | |
| current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y)) | |
| reset_attributes() | |
| output.erase_end_of_line() | |
| # Correctly reserve vertical space as required by the layout. | |
| # When this is a new screen (drawn for the first time), or for some reason | |
| # higher than the previous one. Move the cursor once to the bottom of the | |
| # output. That way, we're sure that the terminal scrolls up, even when the | |
| # lower lines of the canvas just contain whitespace. | |
| # The most obvious reason that we actually want this behavior is the avoid | |
| # the artifact of the input scrolling when the completion menu is shown. | |
| # (If the scrolling is actually wanted, the layout can still be build in a | |
| # way to behave that way by setting a dynamic height.) | |
| if current_height > previous_screen.height: | |
| current_pos = move_cursor(Point(x=0, y=current_height - 1)) | |
| # Move cursor: | |
| if is_done: | |
| current_pos = move_cursor(Point(x=0, y=current_height)) | |
| output.erase_down() | |
| else: | |
| current_pos = move_cursor(screen.get_cursor_position(app.layout.current_window)) | |
| if is_done or not full_screen: | |
| output.enable_autowrap() | |
| # Always reset the color attributes. This is important because a background | |
| # thread could print data to stdout and we want that to be displayed in the | |
| # default colors. (Also, if a background color has been set, many terminals | |
| # give weird artifacts on resize events.) | |
| reset_attributes() | |
| if screen.show_cursor or is_done: | |
| output.show_cursor() | |
| return current_pos, last_style | |
| class HeightIsUnknownError(Exception): | |
| "Information unavailable. Did not yet receive the CPR response." | |
| class _StyleStringToAttrsCache(Dict[str, Attrs]): | |
| """ | |
| A cache structure that maps style strings to :class:`.Attr`. | |
| (This is an important speed up.) | |
| """ | |
| def __init__( | |
| self, | |
| get_attrs_for_style_str: Callable[[str], Attrs], | |
| style_transformation: StyleTransformation, | |
| ) -> None: | |
| self.get_attrs_for_style_str = get_attrs_for_style_str | |
| self.style_transformation = style_transformation | |
| def __missing__(self, style_str: str) -> Attrs: | |
| attrs = self.get_attrs_for_style_str(style_str) | |
| attrs = self.style_transformation.transform_attrs(attrs) | |
| self[style_str] = attrs | |
| return attrs | |
| class _StyleStringHasStyleCache(Dict[str, bool]): | |
| """ | |
| Cache for remember which style strings don't render the default output | |
| style (default fg/bg, no underline and no reverse and no blink). That way | |
| we know that we should render these cells, even when they're empty (when | |
| they contain a space). | |
| Note: we don't consider bold/italic/hidden because they don't change the | |
| output if there's no text in the cell. | |
| """ | |
| def __init__(self, style_string_to_attrs: dict[str, Attrs]) -> None: | |
| self.style_string_to_attrs = style_string_to_attrs | |
| def __missing__(self, style_str: str) -> bool: | |
| attrs = self.style_string_to_attrs[style_str] | |
| is_default = bool( | |
| attrs.color | |
| or attrs.bgcolor | |
| or attrs.underline | |
| or attrs.strike | |
| or attrs.blink | |
| or attrs.reverse | |
| ) | |
| self[style_str] = is_default | |
| return is_default | |
| class CPR_Support(Enum): | |
| "Enum: whether or not CPR is supported." | |
| SUPPORTED = "SUPPORTED" | |
| NOT_SUPPORTED = "NOT_SUPPORTED" | |
| UNKNOWN = "UNKNOWN" | |
| class Renderer: | |
| """ | |
| Typical usage: | |
| :: | |
| output = Vt100_Output.from_pty(sys.stdout) | |
| r = Renderer(style, output) | |
| r.render(app, layout=...) | |
| """ | |
| CPR_TIMEOUT = 2 # Time to wait until we consider CPR to be not supported. | |
| def __init__( | |
| self, | |
| style: BaseStyle, | |
| output: Output, | |
| full_screen: bool = False, | |
| mouse_support: FilterOrBool = False, | |
| cpr_not_supported_callback: Callable[[], None] | None = None, | |
| ) -> None: | |
| self.style = style | |
| self.output = output | |
| self.full_screen = full_screen | |
| self.mouse_support = to_filter(mouse_support) | |
| self.cpr_not_supported_callback = cpr_not_supported_callback | |
| self._in_alternate_screen = False | |
| self._mouse_support_enabled = False | |
| self._bracketed_paste_enabled = False | |
| self._cursor_key_mode_reset = False | |
| # Future set when we are waiting for a CPR flag. | |
| self._waiting_for_cpr_futures: deque[Future[None]] = deque() | |
| self.cpr_support = CPR_Support.UNKNOWN | |
| if not output.responds_to_cpr: | |
| self.cpr_support = CPR_Support.NOT_SUPPORTED | |
| # Cache for the style. | |
| self._attrs_for_style: _StyleStringToAttrsCache | None = None | |
| self._style_string_has_style: _StyleStringHasStyleCache | None = None | |
| self._last_style_hash: Hashable | None = None | |
| self._last_transformation_hash: Hashable | None = None | |
| self._last_color_depth: ColorDepth | None = None | |
| self.reset(_scroll=True) | |
| def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None: | |
| # Reset position | |
| self._cursor_pos = Point(x=0, y=0) | |
| # Remember the last screen instance between renderers. This way, | |
| # we can create a `diff` between two screens and only output the | |
| # difference. It's also to remember the last height. (To show for | |
| # instance a toolbar at the bottom position.) | |
| self._last_screen: Screen | None = None | |
| self._last_size: Size | None = None | |
| self._last_style: str | None = None | |
| self._last_cursor_shape: CursorShape | None = None | |
| # Default MouseHandlers. (Just empty.) | |
| self.mouse_handlers = MouseHandlers() | |
| #: Space from the top of the layout, until the bottom of the terminal. | |
| #: We don't know this until a `report_absolute_cursor_row` call. | |
| self._min_available_height = 0 | |
| # In case of Windows, also make sure to scroll to the current cursor | |
| # position. (Only when rendering the first time.) | |
| # It does nothing for vt100 terminals. | |
| if _scroll: | |
| self.output.scroll_buffer_to_prompt() | |
| # Quit alternate screen. | |
| if self._in_alternate_screen and leave_alternate_screen: | |
| self.output.quit_alternate_screen() | |
| self._in_alternate_screen = False | |
| # Disable mouse support. | |
| if self._mouse_support_enabled: | |
| self.output.disable_mouse_support() | |
| self._mouse_support_enabled = False | |
| # Disable bracketed paste. | |
| if self._bracketed_paste_enabled: | |
| self.output.disable_bracketed_paste() | |
| self._bracketed_paste_enabled = False | |
| self.output.reset_cursor_shape() | |
| # NOTE: No need to set/reset cursor key mode here. | |
| # Flush output. `disable_mouse_support` needs to write to stdout. | |
| self.output.flush() | |
| def last_rendered_screen(self) -> Screen | None: | |
| """ | |
| The `Screen` class that was generated during the last rendering. | |
| This can be `None`. | |
| """ | |
| return self._last_screen | |
| def height_is_known(self) -> bool: | |
| """ | |
| True when the height from the cursor until the bottom of the terminal | |
| is known. (It's often nicer to draw bottom toolbars only if the height | |
| is known, in order to avoid flickering when the CPR response arrives.) | |
| """ | |
| if self.full_screen or self._min_available_height > 0: | |
| return True | |
| try: | |
| self._min_available_height = self.output.get_rows_below_cursor_position() | |
| return True | |
| except NotImplementedError: | |
| return False | |
| def rows_above_layout(self) -> int: | |
| """ | |
| Return the number of rows visible in the terminal above the layout. | |
| """ | |
| if self._in_alternate_screen: | |
| return 0 | |
| elif self._min_available_height > 0: | |
| total_rows = self.output.get_size().rows | |
| last_screen_height = self._last_screen.height if self._last_screen else 0 | |
| return total_rows - max(self._min_available_height, last_screen_height) | |
| else: | |
| raise HeightIsUnknownError("Rows above layout is unknown.") | |
| def request_absolute_cursor_position(self) -> None: | |
| """ | |
| Get current cursor position. | |
| We do this to calculate the minimum available height that we can | |
| consume for rendering the prompt. This is the available space below te | |
| cursor. | |
| For vt100: Do CPR request. (answer will arrive later.) | |
| For win32: Do API call. (Answer comes immediately.) | |
| """ | |
| # Only do this request when the cursor is at the top row. (after a | |
| # clear or reset). We will rely on that in `report_absolute_cursor_row`. | |
| assert self._cursor_pos.y == 0 | |
| # In full-screen mode, always use the total height as min-available-height. | |
| if self.full_screen: | |
| self._min_available_height = self.output.get_size().rows | |
| return | |
| # For Win32, we have an API call to get the number of rows below the | |
| # cursor. | |
| try: | |
| self._min_available_height = self.output.get_rows_below_cursor_position() | |
| return | |
| except NotImplementedError: | |
| pass | |
| # Use CPR. | |
| if self.cpr_support == CPR_Support.NOT_SUPPORTED: | |
| return | |
| def do_cpr() -> None: | |
| # Asks for a cursor position report (CPR). | |
| self._waiting_for_cpr_futures.append(Future()) | |
| self.output.ask_for_cpr() | |
| if self.cpr_support == CPR_Support.SUPPORTED: | |
| do_cpr() | |
| return | |
| # If we don't know whether CPR is supported, only do a request if | |
| # none is pending, and test it, using a timer. | |
| if self.waiting_for_cpr: | |
| return | |
| do_cpr() | |
| async def timer() -> None: | |
| await sleep(self.CPR_TIMEOUT) | |
| # Not set in the meantime -> not supported. | |
| if self.cpr_support == CPR_Support.UNKNOWN: | |
| self.cpr_support = CPR_Support.NOT_SUPPORTED | |
| if self.cpr_not_supported_callback: | |
| # Make sure to call this callback in the main thread. | |
| self.cpr_not_supported_callback() | |
| get_app().create_background_task(timer()) | |
| def report_absolute_cursor_row(self, row: int) -> None: | |
| """ | |
| To be called when we know the absolute cursor position. | |
| (As an answer of a "Cursor Position Request" response.) | |
| """ | |
| self.cpr_support = CPR_Support.SUPPORTED | |
| # Calculate the amount of rows from the cursor position until the | |
| # bottom of the terminal. | |
| total_rows = self.output.get_size().rows | |
| rows_below_cursor = total_rows - row + 1 | |
| # Set the minimum available height. | |
| self._min_available_height = rows_below_cursor | |
| # Pop and set waiting for CPR future. | |
| try: | |
| f = self._waiting_for_cpr_futures.popleft() | |
| except IndexError: | |
| pass # Received CPR response without having a CPR. | |
| else: | |
| f.set_result(None) | |
| def waiting_for_cpr(self) -> bool: | |
| """ | |
| Waiting for CPR flag. True when we send the request, but didn't got a | |
| response. | |
| """ | |
| return bool(self._waiting_for_cpr_futures) | |
| async def wait_for_cpr_responses(self, timeout: int = 1) -> None: | |
| """ | |
| Wait for a CPR response. | |
| """ | |
| cpr_futures = list(self._waiting_for_cpr_futures) # Make copy. | |
| # When there are no CPRs in the queue. Don't do anything. | |
| if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED: | |
| return None | |
| async def wait_for_responses() -> None: | |
| for response_f in cpr_futures: | |
| await response_f | |
| async def wait_for_timeout() -> None: | |
| await sleep(timeout) | |
| # Got timeout, erase queue. | |
| for response_f in cpr_futures: | |
| response_f.cancel() | |
| self._waiting_for_cpr_futures = deque() | |
| tasks = { | |
| ensure_future(wait_for_responses()), | |
| ensure_future(wait_for_timeout()), | |
| } | |
| _, pending = await wait(tasks, return_when=FIRST_COMPLETED) | |
| for task in pending: | |
| task.cancel() | |
| def render( | |
| self, app: Application[Any], layout: Layout, is_done: bool = False | |
| ) -> None: | |
| """ | |
| Render the current interface to the output. | |
| :param is_done: When True, put the cursor at the end of the interface. We | |
| won't print any changes to this part. | |
| """ | |
| output = self.output | |
| # Enter alternate screen. | |
| if self.full_screen and not self._in_alternate_screen: | |
| self._in_alternate_screen = True | |
| output.enter_alternate_screen() | |
| # Enable bracketed paste. | |
| if not self._bracketed_paste_enabled: | |
| self.output.enable_bracketed_paste() | |
| self._bracketed_paste_enabled = True | |
| # Reset cursor key mode. | |
| if not self._cursor_key_mode_reset: | |
| self.output.reset_cursor_key_mode() | |
| self._cursor_key_mode_reset = True | |
| # Enable/disable mouse support. | |
| needs_mouse_support = self.mouse_support() | |
| if needs_mouse_support and not self._mouse_support_enabled: | |
| output.enable_mouse_support() | |
| self._mouse_support_enabled = True | |
| elif not needs_mouse_support and self._mouse_support_enabled: | |
| output.disable_mouse_support() | |
| self._mouse_support_enabled = False | |
| # Create screen and write layout to it. | |
| size = output.get_size() | |
| screen = Screen() | |
| screen.show_cursor = False # Hide cursor by default, unless one of the | |
| # containers decides to display it. | |
| mouse_handlers = MouseHandlers() | |
| # Calculate height. | |
| if self.full_screen: | |
| height = size.rows | |
| elif is_done: | |
| # When we are done, we don't necessary want to fill up until the bottom. | |
| height = layout.container.preferred_height( | |
| size.columns, size.rows | |
| ).preferred | |
| else: | |
| last_height = self._last_screen.height if self._last_screen else 0 | |
| height = max( | |
| self._min_available_height, | |
| last_height, | |
| layout.container.preferred_height(size.columns, size.rows).preferred, | |
| ) | |
| height = min(height, size.rows) | |
| # When the size changes, don't consider the previous screen. | |
| if self._last_size != size: | |
| self._last_screen = None | |
| # When we render using another style or another color depth, do a full | |
| # repaint. (Forget about the previous rendered screen.) | |
| # (But note that we still use _last_screen to calculate the height.) | |
| if ( | |
| self.style.invalidation_hash() != self._last_style_hash | |
| or app.style_transformation.invalidation_hash() | |
| != self._last_transformation_hash | |
| or app.color_depth != self._last_color_depth | |
| ): | |
| self._last_screen = None | |
| self._attrs_for_style = None | |
| self._style_string_has_style = None | |
| if self._attrs_for_style is None: | |
| self._attrs_for_style = _StyleStringToAttrsCache( | |
| self.style.get_attrs_for_style_str, app.style_transformation | |
| ) | |
| if self._style_string_has_style is None: | |
| self._style_string_has_style = _StyleStringHasStyleCache( | |
| self._attrs_for_style | |
| ) | |
| self._last_style_hash = self.style.invalidation_hash() | |
| self._last_transformation_hash = app.style_transformation.invalidation_hash() | |
| self._last_color_depth = app.color_depth | |
| layout.container.write_to_screen( | |
| screen, | |
| mouse_handlers, | |
| WritePosition(xpos=0, ypos=0, width=size.columns, height=height), | |
| parent_style="", | |
| erase_bg=False, | |
| z_index=None, | |
| ) | |
| screen.draw_all_floats() | |
| # When grayed. Replace all styles in the new screen. | |
| if app.exit_style: | |
| screen.append_style_to_content(app.exit_style) | |
| # Process diff and write to output. | |
| self._cursor_pos, self._last_style = _output_screen_diff( | |
| app, | |
| output, | |
| screen, | |
| self._cursor_pos, | |
| app.color_depth, | |
| self._last_screen, | |
| self._last_style, | |
| is_done, | |
| full_screen=self.full_screen, | |
| attrs_for_style_string=self._attrs_for_style, | |
| style_string_has_style=self._style_string_has_style, | |
| size=size, | |
| previous_width=(self._last_size.columns if self._last_size else 0), | |
| ) | |
| self._last_screen = screen | |
| self._last_size = size | |
| self.mouse_handlers = mouse_handlers | |
| # Handle cursor shapes. | |
| new_cursor_shape = app.cursor.get_cursor_shape(app) | |
| if ( | |
| self._last_cursor_shape is None | |
| or self._last_cursor_shape != new_cursor_shape | |
| ): | |
| output.set_cursor_shape(new_cursor_shape) | |
| self._last_cursor_shape = new_cursor_shape | |
| # Flush buffered output. | |
| output.flush() | |
| # Set visible windows in layout. | |
| app.layout.visible_windows = screen.visible_windows | |
| if is_done: | |
| self.reset() | |
| def erase(self, leave_alternate_screen: bool = True) -> None: | |
| """ | |
| Hide all output and put the cursor back at the first line. This is for | |
| instance used for running a system command (while hiding the CLI) and | |
| later resuming the same CLI.) | |
| :param leave_alternate_screen: When True, and when inside an alternate | |
| screen buffer, quit the alternate screen. | |
| """ | |
| output = self.output | |
| output.cursor_backward(self._cursor_pos.x) | |
| output.cursor_up(self._cursor_pos.y) | |
| output.erase_down() | |
| output.reset_attributes() | |
| output.enable_autowrap() | |
| output.flush() | |
| self.reset(leave_alternate_screen=leave_alternate_screen) | |
| def clear(self) -> None: | |
| """ | |
| Clear screen and go to 0,0 | |
| """ | |
| # Erase current output first. | |
| self.erase() | |
| # Send "Erase Screen" command and go to (0, 0). | |
| output = self.output | |
| output.erase_screen() | |
| output.cursor_goto(0, 0) | |
| output.flush() | |
| self.request_absolute_cursor_position() | |
| def print_formatted_text( | |
| output: Output, | |
| formatted_text: AnyFormattedText, | |
| style: BaseStyle, | |
| style_transformation: StyleTransformation | None = None, | |
| color_depth: ColorDepth | None = None, | |
| ) -> None: | |
| """ | |
| Print a list of (style_str, text) tuples in the given style to the output. | |
| """ | |
| fragments = to_formatted_text(formatted_text) | |
| style_transformation = style_transformation or DummyStyleTransformation() | |
| color_depth = color_depth or output.get_default_color_depth() | |
| # Reset first. | |
| output.reset_attributes() | |
| output.enable_autowrap() | |
| last_attrs: Attrs | None = None | |
| # Print all (style_str, text) tuples. | |
| attrs_for_style_string = _StyleStringToAttrsCache( | |
| style.get_attrs_for_style_str, style_transformation | |
| ) | |
| for style_str, text, *_ in fragments: | |
| attrs = attrs_for_style_string[style_str] | |
| # Set style attributes if something changed. | |
| if attrs != last_attrs: | |
| if attrs: | |
| output.set_attributes(attrs, color_depth) | |
| else: | |
| output.reset_attributes() | |
| last_attrs = attrs | |
| # Print escape sequences as raw output | |
| if "[ZeroWidthEscape]" in style_str: | |
| output.write_raw(text) | |
| else: | |
| # Eliminate carriage returns | |
| text = text.replace("\r", "") | |
| # Insert a carriage return before every newline (important when the | |
| # front-end is a telnet client). | |
| text = text.replace("\n", "\r\n") | |
| output.write(text) | |
| # Reset again. | |
| output.reset_attributes() | |
| output.flush() | |