Beispiel #1
0
 def adjust_cursor_position(self, x=None, y=None):
     d_items = self.displayed_items()
     if y is not None:
         if y < len(d_items):
             self.cursor_position = Point(1, y)
     if len(d_items) == 0:
         self.cursor_position = Point(1, 0)
     elif self.cursor_position.y >= len(d_items):
         self.cursor_position = Point(1, len(d_items) - 1)
Beispiel #2
0
 def update_cursor(self):
     """This function updates the cursor according to the current index
     in the list.
     """
     try:
         index = self.indices.index(self.current_index)
         line = sum(self.options_headers_linecount[i]
                    for i in self.indices[0:index])
         self.cursor = Point(0, line)
     except Exception:
         self.cursor = Point(0, 0)
    def reset(self):
        # 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 = None
        self._last_size = None
        self._last_char = None
        self._last_style = None  # When the style changes, we have to do a full
        # redraw as well.

        #: 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 Windown, also make sure to scroll to the current cursor
        # position.
        if sys.platform == 'win32':
            self.output.scroll_buffer_to_prompt()

        # Quit alternate screen.
        if self._in_alternate_screen:
            self.output.quit_alternate_screen()
            self.output.flush()
            self._in_alternate_screen = False
Beispiel #4
0
 def __init__(self, content):
     lines = [content.get_line(lineno) for lineno in range(content.line_count)] * 3
     super(InfiniteContent, self).__init__(
         get_line=lambda i: lines[i],
         line_count=len(lines),
         cursor_position=Point(
             x=content.cursor_position.x,
             y=content.cursor_position.y + content.line_count),
         default_char=content.default_char)
Beispiel #5
0
    def __init__(self, checklist, session):
        super().__init__(text=self.checklist_text,
                         get_cursor_position=lambda: self.cursor_position,
                         key_bindings=self.build_key_bindings())

        self.checklist = checklist
        self.session = session
        self.cursor_position = Point(1, 0)
        self.undo_stack = []
        self.show_completed = False
Beispiel #6
0
 def __init__(self, message, choices, pointer_index=0):
     self.message = message
     self.pointer_index = pointer_index
     self.answered = False
     self.selected = []
     self._init_choices(choices)
     super(IssuesController, self).__init__(
         self.get_formatted_choices,
         show_cursor=False,
         get_cursor_position=lambda: Point(1, self.pointer_index))
Beispiel #7
0
    def create_content(self, width, height):
        # Report dimensions to the process.
        self.process.set_size(width, height)

        # The first time that this user control is rendered. Keep track of the
        # 'app' object and start the process.
        if not self._running:
            self.process.start()
            self._running = True

        if not self.process.screen:
            return UIContent()

        pt_screen = self.process.screen.pt_screen
        data_buffer = pt_screen.data_buffer
        cursor_y = pt_screen.cursor_position.y

        # Prompt_toolkit needs the amount of characters before the cursor in a
        # UIControl.  This doesn't correspond with the xpos in case of double
        # width characters. That's why we compute the wcwidth.
        cursor_row = data_buffer[pt_screen.cursor_position.y]
        text_before_cursor = ''.join(
            cursor_row[x].char for x in range(0, pt_screen.cursor_position.x))
        cursor_x = len(text_before_cursor)

        def get_line(number):
            row = data_buffer[number]
            empty = True
            if row:
                max_column = max(row)
                empty = False
            else:
                max_column = 0

            if number == cursor_y:
                max_column = max(max_column, cursor_x)
                empty = False

            if empty:
                return [('', ' ')]
            else:
                cells = [row[i] for i in range(max_column + 1)]
                return [(cell.style, cell.char) for cell in cells]

        if data_buffer:
            line_count = max(
                data_buffer
            ) + 1  # TODO: substract all empty lines from the beginning. (If we need to. Not sure.)
        else:
            line_count = 1

        return UIContent(get_line,
                         line_count=line_count,
                         show_cursor=pt_screen.show_cursor,
                         cursor_position=Point(x=cursor_x, y=cursor_y))
