コード例 #1
0
    def render(self, cli, layout, style=None):
        """
        Render the current interface to the output.
        """
        style = style or Style
        output = self.output

        # When we render using another style, do a full repaint. (Forget about
        # the previous rendered screen.)
        if style != self._last_style:
            self._last_screen = None
        self._last_style = style

        # Enter alternate screen.
        if self.use_alternate_screen and not self._in_alternate_screen:
            self._in_alternate_screen = True
            output.enter_alternate_screen()

        # Create screen and write layout to it.
        size = output.get_size()
        screen = Screen(size.columns)

        if cli.is_done:
            height = 0  # When we are done, we don't necessary want to fill up until the bottom.
        else:
            height = self._last_screen.current_height if self._last_screen else 0
            height = max(self._min_available_height, height)

        # When te size changes, don't consider the previous screen.
        if self._last_size != size:
            self._last_screen = None

        layout.write_to_screen(
            cli, screen,
            WritePosition(
                xpos=0,
                ypos=0,
                width=size.columns,
                height=(size.rows if self.use_alternate_screen else height),
                extended_height=size.rows,
            ))

        # When grayed. Replace all tokens in the new screen.
        if cli.is_aborting or cli.is_exiting:
            screen.replace_all_tokens(Token.Aborted)

        # Process diff and write to output.
        self._cursor_pos, self._last_char = output_screen_diff(
            output,
            screen,
            self._cursor_pos,
            self._last_screen,
            self._last_char,
            cli.is_done,
            style=style,
        )
        self._last_screen = screen
        self._last_size = size

        output.flush()
コード例 #2
0
    def render(self, cli, layout, style=None, is_done=False):
        """
        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.
        """
        style = style or Style
        output = self.output

        # When we render using another style, do a full repaint. (Forget about
        # the previous rendered screen.)
        if style != self._last_style:
            self._last_screen = None
        self._last_style = style

        # Enter alternate screen.
        if self.use_alternate_screen and not self._in_alternate_screen:
            self._in_alternate_screen = True
            output.enter_alternate_screen()

        # Create screen and write layout to it.
        size = output.get_size()
        screen = Screen(size.columns)

        if is_done:
            height = 0  # When we are done, we don't necessary want to fill up until the bottom.
        else:
            height = self._last_screen.current_height if self._last_screen else 0
            height = max(self._min_available_height, height)

        # When te size changes, don't consider the previous screen.
        if self._last_size != size:
            self._last_screen = None

        layout.write_to_screen(cli, screen, WritePosition(
            xpos=0,
            ypos=0,
            width=size.columns,
            height=(size.rows if self.use_alternate_screen else height),
            extended_height=size.rows,
        ))

        # When grayed. Replace all tokens in the new screen.
        if cli.is_aborting or cli.is_exiting:
            screen.replace_all_tokens(Token.Aborted)

        # Process diff and write to output.
        self._cursor_pos, self._last_char = output_screen_diff(
            output, screen, self._cursor_pos,
            self._last_screen, self._last_char, is_done,
            style=style,
            )
        self._last_screen = screen
        self._last_size = size

        output.flush()
コード例 #3
0
ファイル: screen.py プロジェクト: riag/ptterm
    def _reset_screen(self):
        """ Reset the Screen content. (also called when switching from/to
        alternate buffer. """
        self.pt_screen = Screen(default_char=Char(
            ' ', ''))  # TODO: maybe stop using this Screen class.

        self.pt_screen.cursor_position = CursorPosition(0, 0)
        self.pt_screen.show_cursor = True

        self.data_buffer = self.pt_screen.data_buffer
        self.pt_cursor_position = self.pt_screen.cursor_position
        self.wrapped_lines = []  # List of line indexes that were wrapped.

        self._attrs = Attrs(color=None,
                            bgcolor=None,
                            bold=False,
                            underline=False,
                            italic=False,
                            blink=False,
                            reverse=False,
                            hidden=False)
        self._style_str = ''

        self.margins = None

        self.max_y = 0  # Max 'y' position to which is written.
