Example #1
0
    def create_content(self, width: int, height: int) -> UIContent:
        """
        Create a UIContent object for this control.
        """
        complete_state = get_app().current_buffer.complete_state
        if complete_state:
            completions = complete_state.completions
            index = complete_state.complete_index  # Can be None!

            # Calculate width of completions menu.
            menu_width = self._get_menu_width(width, complete_state)
            menu_meta_width = self._get_menu_meta_width(
                width - menu_width, complete_state)
            show_meta = self._show_meta(complete_state)

            def get_line(i: int) -> StyleAndTextTuples:
                c = completions[i]
                is_current_completion = i == index
                result = _get_menu_item_fragments(c,
                                                  is_current_completion,
                                                  menu_width,
                                                  space_after=True)

                if show_meta:
                    result += self._get_menu_item_meta_fragments(
                        c, is_current_completion, menu_meta_width)
                return result

            return UIContent(
                get_line=get_line,
                cursor_position=Point(x=0, y=index or 0),
                line_count=len(completions),
            )

        return UIContent()
Example #2
0
 def select(self, index):
     self.index = index
     self.selected = self.index
     self.cursor = Point(0, self.index)
     element = self.current_element
     if element.on_select:
         element.on_select()
Example #3
0
    def _mouse(event: E) -> None:
        """
        Handling of mouse events for Windows.
        """
        assert is_windows()  # This key binding should only exist for Windows.

        # Parse data.
        pieces = event.data.split(";")

        event_type = MouseEventType(pieces[0])
        x = int(pieces[1])
        y = int(pieces[2])

        # Make coordinates absolute to the visible part of the terminal.
        output = event.app.renderer.output

        from prompt_toolkit.output.win32 import Win32Output

        if isinstance(output, Win32Output):
            screen_buffer_info = 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))
 def get_cursor_position(fragment: str = '[SetCursorPosition]') -> Optional[Point]:
     for y, line in enumerate(fragment_lines):
         x = 0
         for style_str, text, *_ in line:
             if fragment in style_str:
                 return Point(x=x, y=y)
             x += len(text)
     return None
    def _clip_point_to_visible_area(self, point: Point,
                                    write_position: WritePosition) -> Point:
        """
        Ensure that the cursor and menu positions always are always reported
        """
        if point.x < write_position.xpos:
            point = point._replace(x=write_position.xpos)
        if point.y < write_position.ypos:
            point = point._replace(y=write_position.ypos)
        if point.x >= write_position.xpos + write_position.width:
            point = point._replace(x=write_position.xpos +
                                   write_position.width - 1)
        if point.y >= write_position.ypos + write_position.height:
            point = point._replace(y=write_position.ypos +
                                   write_position.height - 1)

        return point
 def new_handler(event: MouseEvent) -> None:
     new_event = MouseEvent(
         position=Point(
             x=event.position.x - xpos,
             y=event.position.y + self.vertical_scroll - ypos,
         ),
         event_type=event.event_type,
     )
     handler(new_event)
Example #7
0
 def test_no_mouse_handler(self):
     mouse_event = MouseEvent(Point(0, 0), MouseEventType.MOUSE_UP)
     handler = mock.MagicMock()
     formatted_text = to_formatted_text([("", "hello"),
                                         ("", "world", lambda x: handler(x))
                                         ])
     text_area = FormattedTextArea(formatted_text)
     text_area.control.mouse_handler(mouse_event)
     handler.assert_not_called()
Example #8
0
 def get_cursor_position(self, window: "Window") -> Point:
     """
     Get the cursor position for a given window.
     Returns a `Point`.
     """
     try:
         return self.cursor_positions[window]
     except KeyError:
         return Point(x=0, y=0)
Example #9
0
 def get_menu_position(self, window: "Window") -> Point:
     """
     Get the menu position for a given window.
     (This falls back to the cursor position if no menu position was set.)
     """
     try:
         return self.menu_positions[window]
     except KeyError:
         try:
             return self.cursor_positions[window]
         except KeyError:
             return Point(x=0, y=0)
Example #10
0
    def goto_line(self, line_number):
        if line_number < 0:
            line_number = self.content.line_count + 1 - line_number
            if line_number < 0:
                line_number = 0
        elif line_number >= self.content.line_count:
            line_number = self.content.line_count - 1

        if self.current_line != line_number:
            old_point = self.content.cursor_position
            new_position = Point(x=old_point.x, y=line_number)
            self.content.cursor_position = new_position
