Skip to content

Commit 59057ee

Browse files
authored
Merge pull request #1409 from Textualize/cli-keys
keys command
2 parents df6e4df + c4b74f1 commit 59057ee

File tree

9 files changed

+202
-103
lines changed

9 files changed

+202
-103
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010
### Fixed
1111

1212
- Fixed issues with nested auto dimensions https://github.com/Textualize/textual/issues/1402
13+
- Fixed watch method incorrectly running on first set when value hasn't changed and init=False https://github.com/Textualize/textual/pull/1367
1314

1415
### Added
1516

1617
- Added `textual.actions.SkipAction` exception which can be raised from an action to allow parents to process bindings.
18+
- Added `textual keys` preview.
1719

18-
### Fixed
20+
### Changed
1921

20-
- Fixed watch method incorrectly running on first set when value hasnt changed and init=False https://github.com/Textualize/textual/pull/1367
22+
- Moved Ctrl+C, tab, and shift+tab to App BINDINGS
2123

2224
## [0.7.0] - 2022-12-17
2325

src/textual/app.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,14 @@ class App(Generic[ReturnType], DOMNode):
240240
TITLE: str | None = None
241241
SUB_TITLE: str | None = None
242242

243+
BINDINGS = [
244+
Binding("ctrl+c", "quit", "Quit", show=False, priority=True),
245+
Binding("tab", "focus_next", "Focus Next", show=False, priority=False),
246+
Binding(
247+
"shift+tab", "focus_previous", "Focus Previous", show=False, priority=False
248+
),
249+
]
250+
243251
title: Reactive[str] = Reactive("")
244252
sub_title: Reactive[str] = Reactive("")
245253
dark: Reactive[bool] = Reactive(True)
@@ -301,7 +309,6 @@ def __init__(
301309

302310
self._logger = Logger(self._log)
303311

304-
self._bindings.bind("ctrl+c", "quit", show=False, priority=True)
305312
self._refresh_required = False
306313

307314
self.design = DEFAULT_COLORS
@@ -1898,13 +1905,8 @@ async def _on_layout(self, message: messages.Layout) -> None:
18981905
message.stop()
18991906

19001907
async def _on_key(self, event: events.Key) -> None:
1901-
if event.key == "tab":
1902-
self.screen.focus_next()
1903-
elif event.key == "shift+tab":
1904-
self.screen.focus_previous()
1905-
else:
1906-
if not (await self.check_bindings(event.key)):
1907-
await self.dispatch_key(event)
1908+
if not (await self.check_bindings(event.key)):
1909+
await self.dispatch_key(event)
19081910

19091911
async def _on_shutdown_request(self, event: events.ShutdownRequest) -> None:
19101912
log("shutdown request")
@@ -2124,6 +2126,14 @@ async def action_remove_class_(self, selector: str, class_name: str) -> None:
21242126
async def action_toggle_class(self, selector: str, class_name: str) -> None:
21252127
self.screen.query(selector).toggle_class(class_name)
21262128

2129+
def action_focus_next(self) -> None:
2130+
"""Focus the next widget."""
2131+
self.screen.focus_next()
2132+
2133+
def action_focus_previous(self) -> None:
2134+
"""Focus the previous widget."""
2135+
self.screen.focus_previous()
2136+
21272137
def _on_terminal_supports_synchronized_output(
21282138
self, message: messages.TerminalSupportsSynchronizedOutput
21292139
) -> None:

src/textual/cli/cli.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,11 @@ def colors():
123123
from textual.cli.previews import colors
124124

125125
colors.app.run()
126+
127+
128+
@run.command("keys")
129+
def keys():
130+
"""Show key events"""
131+
from textual.cli.previews import keys
132+
133+
keys.app.run()

src/textual/cli/previews/keys.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from rich.panel import Panel
2+
3+
from textual.app import App, ComposeResult
4+
from textual import events
5+
from textual.containers import Horizontal
6+
from textual.widgets import Button, Header, TextLog
7+
8+
9+
INSTRUCTIONS = """\
10+
Press some keys!
11+
12+
Because we want to display all the keys, Ctrl+C won't work for this example. Use the button below to quit.\
13+
"""
14+
15+
16+
class KeyLog(TextLog, inherit_bindings=False):
17+
"""We don't want to handle scroll keys."""
18+
19+
20+
class KeysApp(App, inherit_bindings=False):
21+
"""Show key events in a text log."""
22+
23+
TITLE = "Textual Keys"
24+
BINDINGS = [("c", "clear", "Clear")]
25+
CSS = """
26+
#buttons {
27+
dock: bottom;
28+
height: 3;
29+
}
30+
Button {
31+
width: 1fr;
32+
}
33+
"""
34+
35+
def compose(self) -> ComposeResult:
36+
yield Header()
37+
yield Horizontal(
38+
Button("Clear", id="clear", variant="warning"),
39+
Button("Quit", id="quit", variant="error"),
40+
id="buttons",
41+
)
42+
yield KeyLog()
43+
44+
def on_ready(self) -> None:
45+
self.query_one(KeyLog).write(Panel(INSTRUCTIONS), expand=True)
46+
47+
def on_key(self, event: events.Key) -> None:
48+
self.query_one(KeyLog).write(event)
49+
50+
def on_button_pressed(self, event: Button.Pressed) -> None:
51+
if event.button.id == "quit":
52+
self.exit()
53+
elif event.button.id == "clear":
54+
self.query_one(KeyLog).clear()
55+
56+
57+
app = KeysApp()
58+
59+
if __name__ == "__main__":
60+
app.run()

src/textual/dom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from ._context import NoActiveAppError
2424
from ._node_list import NodeList
25-
from .binding import Binding, Bindings, BindingType
25+
from .binding import Bindings, BindingType
2626
from .color import BLACK, WHITE, Color
2727
from .css._error_tools import friendly_list
2828
from .css.constants import VALID_DISPLAY, VALID_VISIBILITY

src/textual/events.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ class Key(InputEvent):
199199
key_aliases (list[str]): The aliases for the key, including the key itself
200200
"""
201201

202-
__slots__ = ["key", "char"]
202+
__slots__ = ["key", "char", "key_aliases"]
203203

204204
def __init__(self, sender: MessageTarget, key: str, char: str | None) -> None:
205205
super().__init__(sender)
@@ -209,7 +209,9 @@ def __init__(self, sender: MessageTarget, key: str, char: str | None) -> None:
209209

210210
def __rich_repr__(self) -> rich.repr.Result:
211211
yield "key", self.key
212-
yield "char", self.char, None
212+
yield "char", self.char
213+
yield "is_printable", self.is_printable
214+
yield "key_aliases", self.key_aliases, [self.key_name]
213215

214216
@property
215217
def key_name(self) -> str | None:

src/textual/widgets/_text_log.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,18 @@ def __init__(
6161
def _on_styles_updated(self) -> None:
6262
self._line_cache.clear()
6363

64-
def write(self, content: RenderableType | object) -> None:
64+
def write(
65+
self,
66+
content: RenderableType | object,
67+
width: int | None = None,
68+
expand: bool = False,
69+
) -> None:
6570
"""Write text or a rich renderable.
6671
6772
Args:
6873
content (RenderableType): Rich renderable (or text).
74+
width (int): Width to render or None to use optimal width. Defaults to None.
75+
expand (bool): Enable expand to widget width, or False to use `width`.
6976
"""
7077

7178
renderable: RenderableType
@@ -88,13 +95,17 @@ def write(self, content: RenderableType | object) -> None:
8895
if isinstance(renderable, Text) and not self.wrap:
8996
render_options = render_options.update(overflow="ignore", no_wrap=True)
9097

91-
width = max(
92-
self.min_width,
93-
measure_renderables(console, render_options, [renderable]).maximum,
94-
)
98+
if expand:
99+
render_width = self.scrollable_content_region.width
100+
else:
101+
render_width = (
102+
measure_renderables(console, render_options, [renderable]).maximum
103+
if width is None
104+
else width
105+
)
95106

96107
segments = self.app.console.render(
97-
renderable, render_options.update_width(width)
108+
renderable, render_options.update_width(render_width)
98109
)
99110
lines = list(Segment.split_lines(segments))
100111
if not lines:

0 commit comments

Comments
 (0)