Beispiel #8
0
    def create_content(self, width, height):
        menu_width = self.preferred_width(width)

        def get_line(i):
            item = self._get_items()[i]
            is_selected = (i == self._selection)
            return self._menu_item_fragment(item, is_selected, menu_width)

        return UIContent(get_line=get_line,
                         cursor_position=Point(x=0, y=self._selection or 0),
                         line_count=len(self._get_items()))
Beispiel #9
0
        def mouse_handler(cli, mouse_event):
            """ Wrapper around the mouse_handler of the `UIControl` that turns
            absolute coordinates into relative coordinates. """
            position = mouse_event.position

            # Call the mouse handler of the UIControl first.
            self._mouse_handler(
                cli,
                MouseEvent(position=Point(x=position.x - write_position.xpos,
                                          y=position.y - write_position.ypos +
                                          vertical_scroll),
                           event_type=mouse_event.event_type))
Beispiel #10
0
    def move_cursor(self, count):
        y = self.model.line = max(
            0, min(self.model.line_count() - 1, self.model.line + count))
        self.main.content.content.cursor_position = Point(y, 0)

        if not self.main.render_info:
            return

        w = self.main
        info = w.render_info

        if y > w.vertical_scroll + info.window_height or y < w.vertical_scroll:
            w.vertical_scroll = max(0, y - info.window_height // 2)
Beispiel #11
0
    def reset(self, _scroll=False, leave_alternate_screen=True):
        # 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 = None
        self._last_size = None
        self._last_token = None

        # When the style hash changes, we have to do a full redraw as well as
        # clear the `_attrs_for_token` dictionary.
        self._last_style_hash = None
        self._attrs_for_token = None

        # Default MouseHandlers. (Just empty.)
        self.mouse_handlers = MouseHandlers()

        # Remember the last title. Only set the title when it changes.
        self._last_title = None

        #: 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 Windown, also make sure to scroll to the current cursor
        # position. (Only when rendering the first time.)
        if is_windows() and _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

        # Flush output. `disable_mouse_support` needs to write to stdout.
        self.output.flush()
Beispiel #12
0
    def _copy_body(self, cli, temp_screen, new_screen, write_position,
                   vertical_scroll, width):
        """
        Copy characters from the temp screen that we got from the `UIControl`
        to the real screen.
        """
        xpos = write_position.xpos
        ypos = write_position.ypos
        height = write_position.height

        temp_buffer = temp_screen.data_buffer
        new_buffer = new_screen.data_buffer
        temp_screen_height = temp_screen.height

        vertical_scroll = self.process.screen.line_offset
        y = 0

        # Now copy the region we need to the real screen.
        for y in range(0, height):
            # We keep local row variables. (Don't look up the row in the dict
            # for each iteration of the nested loop.)
            new_row = new_buffer[y + ypos]

            if y >= temp_screen_height and y >= write_position.height:
                # Break out of for loop when we pass after the last row of the
                # temp screen. (We use the 'y' position for calculation of new
                # screen's height.)
                break
            else:
                temp_row = temp_buffer[y + vertical_scroll]

                # Copy row content, except for transparent tokens.
                # (This is useful in case of floats.)
                for x in range(0, width):
                    new_row[x + xpos] = temp_row[x]

        if self.has_focus(cli):
            new_screen.cursor_position = Point(
                y=temp_screen.cursor_position.y + ypos - vertical_scroll,
                x=temp_screen.cursor_position.x + xpos)

            new_screen.show_cursor = temp_screen.show_cursor

        # Update height of the output screen. (new_screen.write_data is not
        # called, so the screen is not aware of its height.)
        new_screen.height = max(new_screen.height, ypos + y + 1)
Beispiel #13
0
    def _(event):
        """
        Handling of mouse events for Windows.
        """
        assert is_windows()  # This key binding should only exist for Windows.

        # Parse data.
        event_type, x, y = event.data.split(';')
        x = int(x)
        y = int(y)

        # Make coordinates absolute to the visible part of the terminal.
        screen_buffer_info = event.app.renderer.output.get_win32_screen_buffer_info()
        rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.app.renderer._cursor_pos.y
        y -= rows_above_cursor

        # Call the mouse event handler.
        handler = event.app.renderer.mouse_handlers.mouse_handlers[x, y]
        handler(MouseEvent(position=Point(x=x, y=y), event_type=event_type))
Beispiel #14
0
    def scroll(self, count, half):
        info = self.main.render_info
        if not info:
            return

        if half is not None:
            count *= info.window_height
            if half:
                count = count // 2

        self.main.vertical_scroll = max(
            0,
            min(self.model.line_count() - 1,
                self.main.vertical_scroll + count))
        if self.model.line < self.main.vertical_scroll or \
                self.model.line > self.main.vertical_scroll + info.window_height:

            y = self.model.line = self.main.vertical_scroll
            self.main.content.content.cursor_position = Point(y, 0)
Beispiel #15
0
    def create_content(self, app, width, height):
        self.process.set_size(width, height)

        if not self.process.screen:
            return UIContent()

        pt_screen = self.process.screen.pt_screen
        data_buffer = pt_screen.data_buffer
        cursor_y = pt_screen.cursor_position.y
        cursor_x = pt_screen.cursor_position.x

        def get_line(number):
            row = data_buffer[number]
            empty = True
            if row:
                max_column = max(row)
                empty = False
            else:
                max_column = 0

            if number == cursor_y:
                max_column = max(max_column, cursor_x)
                empty = False

            if empty:
                return [(Token, ' ')]
            else:
                cells = [row[i] for i in range(max_column + 1)]
                return [(cell.token, cell.char) for cell in cells]

        if data_buffer:
            line_count = max(data_buffer) + 1
        else:
            line_count = 1

        return UIContent(get_line,
                         line_count=line_count,
                         cursor_position=Point(x=pt_screen.cursor_position.x,
                                               y=pt_screen.cursor_position.y))
Beispiel #16
0
    def __init__(self,
                 options: Sequence[Option],
                 default_index: int = 0,
                 header_filter: Callable[[Option], str] = str,
                 match_filter: Callable[[Option], str] = str,
                 custom_filter: Optional[Callable[[str], bool]] = None,
                 search_buffer: Buffer = Buffer(multiline=False),
                 cpu_count: int = os.cpu_count()):

        self.search_buffer = search_buffer
        self.last_query_text = ''  # type: str
        self.search_buffer.on_text_changed += self.update

        self.header_filter = header_filter
        self.match_filter = match_filter
        self.current_index = default_index  # type: Optional[int]
        self.entries_left_offset = 0
        self.cpu_count = cpu_count

        self.options_headers_linecount = []  # type: List[int]
        self._indices_to_lines = []  # type: List[int]

        self.options_headers = []  # type: FormattedText
        self.options_matchers = []  # type: List[str]
        self.indices = []  # type: List[int]
        self._options = []  # type: Sequence[Option]
        self.marks = []  # type: List[int]
        self.max_entry_height = 1  # type: int

        # options are processed here also through the setter
        # ##################################################
        self.set_options(options)
        self.cursor = Point(0, 0)  # type: Point
        # ##################################################

        self.content = FormattedTextControl(
            text=self.get_tokens,
            focusable=False,
            key_bindings=None,
            get_cursor_position=lambda: self.cursor,
        )
        self.content_window = Window(
            content=self.content,
            wrap_lines=False,
            allow_scroll_beyond_bottom=True,
            scroll_offsets=ScrollOffsets(bottom=self.max_entry_height),
            cursorline=False,
            cursorcolumn=False,
            # right_margins=[NumberedMargin()],
            # left_margins=[NumberedMargin()],
            align=WindowAlign.LEFT,
            height=None,
            get_line_prefix=self.get_line_prefix
            # get_line_prefix=lambda line, b: [('bg:red', '  ')]
        )

        self.update()

        super(OptionsList,
              self).__init__(content=self.content_window,
                             filter=(custom_filter if custom_filter is not None
                                     else has_focus(self.search_buffer)))
Beispiel #17
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]
Beispiel #18
0
def test_basic():
    ol = OptionsList(['hello', 'world', '<bye'])
    assert (ol.get_selection() == ['hello'])
    assert (len(ol.marks) == 0)
    assert (len(ol.indices) == 3)
    ol.move_down()
    assert (ol.get_selection() == ['world'])
    ol.move_up()
    assert (ol.get_selection() == ['hello'])
    ol.mark_current_selection()
    assert (ol.marks == [0])
    ol.move_down()
    ol.move_down()
    ol.mark_current_selection()
    assert (ol.marks == [0, 2])
    ol.toggle_mark_current_selection()
    assert (ol.marks == [0])
    # fg:red because it failed
    assert (ol.get_tokens() == [('', 'hello\n'), ('', 'world\n'),
                                ('fg:red', '<bye\n')])
    assert (ol.get_line_prefix(2, None) == [
        ('class:options_list.selected_margin', '|')
    ])
    assert (ol.get_line_prefix(1, None) == [
        ('class:options_list.unselected_margin', ' ')
    ])
    assert (ol.get_line_prefix(0, None) == [
        ('class:options_list.marked_margin', '#')
    ])

    assert (ol.search_regex == re.compile('.*', re.I))
    ol.search_buffer.text = 'l'
    assert (ol.search_regex == re.compile('.*l', re.I))
    assert (ol.indices == [0, 1])
    ol.search_buffer.text = 'l  '
    assert (ol.search_regex == re.compile('.*l.*', re.I))
    assert (ol.indices == [0, 1])
    assert (len(ol.get_options()) == 3)
    ol.deselect()
    assert (ol.current_index is None)

    ol.set_options([str(i) for i in range(1000)])
    assert (len(ol.marks) == 0)
    assert (len(ol.indices) == 1000)
    ol.go_top()
    assert (ol.get_selection() == ['0'])
    ol.move_up()
    assert (ol.get_selection() == ['999'])
    ol.move_down()
    assert (ol.get_selection() == ['0'])
    ol.go_bottom()
    assert (ol.get_selection() == ['999'])
    ol.search_buffer.text = 'asdfadsf'
    assert (ol.indices == [])
    ol.update_cursor()
    assert (ol.cursor == Point(0, 0))
    # when there is nothing selected and appearing it's ok to
    # move up and down
    ol.move_down()
    ol.move_up()

    ol.go_top()
    ol.search_buffer.text = '99'
    assert (len(ol.indices) == 19)
    ol.update()
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]
Beispiel #20
0
    def _(event):
        """
        Handling of incoming mouse event.
        """
        # Typical:   "Esc[MaB*"
        # Urxvt:     "Esc[96;14;13M"
        # Xterm SGR: "Esc[<64;85;12M"

        # Parse incoming packet.
        if event.data[2] == 'M':
            # Typical.
            mouse_event, x, y = map(ord, event.data[3:])
            mouse_event = {
                32: MouseEventType.MOUSE_DOWN,
                35: MouseEventType.MOUSE_UP,
                96: MouseEventType.SCROLL_UP,
                97: MouseEventType.SCROLL_DOWN,
            }.get(mouse_event)

            # Handle situations where `PosixStdinReader` used surrogateescapes.
            if x >= 0xdc00: x-= 0xdc00
            if y >= 0xdc00: y-= 0xdc00

            x -= 32
            y -= 32
        else:
            # Urxvt and Xterm SGR.
            # When the '<' is not present, we are not using the Xterm SGR mode,
            # but Urxvt instead.
            data = event.data[2:]
            if data[:1] == '<':
                sgr = True
                data = data[1:]
            else:
                sgr = False

            # Extract coordinates.
            mouse_event, x, y = map(int, data[:-1].split(';'))
            m = data[-1]

            # Parse event type.
            if sgr:
                mouse_event = {
                    (0, 'M'): MouseEventType.MOUSE_DOWN,
                    (0, 'm'): MouseEventType.MOUSE_UP,
                    (64, 'M'): MouseEventType.SCROLL_UP,
                    (65, 'M'): MouseEventType.SCROLL_DOWN,
                }.get((mouse_event, m))
            else:
                mouse_event = {
                    32: MouseEventType.MOUSE_DOWN,
                    35: MouseEventType.MOUSE_UP,
                    96: MouseEventType.SCROLL_UP,
                    97: MouseEventType.SCROLL_DOWN,
                    }.get(mouse_event)

        x -= 1
        y -= 1

        # Only handle mouse events when we know the window height.
        if event.cli.renderer.height_is_known and mouse_event is not None:
            # Take region above the layout into account. The reported
            # coordinates are absolute to the visible part of the terminal.
            try:
                y -= event.cli.renderer.rows_above_layout
            except HeightIsUnknownError:
                return

            # Call the mouse handler from the renderer.
            handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y]
            handler(event.cli, MouseEvent(position=Point(x=x, y=y),
                                          event_type=mouse_event))
Beispiel #21
0
    def __init__(self,
                 options,
                 default_index=0,
                 header_filter=lambda x: x,
                 match_filter=lambda x: x,
                 custom_filter=None,
                 search_buffer=Buffer(multiline=False),
                 cpu_count=multiprocessing.cpu_count()):

        assert (isinstance(options, list))
        assert (callable(header_filter))
        assert (callable(match_filter))
        assert (isinstance(default_index, int))

        self.search_buffer = search_buffer
        self.last_query_text = ''
        self.search_buffer.on_text_changed += self.update

        self.header_filter = header_filter
        self.match_filter = match_filter
        self.current_index = default_index
        self.entries_left_offset = 0
        self.pool = multiprocessing.Pool(cpu_count)

        self.options_headers_linecount = []
        self._indices_to_lines = []

        self._options = []
        self.marks = []
        self.max_entry_height = 1
        # Options are processed here also through the setter
        self.options = options
        self.cursor = Point(0, 0)

        self.content = FormattedTextControl(
            text=self.get_tokens,
            focusable=False,
            key_bindings=None,
            get_cursor_position=lambda: self.cursor,
        )
        self.content_window = Window(
            content=self.content,
            wrap_lines=False,
            allow_scroll_beyond_bottom=True,
            scroll_offsets=ScrollOffsets(bottom=self.max_entry_height),
            cursorline=False,
            cursorcolumn=False,
            # right_margins=[NumberedMargin()],
            # left_margins=[NumberedMargin()],
            align=WindowAlign.LEFT,
            height=None,
            get_line_prefix=self.get_line_prefix
            # get_line_prefix=lambda line, b: [('bg:red', '  ')]
        )

        self.update()

        super(OptionsList,
              self).__init__(content=self.content_window,
                             filter=(custom_filter if custom_filter is not None
                                     else has_focus(self.search_buffer)))
Beispiel #22
0
def _output_screen_diff(app,
                        output,
                        screen,
                        current_pos,
                        color_depth,
                        previous_screen=None,
                        last_style=None,
                        is_done=False,
                        full_screen=False,
                        attrs_for_style_string=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_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

    #: Remember the last printed character.
    last_style = [last_style]  # 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_style[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 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):
        """
        Write the output of this character.
        """
        # If the last printed character has the same style, don't output the
        # style again.
        the_last_style = last_style[0]  # Either `None` or a style string.

        if the_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 the_last_style or new_attrs != attrs_for_style_string[
                    the_last_style]:
                _output_set_attributes(new_attrs, color_depth)

            write(char.char)
            last_style[0] = 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[0]
Beispiel #23
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]
Beispiel #24
0
 def move_cursor(self, y):
     content.cursor_position = Point(y, 0)
 def __call__(self):
     global selected
     y = selected
     return Point(0, y)