Example #11
0
    def test_mouse_handler(self):
        handler = mock.MagicMock()
        handler_two = mock.MagicMock()
        mouse_event = MouseEvent(Point(0, 0), MouseEventType.MOUSE_UP)
        formatted_text = to_formatted_text([
            ("", "hello", lambda x: handler(x)),
            ("", "world", lambda x: handler_two(x)),
        ])
        text_area = FormattedTextArea(formatted_text)

        # Click on first character.
        text_area.control.mouse_handler(mouse_event)
        handler.assert_called_once()
        handler_two.assert_not_called()

        # Click outside the text area.
        mouse_event = MouseEvent(Point(99, 99), MouseEventType.MOUSE_UP)
        handler.reset_mock()
        handler_two.reset_mock()
        text_area.control.mouse_handler(mouse_event)
        handler.assert_not_called()
        handler_two.assert_not_called()
Example #12
0
    def test_list(self):
        title = to_formatted_text("Title", style="bg:red")
        elements = [
            ListElement("One", on_select=lambda: print("Select")),
            ListElement("Two"),
            ListElement("Three", on_focus=lambda: print("Focus")),
        ]

        list = List(
            title=title,
            elements=elements,
        )

        assert list.title_window.text == "Title"

        expected = dedent("""
            One
            Two
            Three
            """).strip()
        assert to_text(list.list_window) == expected

        list.next()
        assert list.current_element.text == "Two"
        assert to_text(list.list_window) == expected

        list.previous()
        assert list.current_element.text == "One"
        assert to_text(list.list_window) == expected

        list.focus(2)
        assert list.current_element.text == "Three"
        assert to_text(list.list_window) == expected

        list.select(0)
        assert list.current_element.text == "One"
        assert to_text(list.list_window) == expected

        list.select(1)
        assert list.current_element.text == "Two"
        assert to_text(list.list_window) == expected

        mouse_event = MouseEvent(Point(0, 0), MouseEventType.MOUSE_DOWN)
        list.mouse_select(index=0, mouse_event=mouse_event)
        assert list.current_element.text == "Two"
        assert to_text(list.list_window) == expected

        mouse_event.event_type = MouseEventType.MOUSE_UP
        list.mouse_select(index=0, mouse_event=mouse_event)
        assert list.current_element.text == "One"
        assert to_text(list.list_window) == expected
    def __init__(self,
                 get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []),
                 line_count: int = 0,
                 cursor_position: Optional[Point] = None,
                 menu_position: Optional[Point] = None,
                 show_cursor: bool = True):

        self.get_line = get_line
        self.line_count = line_count
        self.cursor_position = cursor_position or Point(x=0, y=0)
        self.menu_position = menu_position
        self.show_cursor = show_cursor

        # Cache for line heights. Maps cache key -> height
        self._line_heights_cache: Dict[Any, int] = {}
Example #14
0
    def reset(self,
              _scroll: bool = False,
              leave_alternate_screen: bool = True) -> None:

        # Reset position
        self._cursor_pos = Point(x=0, y=0)

        # Remember the last screen instance between renderers. This way,
        # we can create a `diff` between two screens and only output the
        # difference. It's also to remember the last height. (To show for
        # instance a toolbar at the bottom position.)
        self._last_screen: Optional[Screen] = None
        self._last_size: Optional[Size] = None
        self._last_style: Optional[str] = None

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

        #: Space from the top of the layout, until the bottom of the terminal.
        #: We don't know this until a `report_absolute_cursor_row` call.
        self._min_available_height = 0

        # In case of Windows, also make sure to scroll to the current cursor
        # position. (Only when rendering the first time.)
        # It does nothing for vt100 terminals.
        if _scroll:
            self.output.scroll_buffer_to_prompt()

        # Quit alternate screen.
        if self._in_alternate_screen and leave_alternate_screen:
            self.output.quit_alternate_screen()
            self._in_alternate_screen = False

        # Disable mouse support.
        if self._mouse_support_enabled:
            self.output.disable_mouse_support()
            self._mouse_support_enabled = False

        # Disable bracketed paste.
        if self._bracketed_paste_enabled:
            self.output.disable_bracketed_paste()
            self._bracketed_paste_enabled = False

        # NOTE: No need to set/reset cursor key mode here.

        # Flush output. `disable_mouse_support` needs to write to stdout.
        self.output.flush()
