Skip to content

screens

ruff_sync.tui.screens

Screens for the Ruff-Sync Terminal User Interface.

MAX_SEARCH_RESULTS module-attribute

MAX_SEARCH_RESULTS = 15

OmniboxScreen

Bases: ModalScreen[str]

A modal search screen for quickly finding Ruff rules.

Source code in src/ruff_sync/tui/screens.py
class OmniboxScreen(ModalScreen[str]):
    """A modal search screen for quickly finding Ruff rules."""

    CSS = """
    OmniboxScreen {
        align: center middle;
    }

    #omnibox-container {
        width: 60;
        height: auto;
        max-height: 20;
        background: $boost;
        border: thick $primary;
        padding: 1;
    }

    #omnibox-input {
        margin-bottom: 1;
    }

    #omnibox-results {
        height: auto;
        max-height: 12;
        border: none;
        background: $surface;
    }
    """

    def __init__(self, all_rules: list[RuffRule], **kwargs: Any) -> None:
        """Initialize the search screen.

        Args:
            all_rules: The list of all rules to search through.
            **kwargs: Additional keyword arguments.
        """
        super().__init__(**kwargs)
        self.all_rules = all_rules

    @override
    def compose(self) -> ComposeResult:
        """Compose the search interface."""
        with Vertical(id="omnibox-container"):
            yield Static("[b]Search Ruff Rules[/b] (e.g. F401, unused)", id="omnibox-title")
            yield Input(placeholder="Start typing...", id="omnibox-input")
            yield OptionList(id="omnibox-results")

    def on_mount(self) -> None:
        """Focus the input on mount."""
        self.query_one(Input).focus()

    @on(Input.Changed)
    def handle_input_changed(self, event: Input.Changed) -> None:
        """Filter rules based on search input.

        Args:
            event: The input changed event.
        """
        search_query = event.value.strip().lower()
        results_list = self.query_one(OptionList)
        results_list.clear_options()

        if not search_query:
            return

        matches = []
        for rule in self.all_rules:
            code = rule["code"].lower()
            name = rule["name"].lower()
            if search_query in code or search_query in name:
                matches.append(rule)
                if len(matches) >= MAX_SEARCH_RESULTS:  # Limit results
                    break

        for match in matches:
            results_list.add_option(
                Option(f"[b]{match['code']}[/b] - {match['name']}", id=match["code"])
            )

    @on(Input.Submitted)
    def handle_input_submitted(self) -> None:
        """Handle enter key in the input."""
        results_list = self.query_one(OptionList)
        if results_list.option_count > 0:
            # If there's a selected option, use it. Otherwise use the first matching one.
            index = results_list.highlighted if results_list.highlighted is not None else 0
            option = results_list.get_option_at_index(index)
            if option.id:
                self.dismiss(str(option.id))

    @on(OptionList.OptionSelected)
    def handle_option_selected(self, event: OptionList.OptionSelected) -> None:
        """Handle selection from the results list."""
        if event.option.id:
            self.dismiss(str(event.option.id))

    def action_cancel(self) -> None:
        """Close the screen without selection."""
        self.dismiss()

CSS class-attribute instance-attribute

CSS = "\n    OmniboxScreen {\n        align: center middle;\n    }\n\n    #omnibox-container {\n        width: 60;\n        height: auto;\n        max-height: 20;\n        background: $boost;\n        border: thick $primary;\n        padding: 1;\n    }\n\n    #omnibox-input {\n        margin-bottom: 1;\n    }\n\n    #omnibox-results {\n        height: auto;\n        max-height: 12;\n        border: none;\n        background: $surface;\n    }\n    "

all_rules instance-attribute

all_rules = all_rules

__init__

__init__(all_rules, **kwargs)

Initialize the search screen.

Parameters:

Name Type Description Default
all_rules list[RuffRule]

The list of all rules to search through.

required
**kwargs Any

Additional keyword arguments.

{}
Source code in src/ruff_sync/tui/screens.py
def __init__(self, all_rules: list[RuffRule], **kwargs: Any) -> None:
    """Initialize the search screen.

    Args:
        all_rules: The list of all rules to search through.
        **kwargs: Additional keyword arguments.
    """
    super().__init__(**kwargs)
    self.all_rules = all_rules

compose

compose()

Compose the search interface.

Source code in src/ruff_sync/tui/screens.py
@override
def compose(self) -> ComposeResult:
    """Compose the search interface."""
    with Vertical(id="omnibox-container"):
        yield Static("[b]Search Ruff Rules[/b] (e.g. F401, unused)", id="omnibox-title")
        yield Input(placeholder="Start typing...", id="omnibox-input")
        yield OptionList(id="omnibox-results")

on_mount

on_mount()

Focus the input on mount.

Source code in src/ruff_sync/tui/screens.py
def on_mount(self) -> None:
    """Focus the input on mount."""
    self.query_one(Input).focus()

handle_input_changed

handle_input_changed(event)

Filter rules based on search input.

Parameters:

Name Type Description Default
event Changed

The input changed event.

