added char attribute to Text event

This commit is contained in:
Will McGugan
2022-09-04 11:02:24 +01:00
parent 1cc52bc913
commit b271ca7c2f
10 changed files with 72 additions and 29 deletions

View File

@@ -62,7 +62,7 @@ class FileSearchApp(App):
self.file_table.filter = event.value self.file_table.filter = event.value
app = FileSearchApp(log_path="textual.log", css_path="file_search.scss", watch_css=True) app = FileSearchApp(css_path="file_search.scss", watch_css=True)
if __name__ == "__main__": if __name__ == "__main__":
result = app.run() result = app.run()

View File

@@ -1,6 +1,6 @@
Screen { Screen {
layout: dock;
docks: top=top bottom=bottom;
} }
#file_table_wrapper { #file_table_wrapper {

17
sandbox/will/input.py Normal file
View File

@@ -0,0 +1,17 @@
from textual.app import App
from textual.widgets import TextInput
class InputApp(App):
CSS = """
TextInput {
}
"""
def compose(self):
yield TextInput(initial="foo")
app = InputApp()

View File

@@ -101,7 +101,7 @@ class XTermParser(Parser[events.Event]):
key_events = sequence_to_key_events(character) key_events = sequence_to_key_events(character)
for event in key_events: for event in key_events:
if event.key == "escape": if event.key == "escape":
event = events.Key(event.sender, key="^") event = events.Key(event.sender, "^", None)
on_token(event) on_token(event)
while not self.is_eof: while not self.is_eof:
@@ -229,7 +229,21 @@ class XTermParser(Parser[events.Event]):
on_token(event) on_token(event)
def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]: def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
default = (sequence,) if len(sequence) == 1 else () """Map a sequence of code points on to a sequence of keys.
keys = ANSI_SEQUENCES_KEYS.get(sequence, default)
for key in keys: Args:
yield events.Key(self.sender, key) sequence (str): Sequence of code points.
Returns:
Iterable[events.Key]: keys
"""
keys = ANSI_SEQUENCES_KEYS.get(sequence)
if keys is not None:
for key in keys:
yield events.Key(
self.sender, key.value, sequence if len(sequence) == 1 else None
)
elif len(sequence) == 1:
yield events.Key(self.sender, sequence, sequence)

View File

