1""" 
    2Key binding handlers for displaying completions. 
    3""" 
    4 
    5from __future__ import annotations 
    6 
    7import asyncio 
    8import math 
    9from typing import TYPE_CHECKING 
    10 
    11from prompt_toolkit.application.run_in_terminal import in_terminal 
    12from prompt_toolkit.completion import ( 
    13    CompleteEvent, 
    14    Completion, 
    15    get_common_complete_suffix, 
    16) 
    17from prompt_toolkit.formatted_text import StyleAndTextTuples 
    18from prompt_toolkit.key_binding.key_bindings import KeyBindings 
    19from prompt_toolkit.key_binding.key_processor import KeyPressEvent 
    20from prompt_toolkit.keys import Keys 
    21from prompt_toolkit.utils import get_cwidth 
    22 
    23if TYPE_CHECKING: 
    24    from prompt_toolkit.application import Application 
    25    from prompt_toolkit.shortcuts import PromptSession 
    26 
    27__all__ = [ 
    28    "generate_completions", 
    29    "display_completions_like_readline", 
    30] 
    31 
    32E = KeyPressEvent 
    33 
    34 
    35def generate_completions(event: E) -> None: 
    36    r""" 
    37    Tab-completion: where the first tab completes the common suffix and the 
    38    second tab lists all the completions. 
    39    """ 
    40    b = event.current_buffer 
    41 
    42    # When already navigating through completions, select the next one. 
    43    if b.complete_state: 
    44        b.complete_next() 
    45    else: 
    46        b.start_completion(insert_common_part=True) 
    47 
    48 
    49def display_completions_like_readline(event: E) -> None: 
    50    """ 
    51    Key binding handler for readline-style tab completion. 
    52    This is meant to be as similar as possible to the way how readline displays 
    53    completions. 
    54 
    55    Generate the completions immediately (blocking) and display them above the 
    56    prompt in columns. 
    57 
    58    Usage:: 
    59 
    60        # Call this handler when 'Tab' has been pressed. 
    61        key_bindings.add(Keys.ControlI)(display_completions_like_readline) 
    62    """ 
    63    # Request completions. 
    64    b = event.current_buffer 
    65    if b.completer is None: 
    66        return 
    67    complete_event = CompleteEvent(completion_requested=True) 
    68    completions = list(b.completer.get_completions(b.document, complete_event)) 
    69 
    70    # Calculate the common suffix. 
    71    common_suffix = get_common_complete_suffix(b.document, completions) 
    72 
    73    # One completion: insert it. 
    74    if len(completions) == 1: 
    75        b.delete_before_cursor(-completions[0].start_position) 
    76        b.insert_text(completions[0].text) 
    77    # Multiple completions with common part. 
    78    elif common_suffix: 
    79        b.insert_text(common_suffix) 
    80    # Otherwise: display all completions. 
    81    elif completions: 
    82        _display_completions_like_readline(event.app, completions) 
    83 
    84 
    85def _display_completions_like_readline( 
    86    app: Application[object], completions: list[Completion] 
    87) -> asyncio.Task[None]: 
    88    """ 
    89    Display the list of completions in columns above the prompt. 
    90    This will ask for a confirmation if there are too many completions to fit 
    91    on a single page and provide a paginator to walk through them. 
    92    """ 
    93    from prompt_toolkit.formatted_text import to_formatted_text 
    94    from prompt_toolkit.shortcuts.prompt import create_confirm_session 
    95 
    96    # Get terminal dimensions. 
    97    term_size = app.output.get_size() 
    98    term_width = term_size.columns 
    99    term_height = term_size.rows 
    100 
    101    # Calculate amount of required columns/rows for displaying the 
    102    # completions. (Keep in mind that completions are displayed 
    103    # alphabetically column-wise.) 
    104    max_compl_width = min( 
    105        term_width, max(get_cwidth(c.display_text) for c in completions) + 1 
    106    ) 
    107    column_count = max(1, term_width // max_compl_width) 
    108    completions_per_page = column_count * (term_height - 1) 
    109    page_count = int(math.ceil(len(completions) / float(completions_per_page))) 
    110    # Note: math.ceil can return float on Python2. 
    111 
    112    def display(page: int) -> None: 
    113        # Display completions. 
    114        page_completions = completions[ 
    115            page * completions_per_page : (page + 1) * completions_per_page 
    116        ] 
    117 
    118        page_row_count = int(math.ceil(len(page_completions) / float(column_count))) 
    119        page_columns = [ 
    120            page_completions[i * page_row_count : (i + 1) * page_row_count] 
    121            for i in range(column_count) 
    122        ] 
    123 
    124        result: StyleAndTextTuples = [] 
    125 
    126        for r in range(page_row_count): 
    127            for c in range(column_count): 
    128                try: 
    129                    completion = page_columns[c][r] 
    130                    style = "class:readline-like-completions.completion " + ( 
    131                        completion.style or "" 
    132                    ) 
    133 
    134                    result.extend(to_formatted_text(completion.display, style=style)) 
    135 
    136                    # Add padding. 
    137                    padding = max_compl_width - get_cwidth(completion.display_text) 
    138                    result.append((completion.style, " " * padding)) 
    139                except IndexError: 
    140                    pass 
    141            result.append(("", "\n")) 
    142 
    143        app.print_text(to_formatted_text(result, "class:readline-like-completions")) 
    144 
    145    # User interaction through an application generator function. 
    146    async def run_compl() -> None: 
    147        "Coroutine." 
    148        async with in_terminal(render_cli_done=True): 
    149            if len(completions) > completions_per_page: 
    150                # Ask confirmation if it doesn't fit on the screen. 
    151                confirm = await create_confirm_session( 
    152                    f"Display all {len(completions)} possibilities?", 
    153                ).prompt_async() 
    154 
    155                if confirm: 
    156                    # Display pages. 
    157                    for page in range(page_count): 
    158                        display(page) 
    159 
    160                        if page != page_count - 1: 
    161                            # Display --MORE-- and go to the next page. 
    162                            show_more = await _create_more_session( 
    163                                "--MORE--" 
    164                            ).prompt_async() 
    165 
    166                            if not show_more: 
    167                                return 
    168                else: 
    169                    app.output.flush() 
    170            else: 
    171                # Display all completions. 
    172                display(0) 
    173 
    174    return app.create_background_task(run_compl()) 
    175 
    176 
    177def _create_more_session(message: str = "--MORE--") -> PromptSession[bool]: 
    178    """ 
    179    Create a `PromptSession` object for displaying the "--MORE--". 
    180    """ 
    181    from prompt_toolkit.shortcuts import PromptSession 
    182 
    183    bindings = KeyBindings() 
    184 
    185    @bindings.add(" ") 
    186    @bindings.add("y") 
    187    @bindings.add("Y") 
    188    @bindings.add(Keys.ControlJ) 
    189    @bindings.add(Keys.ControlM) 
    190    @bindings.add(Keys.ControlI)  # Tab. 
    191    def _yes(event: E) -> None: 
    192        event.app.exit(result=True) 
    193 
    194    @bindings.add("n") 
    195    @bindings.add("N") 
    196    @bindings.add("q") 
    197    @bindings.add("Q") 
    198    @bindings.add(Keys.ControlC) 
    199    def _no(event: E) -> None: 
    200        event.app.exit(result=False) 
    201 
    202    @bindings.add(Keys.Any) 
    203    def _ignore(event: E) -> None: 
    204        "Disable inserting of text." 
    205 
    206    return PromptSession(message, key_bindings=bindings, erase_when_done=True)