diff --git a/engine/app.py b/engine/app.py index 96866aa..75681cd 100644 --- a/engine/app.py +++ b/engine/app.py @@ -51,6 +51,7 @@ def run_pipeline_mode(preset_name: str = "demo"): from engine.fetch import fetch_all, fetch_poetry, load_cache from engine.pipeline.adapters import ( RenderStage, + SourceItemsToBufferStage, create_items_stage, create_stage_from_display, create_stage_from_effect, @@ -85,19 +86,29 @@ def run_pipeline_mode(preset_name: str = "demo"): ) print(" \033[38;5;245mFetching content...\033[0m") - cached = load_cache() - if cached: - items = cached - elif preset.source == "poetry": - items, _, _ = fetch_poetry() + + # Handle special sources that don't need traditional fetching + introspection_source = None + if preset.source == "pipeline-inspect": + items = [] + print(" \033[38;5;245mUsing pipeline introspection source\033[0m") + elif preset.source == "empty": + items = [] + print(" \033[38;5;245mUsing empty source (no content)\033[0m") else: - items, _, _ = fetch_all() + cached = load_cache() + if cached: + items = cached + elif preset.source == "poetry": + items, _, _ = fetch_poetry() + else: + items, _, _ = fetch_all() - if not items: - print(" \033[38;5;196mNo content available\033[0m") - sys.exit(1) + if not items: + print(" \033[38;5;196mNo content available\033[0m") + sys.exit(1) - print(f" \033[38;5;82mLoaded {len(items)} items\033[0m") + print(f" \033[38;5;82mLoaded {len(items)} items\033[0m") display = DisplayRegistry.create(preset.display) if not display: @@ -108,18 +119,45 @@ def run_pipeline_mode(preset_name: str = "demo"): effect_registry = get_registry() - pipeline.add_stage("source", create_items_stage(items, preset.source)) - pipeline.add_stage( - "render", - RenderStage( - items, - width=80, - height=24, - camera_speed=params.camera_speed, - camera_mode=preset.camera, - firehose_enabled=params.firehose_enabled, - ), - ) + # Create source stage based on preset source type + if preset.source == "pipeline-inspect": + from engine.data_sources.pipeline_introspection import ( + PipelineIntrospectionSource, + ) + from engine.pipeline.adapters import DataSourceStage + + introspection_source = PipelineIntrospectionSource( + pipeline=None, # Will be set after pipeline.build() + viewport_width=80, + viewport_height=24, + ) + pipeline.add_stage( + "source", DataSourceStage(introspection_source, name="pipeline-inspect") + ) + elif preset.source == "empty": + from engine.data_sources.sources import EmptyDataSource + from engine.pipeline.adapters import DataSourceStage + + empty_source = EmptyDataSource(width=80, height=24) + pipeline.add_stage("source", DataSourceStage(empty_source, name="empty")) + else: + pipeline.add_stage("source", create_items_stage(items, preset.source)) + + # Add appropriate render stage + if preset.source in ("pipeline-inspect", "empty"): + pipeline.add_stage("render", SourceItemsToBufferStage(name="items-to-buffer")) + else: + pipeline.add_stage( + "render", + RenderStage( + items, + width=80, + height=24, + camera_speed=params.camera_speed, + camera_mode=preset.camera, + firehose_enabled=params.firehose_enabled, + ), + ) for effect_name in preset.effects: effect = effect_registry.get(effect_name) @@ -132,6 +170,10 @@ def run_pipeline_mode(preset_name: str = "demo"): pipeline.build() + # For pipeline-inspect, set the pipeline after build to avoid circular dependency + if introspection_source is not None: + introspection_source.set_pipeline(pipeline) + if not pipeline.initialize(): print(" \033[38;5;196mFailed to initialize pipeline\033[0m") sys.exit(1) @@ -162,7 +204,7 @@ def run_pipeline_mode(preset_name: str = "demo"): result = pipeline.execute(items) if result.success: - display.show(result.data) + display.show(result.data, border=params.border) if hasattr(display, "is_quit_requested") and display.is_quit_requested(): if hasattr(display, "clear_quit_request"): diff --git a/engine/display/backends/multi.py b/engine/display/backends/multi.py index 496eda9..131972a 100644 --- a/engine/display/backends/multi.py +++ b/engine/display/backends/multi.py @@ -30,9 +30,9 @@ class MultiDisplay: for d in self.displays: d.init(width, height, reuse=reuse) - def show(self, buffer: list[str]) -> None: + def show(self, buffer: list[str], border: bool = False) -> None: for d in self.displays: - d.show(buffer) + d.show(buffer, border=border) def clear(self) -> None: for d in self.displays: diff --git a/engine/display/backends/websocket.py b/engine/display/backends/websocket.py index 7ac31ce..5c0c141 100644 --- a/engine/display/backends/websocket.py +++ b/engine/display/backends/websocket.py @@ -24,7 +24,7 @@ class Display(Protocol): """Initialize display with dimensions.""" ... - def show(self, buffer: list[str]) -> None: + def show(self, buffer: list[str], border: bool = False) -> None: """Show buffer on display.""" ... diff --git a/tests/test_display.py b/tests/test_display.py index 46632aa..1491b83 100644 --- a/tests/test_display.py +++ b/tests/test_display.py @@ -165,10 +165,10 @@ class TestMultiDisplay: multi = MultiDisplay([mock_display1, mock_display2]) buffer = ["line1", "line2"] - multi.show(buffer) + multi.show(buffer, border=False) - mock_display1.show.assert_called_once_with(buffer) - mock_display2.show.assert_called_once_with(buffer) + mock_display1.show.assert_called_once_with(buffer, border=False) + mock_display2.show.assert_called_once_with(buffer, border=False) def test_clear_forwards_to_all_displays(self): """clear forwards to all displays."""