hh
This commit is contained in:
@@ -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 = []
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user