Created
February 6, 2021 18:13
-
-
Save daddycocoaman/b3b86fd5c0b89e42c53db6efbb061cec to your computer and use it in GitHub Desktop.
Custom Prompt-Toolkit Radio List
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Customized from https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/widgets/base.py | |
from typing import Generic, Sequence, Tuple, TypeVar | |
from prompt_toolkit.application import get_app | |
from prompt_toolkit.filters import Condition | |
from prompt_toolkit.formatted_text import ( | |
AnyFormattedText, | |
StyleAndTextTuples, | |
to_formatted_text, | |
) | |
from prompt_toolkit.key_binding.key_bindings import KeyBindings | |
from prompt_toolkit.key_binding.key_processor import KeyPressEvent | |
from prompt_toolkit.layout import ScrollOffsets | |
from prompt_toolkit.layout.containers import Container, Window, WindowAlign | |
from prompt_toolkit.layout.controls import FormattedTextControl | |
from prompt_toolkit.layout.margins import ConditionalMargin, ScrollbarMargin | |
from prompt_toolkit.lexers import DynamicLexer, PygmentsLexer | |
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType | |
from prompt_toolkit.widgets import TextArea | |
E = KeyPressEvent | |
_T = TypeVar("_T") | |
class DCSRadioList(Generic[_T]): | |
""" | |
List of radio buttons. Only one can be checked at the same time. | |
:param values: List of (value, label) tuples. | |
""" | |
open_character = "[" | |
close_character = "]" | |
container_style = "class:radio-list" | |
default_style = "class:radio" | |
selected_style = "class:radio-selected" | |
checked_style = "class:radio-checked" | |
show_scrollbar: bool = True | |
def __init__(self, values: Sequence[Tuple[_T, AnyFormattedText]]) -> None: | |
assert len(values) > 0 | |
self.values = values | |
self.values_length = len(self.values) | |
self.current_value: _T = values[0][0] | |
self._selected_index = 0 | |
# Key bindings. | |
kb = KeyBindings() | |
@kb.add("up") | |
def _up(event: E) -> None: | |
self._selected_index = max(0, self._selected_index - 1) | |
self._handle_enter() | |
@kb.add("down") | |
def _down(event: E) -> None: | |
self._selected_index = min(self.values_length - 1, self._selected_index + 1) | |
self._handle_enter() | |
@kb.add("pageup") | |
def _pageup(event: E) -> None: | |
w = event.app.layout.current_window | |
if w.render_info: | |
self._selected_index = max( | |
0, self._selected_index - len(w.render_info.displayed_lines) | |
) | |
self._handle_enter() | |
@kb.add("pagedown") | |
def _pagedown(event: E) -> None: | |
w = event.app.layout.current_window | |
if w.render_info: | |
self._selected_index = min( | |
self.values_length - 1, | |
self._selected_index + len(w.render_info.displayed_lines), | |
) | |
self._handle_enter() | |
@kb.add("enter") | |
@kb.add(" ") | |
def _click(event: E) -> None: | |
self._handle_enter() | |
# Control and window. | |
self.control = FormattedTextControl( | |
self._get_text_fragments, key_bindings=kb, focusable=True | |
) | |
self.window = Window( | |
content=self.control, | |
style=self.container_style, | |
right_margins=[ | |
ConditionalMargin( | |
margin=ScrollbarMargin(display_arrows=True), | |
filter=Condition(lambda: self.show_scrollbar), | |
), | |
], | |
scroll_offsets=ScrollOffsets(bottom=10), | |
wrap_lines=False, | |
allow_scroll_beyond_bottom=True, | |
ignore_content_height=True, | |
align=WindowAlign.LEFT, | |
width=60, | |
) | |
def _handle_enter(self) -> None: | |
"""Update the text display area when a new radio option is selected.""" | |
self.current_value = self.values[self._selected_index][0] | |
# DO SOME STUFF HERE! In this case, I display file contents in another window | |
app = get_app() | |
# Configure text area | |
text_area = TextArea( | |
self.current_value.source.read_text("latin-1"), | |
lexer=DynamicLexer( | |
lambda: PygmentsLexer.from_filename(str(self.current_value.source)) | |
), | |
scrollbar=True, | |
line_numbers=True, | |
focus_on_click=True, | |
read_only=True, | |
) | |
if self.current_value.source.suffix == ".config": | |
text_area.lexer = DynamicLexer(lambda: PygmentsLexer.from_filename(".xml")) | |
app.layout.container.children[1].children[1] = text_area.window | |
text_area.buffer.cursor_down(self.current_value.lineno + 15) | |
def _get_text_fragments(self) -> StyleAndTextTuples: | |
def mouse_handler(mouse_event: MouseEvent) -> None: | |
""" | |
Set `_selected_index` and `current_value` according to the y | |
position of the mouse click event. | |
""" | |
if mouse_event.event_type == MouseEventType.MOUSE_UP: | |
self._selected_index = mouse_event.position.y | |
result: StyleAndTextTuples = [] | |
# We only need to render what is on the screen for massive lists. Try 15. | |
for i, value in enumerate( | |
self.values[self._selected_index : self._selected_index + 15], | |
start=self._selected_index, | |
): | |
checked = value[0] == self.current_value | |
selected = i == self._selected_index | |
style = "" | |
if checked: | |
style += " " + self.checked_style | |
if selected: | |
style += " " + self.selected_style | |
result.append((style, self.open_character)) | |
if selected: | |
result.append(("[SetCursorPosition]", "")) | |
if checked: | |
result.append((style, "*" * len(str(i)))) | |
else: | |
result.append((style, str(i + 1))) | |
result.append((style, self.close_character)) | |
result.append((self.default_style, "\n")) | |
result.extend(to_formatted_text(value[1], style=self.default_style)) | |
result.append(("", "\n")) | |
# Add mouse handler to all fragments. | |
for i in range(10): | |
result[i] = (result[i][0], result[i][1], mouse_handler) | |
return result | |
def __pt_container__(self) -> Container: | |
return self.window |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment