Skip to content

Commit 458b3ce

Browse files
Color resume picker previews with active theme
1 parent f048524 commit 458b3ce

2 files changed

Lines changed: 84 additions & 20 deletions

File tree

code_puppy/command_line/autosave_menu.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from rich.console import Console
1414
from rich.markdown import Markdown
1515
from termflow.ansi.codes import BOLD_ON, DIM_ON, RESET
16+
from termflow.ansi.color import fg_color
17+
from termflow.render.style import RenderStyle
1618
from termflow.tui import MenuBuilder, MenuItem
1719
from termflow.tui.menu import MenuResult
1820

@@ -22,7 +24,7 @@
2224
iter_alphabet_bindings,
2325
)
2426
from code_puppy.command_line.menu_session import menu_session
25-
from code_puppy.command_line.tui_style import themed
27+
from code_puppy.command_line.tui_style import menu_style, themed
2628
from code_puppy.config import AUTOSAVE_DIR
2729
from code_puppy.session_storage import compute_scope_key, list_sessions, load_session
2830
from code_puppy.tools.command_runner import set_awaiting_user_input
@@ -116,50 +118,81 @@ def _markdown(text: str, width: int = 72) -> str:
116118
def _render_message_browser_panel(
117119
history: list, message_idx: int, session_name: str
118120
) -> list:
121+
lines = [("class:tui.header", "MESSAGE BROWSER"), ("", "\n\n")]
119122
if not history:
120-
return [
121-
("class:tui.warning", "MESSAGE BROWSER\n\nNo messages in this session.")
122-
]
123+
return lines + [("class:tui.error", "No messages in this session.")]
123124
message_idx = max(0, min(message_idx, len(history) - 1))
124125
role, content = _extract_message_content(history[-1 - message_idx])
125126
rendered = content if role == "tool" else _markdown(content)
126-
return [
127-
(
128-
"class:tui.header",
129-
f"MESSAGE BROWSER\n\nSession: {session_name}\nMessage {message_idx + 1} of {len(history)}\n\n{role.upper()}\n{'─' * 40}\n{rendered}\n\nUp older Down newer Esc exit",
130-
)
127+
role_style = "class:tui.user" if role == "user" else "class:tui.title"
128+
return lines + [
129+
("class:tui.label", "Session: "),
130+
("class:tui.header", session_name),
131+
("", "\n"),
132+
("class:tui.label", "Message: "),
133+
("", f"{message_idx + 1} of {len(history)}\n\n"),
134+
(role_style, role.upper()),
135+
("", "\n"),
136+
("class:tui.divider", "─" * 40),
137+
("", f"\n{rendered}\n\n"),
138+
("class:tui.hint", "Up older Down newer Esc exit"),
131139
]
132140

133141

134142
def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) -> list:
135143
if not entry:
136-
return [("class:tui.warning", "PREVIEW\n\nNo session selected.")]
144+
return [
145+
("class:tui.header", "PREVIEW"),
146+
("class:tui.error", "\n\nNo session selected."),
147+
]
137148
name, metadata = entry
138149
timestamp = metadata.get("timestamp", "unknown")
139150
try:
140151
timestamp = datetime.fromisoformat(timestamp).strftime("%Y-%m-%d %H:%M:%S")
141152
except (TypeError, ValueError):
142153
pass
154+
error = None
143155
try:
144156
message = _markdown(
145157
_extract_last_user_message(load_session(name, base_dir)), 76
146158
)
147159
except Exception as exc:
148-
message = f"Error loading preview: {exc}"
149-
text = f"PREVIEW\n\nSession: {name}\nSaved: {timestamp}\nMessages: {metadata.get('message_count', 0)} • Tokens: {metadata.get('total_tokens', 0):,}\n\nLast Message:\n(press 'e' to browse full history)\n{message}"
150-
return [("class:tui.muted", text)]
160+
message = ""
161+
error = f"Error loading preview: {exc}"
162+
lines = [
163+
("class:tui.header", "PREVIEW"),
164+
("", "\n\n"),
165+
("class:tui.label", "Session: "),
166+
("", f"{name}\n"),
167+
("class:tui.label", "Saved: "),
168+
("", f"{timestamp}\n"),
169+
("class:tui.label", "Messages: "),
170+
("", str(metadata.get("message_count", 0))),
171+
("class:tui.label", " Tokens: "),
172+
("", f"{metadata.get('total_tokens', 0):,}\n\n"),
173+
("class:tui.title", "Last Message:"),
174+
("class:tui.hint", "\n(press 'e' to browse full history)\n"),
175+
]
176+
lines.append(("class:tui.error" if error else "", error or message))
177+
return lines
151178

152179

153180
def _fragments_to_ansi(fragments: list) -> str:
154-
styles = {
155-
"class:tui.header": BOLD_ON,
156-
"class:tui.title": BOLD_ON,
157-
"class:tui.label": BOLD_ON,
158-
"class:tui.muted": DIM_ON,
181+
"""Color semantic preview fragments using the current terminal theme."""
182+
style = menu_style() or RenderStyle.default()
183+
sgr = {
184+
"class:tui.header": fg_color(style.bright) + BOLD_ON,
185+
"class:tui.label": fg_color(style.symbol),
186+
"class:tui.title": fg_color(style.head) + BOLD_ON,
187+
"class:tui.user": fg_color(style.symbol) + BOLD_ON,
188+
"class:tui.hint": fg_color(style.grey) + DIM_ON,
189+
"class:tui.divider": fg_color(style.grey),
190+
"class:tui.muted": fg_color(style.grey) + DIM_ON,
191+
"class:tui.error": fg_color(style.error),
159192
}
160193
return "".join(
161-
f"{styles.get(style, '')}{text}{RESET if style else ''}"
162-
for style, text in fragments
194+
f"{sgr.get(fragment_style, '')}{text}{RESET if fragment_style else ''}"
195+
for fragment_style, text in fragments
163196
)
164197

165198

tests/command_line/test_autosave_menu.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,37 @@ def drive(self, entries, script, **patches):
477477
)
478478
return menu, menu.run(), visible(output.getvalue())
479479

480+
def test_preview_uses_active_theme_accents(self):
481+
from io import StringIO
482+
483+
from termflow.ansi.color import fg_color
484+
485+
from code_puppy.command_line.autosave_menu import build_resume_menu
486+
487+
ansi = ["#000000"] * 16
488+
ansi[5] = "#345678"
489+
ansi[8] = "#456789"
490+
ansi[9] = "#ff0000"
491+
ansi[10] = "#234567"
492+
ansi[12] = "#123456"
493+
palette = {"ansi": ansi, "bg": "#010101"}
494+
output = StringIO()
495+
with patch(
496+
"code_puppy.command_line.tui_style.get_value",
497+
return_value=json.dumps(palette),
498+
):
499+
menu = build_resume_menu(
500+
entries=[("one", {})],
501+
base_dir=Path("/fake"),
502+
key_source=lambda: "escape",
503+
output=output,
504+
size=lambda: (120, 30),
505+
alt_screen=False,
506+
)
507+
menu.run()
508+
509+
assert fg_color(ansi[12]) in output.getvalue()
510+
480511
def test_select_and_cancel(self):
481512
entries = [("one", {}), ("two", {})]
482513
_, result, output = self.drive(entries, iter(["down", "enter"]))

0 commit comments

Comments
 (0)