From 37fbeee7813ba7b54e98618b9e37062da19e3f92 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 11:32:39 +0100 Subject: [PATCH 01/18] context sensitive help --- src/textual/app.py | 8 +-- src/textual/dom.py | 3 ++ src/textual/system_commands.py | 2 +- src/textual/widgets/__init__.py | 2 + src/textual/widgets/__init__.pyi | 1 + src/textual/widgets/_button.py | 14 ++++++ src/textual/widgets/_help_panel.py | 81 ++++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 src/textual/widgets/_help_panel.py diff --git a/src/textual/app.py b/src/textual/app.py index c03838abab..2fdce93a9a 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -3529,16 +3529,16 @@ def action_focus_previous(self) -> None: def action_hide_keys(self) -> None: """Hide the keys panel (if present).""" - self.screen.query("KeyPanel").remove() + self.screen.query("KelpPanel").remove() def action_show_keys(self) -> None: """Show the keys panel.""" - from .widgets import KeyPanel + from .widgets import HelpPanel try: - self.query_one(KeyPanel) + self.query_one(HelpPanel) except NoMatches: - self.mount(KeyPanel()) + self.mount(HelpPanel()) def _on_terminal_supports_synchronized_output( self, message: messages.TerminalSupportsSynchronizedOutput diff --git a/src/textual/dom.py b/src/textual/dom.py index 29328874ac..fe00a09933 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -145,6 +145,9 @@ class DOMNode(MessagePump): SCOPED_CSS: ClassVar[bool] = True """Should default css be limited to the widget type?""" + HELP: ClassVar[str | None] = None + """Optional help text shown in help panel (Markdown format).""" + # True if this node inherits the CSS from the base class. _inherit_css: ClassVar[bool] = True diff --git a/src/textual/system_commands.py b/src/textual/system_commands.py index 705303c29e..38c048d284 100644 --- a/src/textual/system_commands.py +++ b/src/textual/system_commands.py @@ -47,7 +47,7 @@ def _system_commands(self) -> Iterable[tuple[str, IgnoreReturnCallbackType, str] "Quit the application as soon as possible", ) - if self.screen.query("KeyPanel"): + if self.screen.query("HelpPanel"): yield ("Hide keys", self.app.action_hide_keys, "Hide the keys panel") else: yield ( diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index bff80be2ce..8361aad00c 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -20,6 +20,7 @@ from ._directory_tree import DirectoryTree from ._footer import Footer from ._header import Header + from ._help_panel import HelpPanel from ._input import Input from ._key_panel import KeyPanel from ._label import Label @@ -59,6 +60,7 @@ "DirectoryTree", "Footer", "Header", + "HelpPanel", "Input", "KeyPanel", "Label", diff --git a/src/textual/widgets/__init__.pyi b/src/textual/widgets/__init__.pyi index f09f042d97..aaf2e3b9f4 100644 --- a/src/textual/widgets/__init__.pyi +++ b/src/textual/widgets/__init__.pyi @@ -9,6 +9,7 @@ from ._digits import Digits as Digits from ._directory_tree import DirectoryTree as DirectoryTree from ._footer import Footer as Footer from ._header import Header as Header +from ._help_panel import HelpPanel as HelpPanel from ._input import Input as Input from ._key_panel import KeyPanel as KeyPanel from ._label import Label as Label diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 7024f2bac6..5e23997830 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -145,6 +145,20 @@ class Button(Widget, can_focus=True): } """ + HELP = """ + A simple button. + + 1. Foo + 2. Bar + 3. Baz + + ```python + import this + for n in range(100): + print(n) + ``` + """ + BINDINGS = [Binding("enter", "press", "Press Button", show=False)] label: reactive[TextType] = reactive[TextType]("") diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py new file mode 100644 index 0000000000..c356c0f7fa --- /dev/null +++ b/src/textual/widgets/_help_panel.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from textwrap import dedent + +from textual.app import ComposeResult +from textual.widget import Widget +from textual.widgets import KeyPanel, Markdown + +MD = """\ +Hello *world* + +1. Foo +2. Bar +""" + + +class HelpPanel(Widget): + DEFAULT_CSS = """ + + HelpPanel { + split: right; + width: 33%; + min-width: 30; + max-width: 60; + border-left: vkey $foreground 30%; + padding: 0 1; + height: 1fr; + padding-right: 1; + layout: vertical; + height: 100%; + + + #widget-help { + height: auto; + max-height: 50%; + width: 100%; + padding: 0; + margin: 0; + padding: 1 0; + margin-top: 1; + display: none; + background: $panel; + + MarkdownBlock { + padding-left: 2; + padding-right: 2; + } + } + + &.-show-help #widget-help { + display: block; + } + + KeyPanel#keys-help { + width: 100%; + height: 1fr; + split: initial; + border-left: none; + padding: 0; + } + + } + + """ + + def on_mount(self): + self.watch(self.screen, "focused", self.update_help) + + def update_help(self, focused_widget: Widget | None) -> None: + if not self.app.app_focus: + return + self.set_class(focused_widget is not None, "-show-help") + if focused_widget is not None: + help = focused_widget.HELP or "" + if not help: + self.remove_class("-show-help") + self.query_one(Markdown).update(dedent(help.rstrip())) + + def compose(self) -> ComposeResult: + yield Markdown(MD, id="widget-help") + yield KeyPanel(id="keys-help") From 9a3f5b5f57463ae1b209de60583b30997fc6d3c9 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 13:21:17 +0100 Subject: [PATCH 02/18] rename panel --- CHANGELOG.md | 1 + src/textual/app.py | 6 +++--- src/textual/system_commands.py | 12 ++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f49737faed..e32174597e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `App.COMMAND_PALETTE_KEY` to change default command palette key binding https://github.com/Textualize/textual/pull/4867 - Added `App.get_key_display` https://github.com/Textualize/textual/pull/4890 - Added `DOMNode.BINDING_GROUP` https://github.com/Textualize/textual/pull/4906 +- Added `DOMNode.HELP` classvar https://github.com/Textualize/textual/pull/4915 ### Changed diff --git a/src/textual/app.py b/src/textual/app.py index 2fdce93a9a..c12ba3cdaa 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -3527,11 +3527,11 @@ def action_focus_previous(self) -> None: """An [action](/guide/actions) to focus the previous widget.""" self.screen.focus_previous() - def action_hide_keys(self) -> None: + def action_hide_help_panel(self) -> None: """Hide the keys panel (if present).""" - self.screen.query("KelpPanel").remove() + self.screen.query("HelpPanel").remove() - def action_show_keys(self) -> None: + def action_show_help_panel(self) -> None: """Show the keys panel.""" from .widgets import HelpPanel diff --git a/src/textual/system_commands.py b/src/textual/system_commands.py index 38c048d284..d2ad79c5b6 100644 --- a/src/textual/system_commands.py +++ b/src/textual/system_commands.py @@ -48,12 +48,16 @@ def _system_commands(self) -> Iterable[tuple[str, IgnoreReturnCallbackType, str] ) if self.screen.query("HelpPanel"): - yield ("Hide keys", self.app.action_hide_keys, "Hide the keys panel") + yield ( + "Hide keys and help panel", + self.app.action_hide_help_panel, + "Hide the keys and widget help panel", + ) else: yield ( - "Show keys", - self.app.action_show_keys, - "Show a summary of available keys", + "Show keys and help panel", + self.app.action_show_help_panel, + "Show help for the focused widget and a summary of available keys", ) async def discover(self) -> Hits: From 0aae4e11c6d8faf47377580cf681fcc3b3e44d21 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 13:25:43 +0100 Subject: [PATCH 03/18] comments --- src/textual/widgets/_help_panel.py | 16 +++++++--------- src/textual/widgets/_key_panel.py | 4 ++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index c356c0f7fa..dc5cb8c064 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -6,15 +6,12 @@ from textual.widget import Widget from textual.widgets import KeyPanel, Markdown -MD = """\ -Hello *world* - -1. Foo -2. Bar -""" - class HelpPanel(Widget): + """ + Shows context sensitive help for the currently focused widget. + """ + DEFAULT_CSS = """ HelpPanel { @@ -58,11 +55,12 @@ class HelpPanel(Widget): border-left: none; padding: 0; } - } """ + DEFAULT_CLASSES = "-textual-system" + def on_mount(self): self.watch(self.screen, "focused", self.update_help) @@ -77,5 +75,5 @@ def update_help(self, focused_widget: Widget | None) -> None: self.query_one(Markdown).update(dedent(help.rstrip())) def compose(self) -> ComposeResult: - yield Markdown(MD, id="widget-help") + yield Markdown(id="widget-help") yield KeyPanel(id="keys-help") diff --git a/src/textual/widgets/_key_panel.py b/src/textual/widgets/_key_panel.py index 8b1b058e41..0f8fccd5ce 100644 --- a/src/textual/widgets/_key_panel.py +++ b/src/textual/widgets/_key_panel.py @@ -103,6 +103,10 @@ def render(self) -> Table: class KeyPanel(VerticalScroll, can_focus=False): + """ + Shows bindings for currently focused widget. + """ + DEFAULT_CSS = """ KeyPanel { split: right; From 0ffa74c71ceed5ae93e85a05ceb52df134c268bf Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 13:26:31 +0100 Subject: [PATCH 04/18] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e32174597e..a2d622eaf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `App.COMMAND_PALETTE_KEY` to change default command palette key binding https://github.com/Textualize/textual/pull/4867 - Added `App.get_key_display` https://github.com/Textualize/textual/pull/4890 - Added `DOMNode.BINDING_GROUP` https://github.com/Textualize/textual/pull/4906 -- Added `DOMNode.HELP` classvar https://github.com/Textualize/textual/pull/4915 +- Added `DOMNode.HELP` classvar which contains Markdown help to be shown in the help panel https://github.com/Textualize/textual/pull/4915 ### Changed From c7c491bea81871e843df5bce210567bec8c95ff7 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 13:33:46 +0100 Subject: [PATCH 05/18] remove debug help --- src/textual/widgets/_button.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 5e23997830..7024f2bac6 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -145,20 +145,6 @@ class Button(Widget, can_focus=True): } """ - HELP = """ - A simple button. - - 1. Foo - 2. Bar - 3. Baz - - ```python - import this - for n in range(100): - print(n) - ``` - """ - BINDINGS = [Binding("enter", "press", "Press Button", show=False)] label: reactive[TextType] = reactive[TextType]("") From bcaa31b634cbf3df7650e0ad508a3d71dc15f40c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 13:40:23 +0100 Subject: [PATCH 06/18] hardening --- src/textual/widgets/_help_panel.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index dc5cb8c064..1626e75a7f 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -2,9 +2,10 @@ from textwrap import dedent -from textual.app import ComposeResult -from textual.widget import Widget -from textual.widgets import KeyPanel, Markdown +from ..app import ComposeResult +from ..css.query import NoMatches +from ..widget import Widget +from ..widgets import KeyPanel, Markdown class HelpPanel(Widget): @@ -72,7 +73,10 @@ def update_help(self, focused_widget: Widget | None) -> None: help = focused_widget.HELP or "" if not help: self.remove_class("-show-help") - self.query_one(Markdown).update(dedent(help.rstrip())) + try: + self.query_one(Markdown).update(dedent(help.rstrip())) + except NoMatches: + pass def compose(self) -> ComposeResult: yield Markdown(id="widget-help") From 990b1b3ac2448549edd11d281487ebe287d549d8 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 13:43:16 +0100 Subject: [PATCH 07/18] docstring --- src/textual/widgets/_help_panel.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index 1626e75a7f..9ed8421e1b 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -66,6 +66,11 @@ def on_mount(self): self.watch(self.screen, "focused", self.update_help) def update_help(self, focused_widget: Widget | None) -> None: + """Update the help for the focused widget. + + Args: + focused_widget: The currently focused widget, or `None` if no widget was focused. + """ if not self.app.app_focus: return self.set_class(focused_widget is not None, "-show-help") From a5e8541ad9edd41a6a2bfe80a166dc650400be93 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 16:11:25 +0100 Subject: [PATCH 08/18] remove jankiness as the CP is populated --- src/textual/command.py | 17 +++++++++++++++-- src/textual/widgets/_option_list.py | 7 +++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/textual/command.py b/src/textual/command.py index 2228a1d589..07ec5f62c1 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -443,7 +443,17 @@ class CommandPalette(SystemModalScreen): } CommandPalette { background: $background 60%; - align-horizontal: center; + align-horizontal: center; + + #--container { + display: none; + } + } + + CommandPalette.-ready { + #--container { + display: block; + } } CommandPalette > .command-palette--help-text { @@ -741,7 +751,7 @@ def _show_no_matches() -> None: _show_no_matches, ) - def _watch__list_visible(self) -> None: + async def _watch__list_visible(self) -> None: """React to the list visible flag being toggled.""" self.query_one(CommandList).set_class(self._list_visible, "--visible") self.query_one("#--input", Horizontal).set_class( @@ -883,6 +893,7 @@ def _refresh_command_list( command_list.clear_options().add_options(commands) if highlighted is not None and highlighted.id: command_list.highlighted = command_list.get_option_index(highlighted.id) + self._list_visible = bool(command_list.option_count) self._hit_count = command_list.option_count @@ -1019,6 +1030,8 @@ async def _gather_commands(self, search_value: str) -> None: self._hit_count = 0 self._start_no_matches_countdown(search_value) + self.add_class("-ready") + def _cancel_gather_commands(self) -> None: """Cancel any operation that is gather commands.""" self.workers.cancel_group(self, self._GATHER_COMMANDS_GROUP) diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index 25fbaee6d4..06c93629ab 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -398,6 +398,13 @@ def get_content_width(self, container: Size, viewport: Size) -> int: for option in self._options ) + def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + console = self.app.console + options = console.options.update(highlight=False) + return sum( + len(console.render_lines(option, options)) for option in self._options + ) + def _on_mouse_move(self, event: events.MouseMove) -> None: """React to the mouse moving. From 41568f3a555892a9ea3858708dd6e56cbba343e8 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 16:24:58 +0100 Subject: [PATCH 09/18] content height --- src/textual/widgets/_option_list.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index 06c93629ab..53f9e2a2a8 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -399,10 +399,16 @@ def get_content_width(self, container: Size, viewport: Size) -> int: ) def get_content_height(self, container: Size, viewport: Size, width: int) -> int: - console = self.app.console - options = console.options.update(highlight=False) + style = self.rich_style return sum( - len(console.render_lines(option, options)) for option in self._options + ( + 1 + if isinstance(content, Separator) + else len( + self._render_option_content(index, content, style, container.width) + ) + ) + for index, content in enumerate(self._contents) ) def _on_mouse_move(self, event: events.MouseMove) -> None: From dd13211ed09164cf9a5854204a15f7559645dfb1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 16:34:09 +0100 Subject: [PATCH 10/18] snapshots --- .../test_snapshots/test_command_palette.svg | 124 +++++++++--------- .../test_command_palette_discovery.svg | 124 +++++++++--------- 2 files changed, 124 insertions(+), 124 deletions(-) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg index 5241118c28..bbc56a92ea 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg @@ -19,138 +19,138 @@ font-weight: 700; } - .terminal-1919115509-matrix { + .terminal-3691012651-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1919115509-title { + .terminal-3691012651-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1919115509-r1 { fill: #646464 } -.terminal-1919115509-r2 { fill: #c5c8c6 } -.terminal-1919115509-r3 { fill: #004578 } -.terminal-1919115509-r4 { fill: #dfe1e2 } -.terminal-1919115509-r5 { fill: #00ff00 } -.terminal-1919115509-r6 { fill: #000000 } -.terminal-1919115509-r7 { fill: #1e1e1e } -.terminal-1919115509-r8 { fill: #dfe1e2;font-weight: bold } -.terminal-1919115509-r9 { fill: #fea62b;font-weight: bold } + .terminal-3691012651-r1 { fill: #646464 } +.terminal-3691012651-r2 { fill: #c5c8c6 } +.terminal-3691012651-r3 { fill: #004578 } +.terminal-3691012651-r4 { fill: #dfe1e2 } +.terminal-3691012651-r5 { fill: #00ff00 } +.terminal-3691012651-r6 { fill: #000000 } +.terminal-3691012651-r7 { fill: #1e1e1e } +.terminal-3691012651-r8 { fill: #dfe1e2;font-weight: bold } +.terminal-3691012651-r9 { fill: #fea62b;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - - - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎A - - -  This is a test of this code 0                                                  -  This is a test of this code 1                                                  -  This is a test of this code 2                                                  -  This is a test of this code 3                                                  -  This is a test of this code 4                                                  -  This is a test of this code 5                                                  -  This is a test of this code 6                                                  -  This is a test of this code 7                                                  -  This is a test of this code 8                                                  -  This is a test of this code 9                                                  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎A + + +  This is a test of this code 0                                                  +  This is a test of this code 1                                                  +  This is a test of this code 2                                                  +  This is a test of this code 3                                                  +  This is a test of this code 4                                                  +  This is a test of this code 5                                                  +  This is a test of this code 6                                                  +  This is a test of this code 7                                                  +  This is a test of this code 8                                                  +  This is a test of this code 9                                                  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg index 7202c8e17d..3727fb2fb6 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg @@ -19,138 +19,138 @@ font-weight: 700; } - .terminal-1425100236-matrix { + .terminal-3950137090-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1425100236-title { + .terminal-3950137090-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1425100236-r1 { fill: #646464 } -.terminal-1425100236-r2 { fill: #c5c8c6 } -.terminal-1425100236-r3 { fill: #004578 } -.terminal-1425100236-r4 { fill: #dfe1e2 } -.terminal-1425100236-r5 { fill: #00ff00 } -.terminal-1425100236-r6 { fill: #000000 } -.terminal-1425100236-r7 { fill: #1e1e1e } -.terminal-1425100236-r8 { fill: #697278 } -.terminal-1425100236-r9 { fill: #dfe1e2;font-weight: bold } + .terminal-3950137090-r1 { fill: #646464 } +.terminal-3950137090-r2 { fill: #c5c8c6 } +.terminal-3950137090-r3 { fill: #004578 } +.terminal-3950137090-r4 { fill: #dfe1e2 } +.terminal-3950137090-r5 { fill: #00ff00 } +.terminal-3950137090-r6 { fill: #000000 } +.terminal-3950137090-r7 { fill: #1e1e1e } +.terminal-3950137090-r8 { fill: #697278 } +.terminal-3950137090-r9 { fill: #dfe1e2;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - - - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎Search for commands… - - -  This is a test of this code 0                                                  -  This is a test of this code 1                                                  -  This is a test of this code 2                                                  -  This is a test of this code 3                                                  -  This is a test of this code 4                                                  -  This is a test of this code 5                                                  -  This is a test of this code 6                                                  -  This is a test of this code 7                                                  -  This is a test of this code 8                                                  -  This is a test of this code 9                                                  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎Search for commands… + + +  This is a test of this code 0                                                  +  This is a test of this code 1                                                  +  This is a test of this code 2                                                  +  This is a test of this code 3                                                  +  This is a test of this code 4                                                  +  This is a test of this code 5                                                  +  This is a test of this code 6                                                  +  This is a test of this code 7                                                  +  This is a test of this code 8                                                  +  This is a test of this code 9                                                  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + From 8ca468235f04d097d9b8bd6980ec017735476e44 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 17:25:31 +0100 Subject: [PATCH 11/18] fix height calculation --- src/textual/widgets/_option_list.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index 53f9e2a2a8..d356f07d2d 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -399,17 +399,18 @@ def get_content_width(self, container: Size, viewport: Size) -> int: ) def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + # Get the content height without requiring a refresh + # TODO: Internal data structure could be simplified style = self.rich_style - return sum( - ( - 1 - if isinstance(content, Separator) - else len( - self._render_option_content(index, content, style, container.width) - ) - ) - for index, content in enumerate(self._contents) + _render_option_content = self._render_option_content + heights = [ + len(_render_option_content(index, option, style, width)) + for index, option in enumerate(self._options) + ] + separator_count = sum( + 1 for content in self._contents if isinstance(content, Separator) ) + return sum(heights) + separator_count def _on_mouse_move(self, event: events.MouseMove) -> None: """React to the mouse moving. From 616baa030711d0884983ced7f9c6dcaa8910ee90 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 17:27:39 +0100 Subject: [PATCH 12/18] don't update help for inactive screen --- src/textual/widgets/_help_panel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index 9ed8421e1b..fe735f2083 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -73,6 +73,8 @@ def update_help(self, focused_widget: Widget | None) -> None: """ if not self.app.app_focus: return + if not self.screen.is_active: + return self.set_class(focused_widget is not None, "-show-help") if focused_widget is not None: help = focused_widget.HELP or "" From 5fdee1f39f41e8bb84e25cb9ba0d8e3e15736f26 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 20:22:27 +0100 Subject: [PATCH 13/18] fix sizing issue --- src/textual/widgets/_help_panel.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index fe735f2083..3c4259df16 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -31,7 +31,7 @@ class HelpPanel(Widget): #widget-help { height: auto; max-height: 50%; - width: 100%; + width: 1fr; padding: 0; margin: 0; padding: 1 0; @@ -50,8 +50,9 @@ class HelpPanel(Widget): } KeyPanel#keys-help { - width: 100%; - height: 1fr; + width: 1fr; + height: 1fr; + min-width: initial; split: initial; border-left: none; padding: 0; From de7ca7ab24a2134bda1cf298a9f41aa299ffe999 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 21:10:27 +0100 Subject: [PATCH 14/18] simpler matching --- src/textual/command.py | 2 +- src/textual/fuzzy.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/textual/command.py b/src/textual/command.py index 07ec5f62c1..226269ac98 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -751,7 +751,7 @@ def _show_no_matches() -> None: _show_no_matches, ) - async def _watch__list_visible(self) -> None: + def _watch__list_visible(self) -> None: """React to the list visible flag being toggled.""" self.query_one(CommandList).set_class(self._list_visible, "--visible") self.query_one("#--input", Horizontal).set_class( diff --git a/src/textual/fuzzy.py b/src/textual/fuzzy.py index b09f8e1604..5b0282b42b 100644 --- a/src/textual/fuzzy.py +++ b/src/textual/fuzzy.py @@ -107,10 +107,15 @@ def highlight(self, candidate: str) -> Text: if match is None: return text assert match.lastindex is not None - offsets = [ - match.span(group_no)[0] for group_no in range(1, match.lastindex + 1) - ] - for offset in offsets: - text.stylize(self._match_style, offset, offset + 1) + if self._query in text.plain: + # Favor complete matches + offset = text.plain.index(self._query) + text.stylize(self._match_style, offset, offset + len(self._query)) + else: + offsets = [ + match.span(group_no)[0] for group_no in range(1, match.lastindex + 1) + ] + for offset in offsets: + text.stylize(self._match_style, offset, offset + 1) return text From 07efeaf713a80d0de083ed4885363f82f4a25437 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Aug 2024 21:28:19 +0100 Subject: [PATCH 15/18] UK spelling --- src/textual/widgets/_sparkline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_sparkline.py b/src/textual/widgets/_sparkline.py index d691f5f435..f638b28314 100644 --- a/src/textual/widgets/_sparkline.py +++ b/src/textual/widgets/_sparkline.py @@ -32,7 +32,7 @@ class Sparkline(Widget): | Class | Description | | :- | :- | | `sparkline--max-color` | The color used for the larger values in the data. | - | `sparkline--min-color` | The colour used for the smaller values in the data. | + | `sparkline--min-color` | The color used for the smaller values in the data. | """ DEFAULT_CSS = """ From 050408878bb4ed0974bf1145d1f61ce16bd27b6f Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 22 Aug 2024 10:55:42 +0100 Subject: [PATCH 16/18] title --- src/textual/widgets/_help_panel.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index 3c4259df16..84177a753e 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -5,7 +5,7 @@ from ..app import ComposeResult from ..css.query import NoMatches from ..widget import Widget -from ..widgets import KeyPanel, Markdown +from ..widgets import KeyPanel, Label, Markdown class HelpPanel(Widget): @@ -27,7 +27,13 @@ class HelpPanel(Widget): layout: vertical; height: 100%; - + #title { + width: 1fr; + text-align: center; + text-style: bold; + dock: top; + } + #widget-help { height: auto; max-height: 50%; @@ -87,5 +93,6 @@ def update_help(self, focused_widget: Widget | None) -> None: pass def compose(self) -> ComposeResult: + yield Label("Help", id="title") yield Markdown(id="widget-help") yield KeyPanel(id="keys-help") From 75218214f7aa79b00da7c92c9b44d5f4db45d343 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 22 Aug 2024 11:04:48 +0100 Subject: [PATCH 17/18] hide panel title --- src/textual/widgets/_help_panel.py | 1 + tests/test_resolve.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/textual/widgets/_help_panel.py b/src/textual/widgets/_help_panel.py index 84177a753e..ab1ed0f471 100644 --- a/src/textual/widgets/_help_panel.py +++ b/src/textual/widgets/_help_panel.py @@ -32,6 +32,7 @@ class HelpPanel(Widget): text-align: center; text-style: bold; dock: top; + display: none; } #widget-help { diff --git a/tests/test_resolve.py b/tests/test_resolve.py index edb7eeaf1e..177939d02b 100644 --- a/tests/test_resolve.py +++ b/tests/test_resolve.py @@ -133,15 +133,16 @@ def test_resolve_fraction_unit_stress_test(): # We're mainly checking for the absence of zero division errors, # which is a reoccurring theme for this code. - for remaining_space in range(1, 101, 10): + for remaining_space in range(1, 51, 10): for max_width in range(1, remaining_space): styles.max_width = max_width - for width in range(1, remaining_space): + for width in range(1, remaining_space, 2): + size = Size(width, 24) resolved_unit = resolve_fraction_unit( [styles, styles, styles], - Size(width, 24), - Size(width, 24), + size, + size, Fraction(remaining_space), "width", ) From 710f9a080e1f2637d68b699f6c26a1f3f9c1128b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 22 Aug 2024 11:16:25 +0100 Subject: [PATCH 18/18] snapshot --- .../test_snapshots/test_help_panel.svg | 181 ++++++++++++++++++ .../snapshot_apps/help_panel.py | 12 ++ tests/snapshot_tests/test_snapshots.py | 9 + 3 files changed, 202 insertions(+) create mode 100644 tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel.svg create mode 100644 tests/snapshot_tests/snapshot_apps/help_panel.py diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel.svg new file mode 100644 index 0000000000..929724e148 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HelpPanelApp + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ────────────────────────────  +Input +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁        ←cursor left        +       ^←cursor left word   +        →cursor right       +       ^→cursor right word  +        ⌫delete left        +     homehome               +      endend                +      deldelete right       +        ⏎submit             +       ^wdelete left to     +start of word      +       ^udelete all to the  +left               +       ^fdelete right to    +start of word      +       ^kdelete all to the  +right              + ────────────────────────────  +Screen +      tabFocus Next         +shift+tabFocus Previous     + ────────────────────────────  +App +       ^cQuit               +       ^ppalette Open  +command palette + ────────────────────────────  + + + + diff --git a/tests/snapshot_tests/snapshot_apps/help_panel.py b/tests/snapshot_tests/snapshot_apps/help_panel.py new file mode 100644 index 0000000000..05512865be --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/help_panel.py @@ -0,0 +1,12 @@ +from textual.app import App, ComposeResult +from textual.widgets import Input + + +class HelpPanelApp(App): + def compose(self) -> ComposeResult: + yield Input() + + +if __name__ == "__main__": + app = HelpPanelApp() + app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 82c131c954..219985817d 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -1448,3 +1448,12 @@ def test_command_palette_key_change(snap_compare): def test_split(snap_compare): """Test split rule.""" assert snap_compare(SNAPSHOT_APPS_DIR / "split.py", terminal_size=(100, 30)) + + +def test_help_panel(snap_compare): + """Test help panel.""" + assert snap_compare( + SNAPSHOT_APPS_DIR / "help_panel.py", + terminal_size=(100, 30), + press=["ctrl+p", *"keys", "enter"], + )