Skip to content

Instantly share code, notes, and snippets.

@daddycocoaman
Created February 6, 2021 18:13
Show Gist options
  • Select an option

  • Save daddycocoaman/b3b86fd5c0b89e42c53db6efbb061cec to your computer and use it in GitHub Desktop.

Select an option

Save daddycocoaman/b3b86fd5c0b89e42c53db6efbb061cec to your computer and use it in GitHub Desktop.

Revisions

  1. daddycocoaman created this gist Feb 6, 2021.
    178 changes: 178 additions & 0 deletions radiolist.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,178 @@
    # 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