Skip to content

Instantly share code, notes, and snippets.

@daddycocoaman
Created February 6, 2021 18:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daddycocoaman/b3b86fd5c0b89e42c53db6efbb061cec to your computer and use it in GitHub Desktop.
Save daddycocoaman/b3b86fd5c0b89e42c53db6efbb061cec to your computer and use it in GitHub Desktop.
Custom Prompt-Toolkit Radio List
# 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