diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96ae77a638..7032ca1ee4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## Unreleased
+
+### Fixed
+
+- Fixed duplicated key displays in the help panel https://github.com/Textualize/textual/issues/5037
+
## [0.85.2] - 2024-11-02
- Fixed broken focus-within https://github.com/Textualize/textual/pull/5190
diff --git a/src/textual/widgets/_key_panel.py b/src/textual/widgets/_key_panel.py
index 2b195c03e7..ea5a7173c5 100644
--- a/src/textual/widgets/_key_panel.py
+++ b/src/textual/widgets/_key_panel.py
@@ -90,11 +90,13 @@ def render_description(binding: Binding) -> Text:
get_key_display = self.app.get_key_display
for multi_bindings in action_to_bindings.values():
binding, enabled, tooltip = multi_bindings[0]
- key_display = " ".join(
- get_key_display(binding) for binding, _, _ in multi_bindings
+ keys_display = " ".join(
+ dict.fromkeys( # Remove duplicates while preserving order
+ get_key_display(binding) for binding, _, _ in multi_bindings
+ )
)
table.add_row(
- Text(key_display, style=key_style),
+ Text(keys_display, style=key_style),
render_description(binding),
)
if namespace != previous_namespace:
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel_key_display_not_duplicated.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel_key_display_not_duplicated.svg
new file mode 100644
index 0000000000..d93fb1f8f2
--- /dev/null
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_help_panel_key_display_not_duplicated.svg
@@ -0,0 +1,158 @@
+
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_keymap_bindings_key_display.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_keymap_bindings_key_display.svg
index cc232f86be..696b127286 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_keymap_bindings_key_display.svg
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_keymap_bindings_key_display.svg
@@ -19,140 +19,140 @@
font-weight: 700;
}
- .terminal-4136079827-matrix {
+ .terminal-2646538147-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-4136079827-title {
+ .terminal-2646538147-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-4136079827-r1 { fill: #e1e1e1 }
-.terminal-4136079827-r2 { fill: #5c5c5c }
-.terminal-4136079827-r3 { fill: #c5c8c6 }
-.terminal-4136079827-r4 { fill: #1e1e1e }
-.terminal-4136079827-r5 { fill: #fea62b;font-weight: bold }
-.terminal-4136079827-r6 { fill: #ededed }
-.terminal-4136079827-r7 { fill: #9a9a9a }
-.terminal-4136079827-r8 { fill: #a7a9ab }
-.terminal-4136079827-r9 { fill: #e2e3e3 }
-.terminal-4136079827-r10 { fill: #4c5055 }
+ .terminal-2646538147-r1 { fill: #e1e1e1 }
+.terminal-2646538147-r2 { fill: #5c5c5c }
+.terminal-2646538147-r3 { fill: #c5c8c6 }
+.terminal-2646538147-r4 { fill: #1e1e1e }
+.terminal-2646538147-r5 { fill: #fea62b;font-weight: bold }
+.terminal-2646538147-r6 { fill: #ededed }
+.terminal-2646538147-r7 { fill: #9a9a9a }
+.terminal-2646538147-r8 { fill: #a7a9ab }
+.terminal-2646538147-r9 { fill: #e2e3e3 }
+.terminal-2646538147-r10 { fill: #4c5055 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- MyApp
+ MyApp
-
-
-
- Check the footer and help panel ▏
-▏ tabFocus Next
-▏ shift+tabFocus
-▏Previous
-▏
-▏ ^cQuit
-▏ ^ppalette Open
-▏command
-▏palette
-▏ correctIncrement
-▏ correct
-▏ correct
-▏ correct
-▏
-▏
-▏
-▏
-▏
-▏
-▏
-▏
-▏
-▏
- correct Increment ▏^p palette▏
+
+
+
+ Check the footer and help panel ▏
+▏ tabFocus Next
+▏shift+tabFocus Previous
+▏
+▏ ^cQuit
+▏ ^ppalette Open
+▏command palette
+▏ correctIncrement
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+▏
+ correct Increment ▏^p palette▏
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
index 0fa1678090..d7775232e8 100644
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -2347,7 +2347,7 @@ def test_fr_and_margin(snap_compare):
class FRApp(App):
CSS = """
- #first-container {
+ #first-container {
background: green;
height: auto;
}
@@ -2355,7 +2355,7 @@ class FRApp(App):
#second-container {
margin: 2;
background: red;
- height: auto;
+ height: auto;
}
#third-container {
@@ -2416,3 +2416,21 @@ def test_split_segments_infinite_loop(snap_compare):
"""
assert snap_compare(SNAPSHOT_APPS_DIR / "split_segments.py")
+
+
+def test_help_panel_key_display_not_duplicated(snap_compare):
+ """Regression test for https://github.com/Textualize/textual/issues/5037"""
+
+ class HelpPanelApp(App):
+ BINDINGS = [
+ Binding("b,e,l", "bell", "Ring the bell", key_display="foo"),
+ ]
+
+ def compose(self) -> ComposeResult:
+ yield Footer()
+
+ async def run_before(pilot: Pilot):
+ pilot.app.action_show_help_panel()
+
+ app = HelpPanelApp()
+ assert snap_compare(app, run_before=run_before)