This commit is contained in:
2026-02-02 14:09:42 -08:00
parent ccd47db869
commit 6309a3ff3e
7 changed files with 134 additions and 82 deletions

View File

@@ -1923,14 +1923,9 @@ class PipelineExecutor:
# PHASE 4: Retrieve and filter items from current result set # PHASE 4: Retrieve and filter items from current result set
# ==================================================================== # ====================================================================
# Cache items_list to avoid redundant lookups in helper functions below. # Cache items_list to avoid redundant lookups in helper functions below.
# Priority: display items (from overlays like get-metadata) > last result items
try: try:
if display_table is not None and stage_table is display_table: items_list = ctx.get_last_result_items() or []
items_list = ctx.get_last_result_items() or []
else:
if hasattr(ctx, "get_last_selectable_result_items"):
items_list = ctx.get_last_selectable_result_items() or []
else:
items_list = ctx.get_last_result_items() or []
except Exception as exc: except Exception as exc:
debug(f"@N: Exception getting items_list: {exc}") debug(f"@N: Exception getting items_list: {exc}")
items_list = [] items_list = []

View File

@@ -3825,3 +3825,65 @@ def check_url_exists_in_storage(
_mark_preflight_checked() _mark_preflight_checked()
return True return True
def display_and_persist_items(
items: List[Any],
title: str = "Result",
subject: Optional[Any] = None,
display_type: str = "item",
table: Optional[Table] = None,
) -> None:
"""Display detail panels and persist items for @N selection.
This helper function:
1. Renders individual detail panels for each item
2. Creates a result table with all items (or uses provided table)
3. Persists the table so @N selection works across command boundaries
Args:
items: List of items/dicts to display and make selectable
title: Title for the result table (default: "Result")
subject: Optional subject to associate with the results (usually first item or list)
display_type: Type of display - "item" (default), "tag", or "custom" (no panels)
table: Optional pre-built Table object (if None, creates new Table with title)
"""
if not items:
return
try:
# Create result table if not provided
if table is None:
table = Table(title=title)
# Render detail panels for each item (unless display_type is "custom")
if display_type != "custom":
try:
from SYS.rich_display import render_item_details_panel
# Determine panel title prefix based on display type
panel_prefix = "Tag" if display_type == "tag" else "Item"
# Render detail panel for each item
for idx, item in enumerate(items, 1):
try:
render_item_details_panel(item, title=f"#{idx} {panel_prefix} Details")
except Exception:
pass
except Exception:
pass
# Add items to table if not already added by caller
if not getattr(table, "_items_added", False):
for item in items:
table.add_result(item)
setattr(table, "_rendered_by_cmdlet", True)
# Use provided subject or default to first item
if subject is None:
subject = items[0] if len(items) == 1 else list(items)
# Persist table for @N selection across command boundaries
pipeline_context.set_last_result_table(table, list(items), subject=subject)
except Exception:
pass

View File

@@ -704,19 +704,10 @@ class Add_File(Cmdlet):
except Exception: except Exception:
pass pass
for idx, payload in enumerate(collected_payloads, 1):
render_item_details_panel(payload, title=f"#{idx} Item Details")
table = Table("Result")
for payload in collected_payloads:
table.add_result(payload)
setattr(table, "_rendered_by_cmdlet", True)
subject = collected_payloads[0] if len(collected_payloads) == 1 else collected_payloads subject = collected_payloads[0] if len(collected_payloads) == 1 else collected_payloads
ctx.set_last_result_table_overlay( # Use helper to display items and make them @-selectable
table, from ._shared import display_and_persist_items
collected_payloads, display_and_persist_items(collected_payloads, title="Result", subject=subject)
subject=subject
)
try: try:
ctx.set_last_result_items_only(list(collected_payloads)) ctx.set_last_result_items_only(list(collected_payloads))

View File

@@ -1082,15 +1082,10 @@ class Add_Tag(Cmdlet):
from SYS.rich_display import render_item_details_panel from SYS.rich_display import render_item_details_panel
from SYS.result_table import Table from SYS.result_table import Table
for idx, item in enumerate(display_items, 1):
render_item_details_panel(item, title=f"#{idx} Item Details")
table = Table("Result")
for item in display_items:
table.add_result(item)
setattr(table, "_rendered_by_cmdlet", True)
subject = display_items[0] if len(display_items) == 1 else list(display_items) subject = display_items[0] if len(display_items) == 1 else list(display_items)
ctx.set_last_result_table_overlay(table, list(display_items), subject=subject) # Use helper to display items and make them @-selectable
from ._shared import display_and_persist_items
display_and_persist_items(list(display_items), title="Result", subject=subject)
except Exception: except Exception:
pass pass

View File

@@ -743,23 +743,10 @@ class Download_File(Cmdlet):
pass pass
try: try:
from SYS.rich_display import render_item_details_panel
from SYS.result_table import Table
for idx, item in enumerate(emitted_items, 1):
render_item_details_panel(item, title=f"#{idx} Item Details")
table = Table("Result")
for item in emitted_items:
table.add_result(item)
setattr(table, "_rendered_by_cmdlet", True)
subject = emitted_items[0] if len(emitted_items) == 1 else list(emitted_items) subject = emitted_items[0] if len(emitted_items) == 1 else list(emitted_items)
pipeline_context.set_last_result_table_overlay( # Use helper to display items and make them @-selectable
table, from ._shared import display_and_persist_items
list(emitted_items), display_and_persist_items(list(emitted_items), title="Result", subject=subject)
subject=subject,
)
except Exception: except Exception:
pass pass
@@ -1969,7 +1956,12 @@ class Download_File(Cmdlet):
else: else:
import re import re
if not re.match(r"^\s*#?\d+\s*$", str(query_format)): if re.match(r"^\s*#?\d+\s*$", str(query_format)):
# Numeric format like "720" or "1080p" - will be resolved later via resolve_height_selector
# Don't set ytdl_format yet; let it fall through to per-URL resolution
pass
else:
# Non-numeric format string - use as literal
ytdl_format = query_format ytdl_format = query_format
debug(f"DEBUG: [download-file] Using literal query_format '{query_format}' as ytdl_format") debug(f"DEBUG: [download-file] Using literal query_format '{query_format}' as ytdl_format")
playlist_selection_handled = False playlist_selection_handled = False

View File

@@ -317,14 +317,9 @@ class Get_Metadata(Cmdlet):
"get-metadata", "get-metadata",
list(args)) list(args))
self._add_table_body_row(table, row) self._add_table_body_row(table, row)
ctx.set_last_result_table_overlay(table, [row], row) # Use helper to display item and make it @-selectable
try: from ._shared import display_and_persist_items
from SYS.rich_display import render_item_details_panel display_and_persist_items([row], title=table_title, subject=row)
render_item_details_panel(row)
table._rendered_by_cmdlet = True
except Exception:
pass
ctx.emit(row) ctx.emit(row)
return 0 return 0

