gh-138122: Integrate live profiler TUI with _colorize theming system

The Tachyon profiler's curses-based TUI now uses the centralized theming
infrastructure in _colorize.py, enabling users to customize colors via
the standard Python theming API. This adds a LiveProfiler theme section
with two pre-configured themes: the default dark theme optimized for
dark terminal backgrounds, and LiveProfilerLight for white/light
backgrounds. Users can switch themes by calling _colorize.set_theme() in
their PYTHONSTARTUP or sitecustomize.py.

The table header rendering was also improved to draw a continuous
background, eliminating visual gaps between columns when using reverse
video styling.
This commit is contained in:
Pablo Galindo Salgado 2025-12-06 19:49:11 +00:00
parent eba449a198
commit b024e50773
4 changed files with 197 additions and 76 deletions

View file

@ -525,79 +525,57 @@ def _cycle_sort(self, reverse=False):
def _setup_colors(self):
"""Set up color pairs and return color attributes."""
A_BOLD = self.display.get_attr("A_BOLD")
A_REVERSE = self.display.get_attr("A_REVERSE")
A_UNDERLINE = self.display.get_attr("A_UNDERLINE")
A_NORMAL = self.display.get_attr("A_NORMAL")
# Check both curses color support and _colorize.can_colorize()
if self.display.has_colors() and self._can_colorize:
with contextlib.suppress(Exception):
# Color constants (using curses values for compatibility)
COLOR_CYAN = 6
COLOR_GREEN = 2
COLOR_YELLOW = 3
COLOR_BLACK = 0
COLOR_MAGENTA = 5
COLOR_RED = 1
theme = _colorize.get_theme(force_color=True)
profiler_theme = theme.live_profiler
default_bg = -1
# Initialize all color pairs used throughout the UI
self.display.init_color_pair(1, profiler_theme.samples_fg, default_bg)
self.display.init_color_pair(2, profiler_theme.file_fg, default_bg)
self.display.init_color_pair(3, profiler_theme.func_fg, default_bg)
header_bg = 2 if profiler_theme.background_style == "dark" else 4
self.display.init_color_pair(COLOR_PAIR_HEADER_BG, 0, header_bg)
self.display.init_color_pair(COLOR_PAIR_CYAN, profiler_theme.pid_fg, default_bg)
self.display.init_color_pair(COLOR_PAIR_YELLOW, profiler_theme.time_fg, default_bg)
self.display.init_color_pair(COLOR_PAIR_GREEN, profiler_theme.uptime_fg, default_bg)
self.display.init_color_pair(COLOR_PAIR_MAGENTA, profiler_theme.interval_fg, default_bg)
self.display.init_color_pair(COLOR_PAIR_RED, profiler_theme.off_gil_fg, default_bg)
self.display.init_color_pair(
1, COLOR_CYAN, -1
) # Data colors for stats rows
self.display.init_color_pair(2, COLOR_GREEN, -1)
self.display.init_color_pair(3, COLOR_YELLOW, -1)
self.display.init_color_pair(
COLOR_PAIR_HEADER_BG, COLOR_BLACK, COLOR_GREEN
)
self.display.init_color_pair(
COLOR_PAIR_CYAN, COLOR_CYAN, COLOR_BLACK
)
self.display.init_color_pair(
COLOR_PAIR_YELLOW, COLOR_YELLOW, COLOR_BLACK
)
self.display.init_color_pair(
COLOR_PAIR_GREEN, COLOR_GREEN, COLOR_BLACK
)
self.display.init_color_pair(
COLOR_PAIR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK
)
self.display.init_color_pair(
COLOR_PAIR_RED, COLOR_RED, COLOR_BLACK
)
self.display.init_color_pair(
COLOR_PAIR_SORTED_HEADER, COLOR_BLACK, COLOR_YELLOW
COLOR_PAIR_SORTED_HEADER,
profiler_theme.sorted_header_fg,
profiler_theme.sorted_header_bg,
)
TREND_UP_PAIR = 11
TREND_DOWN_PAIR = 12
self.display.init_color_pair(TREND_UP_PAIR, profiler_theme.trend_up_fg, default_bg)
self.display.init_color_pair(TREND_DOWN_PAIR, profiler_theme.trend_down_fg, default_bg)
return {
"header": self.display.get_color_pair(COLOR_PAIR_HEADER_BG)
| A_BOLD,
"cyan": self.display.get_color_pair(COLOR_PAIR_CYAN)
| A_BOLD,
"yellow": self.display.get_color_pair(COLOR_PAIR_YELLOW)
| A_BOLD,
"green": self.display.get_color_pair(COLOR_PAIR_GREEN)
| A_BOLD,
"magenta": self.display.get_color_pair(COLOR_PAIR_MAGENTA)
| A_BOLD,
"red": self.display.get_color_pair(COLOR_PAIR_RED)
| A_BOLD,
"sorted_header": self.display.get_color_pair(
COLOR_PAIR_SORTED_HEADER
)
| A_BOLD,
"header": self.display.get_color_pair(COLOR_PAIR_HEADER_BG) | A_BOLD,
"cyan": self.display.get_color_pair(COLOR_PAIR_CYAN) | A_BOLD,
"yellow": self.display.get_color_pair(COLOR_PAIR_YELLOW) | A_BOLD,
"green": self.display.get_color_pair(COLOR_PAIR_GREEN) | A_BOLD,
"magenta": self.display.get_color_pair(COLOR_PAIR_MAGENTA) | A_BOLD,
"red": self.display.get_color_pair(COLOR_PAIR_RED) | A_BOLD,
"sorted_header": self.display.get_color_pair(COLOR_PAIR_SORTED_HEADER) | A_BOLD,
"normal_header": A_REVERSE | A_BOLD,
"color_samples": self.display.get_color_pair(1),
"color_file": self.display.get_color_pair(2),
"color_func": self.display.get_color_pair(3),
# Trend colors (stock-like indicators)
"trend_up": self.display.get_color_pair(COLOR_PAIR_GREEN) | A_BOLD,
"trend_down": self.display.get_color_pair(COLOR_PAIR_RED) | A_BOLD,
"trend_up": self.display.get_color_pair(TREND_UP_PAIR) | A_BOLD,
"trend_down": self.display.get_color_pair(TREND_DOWN_PAIR) | A_BOLD,
"trend_stable": A_NORMAL,
}
# Fallback to non-color attributes
return {
"header": A_REVERSE | A_BOLD,
"cyan": A_BOLD,
@ -610,7 +588,6 @@ def _setup_colors(self):
"color_samples": A_NORMAL,
"color_file": A_NORMAL,
"color_func": A_NORMAL,
# Trend colors (fallback to bold/normal for monochrome)
"trend_up": A_BOLD,
"trend_down": A_BOLD,
"trend_stable": A_NORMAL,