Add help overlay with keyboard shortcuts (h/F1/?)

Full-screen popup showing all keybindings grouped by category:
navigation, sorting, process management, and general controls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lukasz@orzechowski.eu
2026-03-24 00:32:00 +01:00
parent c61509ce55
commit e0d3a48240

View File

@@ -437,6 +437,37 @@ C_POPUP_BG = 12
C_POPUP_HL = 13 C_POPUP_HL = 13
HELP_LINES = [
("NAVIGATION", ""),
("j / Down", "Move cursor down"),
("k / Up", "Move cursor up"),
("PgDn", "Scroll down 20"),
("PgUp", "Scroll up 20"),
("Home", "Go to top"),
("End", "Go to bottom"),
("", ""),
("SORTING", ""),
("c", "Sort by CPU%"),
("m", "Sort by MEM%"),
("p", "Sort by PID"),
("n", "Sort by name"),
("r", "Reverse sort order"),
("", ""),
("PROCESS MGMT", ""),
("T", "Send SIGTERM to process"),
("K", "Send SIGKILL to process"),
("F9", "Signal menu (choose signal)"),
("+ / F8", "Renice process"),
("- / F7", "Renice process (negative)"),
("d / F5 / Enter", "Process details"),
("/ / f", "Filter processes"),
("", ""),
("GENERAL", ""),
("h / F1 / ?", "Toggle this help"),
("q / Q / Esc", "Quit"),
]
class ProcessManager: class ProcessManager:
"""Interactive process management: kill, nice, filter, details.""" """Interactive process management: kill, nice, filter, details."""
@@ -586,6 +617,7 @@ class Renderer:
self.data = data self.data = data
self.scroll_offset = 0 self.scroll_offset = 0
self.proc_mgr = ProcessManager() self.proc_mgr = ProcessManager()
self.show_help = False
def draw(self): def draw(self):
self.scr.erase() self.scr.erase()
@@ -623,6 +655,10 @@ class Renderer:
# Processes (rest of screen) # Processes (rest of screen)
self._draw_processes(y, 0, w, h - y) self._draw_processes(y, 0, w, h - y)
# Help overlay
if self.show_help:
self._draw_help(h, w)
self.scr.refresh() self.scr.refresh()
def _draw_header(self, y, w): def _draw_header(self, y, w):
@@ -1029,6 +1065,41 @@ class Renderer:
" Enter:apply Esc:cancel ".center(popup_w), " Enter:apply Esc:cancel ".center(popup_w),
curses.color_pair(C_POPUP_BG) | curses.A_DIM) curses.color_pair(C_POPUP_BG) | curses.A_DIM)
def _draw_help(self, h, w):
popup_w = min(w - 4, 52)
popup_h = min(h - 2, len(HELP_LINES) + 4)
mx = (w - popup_w) // 2
my = (h - popup_h) // 2
# Background
for row in range(popup_h):
safe_addstr(self.scr, my + row, mx, " " * popup_w, curses.color_pair(C_POPUP_BG))
# Title
title = " SYSTATS - Keyboard Shortcuts "
safe_addstr(self.scr, my, mx, title.center(popup_w),
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
for i, (key, desc) in enumerate(HELP_LINES):
row_y = my + 2 + i
if row_y >= my + popup_h - 1:
break
if not key and not desc:
continue
if not desc:
# Section header
safe_addstr(self.scr, row_y, mx + 2, key,
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
else:
line = f" {key:<20s} {desc}"
safe_addstr(self.scr, row_y, mx, line[:popup_w].ljust(popup_w),
curses.color_pair(C_POPUP_BG))
safe_addstr(self.scr, my + popup_h - 1, mx,
" Press h, F1 or ? to close ".center(popup_w),
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
def _draw_filter_bar(self, y, x, w): def _draw_filter_bar(self, y, x, w):
pm = self.proc_mgr pm = self.proc_mgr
bar = f" Filter: {pm.filter_text}_ " bar = f" Filter: {pm.filter_text}_ "
@@ -1060,6 +1131,12 @@ def main(stdscr):
processes = pm.filtered_processes(data.processes) processes = pm.filtered_processes(data.processes)
max_idx = max(0, len(processes) - 1) max_idx = max(0, len(processes) - 1)
# ── Help overlay ──
if renderer.show_help:
if key in (ord("h"), ord("?"), curses.KEY_F1, 27):
renderer.show_help = False
continue
# ── Filter mode input ── # ── Filter mode input ──
if pm.filter_mode: if pm.filter_mode:
if key == 27: # ESC if key == 27: # ESC
@@ -1122,6 +1199,8 @@ def main(stdscr):
break break
elif key == 27: # ESC elif key == 27: # ESC
break break
elif key in (ord("h"), ord("?"), curses.KEY_F1):
renderer.show_help = True
elif key == ord("c"): elif key == ord("c"):
data.process_sort = "cpu" data.process_sort = "cpu"
data.process_sort_reverse = True data.process_sort_reverse = True