@@ -656,7 +656,9 @@ class App(Generic[ReturnType], DOMNode):
await asyncio.sleep(0.05) await asyncio.sleep(0.05)
else: else:
print(f"press {key!r}") print(f"press {key!r}")
driver.send_event(events.Key(self, key)) driver.send_event(
events.Key(self, key, key if len(key) == 1 else None)
)
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
if screenshot: if screenshot:
self._screenshot = self.export_screenshot( self._screenshot = self.export_screenshot(

View File

@@ -188,18 +188,26 @@ class Key(InputEvent):
"""Sent when the user hits a key on the keyboard. """Sent when the user hits a key on the keyboard.
Args: Args:
sender (MessageTarget): The sender of the event (the App) sender (MessageTarget): The sender of the event (the App).
key (str): The pressed key if a single character (or a longer string for special characters) key (str): A key name (textual.keys.Keys).
char (str | None, optional): A printable character or None if it is not printable.
""" """
__slots__ = ["key"] __slots__ = ["key", "char"]
def __init__(self, sender: MessageTarget, key: str) -> None: def __init__(self, sender: MessageTarget, key: str, char: str | None) -> None:
super().__init__(sender) super().__init__(sender)
self.key = key.value if isinstance(key, Keys) else key self.key = key
self.char = (key if len(key) == 1 else None) if char is None else char
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
yield "key", self.key yield "key", self.key
yield "char", self.char, None
@property
def key_name(self) -> str | None:
"""Name of a key suitable for use as a Python identifier."""
return self.key.replace("+", "_")
@property @property
def is_printable(self) -> bool: def is_printable(self) -> bool:
@@ -209,7 +217,7 @@ class Key(InputEvent):
Returns: Returns:
bool: True if the key is printable. bool: True if the key is printable.
""" """
return self.key == Keys.Space or self.key not in KEY_VALUES return False if self.char is None else self.char.isprintable()
@rich.repr.auto @rich.repr.auto

View File

@@ -517,7 +517,7 @@ class MessagePump(metaclass=MessagePumpMeta):
Args: Args:
event (events.Key): A key event. event (events.Key): A key event.
""" """
key_method = getattr(self, f"key_{event.key}", None) key_method = getattr(self, f"key_{event.key_name}", None)
if key_method is not None: if key_method is not None:
if await invoke(key_method, event): if await invoke(key_method, event):
event.prevent_default() event.prevent_default()

View File

@@ -19,6 +19,7 @@ __all__ = [
"Placeholder", "Placeholder",
"Pretty", "Pretty",
"Static", "Static",
"TextInput",
"TreeControl", "TreeControl",
] ]

View File

@@ -1,10 +1,11 @@
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list: # This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
from ._button import Button as Button from ._button import Button as Button
from ._data_table import DataTable from ._data_table import DataTable as DataTable
from ._directory_tree import DirectoryTree as DirectoryTree from ._directory_tree import DirectoryTree as DirectoryTree
from ._footer import Footer as Footer from ._footer import Footer as Footer
from ._header import Header as Header from ._header import Header as Header
from ._placeholder import Placeholder as Placeholder from ._placeholder import Placeholder as Placeholder
from ._pretty import Pretty as Pretty from ._pretty import Pretty as Pretty
from ._static import Static as Static from ._static import Static as Static
from ._text_input import TextInput as TextInput
from ._tree_control import TreeControl as TreeControl from ._tree_control import TreeControl as TreeControl

View File

@@ -40,12 +40,9 @@ class TextWidgetBase(Widget):
key = event.key key = event.key
if key == "escape": if key == "escape":
return return
elif key == "space":
key = " "
changed = False changed = False
if event.is_printable: if event.char is not None and event.is_printable:
changed = self._editor.insert(key) changed = self._editor.insert(event.char)
elif key == "ctrl+h": elif key == "ctrl+h":
changed = self._editor.delete_back() changed = self._editor.delete_back()
elif key == "ctrl+d": elif key == "ctrl+d":
@@ -59,11 +56,11 @@ class TextWidgetBase(Widget):
elif key == "end" or key == "ctrl+e": elif key == "end" or key == "ctrl+e":
self._editor.cursor_text_end() self._editor.cursor_text_end()
self.refresh(layout=True)
if changed: if changed:
self.post_message_no_wait(self.Changed(self, value=self._editor.content)) self.post_message_no_wait(self.Changed(self, value=self._editor.content))
self.refresh(layout=True)
def _apply_cursor_to_text(self, display_text: Text, index: int) -> Text: def _apply_cursor_to_text(self, display_text: Text, index: int) -> Text:
if index < 0: if index < 0:
return display_text return display_text
@@ -115,10 +112,9 @@ class TextInput(TextWidgetBase, can_focus=True):
DEFAULT_CSS = """ DEFAULT_CSS = """
TextInput { TextInput {
width: auto; width: auto;
background: $surface;
height: 3; height: 3;
padding: 0 1; padding: 1;
content-align: left middle; background: $surface;
} }
""" """
@@ -165,11 +161,15 @@ class TextInput(TextWidgetBase, can_focus=True):
self._editor.cursor_text_end() self._editor.cursor_text_end()
self.refresh() self.refresh()
def on_resize(self, event: events.Resize) -> None: def get_content_width(self, container: Size, viewport: Size) -> int:
# TODO: Why does this need +2 ?
return min(cell_len(self._editor.content) + 2, container.width)
def _on_resize(self, event: events.Resize) -> None:
# Ensure the cursor remains visible when the widget is resized # Ensure the cursor remains visible when the widget is resized
self._reset_visible_range() self._reset_visible_range()
def on_click(self, event: events.Click) -> None: def _on_click(self, event: events.Click) -> None:
"""When the user clicks on the text input, the cursor moves to the """When the user clicks on the text input, the cursor moves to the
character that was clicked on. Double-width characters makes this more character that was clicked on. Double-width characters makes this more
difficult.""" difficult."""