From 6107b43ac5e3542939616d1089812de18ec95ffa Mon Sep 17 00:00:00 2001 From: phg Date: Sat, 16 Aug 2025 22:37:29 +0200 Subject: [PATCH] Refactor keybindings and footer management: streamline keybinding processing and enhance footer item display with key-description pairs for improved clarity and usability. --- src/hosts/tui/app.py | 15 ++-- src/hosts/tui/custom_footer.py | 122 +++++++++++++++++++++++++++++---- src/hosts/tui/keybindings.py | 20 ++++-- 3 files changed, 133 insertions(+), 24 deletions(-) diff --git a/src/hosts/tui/app.py b/src/hosts/tui/app.py index c903be0..f2de666 100644 --- a/src/hosts/tui/app.py +++ b/src/hosts/tui/app.py @@ -147,26 +147,27 @@ class HostsManagerApp(App): # Process keybindings and add to appropriate sections for binding in self.BINDINGS: + # Skip tuple-style bindings and only process Binding objects + if not hasattr(binding, "show"): + continue + # Only show bindings marked with show=True - if hasattr(binding, "show") and binding.show: + if binding.show: # Get the display key key_display = getattr(binding, "key_display", None) or binding.key # Get the description description = binding.description or binding.action - # Format the item - item = f"{key_display}: {description}" - # Determine positioning from id attribute binding_id = getattr(binding, "id", None) if binding_id and binding_id.startswith("left:"): - footer.add_left_item(item) + footer.add_left_item(key_display, description) elif binding_id and binding_id.startswith("right:"): - footer.add_right_item(item) + footer.add_right_item(key_display, description) else: # Default to right if no specific positioning - footer.add_right_item(item) + footer.add_right_item(key_display, description) # Status section will be updated by update_status self._update_footer_status() diff --git a/src/hosts/tui/custom_footer.py b/src/hosts/tui/custom_footer.py index 00aa63e..a2f46ad 100644 --- a/src/hosts/tui/custom_footer.py +++ b/src/hosts/tui/custom_footer.py @@ -11,6 +11,42 @@ from textual.app import ComposeResult from textual.containers import Horizontal from textual.widgets import Static from textual.widget import Widget +from rich.text import Text + + +class FooterKey(Widget): + """A key/action pair widget for the footer, styled similar to Textual's original FooterKey.""" + + DEFAULT_CSS = """ + FooterKey { + width: auto; + height: 1; + content-align: center middle; + padding: 0 1; + } + + .footer-key--key { + text-style: bold; + color: $text; + } + + .footer-key--description { + color: $text-muted; + } + """ + + def __init__(self, key: str, description: str, **kwargs): + super().__init__(**kwargs) + self.key = key + self.description = description + + def render(self) -> Text: + """Render the key-description pair with proper styling.""" + text = Text() + text.append(f"{self.key}", style="bold") + text.append(" ") + text.append(self.description, style="dim") + return text class CustomFooter(Widget): @@ -39,7 +75,6 @@ class CustomFooter(Widget): .footer-left { width: auto; text-align: left; - text-style: dim; height: 1; content-align: left middle; } @@ -52,7 +87,6 @@ class CustomFooter(Widget): .footer-right { width: auto; text-align: right; - text-style: dim; height: 1; content-align: right middle; } @@ -73,12 +107,25 @@ class CustomFooter(Widget): height: 1; content-align: right middle; } + + /* Enhanced styling for footer key components */ + .footer-left .footer-key--key, + .footer-right .footer-key--key { + text-style: bold; + color: $text; + } + + .footer-left .footer-key--description, + .footer-right .footer-key--description { + color: $text-muted; + text-style: dim; + } """ def __init__(self, **kwargs): super().__init__(**kwargs) - self._left_items = [] - self._right_items = [] + self._left_items = [] # List of FooterKey widgets + self._right_items = [] # List of FooterKey widgets self._status_text = "" def compose(self) -> ComposeResult: @@ -90,16 +137,43 @@ class CustomFooter(Widget): yield Static(" │ ", id="footer-separator", classes="footer-separator") yield Static("", id="footer-status", classes="footer-status") - def add_left_item(self, item: str) -> None: - """Add an item to the left section.""" - self._left_items.append(item) + def add_left_item(self, key: str, description: str) -> None: + """Add a key-description pair to the left section.""" + footer_key = FooterKey(key, description) + self._left_items.append(footer_key) self._update_left_section() - def add_right_item(self, item: str) -> None: - """Add an item to the right section.""" - self._right_items.append(item) + def add_right_item(self, key: str, description: str) -> None: + """Add a key-description pair to the right section.""" + footer_key = FooterKey(key, description) + self._right_items.append(footer_key) self._update_right_section() + def add_left_item_legacy(self, item: str) -> None: + """Add a legacy item (key: description format) to the left section.""" + if ": " in item: + key, description = item.split(": ", 1) + self.add_left_item(key, description) + else: + self.add_left_item(item, "") + + def add_right_item_legacy(self, item: str) -> None: + """Add a legacy item (key: description format) to the right section.""" + if ": " in item: + key, description = item.split(": ", 1) + self.add_right_item(key, description) + else: + self.add_right_item(item, "") + + # Backward compatibility - temporarily add the old single parameter methods + def add_left_item_old(self, item: str) -> None: + """Backward compatibility method.""" + self.add_left_item_legacy(item) + + def add_right_item_old(self, item: str) -> None: + """Backward compatibility method.""" + self.add_right_item_legacy(item) + def clear_left_items(self) -> None: """Clear all items from the left section.""" self._left_items.clear() @@ -119,7 +193,19 @@ class CustomFooter(Widget): """Update the left section display.""" try: left_static = self.query_one("#footer-left", Static) - left_static.update(" ".join(self._left_items)) + if self._left_items: + # Combine all FooterKey renderings with spacing + combined_text = Text() + for i, footer_key in enumerate(self._left_items): + if i > 0: + combined_text.append(" ") # Add spacing between items + # Render individual key-description pair with styling + combined_text.append(footer_key.key, style="bold") + combined_text.append(" ") + combined_text.append(footer_key.description, style="dim") + left_static.update(combined_text) + else: + left_static.update("") except Exception: pass # Widget not ready yet @@ -127,7 +213,19 @@ class CustomFooter(Widget): """Update the right section display.""" try: right_static = self.query_one("#footer-right", Static) - right_static.update(" ".join(self._right_items)) + if self._right_items: + # Combine all FooterKey renderings with spacing + combined_text = Text() + for i, footer_key in enumerate(self._right_items): + if i > 0: + combined_text.append(" ") # Add spacing between items + # Render individual key-description pair with styling + combined_text.append(footer_key.key, style="bold") + combined_text.append(" ") + combined_text.append(footer_key.description, style="dim") + right_static.update(combined_text) + else: + right_static.update("") except Exception: pass # Widget not ready yet diff --git a/src/hosts/tui/keybindings.py b/src/hosts/tui/keybindings.py index c84d63d..d455379 100644 --- a/src/hosts/tui/keybindings.py +++ b/src/hosts/tui/keybindings.py @@ -12,8 +12,20 @@ HOSTS_MANAGER_BINDINGS = [ Binding("a", "add_entry", "Add new entry", show=True, id="left:add_entry"), Binding("d", "delete_entry", "Delete entry", show=True, id="left:delete_entry"), Binding("e", "edit_entry", "Edit entry", show=True, id="left:edit_entry"), - Binding("space", "toggle_entry", "Toggle active/inactive", show=True, id="left:toggle_entry"), - Binding("ctrl+e", "toggle_edit_mode", "Toggle edit mode", show=True, id="left:toggle_edit_mode"), + Binding( + "space", + "toggle_entry", + "Toggle active/inactive", + show=True, + id="left:toggle_entry", + ), + Binding( + "ctrl+e", + "toggle_edit_mode", + "Toggle edit mode", + show=True, + id="left:toggle_edit_mode", + ), Binding("c", "config", "Configuration", show=True, id="right:config"), Binding( "question_mark", @@ -26,9 +38,7 @@ HOSTS_MANAGER_BINDINGS = [ Binding("q", "quit", "Quit", show=True, id="right:quit"), Binding("r", "reload", "Reload hosts file", show=False), Binding("i", "sort_by_ip", "Sort by IP address", show=False), - Binding( - "h", "sort_by_hostname", "Sort by hostname", show=False - ), + Binding("h", "sort_by_hostname", "Sort by hostname", show=False), Binding("ctrl+s", "save_file", "Save hosts file", show=False), Binding("shift+up", "move_entry_up", "Move entry up", show=False), Binding("shift+down", "move_entry_down", "Move entry down", show=False),