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()
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()
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)
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()
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)
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)
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
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()
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] = {}
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()
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()
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))
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
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
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
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, ))
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), )
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
def focus(self, index): self.index = index self.cursor = Point(0, self.index) element = self.current_element if element.on_focus: element.on_focus()
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, )
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)
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))