From 78eaaf35e23a3677ca1bc3a70c60c17be035baa4 Mon Sep 17 00:00:00 2001 From: "n loewen (aider)" Date: Sun, 8 Jun 2025 02:35:03 +0100 Subject: [PATCH] feat: Add help popup with keyboard shortcuts triggered by "?" --- gtm | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/gtm b/gtm index c7d11ca..2d20996 100755 --- a/gtm +++ b/gtm @@ -145,6 +145,9 @@ class AppState: search_query: str = "" search_matches: List[int] = field(default_factory=list) # Line numbers of matches current_match_idx: int = -1 # Index in search_matches + + # Help popup + show_help: bool = False # --- Actions (Controller) --- @@ -646,6 +649,99 @@ def draw_selection(stdscr, state): except curses.error: pass +def draw_help_popup(stdscr, state): + """Draw a popup with keyboard shortcut help.""" + if not state.show_help: + return + + # Calculate popup dimensions and position + popup_width = 60 + popup_height = 20 + popup_x = max(0, (state.width - popup_width) // 2) + popup_y = max(0, (state.height - popup_height) // 2) + + # Draw popup border + for y in range(popup_y, popup_y + popup_height): + for x in range(popup_x, popup_x + popup_width): + if y == popup_y or y == popup_y + popup_height - 1: + # Top and bottom borders + try: + stdscr.addch(y, x, curses.ACS_HLINE) + except curses.error: + pass + elif x == popup_x or x == popup_x + popup_width - 1: + # Left and right borders + try: + stdscr.addch(y, x, curses.ACS_VLINE) + except curses.error: + pass + + # Draw corners + try: + stdscr.addch(popup_y, popup_x, curses.ACS_ULCORNER) + stdscr.addch(popup_y, popup_x + popup_width - 1, curses.ACS_URCORNER) + stdscr.addch(popup_y + popup_height - 1, popup_x, curses.ACS_LLCORNER) + stdscr.addch(popup_y + popup_height - 1, popup_x + popup_width - 1, curses.ACS_LRCORNER) + except curses.error: + pass + + # Draw title + title = " Keyboard Shortcuts " + title_x = popup_x + (popup_width - len(title)) // 2 + try: + stdscr.addstr(popup_y, title_x, title, curses.A_BOLD) + except curses.error: + pass + + # Define help content + help_items = [ + ("Navigation", ""), + ("j / Down", "Scroll down"), + ("k / Up", "Scroll up"), + ("Space / Page Down", "Page down"), + ("b / Page Up", "Page up"), + ("h / Left", "Focus left pane"), + ("l / Right", "Focus right pane"), + ("", ""), + ("Features", ""), + ("/ (slash)", "Search in file"), + ("n", "Next search match"), + ("N", "Previous search match"), + ("c", "Next change"), + ("C", "Previous change"), + ("s", "Toggle sidebar"), + ("w", "Toggle line wrapping"), + ("L", "Toggle line numbers"), + ("q / Esc", "Quit"), + ("? (question mark)", "Show/hide this help") + ] + + # Draw help content + for i, (key, desc) in enumerate(help_items): + y = popup_y + 2 + i + if y < popup_y + popup_height - 1: + if key == "Navigation" or key == "Features": + # Section headers + try: + stdscr.addstr(y, popup_x + 2, key, curses.A_BOLD | curses.A_UNDERLINE) + except curses.error: + pass + elif key: + # Key and description + try: + stdscr.addstr(y, popup_x + 2, key, curses.A_BOLD) + stdscr.addstr(y, popup_x + 16, desc) + except curses.error: + pass + + # Draw footer + footer = " Press any key to close " + footer_x = popup_x + (popup_width - len(footer)) // 2 + try: + stdscr.addstr(popup_y + popup_height - 1, footer_x, footer, curses.A_BOLD) + except curses.error: + pass + def draw_status_bars(stdscr, state): # We'll use height-1 for the single status bar visible_height = state.height - 1 @@ -780,6 +876,7 @@ def draw_ui(stdscr, state): draw_divider(stdscr, state) draw_status_bars(stdscr, state) draw_selection(stdscr, state) + draw_help_popup(stdscr, state) stdscr.refresh() # --- Input Handling Functions (Controller) --- @@ -972,6 +1069,10 @@ def handle_mouse_input(stdscr, state: AppState) -> AppState: return state def handle_keyboard_input(key, state: AppState) -> AppState: + # If help popup is open, any key closes it + if state.show_help: + return replace(state, show_help=False) + # If in search mode, handle search input if state.search_mode: if key == 27: # Escape key - exit search mode @@ -993,10 +1094,14 @@ def handle_keyboard_input(key, state: AppState) -> AppState: if key in [ord('q')]: return replace(state, should_exit=True) elif key == 27: # Escape key - if state.is_selecting: + if state.show_help: + return replace(state, show_help=False) + elif state.is_selecting: return replace(state, is_selecting=False, selection_start_coord=None, selection_end_coord=None) else: return replace(state, should_exit=True) + elif key == ord('?'): # Toggle help popup + return replace(state, show_help=not state.show_help) elif key == ord('/'): # Start search return replace(state, search_mode=True, search_query="") elif key == ord('s'):