コード例 #4
0
ファイル: layout.py プロジェクト: ABaldwinHunter/pymux-clone
    def create_screen(self, cli, width, height):
        screen = Screen(initial_width=width)

        if self.pymux.arrangement.get_active_pane(cli) == self.arrangement_pane:
            token = Token.PaneNumber.Focussed
        else:
            token = Token.PaneNumber

        for i, d in enumerate('%s' % (self._get_index(cli))):
            _draw_number(screen, i * 6, int(d),
                         token=token, default_token=Token.Transparent)

        return screen
コード例 #5
0
ファイル: screen.py プロジェクト: walkinreeds/pymux
    def _reset_screen(self):
        """ Reset the Screen content. (also called when switching from/to
        alternate buffer. """
        self.pt_screen = Screen(default_char=Char(' ', DEFAULT_TOKEN))

        self.pt_screen.cursor_position = CursorPosition(0, 0)
        self.pt_screen.show_cursor = True

        self.data_buffer = self.pt_screen.data_buffer
        self.pt_cursor_position = self.pt_screen.cursor_position

        self._attrs = Attrs(color=None, bgcolor=None, bold=False,
                            underline=False, italic=False, blink=False, reverse=False)

        self.margins = None

        self.max_y = 0  # Max 'y' position to which is written.
コード例 #6
0
    def _reset_screen(self):
        """(BetterScreen) -> NoneType
        Reset the Screen content. (also called when switching from/to
        alternate buffer. """
        self.pt_screen = Screen(default_char=Char(' ', DEFAULT_TOKEN))

        self.pt_screen.cursor_position = CursorPosition(0, 0)
        self.pt_screen.show_cursor = True

        self.data_buffer = self.pt_screen.data_buffer

        self._attrs = Attrs(color=None, bgcolor=None, bold=False,
                            underline=False, italic=False, blink=False, reverse=False)

        self.margins = Margins(0, self.lines - 1)

        self.line_offset = 0  # Index of the line that's currently displayed on top.
        self.max_y = 0  # Max 'y' position to which is written.
