mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
added char attribute to Text event
This commit is contained in:
@@ -62,7 +62,7 @@ class FileSearchApp(App):
|
||||
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__":
|
||||
result = app.run()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Screen {
|
||||
layout: dock;
|
||||
docks: top=top bottom=bottom;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#file_table_wrapper {
|
||||
|
||||
17
sandbox/will/input.py
Normal file
17
sandbox/will/input.py
Normal 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()
|
||||
@@ -101,7 +101,7 @@ class XTermParser(Parser[events.Event]):
|
||||
key_events = sequence_to_key_events(character)
|
||||
for event in key_events:
|
||||
if event.key == "escape":
|
||||
event = events.Key(event.sender, key="^")
|
||||
event = events.Key(event.sender, "^", None)
|
||||
on_token(event)
|
||||
|
||||
while not self.is_eof:
|
||||
@@ -229,7 +229,21 @@ class XTermParser(Parser[events.Event]):
|
||||
on_token(event)
|
||||
|
||||
def _sequence_to_key_events(self, sequence: str) -> Iterable[events.Key]:
|
||||
default = (sequence,) if len(sequence) == 1 else ()
|
||||
keys = ANSI_SEQUENCES_KEYS.get(sequence, default)
|
||||
for key in keys:
|
||||
yield events.Key(self.sender, key)
|
||||
"""Map a sequence of code points on to a sequence of keys.
|
||||
|
||||
Args:
|
||||
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)
|
||||
|
||||
@@ -656,7 +656,9 @@ class App(Generic[ReturnType], DOMNode):
|
||||
await asyncio.sleep(0.05)
|
||||
else:
|
||||
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)
|
||||
if screenshot:
|
||||
self._screenshot = self.export_screenshot(
|
||||
|
||||
@@ -188,18 +188,26 @@ class Key(InputEvent):
|
||||
"""Sent when the user hits a key on the keyboard.
|
||||
|
||||
Args:
|
||||
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)
|
||||
sender (MessageTarget): The sender of the event (the App).
|
||||
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)
|
||||
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:
|
||||
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
|
||||
def is_printable(self) -> bool:
|
||||
@@ -209,7 +217,7 @@ class Key(InputEvent):
|
||||
Returns:
|
||||
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
|
||||
|
||||
@@ -517,7 +517,7 @@ class MessagePump(metaclass=MessagePumpMeta):
|
||||
Args:
|
||||
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 await invoke(key_method, event):
|
||||
event.prevent_default()
|
||||
|
||||
@@ -19,6 +19,7 @@ __all__ = [
|
||||
"Placeholder",
|
||||
"Pretty",
|
||||
"Static",
|
||||
"TextInput",
|
||||
"TreeControl",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
|
||||
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 ._footer import Footer as Footer
|
||||
from ._header import Header as Header
|
||||
from ._placeholder import Placeholder as Placeholder
|
||||
from ._pretty import Pretty as Pretty
|
||||
from ._static import Static as Static
|
||||
from ._text_input import TextInput as TextInput
|
||||
from ._tree_control import TreeControl as TreeControl
|
||||
|
||||
@@ -40,12 +40,9 @@ class TextWidgetBase(Widget):
|
||||
key = event.key
|
||||
if key == "escape":
|
||||
return
|
||||
elif key == "space":
|
||||
key = " "
|
||||
|
||||
changed = False
|
||||
if event.is_printable:
|
||||
changed = self._editor.insert(key)
|
||||
if event.char is not None and event.is_printable:
|
||||
changed = self._editor.insert(event.char)
|
||||
elif key == "ctrl+h":
|
||||
changed = self._editor.delete_back()
|
||||
elif key == "ctrl+d":
|
||||
@@ -59,11 +56,11 @@ class TextWidgetBase(Widget):
|
||||
elif key == "end" or key == "ctrl+e":
|
||||
self._editor.cursor_text_end()
|
||||
|
||||
self.refresh(layout=True)
|
||||
|
||||
if changed:
|
||||
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:
|
||||
if index < 0:
|
||||
return display_text
|
||||
@@ -115,10 +112,9 @@ class TextInput(TextWidgetBase, can_focus=True):
|
||||
DEFAULT_CSS = """
|
||||
TextInput {
|
||||
width: auto;
|
||||
background: $surface;
|
||||
height: 3;
|
||||
padding: 0 1;
|
||||
content-align: left middle;
|
||||
padding: 1;
|
||||
background: $surface;
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -165,11 +161,15 @@ class TextInput(TextWidgetBase, can_focus=True):
|
||||
self._editor.cursor_text_end()
|
||||
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
|
||||
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
|
||||
character that was clicked on. Double-width characters makes this more
|
||||
difficult."""
|
||||
Reference in New Issue
Block a user