Example #15
0
    def search(self,
               search_state: SearchState,
               include_current_position=True,
               count=1):
        LOG.debug('applying search %r, %r, %r', search_state,
                  include_current_position, count)
        self.search_state = search_state
        index = self.cursor_position.y
        new_position = self.cursor_position.y
        LOG.debug('Current position %r', index)
        needle = self.search_state.text
        STATUS.set_status("Searching for '%s'" % needle)
        if self.search_state.direction == SearchDirection.FORWARD:
            if not include_current_position:
                index += 1
            while True:
                try:
                    commit = self.commit_list[index]
                except IndexError:
                    if not self.fill_up(utils.screen_height()):
                        break

                    commit = self.commit_list[index]

                if needle in commit.short_id or needle in commit.subject \
                        or needle in commit.author_name \
                        or any(needle in haystack for haystack in commit.branches):
                    new_position = index
                    break

                index += 1
        else:
            if not include_current_position and index > 0:
                index -= 1
            while index >= 0:
                commit = self.commit_list[index]
                if needle in commit.short_id() or needle in commit.subject \
                        or needle in commit.author_name():
                    new_position = index
                    break

                index -= 1

        if new_position != self.cursor_position.y:
            self.cursor_position = Point(x=self.cursor_position.x, y=index)
        STATUS.clear()
Example #16
0
    def _(event: E) -> None:
        """
        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))
Example #17
0
    def test_test_block_edit_action_none(self, click_edit):
        toc = self.chapters[1].toc()
        section = toc[1][0]
        renderer = Renderer(tui=self.tui, section=section, width=37)
        renderer.render()
        text = "He's the creator of something..."
        node = section.children[1]
        assert node.tagname == "TestBlock"
        assert node.text() == text

        click_edit.return_value = None

        mouse_event = MouseEvent(Point(0, 0), MouseEventType.MOUSE_UP)
        renderer._edit_action(node, mouse_event)
        click_edit.assert_called_once_with(
            text=text,
            extension=".txt",
        )
        assert node.text() == text
Example #18
0
    def _mouse(event: E) -> "NotImplementedOrNone":
        """
        Handling of mouse events for Windows.
        """
        # This key binding should only exist for Windows.
        if sys.platform == "win32":
            # Parse data.
            pieces = event.data.split(";")

            button = MouseButton(pieces[0])
            event_type = MouseEventType(pieces[1])
            x = int(pieces[2])
            y = int(pieces[3])

            # Make coordinates absolute to the visible part of the terminal.
            output = event.app.renderer.output

            from prompt_toolkit.output.win32 import Win32Output
            from prompt_toolkit.output.windows10 import Windows10_Output

            if isinstance(output, (Win32Output, Windows10_Output)):
                screen_buffer_info = 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.
                # (Can return `NotImplemented`.)
                handler = event.app.renderer.mouse_handlers.mouse_handlers[y][
                    x]

                return handler(
                    MouseEvent(
                        position=Point(x=x, y=y),
                        event_type=event_type,
                        button=button,
                        modifiers=UNKNOWN_MODIFIER,
                    ))

        # No mouse handler found. Return `NotImplemented` so that we don't
        # invalidate the UI.
        return NotImplemented
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
Example #20
0
 def move_cursor_up(self):
     old_point = self.content.cursor_position
     if old_point.y > 0:
         new_position = Point(x=old_point.x, y=old_point.y - 1)
         self.content.cursor_position = new_position
Example #21
0
 def goto_last(self):
     old_point = self.content.cursor_position
     if old_point.y < self.content.line_count:
         new_position = Point(x=old_point.x, y=self.content.line_count - 1)
         self.content.cursor_position = new_position
    def _(event: E) -> None:
        """
        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:])

            # TODO: Is it possible to add modifiers here?
            mouse_button, mouse_event_type, mouse_modifier = typical_mouse_events[
                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_button, mouse_event_type, mouse_modifier = xterm_sgr_mouse_events[
                    mouse_event, m]
            else:
                # TODO: I don't know when this is triggered or how this mode works (though my Hyper terminal seems to use it), so I marked the buttons and modifiers UNKNOWN.
                # By replacing these UNKNOWN values with the correct values in urxvt_mouse_events, we can get more functionality in different terminal varieties
                mouse_button, mouse_event_type, mouse_modifier = urxvt_mouse_events.get(
                    mouse_event,
                    (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER))

        x -= 1
        y -= 1

        # Only handle mouse events when we know the window height.
        if event.app.renderer.height_is_known and mouse_event_type is not None:
            # Take region above the layout into account. The reported
            # coordinates are absolute to the visible part of the terminal.
            from prompt_toolkit.renderer import HeightIsUnknownError

            try:
                y -= event.app.renderer.rows_above_layout
            except HeightIsUnknownError:
                return

            # Call the mouse handler from the renderer.
            handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x]
            handler(
                MouseEvent(
                    position=Point(x=x, y=y),
                    event_type=mouse_event_type,
                    button=mouse_button,
                    modifier=mouse_modifier,
                ))
