File size: 6,903 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
"""
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
# When already navigating through completions, select the next one.
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)
"""
# Request completions.
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))
# Calculate the common suffix.
common_suffix = get_common_complete_suffix(b.document, completions)
# One completion: insert it.
if len(completions) == 1:
b.delete_before_cursor(-completions[0].start_position)
b.insert_text(completions[0].text)
# Multiple completions with common part.
elif common_suffix:
b.insert_text(common_suffix)
# Otherwise: display all completions.
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
# Get terminal dimensions.
term_size = app.output.get_size()
term_width = term_size.columns
term_height = term_size.rows
# Calculate amount of required columns/rows for displaying the
# completions. (Keep in mind that completions are displayed
# alphabetically column-wise.)
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)))
# Note: math.ceil can return float on Python2.
def display(page: int) -> None:
# Display completions.
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))
# Add padding.
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"))
# User interaction through an application generator function.
async def run_compl() -> None:
"Coroutine."
async with in_terminal(render_cli_done=True):
if len(completions) > completions_per_page:
# Ask confirmation if it doesn't fit on the screen.
confirm = await create_confirm_session(
f"Display all {len(completions)} possibilities?",
).prompt_async()
if confirm:
# Display pages.
for page in range(page_count):
display(page)
if page != page_count - 1:
# Display --MORE-- and go to the next page.
show_more = await _create_more_session(
"--MORE--"
).prompt_async()
if not show_more:
return
else:
app.output.flush()
else:
# Display all completions.
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) # Tab.
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)
|