|
|
""" |
|
|
Key binding handlers for displaying completions. |
|
|
""" |
|
|
|
|
|
from __future__ import annotations |
|
|
|
|
|
import asyncio |
|
|
import math |
|
|
from typing import TYPE_CHECKING |
|
|
|
|
|
from prompt_toolkit.application.run_in_terminal import in_terminal |
|
|
from prompt_toolkit.completion import ( |
|
|
CompleteEvent, |
|
|
Completion, |
|
|
get_common_complete_suffix, |
|
|
) |
|
|
from prompt_toolkit.formatted_text import StyleAndTextTuples |
|
|
from prompt_toolkit.key_binding.key_bindings import KeyBindings |
|
|
from prompt_toolkit.key_binding.key_processor import KeyPressEvent |
|
|
from prompt_toolkit.keys import Keys |
|
|
from prompt_toolkit.utils import get_cwidth |
|
|
|
|
|
if TYPE_CHECKING: |
|
|
from prompt_toolkit.application import Application |
|
|
from prompt_toolkit.shortcuts import PromptSession |
|
|
|
|
|
__all__ = [ |
|
|
"generate_completions", |
|
|
"display_completions_like_readline", |
|
|
] |
|
|
|
|
|
E = KeyPressEvent |
|
|
|
|
|
|
|
|
def generate_completions(event: E) -> None: |
|
|
r""" |
|
|
Tab-completion: where the first tab completes the common suffix and the |
|
|
second tab lists all the completions. |
|
|
""" |
|
|
b = event.current_buffer |
|
|
|
|
|
|
|
|
if b.complete_state: |
|
|
b.complete_next() |
|
|
else: |
|
|
b.start_completion(insert_common_part=True) |
|
|
|
|
|
|
|
|
def display_completions_like_readline(event: E) -> None: |
|
|
""" |
|
|
Key binding handler for readline-style tab completion. |
|
|
This is meant to be as similar as possible to the way how readline displays |
|
|
completions. |
|
|
|
|
|
Generate the completions immediately (blocking) and display them above the |
|
|
prompt in columns. |
|
|
|
|
|
Usage:: |
|
|
|
|
|
# Call this handler when 'Tab' has been pressed. |
|
|
key_bindings.add(Keys.ControlI)(display_completions_like_readline) |
|
|
""" |
|
|
|
|
|
b = event.current_buffer |
|
|
if b.completer is None: |
|
|
return |
|
|
complete_event = CompleteEvent(completion_requested=True) |
|
|
completions = list(b.completer.get_completions(b.document, complete_event)) |
|
|
|
|
|
|
|
|
common_suffix = get_common_complete_suffix(b.document, completions) |
|
|
|
|
|
|
|
|
if len(completions) == 1: |
|
|
b.delete_before_cursor(-completions[0].start_position) |
|
|
b.insert_text(completions[0].text) |
|
|
|
|
|
elif common_suffix: |
|
|
b.insert_text(common_suffix) |
|
|
|
|
|
elif completions: |
|
|
_display_completions_like_readline(event.app, completions) |
|
|
|
|
|
|
|
|
def _display_completions_like_readline( |
|
|
app: Application[object], completions: list[Completion] |
|
|
) -> asyncio.Task[None]: |
|
|
""" |
|
|
Display the list of completions in columns above the prompt. |
|
|
This will ask for a confirmation if there are too many completions to fit |
|
|
on a single page and provide a paginator to walk through them. |
|
|
""" |
|
|
from prompt_toolkit.formatted_text import to_formatted_text |
|
|
from prompt_toolkit.shortcuts.prompt import create_confirm_session |
|
|
|
|
|
|
|
|
term_size = app.output.get_size() |
|
|
term_width = term_size.columns |
|
|
term_height = term_size.rows |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
max_compl_width = min( |
|
|
term_width, max(get_cwidth(c.display_text) for c in completions) + 1 |
|
|
) |
|
|
column_count = max(1, term_width // max_compl_width) |
|
|
completions_per_page = column_count * (term_height - 1) |
|
|
page_count = int(math.ceil(len(completions) / float(completions_per_page))) |
|
|
|
|
|
|
|
|
def display(page: int) -> None: |
|
|
|
|
|
page_completions = completions[ |
|
|
page * completions_per_page : (page + 1) * completions_per_page |
|
|
] |
|
|
|
|
|
page_row_count = int(math.ceil(len(page_completions) / float(column_count))) |
|
|
page_columns = [ |
|
|
page_completions[i * page_row_count : (i + 1) * page_row_count] |
|
|
for i in range(column_count) |
|
|
] |
|
|
|
|
|
result: StyleAndTextTuples = [] |
|
|
|
|
|
for r in range(page_row_count): |
|
|
for c in range(column_count): |
|
|
try: |
|
|
completion = page_columns[c][r] |
|
|
style = "class:readline-like-completions.completion " + ( |
|
|
completion.style or "" |
|
|
) |
|
|
|
|
|
result.extend(to_formatted_text(completion.display, style=style)) |
|
|
|
|
|
|
|
|
padding = max_compl_width - get_cwidth(completion.display_text) |
|
|
result.append((completion.style, " " * padding)) |
|
|
except IndexError: |
|
|
pass |
|
|
result.append(("", "\n")) |
|
|
|
|
|
app.print_text(to_formatted_text(result, "class:readline-like-completions")) |
|
|
|
|
|
|
|
|
async def run_compl() -> None: |
|
|
"Coroutine." |
|
|
async with in_terminal(render_cli_done=True): |
|
|
if len(completions) > completions_per_page: |
|
|
|
|
|
confirm = await create_confirm_session( |
|
|
f"Display all {len(completions)} possibilities?", |
|
|
).prompt_async() |
|
|
|
|
|
if confirm: |
|
|
|
|
|
for page in range(page_count): |
|
|
display(page) |
|
|
|
|
|
if page != page_count - 1: |
|
|
|
|
|
show_more = await _create_more_session( |
|
|
"--MORE--" |
|
|
).prompt_async() |
|
|
|
|
|
if not show_more: |
|
|
return |
|
|
else: |
|
|
app.output.flush() |
|
|
else: |
|
|
|
|
|
display(0) |
|
|
|
|
|
return app.create_background_task(run_compl()) |
|
|
|
|
|
|
|
|
def _create_more_session(message: str = "--MORE--") -> PromptSession[bool]: |
|
|
""" |
|
|
Create a `PromptSession` object for displaying the "--MORE--". |
|
|
""" |
|
|
from prompt_toolkit.shortcuts import PromptSession |
|
|
|
|
|
bindings = KeyBindings() |
|
|
|
|
|
@bindings.add(" ") |
|
|
@bindings.add("y") |
|
|
@bindings.add("Y") |
|
|
@bindings.add(Keys.ControlJ) |
|
|
@bindings.add(Keys.ControlM) |
|
|
@bindings.add(Keys.ControlI) |
|
|
def _yes(event: E) -> None: |
|
|
event.app.exit(result=True) |
|
|
|
|
|
@bindings.add("n") |
|
|
@bindings.add("N") |
|
|
@bindings.add("q") |
|
|
@bindings.add("Q") |
|
|
@bindings.add(Keys.ControlC) |
|
|
def _no(event: E) -> None: |
|
|
event.app.exit(result=False) |
|
|
|
|
|
@bindings.add(Keys.Any) |
|
|
def _ignore(event: E) -> None: |
|
|
"Disable inserting of text." |
|
|
|
|
|
return PromptSession(message, key_bindings=bindings, erase_when_done=True) |
|
|
|