Example #23
0
    def __init__(
        self,
        title=None,
        elements=None,
        width=None,
        height=None,
        align=WindowAlign.LEFT,
        get_bullet=None,
        allow_select=True,
        scrollbar=True,
    ):
        self.index = 0
        self.get_bullet = get_bullet
        self.selected = -1
        self.elements = elements or []
        self.title = title
        self.allow_select = allow_select
        self.cursor = Point(0, 0)
        self.scrollbar = scrollbar
        self.control = FormattedTextControl(
            text=self._get_text,
            focusable=True,
            get_cursor_position=lambda: self.cursor,
            key_bindings=self.get_key_bindings(),
        )

        # TODO: figure out how to the make it look nicer
        right_margins = [
            ConditionalMargin(
                ScrollbarMargin(display_arrows=True),
                filter=Condition(lambda: self.scrollbar),
            ),
        ]
        self.title_window = FormattedTextArea(
            text=self.title,
            height=Dimension(min=1),
            width=Dimension(min=1),
        )
        self.list_window = Window(
            content=self.control,
            width=width,
            height=height,
            always_hide_cursor=False,
            style="class:list",
            wrap_lines=True,
            dont_extend_height=True,
            dont_extend_width=False,
            cursorline=False,
            right_margins=right_margins,
            allow_scroll_beyond_bottom=True,
            get_line_prefix=self._get_line_prefix,
        )
        self.window = HSplit(
            children=[
                Box(
                    self.title_window,
                    padding=Dimension.exact(1),
                ),
                Box(
                    self.list_window,
                    padding=Dimension.exact(1),
                    padding_top=Dimension.exact(0),
                ),
            ],
            height=Dimension(min=1),
            width=Dimension(min=1),
        )
