diff options
author | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
---|---|---|
committer | monster <monster@ydb.tech> | 2022-07-07 14:41:37 +0300 |
commit | 06e5c21a835c0e923506c4ff27929f34e00761c2 (patch) | |
tree | 75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py | |
parent | 03f024c4412e3aa613bb543cf1660176320ba8f4 (diff) | |
download | ydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz |
fix ya.make
Diffstat (limited to 'contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py')
-rw-r--r-- | contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py | 723 |
1 files changed, 0 insertions, 723 deletions
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py deleted file mode 100644 index 24d6e46af0..0000000000 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py +++ /dev/null @@ -1,723 +0,0 @@ -import math -from itertools import zip_longest -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Iterable, - List, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, -) - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.buffer import CompletionState -from prompt_toolkit.completion import Completion -from prompt_toolkit.data_structures import Point -from prompt_toolkit.filters import ( - Condition, - FilterOrBool, - has_completions, - is_done, - to_filter, -) -from prompt_toolkit.formatted_text import ( - StyleAndTextTuples, - fragment_list_width, - to_formatted_text, -) -from prompt_toolkit.key_binding.key_processor import KeyPressEvent -from prompt_toolkit.layout.utils import explode_text_fragments -from prompt_toolkit.mouse_events import MouseEvent, MouseEventType -from prompt_toolkit.utils import get_cwidth - -from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window -from .controls import GetLinePrefixCallable, UIContent, UIControl -from .dimension import Dimension -from .margins import ScrollbarMargin - -if TYPE_CHECKING: - from prompt_toolkit.key_binding.key_bindings import ( - KeyBindings, - NotImplementedOrNone, - ) - - -__all__ = [ - "CompletionsMenu", - "MultiColumnCompletionsMenu", -] - -E = KeyPressEvent - - -class CompletionsMenuControl(UIControl): - """ - Helper for drawing the complete menu to the screen. - - :param scroll_offset: Number (integer) representing the preferred amount of - completions to be displayed before and after the current one. When this - is a very high number, the current completion will be shown in the - middle most of the time. - """ - - # Preferred minimum size of the menu control. - # The CompletionsMenu class defines a width of 8, and there is a scrollbar - # of 1.) - MIN_WIDTH = 7 - - def has_focus(self) -> bool: - return False - - def preferred_width(self, max_available_width: int) -> Optional[int]: - complete_state = get_app().current_buffer.complete_state - if complete_state: - menu_width = self._get_menu_width(500, complete_state) - menu_meta_width = self._get_menu_meta_width(500, complete_state) - - return menu_width + menu_meta_width - else: - return 0 - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - - complete_state = get_app().current_buffer.complete_state - if complete_state: - return len(complete_state.completions) - else: - return 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() - - def _show_meta(self, complete_state: CompletionState) -> bool: - """ - Return ``True`` if we need to show a column with meta information. - """ - return any(c.display_meta_text for c in complete_state.completions) - - def _get_menu_width(self, max_width: int, complete_state: CompletionState) -> int: - """ - Return the width of the main column. - """ - return min( - max_width, - max( - self.MIN_WIDTH, - max(get_cwidth(c.display_text) for c in complete_state.completions) + 2, - ), - ) - - def _get_menu_meta_width( - self, max_width: int, complete_state: CompletionState - ) -> int: - """ - Return the width of the meta column. - """ - - def meta_width(completion: Completion) -> int: - return get_cwidth(completion.display_meta_text) - - if self._show_meta(complete_state): - return min( - max_width, max(meta_width(c) for c in complete_state.completions) + 2 - ) - else: - return 0 - - def _get_menu_item_meta_fragments( - self, completion: Completion, is_current_completion: bool, width: int - ) -> StyleAndTextTuples: - - if is_current_completion: - style_str = "class:completion-menu.meta.completion.current" - else: - style_str = "class:completion-menu.meta.completion" - - text, tw = _trim_formatted_text(completion.display_meta, width - 2) - padding = " " * (width - 1 - tw) - - return to_formatted_text( - cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], - style=style_str, - ) - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle mouse events: clicking and scrolling. - """ - b = get_app().current_buffer - - if mouse_event.event_type == MouseEventType.MOUSE_UP: - # Select completion. - b.go_to_completion(mouse_event.position.y) - b.complete_state = None - - elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: - # Scroll up. - b.complete_next(count=3, disable_wrap_around=True) - - elif mouse_event.event_type == MouseEventType.SCROLL_UP: - # Scroll down. - b.complete_previous(count=3, disable_wrap_around=True) - - return None - - -def _get_menu_item_fragments( - completion: Completion, - is_current_completion: bool, - width: int, - space_after: bool = False, -) -> StyleAndTextTuples: - """ - Get the style/text tuples for a menu item, styled and trimmed to the given - width. - """ - if is_current_completion: - style_str = "class:completion-menu.completion.current {} {}".format( - completion.style, - completion.selected_style, - ) - else: - style_str = "class:completion-menu.completion " + completion.style - - text, tw = _trim_formatted_text( - completion.display, (width - 2 if space_after else width - 1) - ) - - padding = " " * (width - 1 - tw) - - return to_formatted_text( - cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)], - style=style_str, - ) - - -def _trim_formatted_text( - formatted_text: StyleAndTextTuples, max_width: int -) -> Tuple[StyleAndTextTuples, int]: - """ - Trim the text to `max_width`, append dots when the text is too long. - Returns (text, width) tuple. - """ - width = fragment_list_width(formatted_text) - - # When the text is too wide, trim it. - if width > max_width: - result = [] # Text fragments. - remaining_width = max_width - 3 - - for style_and_ch in explode_text_fragments(formatted_text): - ch_width = get_cwidth(style_and_ch[1]) - - if ch_width <= remaining_width: - result.append(style_and_ch) - remaining_width -= ch_width - else: - break - - result.append(("", "...")) - - return result, max_width - remaining_width - else: - return formatted_text, width - - -class CompletionsMenu(ConditionalContainer): - # NOTE: We use a pretty big z_index by default. Menus are supposed to be - # above anything else. We also want to make sure that the content is - # visible at the point where we draw this menu. - def __init__( - self, - max_height: Optional[int] = None, - scroll_offset: Union[int, Callable[[], int]] = 0, - extra_filter: FilterOrBool = True, - display_arrows: FilterOrBool = False, - z_index: int = 10**8, - ) -> None: - - extra_filter = to_filter(extra_filter) - display_arrows = to_filter(display_arrows) - - super().__init__( - content=Window( - content=CompletionsMenuControl(), - width=Dimension(min=8), - height=Dimension(min=1, max=max_height), - scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), - right_margins=[ScrollbarMargin(display_arrows=display_arrows)], - dont_extend_width=True, - style="class:completion-menu", - z_index=z_index, - ), - # Show when there are completions but not at the point we are - # returning the input. - filter=has_completions & ~is_done & extra_filter, - ) - - -class MultiColumnCompletionMenuControl(UIControl): - """ - Completion menu that displays all the completions in several columns. - When there are more completions than space for them to be displayed, an - arrow is shown on the left or right side. - - `min_rows` indicates how many rows will be available in any possible case. - When this is larger than one, it will try to use less columns and more - rows until this value is reached. - Be careful passing in a too big value, if less than the given amount of - rows are available, more columns would have been required, but - `preferred_width` doesn't know about that and reports a too small value. - This results in less completions displayed and additional scrolling. - (It's a limitation of how the layout engine currently works: first the - widths are calculated, then the heights.) - - :param suggested_max_column_width: The suggested max width of a column. - The column can still be bigger than this, but if there is place for two - columns of this width, we will display two columns. This to avoid that - if there is one very wide completion, that it doesn't significantly - reduce the amount of columns. - """ - - _required_margin = 3 # One extra padding on the right + space for arrows. - - def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> None: - assert min_rows >= 1 - - self.min_rows = min_rows - self.suggested_max_column_width = suggested_max_column_width - self.scroll = 0 - - # Info of last rendering. - self._rendered_rows = 0 - self._rendered_columns = 0 - self._total_columns = 0 - self._render_pos_to_completion: Dict[Tuple[int, int], Completion] = {} - self._render_left_arrow = False - self._render_right_arrow = False - self._render_width = 0 - - def reset(self) -> None: - self.scroll = 0 - - def has_focus(self) -> bool: - return False - - def preferred_width(self, max_available_width: int) -> Optional[int]: - """ - Preferred width: prefer to use at least min_rows, but otherwise as much - as possible horizontally. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state is None: - return 0 - - column_width = self._get_column_width(complete_state) - result = int( - column_width - * math.ceil(len(complete_state.completions) / float(self.min_rows)) - ) - - # When the desired width is still more than the maximum available, - # reduce by removing columns until we are less than the available - # width. - while ( - result > column_width - and result > max_available_width - self._required_margin - ): - result -= column_width - return result + self._required_margin - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - """ - Preferred height: as much as needed in order to display all the completions. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state is None: - return 0 - - column_width = self._get_column_width(complete_state) - column_count = max(1, (width - self._required_margin) // column_width) - - return int(math.ceil(len(complete_state.completions) / float(column_count))) - - def create_content(self, width: int, height: int) -> UIContent: - """ - Create a UIContent object for this menu. - """ - complete_state = get_app().current_buffer.complete_state - if complete_state is None: - return UIContent() - - column_width = self._get_column_width(complete_state) - self._render_pos_to_completion = {} - - _T = TypeVar("_T") - - def grouper( - n: int, iterable: Iterable[_T], fillvalue: Optional[_T] = None - ) -> Iterable[Sequence[Optional[_T]]]: - "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" - args = [iter(iterable)] * n - return zip_longest(fillvalue=fillvalue, *args) - - def is_current_completion(completion: Completion) -> bool: - "Returns True when this completion is the currently selected one." - return ( - complete_state is not None - and complete_state.complete_index is not None - and c == complete_state.current_completion - ) - - # Space required outside of the regular columns, for displaying the - # left and right arrow. - HORIZONTAL_MARGIN_REQUIRED = 3 - - # There should be at least one column, but it cannot be wider than - # the available width. - column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) - - # However, when the columns tend to be very wide, because there are - # some very wide entries, shrink it anyway. - if column_width > self.suggested_max_column_width: - # `column_width` can still be bigger that `suggested_max_column_width`, - # but if there is place for two columns, we divide by two. - column_width //= column_width // self.suggested_max_column_width - - visible_columns = max(1, (width - self._required_margin) // column_width) - - columns_ = list(grouper(height, complete_state.completions)) - rows_ = list(zip(*columns_)) - - # Make sure the current completion is always visible: update scroll offset. - selected_column = (complete_state.complete_index or 0) // height - self.scroll = min( - selected_column, max(self.scroll, selected_column - visible_columns + 1) - ) - - render_left_arrow = self.scroll > 0 - render_right_arrow = self.scroll < len(rows_[0]) - visible_columns - - # Write completions to screen. - fragments_for_line = [] - - for row_index, row in enumerate(rows_): - fragments: StyleAndTextTuples = [] - middle_row = row_index == len(rows_) // 2 - - # Draw left arrow if we have hidden completions on the left. - if render_left_arrow: - fragments.append(("class:scrollbar", "<" if middle_row else " ")) - elif render_right_arrow: - # Reserve one column empty space. (If there is a right - # arrow right now, there can be a left arrow as well.) - fragments.append(("", " ")) - - # Draw row content. - for column_index, c in enumerate(row[self.scroll :][:visible_columns]): - if c is not None: - fragments += _get_menu_item_fragments( - c, is_current_completion(c), column_width, space_after=False - ) - - # Remember render position for mouse click handler. - for x in range(column_width): - self._render_pos_to_completion[ - (column_index * column_width + x, row_index) - ] = c - else: - fragments.append(("class:completion", " " * column_width)) - - # Draw trailing padding for this row. - # (_get_menu_item_fragments only returns padding on the left.) - if render_left_arrow or render_right_arrow: - fragments.append(("class:completion", " ")) - - # Draw right arrow if we have hidden completions on the right. - if render_right_arrow: - fragments.append(("class:scrollbar", ">" if middle_row else " ")) - elif render_left_arrow: - fragments.append(("class:completion", " ")) - - # Add line. - fragments_for_line.append( - to_formatted_text(fragments, style="class:completion-menu") - ) - - self._rendered_rows = height - self._rendered_columns = visible_columns - self._total_columns = len(columns_) - self._render_left_arrow = render_left_arrow - self._render_right_arrow = render_right_arrow - self._render_width = ( - column_width * visible_columns + render_left_arrow + render_right_arrow + 1 - ) - - def get_line(i: int) -> StyleAndTextTuples: - return fragments_for_line[i] - - return UIContent(get_line=get_line, line_count=len(rows_)) - - def _get_column_width(self, complete_state: CompletionState) -> int: - """ - Return the width of each column. - """ - return max(get_cwidth(c.display_text) for c in complete_state.completions) + 1 - - def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone": - """ - Handle scroll and click events. - """ - b = get_app().current_buffer - - def scroll_left() -> None: - b.complete_previous(count=self._rendered_rows, disable_wrap_around=True) - self.scroll = max(0, self.scroll - 1) - - def scroll_right() -> None: - b.complete_next(count=self._rendered_rows, disable_wrap_around=True) - self.scroll = min( - self._total_columns - self._rendered_columns, self.scroll + 1 - ) - - if mouse_event.event_type == MouseEventType.SCROLL_DOWN: - scroll_right() - - elif mouse_event.event_type == MouseEventType.SCROLL_UP: - scroll_left() - - elif mouse_event.event_type == MouseEventType.MOUSE_UP: - x = mouse_event.position.x - y = mouse_event.position.y - - # Mouse click on left arrow. - if x == 0: - if self._render_left_arrow: - scroll_left() - - # Mouse click on right arrow. - elif x == self._render_width - 1: - if self._render_right_arrow: - scroll_right() - - # Mouse click on completion. - else: - completion = self._render_pos_to_completion.get((x, y)) - if completion: - b.apply_completion(completion) - - return None - - def get_key_bindings(self) -> "KeyBindings": - """ - Expose key bindings that handle the left/right arrow keys when the menu - is displayed. - """ - from prompt_toolkit.key_binding.key_bindings import KeyBindings - - kb = KeyBindings() - - @Condition - def filter() -> bool: - "Only handle key bindings if this menu is visible." - app = get_app() - complete_state = app.current_buffer.complete_state - - # There need to be completions, and one needs to be selected. - if complete_state is None or complete_state.complete_index is None: - return False - - # This menu needs to be visible. - return any(window.content == self for window in app.layout.visible_windows) - - def move(right: bool = False) -> None: - buff = get_app().current_buffer - complete_state = buff.complete_state - - if complete_state is not None and complete_state.complete_index is not None: - # Calculate new complete index. - new_index = complete_state.complete_index - if right: - new_index += self._rendered_rows - else: - new_index -= self._rendered_rows - - if 0 <= new_index < len(complete_state.completions): - buff.go_to_completion(new_index) - - # NOTE: the is_global is required because the completion menu will - # never be focussed. - - @kb.add("left", is_global=True, filter=filter) - def _left(event: E) -> None: - move() - - @kb.add("right", is_global=True, filter=filter) - def _right(event: E) -> None: - move(True) - - return kb - - -class MultiColumnCompletionsMenu(HSplit): - """ - Container that displays the completions in several columns. - When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates - to True, it shows the meta information at the bottom. - """ - - def __init__( - self, - min_rows: int = 3, - suggested_max_column_width: int = 30, - show_meta: FilterOrBool = True, - extra_filter: FilterOrBool = True, - z_index: int = 10**8, - ) -> None: - - show_meta = to_filter(show_meta) - extra_filter = to_filter(extra_filter) - - # Display filter: show when there are completions but not at the point - # we are returning the input. - full_filter = has_completions & ~is_done & extra_filter - - @Condition - def any_completion_has_meta() -> bool: - complete_state = get_app().current_buffer.complete_state - return complete_state is not None and any( - c.display_meta for c in complete_state.completions - ) - - # Create child windows. - # NOTE: We don't set style='class:completion-menu' to the - # `MultiColumnCompletionMenuControl`, because this is used in a - # Float that is made transparent, and the size of the control - # doesn't always correspond exactly with the size of the - # generated content. - completions_window = ConditionalContainer( - content=Window( - content=MultiColumnCompletionMenuControl( - min_rows=min_rows, - suggested_max_column_width=suggested_max_column_width, - ), - width=Dimension(min=8), - height=Dimension(min=1), - ), - filter=full_filter, - ) - - meta_window = ConditionalContainer( - content=Window(content=_SelectedCompletionMetaControl()), - filter=show_meta & full_filter & any_completion_has_meta, - ) - - # Initialise split. - super().__init__([completions_window, meta_window], z_index=z_index) - - -class _SelectedCompletionMetaControl(UIControl): - """ - Control that shows the meta information of the selected completion. - """ - - def preferred_width(self, max_available_width: int) -> Optional[int]: - """ - Report the width of the longest meta text as the preferred width of this control. - - It could be that we use less width, but this way, we're sure that the - layout doesn't change when we select another completion (E.g. that - completions are suddenly shown in more or fewer columns.) - """ - app = get_app() - if app.current_buffer.complete_state: - state = app.current_buffer.complete_state - return 2 + max(get_cwidth(c.display_meta_text) for c in state.completions) - else: - return 0 - - def preferred_height( - self, - width: int, - max_available_height: int, - wrap_lines: bool, - get_line_prefix: Optional[GetLinePrefixCallable], - ) -> Optional[int]: - return 1 - - def create_content(self, width: int, height: int) -> UIContent: - fragments = self._get_text_fragments() - - def get_line(i: int) -> StyleAndTextTuples: - return fragments - - return UIContent(get_line=get_line, line_count=1 if fragments else 0) - - def _get_text_fragments(self) -> StyleAndTextTuples: - style = "class:completion-menu.multi-column-meta" - state = get_app().current_buffer.complete_state - - if ( - state - and state.current_completion - and state.current_completion.display_meta_text - ): - return to_formatted_text( - cast(StyleAndTextTuples, [("", " ")]) - + state.current_completion.display_meta - + [("", " ")], - style=style, - ) - - return [] |