Add key_to_character function

This commit is contained in:
Will McGugan
2024-08-27 17:30:29 +01:00
parent 376cf0fae7
commit 9a0f8e54fa
6 changed files with 49 additions and 7 deletions

View File

@@ -1026,7 +1026,7 @@ class DOMNode(MessagePump):
)
return style
def check_consume_key(self, key: str) -> bool:
def check_consume_key(self, key: str, character: str | None) -> bool:
"""Check if the widget may consume the given key.
This should be implemented in widgets that handle [`Key`][textual.events.Key] events and
@@ -1037,6 +1037,7 @@ class DOMNode(MessagePump):
Args:
key: A key identifier.
character: A printable character associated with they key, or `None` if character is not printable.
Returns:
`True` if the widget may capture the key in its `Key` event handler, or `False` if it won't.

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import unicodedata
from enum import Enum
from functools import lru_cache
# Adapted from prompt toolkit https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/keys.py
@@ -282,6 +283,7 @@ def _get_key_aliases(key: str) -> list[str]:
return [key] + KEY_ALIASES.get(key, [])
@lru_cache(1024)
def format_key(key: str) -> str:
"""Given a key (i.e. the `key` string argument to Binding __init__),
return the value that should be displayed in the app when referring
@@ -303,6 +305,32 @@ def format_key(key: str) -> str:
return tentative_unicode_name
@lru_cache(1024)
def key_to_character(key: str) -> str | None:
"""Given a key identifier, return the character associated with it.
Args:
key: The key identifier.
Returns:
A key if one could be found, otherwise `None`.
"""
_, separator, key = key.rpartition("+")
if separator:
return None
if len(key) == 1:
return key
try:
return unicodedata.lookup(KEY_TO_UNICODE_NAME[key])
except KeyError:
pass
try:
return unicodedata.lookup(key.replace("_", " ").upper())
except KeyError:
pass
return None
def _character_to_key(character: str) -> str:
"""Convert a single character to a key value.

View File

@@ -29,6 +29,8 @@ import rich.repr
from rich.console import RenderableType
from rich.style import Style
from textual.keys import key_to_character
from . import constants, errors, events, messages
from ._arrange import arrange
from ._callback import invoke
@@ -330,7 +332,7 @@ class Screen(Generic[ScreenResultType], Widget):
for filter_namespace in filter_namespaces:
check_consume_key = filter_namespace.check_consume_key
for key in list(bindings_map.key_to_bindings):
if check_consume_key(key):
if check_consume_key(key, key_to_character(key)):
del bindings_map.key_to_bindings[key]
filter_namespaces.append(namespace)

View File

@@ -361,18 +361,19 @@ class Input(Widget, can_focus=True):
"""Flag to indicate if the cursor is at the end"""
return self.cursor_position >= len(self.value)
def check_consume_key(self, key: str) -> bool:
def check_consume_key(self, key: str, character: str | None) -> bool:
"""Check if the widget may consume the given key.
As an input we are expecting to capture printable keys.
Args:
key: A key identifier.
character: A printable character associated with they key, or `None` if character is not printable.
Returns:
`True` if the widget may capture the key in it's `Key` message, or `False` if it won't.
"""
return len(key) == 1 and key.isprintable()
return character is not None and character.isprintable()
def validate_cursor_position(self, cursor_position: int) -> int:
return min(max(0, cursor_position), len(self.value))

View File

@@ -546,13 +546,14 @@ TextArea {
return highlight_query
def check_consume_key(self, key: str) -> bool:
def check_consume_key(self, key: str, character: str | None = None) -> bool:
"""Check if the widget may consume the given key.
As a textarea we are expecting to capture printable keys.
Args:
key: A key identifier.
character: A printable character associated with they key, or `None` if character is not printable.
Returns:
`True` if the widget may capture the key in it's `Key` message, or `False` if it won't.
@@ -561,7 +562,7 @@ TextArea {
return False
if self.tab_behavior == "indent" and key == "tab":
return True
return len(key) == 1 and key.isprintable()
return character is not None and character.isprintable()
def _build_highlight_map(self) -> None:
"""Query the tree for ranges to highlights, and update the internal highlights mapping."""

View File

@@ -2,7 +2,7 @@ import pytest
from textual.app import App
from textual.binding import Binding
from textual.keys import _character_to_key, format_key
from textual.keys import _character_to_key, format_key, key_to_character
@pytest.mark.parametrize(
@@ -67,3 +67,12 @@ def test_get_key_display():
== "shift+^]"
)
assert app.get_key_display(Binding("delete", "", "")) == "del"
def test_key_to_character():
assert key_to_character("f") == "f"
assert key_to_character("F") == "F"
assert key_to_character("space") == " "
assert key_to_character("ctrl+space") is None
assert key_to_character("question_mark") == "?"
assert key_to_character("foo") is None