required
Source code in src/ruff_sync/tui/screens.py
@on(Input.Changed)
def handle_input_changed(self, event: Input.Changed) -> None:
    """Filter rules based on search input.

    Args:
        event: The input changed event.
    """
    search_query = event.value.strip().lower()
    results_list = self.query_one(OptionList)
    results_list.clear_options()

    if not search_query:
        return

    matches = []
    for rule in self.all_rules:
        code = rule["code"].lower()
        name = rule["name"].lower()
        if search_query in code or search_query in name:
            matches.append(rule)
            if len(matches) >= MAX_SEARCH_RESULTS:  # Limit results
                break

    for match in matches:
        results_list.add_option(
            Option(f"[b]{match['code']}[/b] - {match['name']}", id=match["code"])
        )

handle_input_submitted

handle_input_submitted()

Handle enter key in the input.

Source code in src/ruff_sync/tui/screens.py
@on(Input.Submitted)
def handle_input_submitted(self) -> None:
    """Handle enter key in the input."""
    results_list = self.query_one(OptionList)
    if results_list.option_count > 0:
        # If there's a selected option, use it. Otherwise use the first matching one.
        index = results_list.highlighted if results_list.highlighted is not None else 0
        option = results_list.get_option_at_index(index)
        if option.id:
            self.dismiss(str(option.id))

handle_option_selected

handle_option_selected(event)

Handle selection from the results list.

Source code in src/ruff_sync/tui/screens.py
@on(OptionList.OptionSelected)
def handle_option_selected(self, event: OptionList.OptionSelected) -> None:
    """Handle selection from the results list."""
    if event.option.id:
        self.dismiss(str(event.option.id))

action_cancel

action_cancel()

Close the screen without selection.

Source code in src/ruff_sync/tui/screens.py
def action_cancel(self) -> None:
    """Close the screen without selection."""
    self.dismiss()

LegendScreen

Bases: ModalScreen[None]

A toggleable legend explaining TUI status colors and icons.

Source code in src/ruff_sync/tui/screens.py
class LegendScreen(ModalScreen[None]):
    """A toggleable legend explaining TUI status colors and icons."""

    BINDINGS: ClassVar[list[Any]] = [
        ("escape,l,?", "dismiss", "Close Legend"),
    ]

    CSS = """
    LegendScreen {
        align: center middle;
        background: black 50%;
    }

    #legend-container {
        width: 50;
        height: auto;
        background: $boost;
        border: heavy $accent;
        padding: 1 2;
    }

    .legend-title {
        text-align: center;
        text-style: bold;
        margin-bottom: 1;
        color: $accent;
    }

    .legend-section {
        text-style: underline;
        margin-top: 1;
        margin-bottom: 1;
    }

    .legend-row {
        margin-left: 2;
    }

    #legend-footer {
        text-align: center;
        margin-top: 1;
        color: $text-disabled;
    }
    """

    @override
    def compose(self) -> ComposeResult:
        """Compose the legend content."""
        with Vertical(id="legend-container"):
            yield Static("Ruff-Sync TUI Legend", id="legend-title", classes="legend-title")

            yield Static("Rule Status", classes="legend-section")
            yield Static(
                "🟢 [green]Enabled[/green]   - Active in configuration", classes="legend-row"
            )
            yield Static("🟡 [yellow]Ignored[/yellow]   - Explicitly ignored", classes="legend-row")
            yield Static("⚪ [dim]Disabled[/dim]  - Category not selected", classes="legend-row")

            yield Static("Fix Availability", classes="legend-section")
            yield Static("[cyan]Always[/cyan]      - Automatic fix available", classes="legend-row")
            yield Static(
                "[yellow]Sometimes[/yellow]   - Conditional/enforced fix", classes="legend-row"
            )

            yield Static("Press [b]? [/b] or [b]Esc[/b] to close", id="legend-footer")

BINDINGS class-attribute

BINDINGS = [('escape,l,?', 'dismiss', 'Close Legend')]

CSS class-attribute instance-attribute

CSS = "\n    LegendScreen {\n        align: center middle;\n        background: black 50%;\n    }\n\n    #legend-container {\n        width: 50;\n        height: auto;\n        background: $boost;\n        border: heavy $accent;\n        padding: 1 2;\n    }\n\n    .legend-title {\n        text-align: center;\n        text-style: bold;\n        margin-bottom: 1;\n        color: $accent;\n    }\n\n    .legend-section {\n        text-style: underline;\n        margin-top: 1;\n        margin-bottom: 1;\n    }\n\n    .legend-row {\n        margin-left: 2;\n    }\n\n    #legend-footer {\n        text-align: center;\n        margin-top: 1;\n        color: $text-disabled;\n    }\n    "

compose

compose()

Compose the legend content.

Source code in src/ruff_sync/tui/screens.py
@override
def compose(self) -> ComposeResult:
    """Compose the legend content."""
    with Vertical(id="legend-container"):
        yield Static("Ruff-Sync TUI Legend", id="legend-title", classes="legend-title")

        yield Static("Rule Status", classes="legend-section")
        yield Static(
            "🟢 [green]Enabled[/green]   - Active in configuration", classes="legend-row"
        )
        yield Static("🟡 [yellow]Ignored[/yellow]   - Explicitly ignored", classes="legend-row")
        yield Static("⚪ [dim]Disabled[/dim]  - Category not selected", classes="legend-row")

        yield Static("Fix Availability", classes="legend-section")
        yield Static("[cyan]Always[/cyan]      - Automatic fix available", classes="legend-row")
        yield Static(
            "[yellow]Sometimes[/yellow]   - Conditional/enforced fix", classes="legend-row"
        )

        yield Static("Press [b]? [/b] or [b]Esc[/b] to close", id="legend-footer")