mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
Merge b024e50773 into 3db7bf2d18
This commit is contained in:
commit
473558efa6
4 changed files with 197 additions and 76 deletions
133
Lib/_colorize.py
133
Lib/_colorize.py
|
|
@ -68,6 +68,19 @@ class ANSIColors:
|
||||||
ColorCodes = set()
|
ColorCodes = set()
|
||||||
NoColors = ANSIColors()
|
NoColors = ANSIColors()
|
||||||
|
|
||||||
|
|
||||||
|
class CursesColors:
|
||||||
|
"""Curses color constants for terminal UI theming."""
|
||||||
|
BLACK = 0
|
||||||
|
RED = 1
|
||||||
|
GREEN = 2
|
||||||
|
YELLOW = 3
|
||||||
|
BLUE = 4
|
||||||
|
MAGENTA = 5
|
||||||
|
CYAN = 6
|
||||||
|
WHITE = 7
|
||||||
|
DEFAULT = -1
|
||||||
|
|
||||||
for attr, code in ANSIColors.__dict__.items():
|
for attr, code in ANSIColors.__dict__.items():
|
||||||
if not attr.startswith("__"):
|
if not attr.startswith("__"):
|
||||||
ColorCodes.add(code)
|
ColorCodes.add(code)
|
||||||
|
|
@ -223,6 +236,119 @@ class Unittest(ThemeSection):
|
||||||
reset: str = ANSIColors.RESET
|
reset: str = ANSIColors.RESET
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class LiveProfiler(ThemeSection):
|
||||||
|
"""Theme section for the live profiling TUI (Tachyon profiler).
|
||||||
|
|
||||||
|
Colors use CursesColors constants (BLACK, RED, GREEN, YELLOW,
|
||||||
|
BLUE, MAGENTA, CYAN, WHITE, DEFAULT).
|
||||||
|
"""
|
||||||
|
# Header colors
|
||||||
|
title_fg: int = CursesColors.CYAN
|
||||||
|
title_bg: int = CursesColors.DEFAULT
|
||||||
|
|
||||||
|
# Status display colors
|
||||||
|
pid_fg: int = CursesColors.CYAN
|
||||||
|
uptime_fg: int = CursesColors.GREEN
|
||||||
|
time_fg: int = CursesColors.YELLOW
|
||||||
|
interval_fg: int = CursesColors.MAGENTA
|
||||||
|
|
||||||
|
# Thread view colors
|
||||||
|
thread_all_fg: int = CursesColors.GREEN
|
||||||
|
thread_single_fg: int = CursesColors.MAGENTA
|
||||||
|
|
||||||
|
# Progress bar colors
|
||||||
|
bar_good_fg: int = CursesColors.GREEN
|
||||||
|
bar_bad_fg: int = CursesColors.RED
|
||||||
|
|
||||||
|
# Stats colors
|
||||||
|
on_gil_fg: int = CursesColors.GREEN
|
||||||
|
off_gil_fg: int = CursesColors.RED
|
||||||
|
waiting_gil_fg: int = CursesColors.YELLOW
|
||||||
|
gc_fg: int = CursesColors.MAGENTA
|
||||||
|
|
||||||
|
# Function display colors
|
||||||
|
func_total_fg: int = CursesColors.CYAN
|
||||||
|
func_exec_fg: int = CursesColors.GREEN
|
||||||
|
func_stack_fg: int = CursesColors.YELLOW
|
||||||
|
func_shown_fg: int = CursesColors.MAGENTA
|
||||||
|
|
||||||
|
# Table header colors
|
||||||
|
sorted_header_fg: int = CursesColors.BLACK
|
||||||
|
sorted_header_bg: int = CursesColors.YELLOW
|
||||||
|
|
||||||
|
# Data row colors
|
||||||
|
samples_fg: int = CursesColors.CYAN
|
||||||
|
file_fg: int = CursesColors.GREEN
|
||||||
|
func_fg: int = CursesColors.YELLOW
|
||||||
|
|
||||||
|
# Trend indicator colors
|
||||||
|
trend_up_fg: int = CursesColors.GREEN
|
||||||
|
trend_down_fg: int = CursesColors.RED
|
||||||
|
|
||||||
|
# Medal colors for top functions
|
||||||
|
medal_gold_fg: int = CursesColors.RED
|
||||||
|
medal_silver_fg: int = CursesColors.YELLOW
|
||||||
|
medal_bronze_fg: int = CursesColors.GREEN
|
||||||
|
|
||||||
|
# Background style: 'dark' or 'light'
|
||||||
|
background_style: str = "dark"
|
||||||
|
|
||||||
|
|
||||||
|
LiveProfilerLight = LiveProfiler(
|
||||||
|
# Header colors
|
||||||
|
title_fg=CursesColors.BLUE,
|
||||||
|
title_bg=CursesColors.DEFAULT,
|
||||||
|
|
||||||
|
# Status display colors
|
||||||
|
pid_fg=CursesColors.BLUE,
|
||||||
|
uptime_fg=CursesColors.GREEN,
|
||||||
|
time_fg=CursesColors.YELLOW,
|
||||||
|
interval_fg=CursesColors.MAGENTA,
|
||||||
|
|
||||||
|
# Thread view colors
|
||||||
|
thread_all_fg=CursesColors.GREEN,
|
||||||
|
thread_single_fg=CursesColors.MAGENTA,
|
||||||
|
|
||||||
|
# Progress bar colors
|
||||||
|
bar_good_fg=CursesColors.GREEN,
|
||||||
|
bar_bad_fg=CursesColors.RED,
|
||||||
|
|
||||||
|
# Stats colors
|
||||||
|
on_gil_fg=CursesColors.GREEN,
|
||||||
|
off_gil_fg=CursesColors.RED,
|
||||||
|
waiting_gil_fg=CursesColors.YELLOW,
|
||||||
|
gc_fg=CursesColors.MAGENTA,
|
||||||
|
|
||||||
|
# Function display colors
|
||||||
|
func_total_fg=CursesColors.BLUE,
|
||||||
|
func_exec_fg=CursesColors.GREEN,
|
||||||
|
func_stack_fg=CursesColors.YELLOW,
|
||||||
|
func_shown_fg=CursesColors.MAGENTA,
|
||||||
|
|
||||||
|
# Table header colors
|
||||||
|
sorted_header_fg=CursesColors.WHITE,
|
||||||
|
sorted_header_bg=CursesColors.BLUE,
|
||||||
|
|
||||||
|
# Data row colors
|
||||||
|
samples_fg=CursesColors.BLUE,
|
||||||
|
file_fg=CursesColors.GREEN,
|
||||||
|
func_fg=CursesColors.MAGENTA,
|
||||||
|
|
||||||
|
# Trend indicator colors
|
||||||
|
trend_up_fg=CursesColors.GREEN,
|
||||||
|
trend_down_fg=CursesColors.RED,
|
||||||
|
|
||||||
|
# Medal colors for top functions
|
||||||
|
medal_gold_fg=CursesColors.RED,
|
||||||
|
medal_silver_fg=CursesColors.BLUE,
|
||||||
|
medal_bronze_fg=CursesColors.GREEN,
|
||||||
|
|
||||||
|
# Background style
|
||||||
|
background_style="light",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Theme:
|
class Theme:
|
||||||
"""A suite of themes for all sections of Python.
|
"""A suite of themes for all sections of Python.
|
||||||
|
|
@ -235,6 +361,7 @@ class Theme:
|
||||||
syntax: Syntax = field(default_factory=Syntax)
|
syntax: Syntax = field(default_factory=Syntax)
|
||||||
traceback: Traceback = field(default_factory=Traceback)
|
traceback: Traceback = field(default_factory=Traceback)
|
||||||
unittest: Unittest = field(default_factory=Unittest)
|
unittest: Unittest = field(default_factory=Unittest)
|
||||||
|
live_profiler: LiveProfiler = field(default_factory=LiveProfiler)
|
||||||
|
|
||||||
def copy_with(
|
def copy_with(
|
||||||
self,
|
self,
|
||||||
|
|
@ -244,6 +371,7 @@ def copy_with(
|
||||||
syntax: Syntax | None = None,
|
syntax: Syntax | None = None,
|
||||||
traceback: Traceback | None = None,
|
traceback: Traceback | None = None,
|
||||||
unittest: Unittest | None = None,
|
unittest: Unittest | None = None,
|
||||||
|
live_profiler: LiveProfiler | None = None,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""Return a new Theme based on this instance with some sections replaced.
|
"""Return a new Theme based on this instance with some sections replaced.
|
||||||
|
|
||||||
|
|
@ -256,6 +384,7 @@ def copy_with(
|
||||||
syntax=syntax or self.syntax,
|
syntax=syntax or self.syntax,
|
||||||
traceback=traceback or self.traceback,
|
traceback=traceback or self.traceback,
|
||||||
unittest=unittest or self.unittest,
|
unittest=unittest or self.unittest,
|
||||||
|
live_profiler=live_profiler or self.live_profiler,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -272,6 +401,7 @@ def no_colors(cls) -> Self:
|
||||||
syntax=Syntax.no_colors(),
|
syntax=Syntax.no_colors(),
|
||||||
traceback=Traceback.no_colors(),
|
traceback=Traceback.no_colors(),
|
||||||
unittest=Unittest.no_colors(),
|
unittest=Unittest.no_colors(),
|
||||||
|
live_profiler=LiveProfiler.no_colors(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -338,6 +468,9 @@ def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
|
||||||
default_theme = Theme()
|
default_theme = Theme()
|
||||||
theme_no_color = default_theme.no_colors()
|
theme_no_color = default_theme.no_colors()
|
||||||
|
|
||||||
|
# Convenience theme with light profiler colors (for white/light terminal backgrounds)
|
||||||
|
light_profiler_theme = default_theme.copy_with(live_profiler=LiveProfilerLight)
|
||||||
|
|
||||||
|
|
||||||
def get_theme(
|
def get_theme(
|
||||||
*,
|
*,
|
||||||
|
|
|
||||||
|
|
@ -501,79 +501,57 @@ def _cycle_sort(self, reverse=False):
|
||||||
|
|
||||||
def _setup_colors(self):
|
def _setup_colors(self):
|
||||||
"""Set up color pairs and return color attributes."""
|
"""Set up color pairs and return color attributes."""
|
||||||
|
|
||||||
A_BOLD = self.display.get_attr("A_BOLD")
|
A_BOLD = self.display.get_attr("A_BOLD")
|
||||||
A_REVERSE = self.display.get_attr("A_REVERSE")
|
A_REVERSE = self.display.get_attr("A_REVERSE")
|
||||||
A_UNDERLINE = self.display.get_attr("A_UNDERLINE")
|
A_UNDERLINE = self.display.get_attr("A_UNDERLINE")
|
||||||
A_NORMAL = self.display.get_attr("A_NORMAL")
|
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:
|
if self.display.has_colors() and self._can_colorize:
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
# Color constants (using curses values for compatibility)
|
theme = _colorize.get_theme(force_color=True)
|
||||||
COLOR_CYAN = 6
|
profiler_theme = theme.live_profiler
|
||||||
COLOR_GREEN = 2
|
default_bg = -1
|
||||||
COLOR_YELLOW = 3
|
|
||||||
COLOR_BLACK = 0
|
|
||||||
COLOR_MAGENTA = 5
|
|
||||||
COLOR_RED = 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(
|
self.display.init_color_pair(
|
||||||
1, COLOR_CYAN, -1
|
COLOR_PAIR_SORTED_HEADER,
|
||||||
) # Data colors for stats rows
|
profiler_theme.sorted_header_fg,
|
||||||
self.display.init_color_pair(2, COLOR_GREEN, -1)
|
profiler_theme.sorted_header_bg,
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
"header": self.display.get_color_pair(COLOR_PAIR_HEADER_BG)
|
"header": self.display.get_color_pair(COLOR_PAIR_HEADER_BG) | A_BOLD,
|
||||||
| A_BOLD,
|
"cyan": self.display.get_color_pair(COLOR_PAIR_CYAN) | A_BOLD,
|
||||||
"cyan": self.display.get_color_pair(COLOR_PAIR_CYAN)
|
"yellow": self.display.get_color_pair(COLOR_PAIR_YELLOW) | A_BOLD,
|
||||||
| A_BOLD,
|
"green": self.display.get_color_pair(COLOR_PAIR_GREEN) | A_BOLD,
|
||||||
"yellow": self.display.get_color_pair(COLOR_PAIR_YELLOW)
|
"magenta": self.display.get_color_pair(COLOR_PAIR_MAGENTA) | A_BOLD,
|
||||||
| A_BOLD,
|
"red": self.display.get_color_pair(COLOR_PAIR_RED) | A_BOLD,
|
||||||
"green": self.display.get_color_pair(COLOR_PAIR_GREEN)
|
"sorted_header": self.display.get_color_pair(COLOR_PAIR_SORTED_HEADER) | A_BOLD,
|
||||||
| 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,
|
"normal_header": A_REVERSE | A_BOLD,
|
||||||
"color_samples": self.display.get_color_pair(1),
|
"color_samples": self.display.get_color_pair(1),
|
||||||
"color_file": self.display.get_color_pair(2),
|
"color_file": self.display.get_color_pair(2),
|
||||||
"color_func": self.display.get_color_pair(3),
|
"color_func": self.display.get_color_pair(3),
|
||||||
# Trend colors (stock-like indicators)
|
"trend_up": self.display.get_color_pair(TREND_UP_PAIR) | A_BOLD,
|
||||||
"trend_up": self.display.get_color_pair(COLOR_PAIR_GREEN) | A_BOLD,
|
"trend_down": self.display.get_color_pair(TREND_DOWN_PAIR) | A_BOLD,
|
||||||
"trend_down": self.display.get_color_pair(COLOR_PAIR_RED) | A_BOLD,
|
|
||||||
"trend_stable": A_NORMAL,
|
"trend_stable": A_NORMAL,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fallback to non-color attributes
|
|
||||||
return {
|
return {
|
||||||
"header": A_REVERSE | A_BOLD,
|
"header": A_REVERSE | A_BOLD,
|
||||||
"cyan": A_BOLD,
|
"cyan": A_BOLD,
|
||||||
|
|
@ -586,7 +564,6 @@ def _setup_colors(self):
|
||||||
"color_samples": A_NORMAL,
|
"color_samples": A_NORMAL,
|
||||||
"color_file": A_NORMAL,
|
"color_file": A_NORMAL,
|
||||||
"color_func": A_NORMAL,
|
"color_func": A_NORMAL,
|
||||||
# Trend colors (fallback to bold/normal for monochrome)
|
|
||||||
"trend_up": A_BOLD,
|
"trend_up": A_BOLD,
|
||||||
"trend_down": A_BOLD,
|
"trend_down": A_BOLD,
|
||||||
"trend_stable": A_NORMAL,
|
"trend_stable": A_NORMAL,
|
||||||
|
|
|
||||||
|
|
@ -639,8 +639,6 @@ def render(self, line, width, **kwargs):
|
||||||
|
|
||||||
def draw_column_headers(self, line, width):
|
def draw_column_headers(self, line, width):
|
||||||
"""Draw column headers with sort indicators."""
|
"""Draw column headers with sort indicators."""
|
||||||
col = 0
|
|
||||||
|
|
||||||
# Determine which columns to show based on width
|
# Determine which columns to show based on width
|
||||||
show_sample_pct = width >= WIDTH_THRESHOLD_SAMPLE_PCT
|
show_sample_pct = width >= WIDTH_THRESHOLD_SAMPLE_PCT
|
||||||
show_tottime = width >= WIDTH_THRESHOLD_TOTTIME
|
show_tottime = width >= WIDTH_THRESHOLD_TOTTIME
|
||||||
|
|
@ -659,38 +657,38 @@ def draw_column_headers(self, line, width):
|
||||||
"cumtime": 4,
|
"cumtime": 4,
|
||||||
}.get(self.collector.sort_by, -1)
|
}.get(self.collector.sort_by, -1)
|
||||||
|
|
||||||
|
# Build the full header line first, then draw it
|
||||||
|
# This avoids gaps between columns when using reverse video
|
||||||
|
header_parts = []
|
||||||
|
col = 0
|
||||||
|
|
||||||
# Column 0: nsamples
|
# Column 0: nsamples
|
||||||
attr = sorted_header if sort_col == 0 else normal_header
|
text = f"{'▼nsamples' if sort_col == 0 else 'nsamples':>13} "
|
||||||
text = f"{'▼nsamples' if sort_col == 0 else 'nsamples':>13}"
|
header_parts.append((col, text, sorted_header if sort_col == 0 else normal_header))
|
||||||
self.add_str(line, col, text, attr)
|
|
||||||
col += 15
|
col += 15
|
||||||
|
|
||||||
# Column 1: sample %
|
# Column 1: sample %
|
||||||
if show_sample_pct:
|
if show_sample_pct:
|
||||||
attr = sorted_header if sort_col == 1 else normal_header
|
text = f"{'▼%' if sort_col == 1 else '%':>5} "
|
||||||
text = f"{'▼%' if sort_col == 1 else '%':>5}"
|
header_parts.append((col, text, sorted_header if sort_col == 1 else normal_header))
|
||||||
self.add_str(line, col, text, attr)
|
|
||||||
col += 7
|
col += 7
|
||||||
|
|
||||||
# Column 2: tottime
|
# Column 2: tottime
|
||||||
if show_tottime:
|
if show_tottime:
|
||||||
attr = sorted_header if sort_col == 2 else normal_header
|
text = f"{'▼tottime' if sort_col == 2 else 'tottime':>10} "
|
||||||
text = f"{'▼tottime' if sort_col == 2 else 'tottime':>10}"
|
header_parts.append((col, text, sorted_header if sort_col == 2 else normal_header))
|
||||||
self.add_str(line, col, text, attr)
|
|
||||||
col += 12
|
col += 12
|
||||||
|
|
||||||
# Column 3: cumul %
|
# Column 3: cumul %
|
||||||
if show_cumul_pct:
|
if show_cumul_pct:
|
||||||
attr = sorted_header if sort_col == 3 else normal_header
|
text = f"{'▼%' if sort_col == 3 else '%':>5} "
|
||||||
text = f"{'▼%' if sort_col == 3 else '%':>5}"
|
header_parts.append((col, text, sorted_header if sort_col == 3 else normal_header))
|
||||||
self.add_str(line, col, text, attr)
|
|
||||||
col += 7
|
col += 7
|
||||||
|
|
||||||
# Column 4: cumtime
|
# Column 4: cumtime
|
||||||
if show_cumtime:
|
if show_cumtime:
|
||||||
attr = sorted_header if sort_col == 4 else normal_header
|
text = f"{'▼cumtime' if sort_col == 4 else 'cumtime':>10} "
|
||||||
text = f"{'▼cumtime' if sort_col == 4 else 'cumtime':>10}"
|
header_parts.append((col, text, sorted_header if sort_col == 4 else normal_header))
|
||||||
self.add_str(line, col, text, attr)
|
|
||||||
col += 12
|
col += 12
|
||||||
|
|
||||||
# Remaining headers
|
# Remaining headers
|
||||||
|
|
@ -700,13 +698,22 @@ def draw_column_headers(self, line, width):
|
||||||
MAX_FUNC_NAME_WIDTH,
|
MAX_FUNC_NAME_WIDTH,
|
||||||
max(MIN_FUNC_NAME_WIDTH, remaining_space // 2),
|
max(MIN_FUNC_NAME_WIDTH, remaining_space // 2),
|
||||||
)
|
)
|
||||||
self.add_str(
|
text = f"{'function':<{func_width}} "
|
||||||
line, col, f"{'function':<{func_width}}", normal_header
|
header_parts.append((col, text, normal_header))
|
||||||
)
|
|
||||||
col += func_width + 2
|
col += func_width + 2
|
||||||
|
|
||||||
if col < width - 10:
|
if col < width - 10:
|
||||||
self.add_str(line, col, "file:line", normal_header)
|
file_text = "file:line"
|
||||||
|
padding = width - col - len(file_text)
|
||||||
|
text = file_text + " " * max(0, padding)
|
||||||
|
header_parts.append((col, text, normal_header))
|
||||||
|
|
||||||
|
# Draw full-width background first
|
||||||
|
self.add_str(line, 0, " " * (width - 1), normal_header)
|
||||||
|
|
||||||
|
# Draw each header part on top
|
||||||
|
for col_pos, text, attr in header_parts:
|
||||||
|
self.add_str(line, col_pos, text.rstrip(), attr)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
line + 1,
|
line + 1,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
The Tachyon profiler's live TUI now integrates with the :mod:`!_colorize`
|
||||||
|
theming system, allowing users to customize colors via
|
||||||
|
:func:`!_colorize.set_theme`. A :class:`!LiveProfilerLight` theme is provided
|
||||||
|
for light terminal backgrounds. Patch by Pablo Galindo.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue