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
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:
"""Interactive process management: kill, nice, filter, details."""
@@ -586,6 +617,7 @@ class Renderer:
self.data = data
self.scroll_offset = 0
self.proc_mgr = ProcessManager()
self.show_help = False
def draw(self):
self.scr.erase()
@@ -623,6 +655,10 @@ class Renderer:
# Processes (rest of screen)
self._draw_processes(y, 0, w, h - y)
# Help overlay
if self.show_help:
self._draw_help(h, w)
self.scr.refresh()
def _draw_header(self, y, w):
@@ -1029,6 +1065,41 @@ class Renderer:
" Enter:apply Esc:cancel ".center(popup_w),
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):
pm = self.proc_mgr
bar = f" Filter: {pm.filter_text}_ "
@@ -1060,6 +1131,12 @@ def main(stdscr):
processes = pm.filtered_processes(data.processes)
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 ──
if pm.filter_mode:
if key == 27: # ESC
@@ -1122,6 +1199,8 @@ def main(stdscr):
break
elif key == 27: # ESC
break
elif key in (ord("h"), ord("?"), curses.KEY_F1):
renderer.show_help = True
elif key == ord("c"):
data.process_sort = "cpu"
data.process_sort_reverse = True