Module textual.binding

Expand source code
from __future__ import annotations

import sys
from dataclasses import dataclass
from typing import Iterable, MutableMapping

import rich.repr

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:  # pragma: no cover
    from typing_extensions import TypeAlias

BindingType: TypeAlias = "Binding | tuple[str, str, str]"


class BindingError(Exception):
    """A binding related error."""


class NoBinding(Exception):
    """A binding was not found."""


@dataclass(frozen=True)
class Binding:
    key: str
    """Key to bind. This can also be a comma-separated list of keys to map multiple keys to a single action."""
    action: str
    """Action to bind to."""
    description: str
    """Description of action."""
    show: bool = True
    """Show the action in Footer, or False to hide."""
    key_display: str | None = None
    """How the key should be shown in footer."""
    universal: bool = False
    """Allow forwarding from app to focused widget."""


@rich.repr.auto
class Bindings:
    """Manage a set of bindings."""

    def __init__(self, bindings: Iterable[BindingType] | None = None) -> None:
        def make_bindings(bindings: Iterable[BindingType]) -> Iterable[Binding]:
            for binding in bindings:
                # If it's a tuple of length 3, convert into a Binding first
                if isinstance(binding, tuple):
                    if len(binding) != 3:
                        raise BindingError(
                            f"BINDINGS must contain a tuple of three strings, not {binding!r}"
                        )
                    binding = Binding(*binding)

                binding_keys = binding.key.split(",")
                if len(binding_keys) > 1:
                    for key in binding_keys:
                        new_binding = Binding(
                            key=key,
                            action=binding.action,
                            description=binding.description,
                            show=binding.show,
                            key_display=binding.key_display,
                            universal=binding.universal,
                        )
                        yield new_binding
                else:
                    yield binding

        self.keys: MutableMapping[str, Binding] = (
            {binding.key: binding for binding in make_bindings(bindings)}
            if bindings
            else {}
        )

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.keys

    @classmethod
    def merge(cls, bindings: Iterable[Bindings]) -> Bindings:
        """Merge a bindings. Subsequence bound keys override initial keys.

        Args:
            bindings (Iterable[Bindings]): A number of bindings.

        Returns:
            Bindings: New bindings.
        """
        keys: dict[str, Binding] = {}
        for _bindings in bindings:
            keys.update(_bindings.keys)
        return Bindings(keys.values())

    @property
    def shown_keys(self) -> list[Binding]:
        """A list of bindings for shown keys.

        Returns:
            list[Binding]: Shown bindings.
        """
        keys = [binding for binding in self.keys.values() if binding.show]
        return keys

    def bind(
        self,
        keys: str,
        action: str,
        description: str = "",
        show: bool = True,
        key_display: str | None = None,
        universal: bool = False,
    ) -> None:
        all_keys = [key.strip() for key in keys.split(",")]
        for key in all_keys:
            self.keys[key] = Binding(
                key,
                action,
                description,
                show=show,
                key_display=key_display,
                universal=universal,
            )

    def get_key(self, key: str) -> Binding:
        """Get a binding if it exists.

        Args:
            key (str): Key to look up.

        Raises:
            NoBinding: If the binding does not exist.

        Returns:
            Binding: A binding object for the key,
        """
        try:
            return self.keys[key]
        except KeyError:
            raise NoBinding(f"No binding for {key}") from None

Classes

class Binding (key: str, action: str, description: str, show: bool = True, key_display: str | None = None, universal: bool = False)

Binding(key: 'str', action: 'str', description: 'str', show: 'bool' = True, key_display: 'str | None' = None, universal: 'bool' = False)

Expand source code
class Binding:
    key: str
    """Key to bind. This can also be a comma-separated list of keys to map multiple keys to a single action."""
    action: str
    """Action to bind to."""
    description: str
    """Description of action."""
    show: bool = True
    """Show the action in Footer, or False to hide."""
    key_display: str | None = None
    """How the key should be shown in footer."""
    universal: bool = False
    """Allow forwarding from app to focused widget."""

Class variables

var action : str

Action to bind to.

var description : str

Description of action.

var key : str

Key to bind. This can also be a comma-separated list of keys to map multiple keys to a single action.

var key_display : str | None

How the key should be shown in footer.

var show : bool

Show the action in Footer, or False to hide.

var universal : bool

Allow forwarding from app to focused widget.

class BindingError (*args, **kwargs)

A binding related error.

Expand source code
class BindingError(Exception):
    """A binding related error."""

Ancestors

  • builtins.Exception
  • builtins.BaseException
class Bindings (bindings: Iterable[BindingType] | None = None)

Manage a set of bindings.

Expand source code
class Bindings:
    """Manage a set of bindings."""

    def __init__(self, bindings: Iterable[BindingType] | None = None) -> None:
        def make_bindings(bindings: Iterable[BindingType]) -> Iterable[Binding]:
            for binding in bindings:
                # If it's a tuple of length 3, convert into a Binding first
                if isinstance(binding, tuple):
                    if len(binding) != 3:
                        raise BindingError(
                            f"BINDINGS must contain a tuple of three strings, not {binding!r}"
                        )
                    binding = Binding(*binding)

                binding_keys = binding.key.split(",")
                if len(binding_keys) > 1:
                    for key in binding_keys:
                        new_binding = Binding(
                            key=key,
                            action=binding.action,
                            description=binding.description,
                            show=binding.show,
                            key_display=binding.key_display,
                            universal=binding.universal,
                        )
                        yield new_binding
                else:
                    yield binding

        self.keys: MutableMapping[str, Binding] = (
            {binding.key: binding for binding in make_bindings(bindings)}
            if bindings
            else {}
        )

    def __rich_repr__(self) -> rich.repr.Result:
        yield self.keys

    @classmethod
    def merge(cls, bindings: Iterable[Bindings]) -> Bindings:
        """Merge a bindings. Subsequence bound keys override initial keys.

        Args:
            bindings (Iterable[Bindings]): A number of bindings.

        Returns:
            Bindings: New bindings.
        """
        keys: dict[str, Binding] = {}
        for _bindings in bindings:
            keys.update(_bindings.keys)
        return Bindings(keys.values())

    @property
    def shown_keys(self) -> list[Binding]:
        """A list of bindings for shown keys.

        Returns:
            list[Binding]: Shown bindings.
        """
        keys = [binding for binding in self.keys.values() if binding.show]
        return keys

    def bind(
        self,
        keys: str,
        action: str,
        description: str = "",
        show: bool = True,
        key_display: str | None = None,
        universal: bool = False,
    ) -> None:
        all_keys = [key.strip() for key in keys.split(",")]
        for key in all_keys:
            self.keys[key] = Binding(
                key,
                action,
                description,
                show=show,
                key_display=key_display,
                universal=universal,
            )

    def get_key(self, key: str) -> Binding:
        """Get a binding if it exists.

        Args:
            key (str): Key to look up.

        Raises:
            NoBinding: If the binding does not exist.

        Returns:
            Binding: A binding object for the key,
        """
        try:
            return self.keys[key]
        except KeyError:
            raise NoBinding(f"No binding for {key}") from None

Static methods

def merge(bindings: Iterable[Bindings]) ‑> Bindings

Merge a bindings. Subsequence bound keys override initial keys.

Args

bindings : Iterable[Bindings]
A number of bindings.

Returns

Bindings
New bindings.
Expand source code
@classmethod
def merge(cls, bindings: Iterable[Bindings]) -> Bindings:
    """Merge a bindings. Subsequence bound keys override initial keys.

    Args:
        bindings (Iterable[Bindings]): A number of bindings.

    Returns:
        Bindings: New bindings.
    """
    keys: dict[str, Binding] = {}
    for _bindings in bindings:
        keys.update(_bindings.keys)
    return Bindings(keys.values())

Instance variables

var shown_keys : list[Binding]

A list of bindings for shown keys.

Returns

list[Binding]
Shown bindings.
Expand source code
@property
def shown_keys(self) -> list[Binding]:
    """A list of bindings for shown keys.

    Returns:
        list[Binding]: Shown bindings.
    """
    keys = [binding for binding in self.keys.values() if binding.show]
    return keys

Methods

def bind(self, keys: str, action: str, description: str = '', show: bool = True, key_display: str | None = None, universal: bool = False) ‑> None
Expand source code
def bind(
    self,
    keys: str,
    action: str,
    description: str = "",
    show: bool = True,
    key_display: str | None = None,
    universal: bool = False,
) -> None:
    all_keys = [key.strip() for key in keys.split(",")]
    for key in all_keys:
        self.keys[key] = Binding(
            key,
            action,
            description,
            show=show,
            key_display=key_display,
            universal=universal,
        )
def get_key(self, key: str) ‑> Binding

Get a binding if it exists.

Args

key : str
Key to look up.

Raises

NoBinding
If the binding does not exist.

Returns

Binding
A binding object for the key,
Expand source code
def get_key(self, key: str) -> Binding:
    """Get a binding if it exists.

    Args:
        key (str): Key to look up.

    Raises:
        NoBinding: If the binding does not exist.

    Returns:
        Binding: A binding object for the key,
    """
    try:
        return self.keys[key]
    except KeyError:
        raise NoBinding(f"No binding for {key}") from None
class NoBinding (*args, **kwargs)

A binding was not found.

Expand source code
class NoBinding(Exception):
    """A binding was not found."""

Ancestors

  • builtins.Exception
  • builtins.BaseException