Example #24
0
    def _(event: E) -> "NotImplementedOrNone":
        """
        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:])

            # TODO: Is it possible to add modifiers here?
            mouse_button, mouse_event_type, mouse_modifiers = typical_mouse_events[
                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:
                try:
                    (
                        mouse_button,
                        mouse_event_type,
                        mouse_modifiers,
                    ) = xterm_sgr_mouse_events[mouse_event, m]
                except KeyError:
                    return NotImplemented

            else:
                # Some other terminals, like urxvt, Hyper terminal, ...
                (
                    mouse_button,
                    mouse_event_type,
                    mouse_modifiers,
                ) = urxvt_mouse_events.get(
                    mouse_event,
                    (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER))

        x -= 1
        y -= 1

        # Only handle mouse events when we know the window height.
        if event.app.renderer.height_is_known and mouse_event_type is not None:
            # Take region above the layout into account. The reported
            # coordinates are absolute to the visible part of the terminal.
            from prompt_toolkit.renderer import HeightIsUnknownError

            try:
                y -= event.app.renderer.rows_above_layout
            except HeightIsUnknownError:
                return NotImplemented

            # Call the mouse handler from the renderer.

            # Note: This can return `NotImplemented` if no mouse handler was
            #       found for this position, or if no repainting needs to
            #       happen. this way, we avoid excessive repaints during mouse
            #       movements.
            handler = event.app.renderer.mouse_handlers.mouse_handlers[y][x]
            return handler(
                MouseEvent(
                    position=Point(x=x, y=y),
                    event_type=mouse_event_type,
                    button=mouse_button,
                    modifiers=mouse_modifiers,
                ))

        return NotImplemented
Example #25
0
 def focus(self, index):
     self.index = index
     self.cursor = Point(0, self.index)
     element = self.current_element
     if element.on_focus:
         element.on_focus()
Example #26
0
 def move_cursor_down(self):
     old_point = self.content.cursor_position
     if old_point.y + 1 < self.content.line_count:
         new_position = Point(x=old_point.x, y=old_point.y + 1)
         self.content.cursor_position = new_position
    def write_to_screen(
        self,
        screen: Screen,
        mouse_handlers: MouseHandlers,
        write_position: WritePosition,
        parent_style: str,
        erase_bg: bool,
        z_index: Optional[int],
    ) -> None:
        """
        Render scrollable pane content.

        This works by rendering on an off-screen canvas, and copying over the
        visible region.
        """
        show_scrollbar = self.show_scrollbar()

        if show_scrollbar:
            virtual_width = write_position.width - 1
        else:
            virtual_width = write_position.width

        # Compute preferred height again.
        virtual_height = self.content.preferred_height(
            virtual_width, self.max_available_height).preferred

        # Ensure virtual height is at least the available height.
        virtual_height = max(virtual_height, write_position.height)
        virtual_height = min(virtual_height, self.max_available_height)

        # First, write the content to a virtual screen, then copy over the
        # visible part to the real screen.
        temp_screen = Screen(default_char=Char(char=" ", style=parent_style))
        temp_write_position = WritePosition(xpos=0,
                                            ypos=0,
                                            width=virtual_width,
                                            height=virtual_height)

        temp_mouse_handlers = MouseHandlers()

        self.content.write_to_screen(
            temp_screen,
            temp_mouse_handlers,
            temp_write_position,
            parent_style,
            erase_bg,
            z_index,
        )
        temp_screen.draw_all_floats()

        # If anything in the virtual screen is focused, move vertical scroll to
        from prompt_toolkit.application import get_app

        focused_window = get_app().layout.current_window

        try:
            visible_win_write_pos = temp_screen.visible_windows_to_write_positions[
                focused_window]
        except KeyError:
            pass  # No window focused here. Don't scroll.
        else:
            # Make sure this window is visible.
            self._make_window_visible(
                write_position.height,
                virtual_height,
                visible_win_write_pos,
                temp_screen.cursor_positions.get(focused_window),
            )

        # Copy over virtual screen and zero width escapes to real screen.
        self._copy_over_screen(screen, temp_screen, write_position,
                               virtual_width)

        # Copy over mouse handlers.
        self._copy_over_mouse_handlers(mouse_handlers, temp_mouse_handlers,
                                       write_position, virtual_width)

        # Set screen.width/height.
        ypos = write_position.ypos
        xpos = write_position.xpos

        screen.width = max(screen.width, xpos + virtual_width)
        screen.height = max(screen.height, ypos + write_position.height)

        # Copy over window write positions.
        self._copy_over_write_positions(screen, temp_screen, write_position)

        if temp_screen.show_cursor:
            screen.show_cursor = True

        # Copy over cursor positions, if they are visible.
        for window, point in temp_screen.cursor_positions.items():
            if (0 <= point.x < write_position.width and self.vertical_scroll <=
                    point.y < write_position.height + self.vertical_scroll):
                screen.cursor_positions[window] = Point(x=point.x + xpos,
                                                        y=point.y + ypos -
                                                        self.vertical_scroll)

        # Copy over menu positions, but clip them to the visible area.
        for window, point in temp_screen.menu_positions.items():
            screen.menu_positions[window] = self._clip_point_to_visible_area(
                Point(x=point.x + xpos,
                      y=point.y + ypos - self.vertical_scroll),
                write_position,
            )

        # Draw scrollbar.
        if show_scrollbar:
            self._draw_scrollbar(
                write_position,
                virtual_height,
                screen,
            )
Example #28
0
 def translate_rowcol(row: int, col: int) -> Point:
     " Return the content column for this coordinate. "
     return Point(x=get_processed_line(row).source_to_display(col),
                  y=row)
Example #29
0
    def _(event: E) -> None:
        """
        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.app.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.
            from prompt_toolkit.renderer import HeightIsUnknownError
            try:
                y -= event.app.renderer.rows_above_layout
            except HeightIsUnknownError:
                return

            # Call the mouse handler from the renderer.
            handler = event.app.renderer.mouse_handlers.mouse_handlers[x, y]
            handler(MouseEvent(position=Point(x=x, y=y),
                               event_type=mouse_event))