View File

@@ -323,10 +323,21 @@ def _emit_tags_as_table(
""" """
from SYS.result_table import ItemDetailView, extract_item_metadata from SYS.result_table import ItemDetailView, extract_item_metadata
# Prepare metadata for the detail view # Prepare metadata for the detail view, extracting all fields from subject first
metadata = extract_item_metadata(subject) metadata = extract_item_metadata(subject) or {}
# Overlays/Overrides from explicit args if subject was partial # Preserve all additional fields from subject dict if it's a dict-like object
if isinstance(subject, dict):
for key, value in subject.items():
# Skip internal/control fields
if not key.startswith("_") and key not in {"selection_action", "selection_args"}:
# Convert keys to readable labels (snake_case -> Title Case)
label = str(key).replace("_", " ").title()
# Only add if not already present from extract_item_metadata
if label not in metadata and value is not None:
metadata[label] = value
# Apply explicit parameter overrides (these take priority)
if item_title: if item_title:
metadata["Title"] = item_title metadata["Title"] = item_title
if file_hash: if file_hash:
@@ -341,7 +352,7 @@ def _emit_tags_as_table(
table = ItemDetailView("Tags", item_metadata=metadata, max_columns=1, exclude_tags=True) table = ItemDetailView("Tags", item_metadata=metadata, max_columns=1, exclude_tags=True)
table.set_source_command("get-tag", []) table.set_source_command("get-tag", [])
# Create TagItem for each tag # Create TagItem for each tag and add to table
tag_items = [] tag_items = []
for idx, tag_name in enumerate(tags_list, start=1): for idx, tag_name in enumerate(tags_list, start=1):
tag_item = TagItem( tag_item = TagItem(
@@ -356,38 +367,38 @@ def _emit_tags_as_table(
table.add_result(tag_item) table.add_result(tag_item)
# Also emit to pipeline for downstream processing # Also emit to pipeline for downstream processing
ctx.emit(tag_item) ctx.emit(tag_item)
# Store the table and items in history so @.. works to go back # Mark that items were already added to the table
# Use overlay mode so it doesn't push the previous search to history stack setattr(table, "_items_added", True)
# This makes get-tag behave like a transient view
table_applied = False # Display the table and persist for @N selection
try:
ctx.set_last_result_table_overlay(table, tag_items, subject)
table_applied = True
except AttributeError:
try:
ctx.set_last_result_table(table, tag_items, subject)
table_applied = True
except Exception:
table_applied = False
except Exception:
table_applied = False
# Display the rich panel (metadata info) if not in quiet/emit-only mode.
# In the TUI, this output is captured and shown in the log pane.
if not quiet: if not quiet:
try: try:
from SYS.rich_display import stdout_console from SYS.rich_display import stdout_console
stdout_console().print(table) stdout_console().print(table)
except Exception: except Exception:
pass pass
if table_applied: # Use the shared helper to persist the table for @N selection
try: try:
if hasattr(ctx, "set_current_stage_table"): from cmdlet._shared import display_and_persist_items
ctx.set_current_stage_table(table) # Skip panel rendering since table already exists with custom ItemDetailView
except Exception: display_and_persist_items(
pass tag_items,
title=table.title if hasattr(table, 'title') else "Tags",
subject=subject,
display_type="custom",
table=table,
)
except Exception:
pass
# Also update the current stage table for TUI
try:
if hasattr(ctx, "set_current_stage_table"):
ctx.set_current_stage_table(table)
except Exception:
pass
# Note: CLI will handle displaying the table via ResultTable formatting # Note: CLI will handle displaying the table via ResultTable formatting
@@ -1705,6 +1716,11 @@ def _run_impl(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
# For store-backed items (Hydrus/Folders), we want the latest state. # For store-backed items (Hydrus/Folders), we want the latest state.
if display_tags and not emit_mode and not is_store_backed: if display_tags and not emit_mode and not is_store_backed:
subject_payload = _subject_payload_with(display_tags) subject_payload = _subject_payload_with(display_tags)
# Merge the full result object into subject_payload so all original metadata is preserved
if isinstance(result, dict):
for key, value in result.items():
if key not in subject_payload and not key.startswith("_"):
subject_payload[key] = value
_emit_tags_as_table( _emit_tags_as_table(
display_tags, display_tags,
file_hash=file_hash, file_hash=file_hash,
@@ -1739,6 +1755,12 @@ def _run_impl(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
current, current,
service_name if source == "hydrus" else None, service_name if source == "hydrus" else None,
) )
# Merge the full result object into subject_payload so all original metadata is preserved
# (e.g., url, source_url, etc. from search results)
if isinstance(result, dict):
for key, value in result.items():
if key not in subject_payload and not key.startswith("_"):
subject_payload[key] = value
_emit_tags_as_table( _emit_tags_as_table(
current, current,
file_hash=file_hash, file_hash=file_hash,