コード例 #7
0
ファイル: layout.py プロジェクト: ABaldwinHunter/pymux-clone
    def create_screen(self, cli, width, height):
        screen = Screen(initial_width=width)

        for y in range(self.HEIGHT):
            for x in range(self.WIDTH):
                screen.data_buffer[y][x] = Char(' ', Token)

        # Display time.
        now = datetime.datetime.now()
        _draw_number(screen, 0, now.hour // 10)
        _draw_number(screen, 6, now.hour % 10)
        _draw_number(screen, 16, now.minute // 10)
        _draw_number(screen, 23, now.minute % 10)

        # Add a colon
        screen.data_buffer[1][13] = Char(' ', Token.Clock)
        screen.data_buffer[3][13] = Char(' ', Token.Clock)

        screen.width = self.WIDTH
        screen.height = self.HEIGHT
        return screen
コード例 #8
0
    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

        # 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 te 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

        if self._attrs_for_style is None:
            self._attrs_for_style = _StyleStringToAttrsCache(
                self.style.get_attrs_for_style_str,
                app.style_transformation)

        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, 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

        output.flush()

        # Set visible windows in layout.
        app.layout.visible_windows = screen.visible_windows

        if is_done:
            self.reset()
コード例 #9
0
def _output_screen_diff(
        app: 'Application[Any]',
        output: Output,
        screen: Screen,
        current_pos: Point,
        color_depth: ColorDepth,
        previous_screen: Optional[Screen],
        last_style: Optional[str],
        is_done: bool,  # XXX: drop is_done
        full_screen: bool,
        attrs_for_style_string: '_StyleStringToAttrsCache',
        size: Size,
        previous_width: int) -> Tuple[Point, Optional[str]]:
    """
    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

    # 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)
    c = 0  # Column counter.

    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, max(new_row.keys()) if new_row else 0)
        previous_max_line_len = min(width - 1, max(previous_row.keys()) if previous_row else 0)

        # Loop over the columns.
        c = 0
        while c < new_max_line_len + 1:
            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 behaviour 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
コード例 #10
0
def output_screen_diff(output,
                       screen,
                       current_pos,
                       previous_screen=None,
                       last_char=None,
                       is_done=False,
                       style=None):  # XXX: drop is_done
    """
    Create diff of this screen with the previous screen.

    This is some performance-critical code which is heavily optimized.
    Don't change things without profiling first.
    """
    #: Remember the last printed character.
    last_char = [last_char]  # nonlocal
    background_turned_on = [False]  # Nonlocal

    #: Variable for capturing the output.
    write = output.write

    # 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

    def reset_attributes():
        " Wrapper around Output.reset_attributes. "
        _output_reset_attributes()
        last_char[0] = None  # Forget last char after resetting attributes.

    def move_cursor(new):
        " 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 meight 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 >= screen.width - 1:
            write('\r')
            _output_cursor_forward(new.x)
        elif new.x < current_x or current_x >= screen.width - 1:
            _output_cursor_backward(current_x - new.x)
        elif new.x > current_x:
            _output_cursor_forward(new.x - current_x)

        return new

    style_for_token = _StyleForTokenCache(style)

    def output_char(char):
        """
        Write the output of this character.
        """
        # If the last printed character has the same token, it also has the
        # same style, so we don't output it.
        if last_char[0] and last_char[0].token == char.token:
            write(char.char)
        else:
            style = style_for_token[char.token]

            if style:
                _output_set_attributes(style['color'],
                                       style['bgcolor'],
                                       bold=style.get('bold', False),
                                       underline=style.get('underline', False))

                # If we print something with a background color, remember that.
                background_turned_on[0] = bool(style['bgcolor'])
            else:
                # Reset previous style and output.
                reset_attributes()

            write(char.char)

        last_char[0] = char

    # Disable autowrap
    if not previous_screen:
        output.disable_autowrap()
        reset_attributes()

    # When the previous screen has a different size, redraw everything anyway.
    # Also when we are done. (We meight take up less rows, so clearing is important.)
    if is_done or not previous_screen or previous_screen.width != screen.width:  # XXX: also consider height??
        current_pos = move_cursor(Point(0, 0))
        reset_attributes()
        output.erase_down()

        previous_screen = Screen(screen.width)

    # Get height of the screen.
    # (current_height changes as we loop over _buffer, so remember the current value.)
    current_height = screen.current_height

    # Loop over the rows.
    row_count = max(screen.current_height, previous_screen.current_height)
    c = 0  # Column counter.

    for y, r in enumerate(range(0, row_count)):
        new_row = screen._buffer[r]
        previous_row = previous_screen._buffer[r]

        new_max_line_len = max(new_row.keys()) if new_row else 0
        previous_max_line_len = max(previous_row.keys()) if previous_row else 0

        # Loop over the columns.
        c = 0
        while c < new_max_line_len + 1:
            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.token != old_char.token:
                current_pos = move_cursor(Point(y=y, x=c))
                output_char(new_char)
                current_pos = current_pos._replace(x=current_pos.x +
                                                   char_width)

            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(y=y, x=new_max_line_len + 1))
            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 behaviour 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 screen.current_height > previous_screen.current_height:
        current_pos = move_cursor(Point(y=screen.current_height - 1, x=0))

    # Move cursor:
    if is_done:
        current_pos = move_cursor(Point(y=current_height, x=0))
        output.erase_down()
    else:
        current_pos = move_cursor(screen.cursor_position)

    if is_done:
        reset_attributes()
        output.enable_autowrap()

    # If the last printed character has a background color, always reset.
    # (Many terminals give weird artifacs on resize events when there is an
    # active background color.)
    if background_turned_on[0]:
        reset_attributes()

    return current_pos, last_char[0]
コード例 #11
0
    def render(self, cli, layout, style=None, is_done=False):
        """
        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.
        """
        style = style or Style
        output = self.output

        # Enter alternate screen.
        if self.use_alternate_screen and not self._in_alternate_screen:
            self._in_alternate_screen = True
            output.enter_alternate_screen()

        # Create screen and write layout to it.
        size = output.get_size()
        screen = Screen(size.columns)

        if is_done:
            height = 0  # When we are done, we don't necessary want to fill up until the bottom.
        else:
            height = self._last_screen.current_height if self._last_screen else 0
            height = max(self._min_available_height, height)

        # When te size changes, don't consider the previous screen.
        if self._last_size != size:
            self._last_screen = None

        # When we render using another style, do a full repaint. (Forget about
        # the previous rendered screen.)
        # (But note that we still use _last_screen to calculate the height.)
        if style != self._last_style:
            self._last_screen = None
        self._last_style = style

        layout.write_to_screen(
            cli, screen,
            WritePosition(
                xpos=0,
                ypos=0,
                width=size.columns,
                height=(size.rows if self.use_alternate_screen else height),
                extended_height=size.rows,
            ))

        # When grayed. Replace all tokens in the new screen.
        if cli.is_aborting or cli.is_exiting:
            screen.replace_all_tokens(Token.Aborted)

        # Process diff and write to output.
        self._cursor_pos, self._last_char = output_screen_diff(
            output,
            screen,
            self._cursor_pos,
            self._last_screen,
            self._last_char,
            is_done,
            style=style,
        )
        self._last_screen = screen
        self._last_size = size

        # Write title if it changed.
        new_title = cli.terminal_title

        if new_title != self._last_title:
            if new_title is None:
                self.output.clear_title()
            else:
                self.output.set_title(new_title)
            self._last_title = new_title

        output.flush()
コード例 #12
0
    def render(self, cli, layout, is_done=False):
        """
        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.use_alternate_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

        # Enable/disable mouse support.
        needs_mouse_support = self.mouse_support(cli)

        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()

        if is_done:
            height = 0  # When we are done, we don't necessary want to fill up until the bottom.
        else:
            height = self._last_screen.height if self._last_screen else 0
            height = max(self._min_available_height, height)

        # When te size changes, don't consider the previous screen.
        if self._last_size != size:
            self._last_screen = None

        # When we render using another style, 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:
            self._last_screen = None
            self._attrs_for_token = None
        if self._attrs_for_token is None:
            self._attrs_for_token = _TokenToAttrsCache(
                self.style.get_attrs_for_token)
        self._last_style_hash = self.style.invalidation_hash()

        layout.write_to_screen(
            cli, screen, mouse_handlers,
            WritePosition(
                xpos=0,
                ypos=0,
                width=size.columns,
                height=(size.rows if self.use_alternate_screen else height),
                extended_height=size.rows,
            ))

        # When grayed. Replace all tokens in the new screen.
        if cli.is_aborting or cli.is_exiting:
            screen.replace_all_tokens(Token.Aborted)

        # Process diff and write to output.
        self._cursor_pos, self._last_char = output_screen_diff(
            output,
            screen,
            self._cursor_pos,
            self._last_screen,
            self._last_char,
            is_done,
            attrs_for_token=self._attrs_for_token,
            width=size.columns,
            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

        # Write title if it changed.
        new_title = cli.terminal_title

        if new_title != self._last_title:
            if new_title is None:
                self.output.clear_title()
            else:
                self.output.set_title(new_title)
            self._last_title = new_title

        output.flush()
コード例 #13
0
 def setUp(self):
     self.screen = Screen(Size(rows=10, columns=80))
コード例 #14
0
def _output_screen_diff(output,
                        screen,
                        current_pos,
                        previous_screen=None,
                        last_char=None,
                        is_done=False,
                        attrs_for_token=None,
                        size=None,
                        previous_width=0):  # XXX: drop is_done
    """
    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_char: `Char` instance that represents the output attributes of
            the last drawn character. (Color/attributes.)
    :param attrs_for_token: :class:`._TokenToAttrsCache` instance.
    :param width: The width of the terminal.
    :param prevous_width: The width of the terminal during the last rendering.
    """
    width, height = size.columns, size.rows

    #: Remember the last printed character.
    last_char = [last_char]  # nonlocal
    background_turned_on = [False]  # Nonlocal

    #: 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():
        " Wrapper around Output.reset_attributes. "
        _output_reset_attributes()
        last_char[0] = None  # Forget last char after resetting attributes.

    def move_cursor(new):
        " 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 meight 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):
        """
        Write the output of this character.
        """
        # If the last printed character has the same token, it also has the
        # same style, so we don't output it.
        if last_char[0] and last_char[0].token == char.token:
            write(char.char)
        else:
            attrs = attrs_for_token[char.token]

            _output_set_attributes(attrs)

            # If we print something with a background color, remember that.
            background_turned_on[0] = bool(attrs.bgcolor)

            write(char.char)

        last_char[0] = char

    # Disable autowrap
    if not previous_screen:
        output.disable_autowrap()
        reset_attributes()

    # When the previous screen has a different size, redraw everything anyway.
    # Also when we are done. (We meight 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(0, 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)
    c = 0  # Column counter.

    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,
                               max(new_row.keys()) if new_row else 0)
        previous_max_line_len = min(
            width - 1,
            max(previous_row.keys()) if previous_row else 0)

        # Loop over the columns.
        c = 0
        while c < new_max_line_len + 1:
            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.token != old_char.token:
                current_pos = move_cursor(Point(y=y, x=c))

                # 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 = current_pos._replace(x=current_pos.x +
                                                   char_width)

            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(y=y, x=new_max_line_len + 1))
            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 behaviour 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(y=current_height - 1, x=0))

    # Move cursor:
    if is_done:
        current_pos = move_cursor(Point(y=current_height, x=0))
        output.erase_down()
    else:
        current_pos = move_cursor(screen.cursor_position)

    if is_done:
        reset_attributes()
        output.enable_autowrap()

    # If the last printed character has a background color, always reset.
    # (Many terminals give weird artifacs on resize events when there is an
    # active background color.)
    if background_turned_on[0]:
        reset_attributes()

    if screen.show_cursor or is_done:
        output.show_cursor()

    return current_pos, last_char[0]
コード例 #15
0
def output_screen_diff(output,
                       screen,
                       current_pos,
                       previous_screen=None,
                       last_char=None,
                       is_done=False,
                       style=None):  # XXX: drop is_done
    """
    Create diff of this screen with the previous screen.
    """
    #: Remember the last printed character.
    last_char = [last_char]  # nonlocal
    background_turned_on = [False]  # Nonlocal

    #: Variable for capturing the output.
    write = output.write

    def move_cursor(new):
        current_x, current_y = current_pos.x, current_pos.y

        if new.y > current_y:
            # Use newlines instead of CURSOR_DOWN, because this meight add new lines.
            # CURSOR_DOWN will never create new lines at the bottom.
            # Also reset attributes, otherwise the newline could draw a
            # background color.
            output.reset_attributes()
            write('\r\n' * (new.y - current_y))
            current_x = 0
            output.cursor_forward(new.x)
            last_char[0] = None  # Forget last char after resetting attributes.
            return new
        elif new.y < current_y:
            output.cursor_up(current_y - new.y)

        if current_x >= screen.width - 1:
            write('\r')
            output.cursor_forward(new.x)
        elif new.x < current_x or current_x >= screen.width - 1:
            output.cursor_backward(current_x - new.x)
        elif new.x > current_x:
            output.cursor_forward(new.x - current_x)

        return new

    style_for_token = _StyleForTokenCache(style)

    def output_char(char):
        """
        Write the output of this character.
        """
        # If the last printed character has the same token, it also has the
        # same style, so we don't output it.
        if last_char[0] and last_char[0].token == char.token:
            write(char.char)
        else:
            style = style_for_token[char.token]

            if style:
                output.set_attributes(style['color'],
                                      style['bgcolor'],
                                      bold=style.get('bold', False),
                                      underline=style.get('underline', False))

                # If we print something with a background color, remember that.
                background_turned_on[0] = bool(style['bgcolor'])
            else:
                # Reset previous style and output.
                output.reset_attributes()

            write(char.char)

        last_char[0] = char

    # Disable autowrap
    if not previous_screen:
        output.disable_autowrap()
        output.reset_attributes()

    # When the previous screen has a different size, redraw everything anyway.
    # Also when we are done. (We meight take up less rows, so clearing is important.)
    if is_done or not previous_screen or previous_screen.width != screen.width:  # XXX: also consider height??
        current_pos = move_cursor(Point(0, 0))
        output.reset_attributes()
        output.erase_down()

        previous_screen = Screen(screen.width)

    # Get height of the screen.
    # (current_height changes as we loop over _buffer, so remember the current value.)
    current_height = screen.current_height

    # Loop over the rows.
    row_count = max(screen.current_height, previous_screen.current_height)
    c = 0  # Column counter.

    for y, r in enumerate(range(0, row_count)):
        new_row = screen._buffer[r]
        previous_row = previous_screen._buffer[r]

        new_max_line_len = max(new_row.keys()) if new_row else 0
        previous_max_line_len = max(previous_row.keys()) if previous_row else 0

        # Loop over the columns.
        c = 0
        while c < new_max_line_len + 1:
            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.token != old_char.token:
                current_pos = move_cursor(Point(y=y, x=c))
                output_char(new_char)
                current_pos = current_pos._replace(x=current_pos.x +
                                                   char_width)

            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(y=y, x=new_max_line_len + 1))
            output.reset_attributes()
            output.erase_end_of_line()
            last_char[0] = None  # Forget last char after resetting attributes.

    # Move cursor:
    if is_done:
        current_pos = move_cursor(Point(y=current_height, x=0))
        output.erase_down()
    else:
        current_pos = move_cursor(screen.cursor_position)

    if is_done:
        output.reset_attributes()
        output.enable_autowrap()

    # If the last printed character has a background color, always reset.
    # (Many terminals give weird artifacs on resize events when there is an
    # active background color.)
    if background_turned_on[0]:
        output.reset_attributes()
        last_char[0] = None

    return current_pos, last_char[0]
コード例 #16
0
    def render(self, cli, layout, is_done=False):
        """
        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.use_alternate_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

        # Enable/disable mouse support.
        needs_mouse_support = self.mouse_support(cli)

        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()

        if is_done:
            height = 0  # When we are done, we don't necessary want to fill up until the bottom.
        else:
            height = self._last_screen.height if self._last_screen else 0
            height = max(self._min_available_height, height)

        # When te size changes, don't consider the previous screen.
        if self._last_size != size:
            self._last_screen = None

        # When we render using another style, 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:
            self._last_screen = None
            self._attrs_for_token = None
        if self._attrs_for_token is None:
            self._attrs_for_token = _TokenToAttrsCache(self.style.get_attrs_for_token)
        self._last_style_hash = self.style.invalidation_hash()

        layout.write_to_screen(cli, screen, mouse_handlers, WritePosition(
            xpos=0,
            ypos=0,
            width=size.columns,
            height=(size.rows if self.use_alternate_screen else height),
            extended_height=size.rows,
        ))

        # When grayed. Replace all tokens in the new screen.
        if cli.is_aborting or cli.is_exiting:
            screen.replace_all_tokens(Token.Aborted)

        # Process diff and write to output.
        self._cursor_pos, self._last_char = _output_screen_diff(
            output, screen, self._cursor_pos,
            self._last_screen, self._last_char, is_done,
            attrs_for_token=self._attrs_for_token,
            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

        # Write title if it changed.
        new_title = cli.terminal_title

        if new_title != self._last_title:
            if new_title is None:
                self.output.clear_title()
            else:
                self.output.set_title(new_title)
            self._last_title = new_title

        output.flush()
コード例 #17
0
ファイル: screen.py プロジェクト: riag/ptterm
    def _reflow(self):
        """
        Reflow the screen using the given width.
        """
        width = self.columns

        data_buffer = self.pt_screen.data_buffer
        new_data_buffer = Screen(default_char=Char(' ', '')).data_buffer
        cursor_position = self.pt_screen.cursor_position
        cy, cx = (cursor_position.y, cursor_position.x)

        cursor_character = data_buffer[cursor_position.y][
            cursor_position.x].char

        # Ensure that the cursor position is present.
        # (and avoid calling min() on empty collection.)
        data_buffer[cursor_position.y][cursor_position.y]

        # Unwrap all the lines.
        offset = min(data_buffer)
        line = []
        all_lines = [line]

        for row_index in range(min(data_buffer), max(data_buffer) + 1):
            row = data_buffer[row_index]

            row[0]  # Avoid calling max() on empty collection.
            for column_index in range(0, max(row) + 1):
                if cy == row_index and cx == column_index:
                    cy = len(all_lines) - 1
                    cx = len(line)

                line.append(row[column_index])

            # Create new line if the next line was not a wrapped line.
            if row_index + 1 not in self.wrapped_lines:
                line = []
                all_lines.append(line)

        # Remove trailing whitespace (unless it contains the cursor).
        # Also make sure that lines consist of at lesat one character,
        # otherwise we can't calculate `max_y` correctly. (This is important
        # for the `clear` command.)
        for row_index, line in enumerate(all_lines):
            # We do this only if no special styling given.
            while len(line) > 1 and line[-1].char.isspace(
            ) and not line[-1].style:
                if row_index == cy and len(line) - 1 == cx:
                    break
                line.pop()

        # Wrap lines again according to the screen width.
        new_row_index = offset
        new_column_index = 0
        new_wrapped_lines = []

        for row_index, line in enumerate(all_lines):
            for column_index, char in enumerate(line):
                # Check for space on the current line.
                if new_column_index + char.width > width:
                    new_row_index += 1
                    new_column_index = 0
                    new_wrapped_lines.append(new_row_index)

                if cy == row_index and cx == column_index:
                    cy = new_row_index
                    cx = new_column_index

                # Add character to new buffer.
                new_data_buffer[new_row_index][new_column_index] = char
                new_column_index += char.width

            new_row_index += 1
            new_column_index = 0

        # TODO: when the window gets smaller, and the cursor is at the top of the screen,
        #       remove lines at the bottom.
        for row_index in range(min(data_buffer), max(data_buffer) + 1):
            if row_index > cy + self.lines:
                del data_buffer[row_index]

        self.pt_screen.data_buffer = new_data_buffer
        self.data_buffer = new_data_buffer
        self.wrapped_lines = new_wrapped_lines

        cursor_position.y, cursor_position.x = cy, cx
        self.pt_screen.cursor_position = cursor_position  # XXX: not needed.
        self.pt_cursor_position = self.pt_screen.cursor_position

        # If everything goes well, the cursor should still be on the same character.
        if cursor_character != new_data_buffer[cursor_position.y][
                cursor_position.x].char:
            # FIXME:
            raise Exception(
                'Reflow failed: %r %r' %
                (cursor_character,
                 new_data_buffer[cursor_position.y][cursor_position.x].char))

        self.max_y = max(self.data_buffer)

        self.max_y = min(self.max_y, cursor_position.y + self.lines - 1)