This commit is contained in:
2026-01-03 21:23:55 -08:00
parent 73f3005393
commit 3acf21a673
10 changed files with 1244 additions and 43 deletions

163
CLI.py
View File

@@ -2168,6 +2168,20 @@ class PipelineExecutor:
table if current_table and hasattr(current_table,
"table") else None
)
# Prefer an explicit provider hint from table metadata when available.
# This keeps @N selectors working even when row payloads don't carry a
# provider key (or when they carry a table-type like hifi.album).
try:
meta = (
current_table.get_table_metadata()
if current_table is not None and hasattr(current_table, "get_table_metadata")
else getattr(current_table, "table_metadata", None)
)
except Exception:
meta = None
if isinstance(meta, dict):
_add(meta.get("provider"))
except Exception:
pass
@@ -2187,6 +2201,23 @@ class PipelineExecutor:
get_provider = None # type: ignore
is_known_provider_name = None # type: ignore
# If we have a table-type like "hifi.album", also try its provider prefix ("hifi")
# when that prefix is a registered provider name.
if is_known_provider_name is not None:
try:
for key in list(candidates):
if not isinstance(key, str):
continue
if "." not in key:
continue
if is_known_provider_name(key):
continue
prefix = str(key).split(".", 1)[0].strip().lower()
if prefix and is_known_provider_name(prefix):
_add(prefix)
except Exception:
pass
if get_provider is not None:
for key in candidates:
try:
@@ -2401,15 +2432,37 @@ class PipelineExecutor:
if not selection_indices:
return True, None
# Selection should operate on the *currently displayed* selectable table.
# Some navigation flows (e.g. @.. back) can show a display table without
# updating current_stage_table. Provider selectors rely on current_stage_table
# to detect table type (e.g. hifi.album -> tracks), so sync it here.
display_table = None
try:
if not ctx.get_current_stage_table_source_command():
display_table = (
ctx.get_display_table() if hasattr(ctx,
"get_display_table") else None
display_table = (
ctx.get_display_table() if hasattr(ctx, "get_display_table") else None
)
except Exception:
display_table = None
current_stage_table = None
try:
current_stage_table = (
ctx.get_current_stage_table()
if hasattr(ctx, "get_current_stage_table") else None
)
except Exception:
current_stage_table = None
try:
if display_table is not None and hasattr(ctx, "set_current_stage_table"):
ctx.set_current_stage_table(display_table)
elif current_stage_table is None and hasattr(ctx, "set_current_stage_table"):
last_table = (
ctx.get_last_result_table()
if hasattr(ctx, "get_last_result_table") else None
)
table_for_stage = display_table or ctx.get_last_result_table()
if table_for_stage:
ctx.set_current_stage_table(table_for_stage)
if last_table is not None:
ctx.set_current_stage_table(last_table)
except Exception:
pass
@@ -2600,6 +2653,67 @@ class PipelineExecutor:
print("No items matched selection in pipeline\n")
return False, None
# Provider selection expansion (non-terminal): allow certain provider tables
# (e.g. hifi.album) to expand to multiple downstream items when the user
# pipes into another stage (e.g. @N | .mpv or @N | add-file).
table_type_hint = None
try:
table_type_hint = (
stage_table.table
if stage_table is not None and hasattr(stage_table, "table")
else None
)
except Exception:
table_type_hint = None
if stages and isinstance(table_type_hint, str) and table_type_hint.strip().lower() == "hifi.album":
try:
from ProviderCore.registry import get_provider
prov = get_provider("hifi", config)
except Exception:
prov = None
if prov is not None and hasattr(prov, "_extract_album_selection_context") and hasattr(prov, "_tracks_for_album"):
try:
album_contexts = prov._extract_album_selection_context(filtered) # type: ignore[attr-defined]
except Exception:
album_contexts = []
track_items: List[Any] = []
seen_track_ids: set[int] = set()
for album_id, album_title, artist_name in album_contexts or []:
try:
track_results = prov._tracks_for_album( # type: ignore[attr-defined]
album_id=album_id,
album_title=album_title,
artist_name=artist_name,
limit=500,
)
except Exception:
track_results = []
for tr in track_results or []:
try:
md = getattr(tr, "full_metadata", None)
tid = None
if isinstance(md, dict):
raw_id = md.get("trackId") or md.get("id")
try:
tid = int(raw_id) if raw_id is not None else None
except Exception:
tid = None
if tid is not None:
if tid in seen_track_ids:
continue
seen_track_ids.add(tid)
except Exception:
pass
track_items.append(tr)
if track_items:
filtered = track_items
table_type_hint = "hifi.track"
if PipelineExecutor._maybe_run_class_selector(
ctx,
config,
@@ -2634,11 +2748,20 @@ class PipelineExecutor:
current_table = ctx.get_last_result_table()
except Exception:
current_table = None
table_type = (
current_table.table
if current_table and hasattr(current_table,
"table") else None
)
table_type = None
try:
if isinstance(table_type_hint, str) and table_type_hint.strip():
table_type = table_type_hint
else:
table_type = (
current_table.table
if current_table and hasattr(current_table, "table") else None
)
except Exception:
table_type = (
current_table.table
if current_table and hasattr(current_table, "table") else None
)
def _norm_cmd(name: Any) -> str:
return str(name or "").replace("_", "-").strip().lower()
@@ -2981,11 +3104,27 @@ class PipelineExecutor:
display_table = None
stage_table = ctx.get_current_stage_table()
# Selection should operate on the table the user sees.
# If a display overlay table exists, force it as the current-stage table
# so provider selectors (e.g. hifi.album -> tracks) behave consistently.
try:
if display_table is not None and hasattr(ctx, "set_current_stage_table"):
ctx.set_current_stage_table(display_table)
stage_table = display_table
except Exception:
pass
if not stage_table and display_table is not None:
stage_table = display_table
if not stage_table:
stage_table = ctx.get_last_result_table()
try:
if hasattr(ctx, "debug_table_state"):
ctx.debug_table_state(f"selection {selection_token}")
except Exception:
pass
if display_table is not None and stage_table is display_table:
items_list = ctx.get_last_result_items() or []
else: