shoko 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.bundle/config +4 -0
- data/.bundle/config.bak +3 -0
- data/.rspec_status +42 -0
- data/.rubocop.yml +124 -0
- data/Gemfile +19 -0
- data/LICENSE +21 -0
- data/README.md +82 -0
- data/Rakefile +29 -0
- data/bin/start +15 -0
- data/lib/shoko/adapters/book_sources/document_service.rb +201 -0
- data/lib/shoko/adapters/book_sources/download_service.rb +95 -0
- data/lib/shoko/adapters/book_sources/epub/epub_resource_loader.rb +137 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/html_processor.rb +151 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/metadata_extractor.rb +53 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/entry_reader.rb +77 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/metadata_extractor.rb +67 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_context.rb +86 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_index.rb +75 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_scanner.rb +47 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_extractor.rb +46 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_label_resolver.rb +83 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_list_item.rb +55 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_result.rb +8 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_selector.rb +100 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_source_locator.rb +93 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_traversal.rb +103 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_walker.rb +56 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf_processor.rb +102 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xhtml_content_parser.rb +661 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xml_text_normalizer.rb +41 -0
- data/lib/shoko/adapters/book_sources/epub_document.rb +253 -0
- data/lib/shoko/adapters/book_sources/epub_finder/directory_scanner.rb +134 -0
- data/lib/shoko/adapters/book_sources/epub_finder/scanner_context.rb +28 -0
- data/lib/shoko/adapters/book_sources/epub_finder.rb +161 -0
- data/lib/shoko/adapters/book_sources/epub_importer.rb +268 -0
- data/lib/shoko/adapters/book_sources/gutendex_client.rb +150 -0
- data/lib/shoko/adapters/book_sources/library_scanner.rb +93 -0
- data/lib/shoko/adapters/book_sources/source_fingerprint.rb +57 -0
- data/lib/shoko/adapters/input/annotations/mouse_handler.rb +84 -0
- data/lib/shoko/adapters/input/command_bridge.rb +148 -0
- data/lib/shoko/adapters/input/command_factory.rb +255 -0
- data/lib/shoko/adapters/input/commands.rb +60 -0
- data/lib/shoko/adapters/input/dispatcher.rb +69 -0
- data/lib/shoko/adapters/input/input_controller.rb +250 -0
- data/lib/shoko/adapters/input/key_definitions.rb +108 -0
- data/lib/shoko/adapters/input/validators/file_path_validator.rb +81 -0
- data/lib/shoko/adapters/input/validators/terminal_size_validator.rb +76 -0
- data/lib/shoko/adapters/monitoring/logger.rb +150 -0
- data/lib/shoko/adapters/monitoring/perf_tracer.rb +183 -0
- data/lib/shoko/adapters/monitoring/performance_monitor.rb +110 -0
- data/lib/shoko/adapters/output/clipboard/clipboard_service.rb +125 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/image_builder.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/text_wrapper.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/tokenizer.rb +91 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler.rb +144 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/plain_lines_builder.rb +54 -0
- data/lib/shoko/adapters/output/formatting/formatting_service.rb +247 -0
- data/lib/shoko/adapters/output/formatting/wrapping_service.rb +228 -0
- data/lib/shoko/adapters/output/instrumentation_service.rb +52 -0
- data/lib/shoko/adapters/output/kitty/image_transcoder.rb +71 -0
- data/lib/shoko/adapters/output/kitty/kitty_graphics.rb +114 -0
- data/lib/shoko/adapters/output/kitty/kitty_image_renderer.rb +239 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders.rb +139 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders_diacritic_codepoints.txt +26 -0
- data/lib/shoko/adapters/output/notification_service.rb +58 -0
- data/lib/shoko/adapters/output/render_registry.rb +45 -0
- data/lib/shoko/adapters/output/rendering/models/line_geometry.rb +60 -0
- data/lib/shoko/adapters/output/rendering/models/page_rendering_context.rb +22 -0
- data/lib/shoko/adapters/output/rendering/models/render_params.rb +28 -0
- data/lib/shoko/adapters/output/rendering/models/rendering_context.rb +58 -0
- data/lib/shoko/adapters/output/terminal/buffer.rb +275 -0
- data/lib/shoko/adapters/output/terminal/constants/terminal_defaults.rb +11 -0
- data/lib/shoko/adapters/output/terminal/input/decoder.rb +347 -0
- data/lib/shoko/adapters/output/terminal/input.rb +161 -0
- data/lib/shoko/adapters/output/terminal/output.rb +105 -0
- data/lib/shoko/adapters/output/terminal/terminal.rb +167 -0
- data/lib/shoko/adapters/output/terminal/terminal_sanitizer.rb +243 -0
- data/lib/shoko/adapters/output/terminal/terminal_service.rb +138 -0
- data/lib/shoko/adapters/output/terminal/text_metrics.rb +273 -0
- data/lib/shoko/adapters/output/ui/builders/page_setup_builder.rb +47 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/footer_renderer.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/geometry.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/note_renderer.rb +86 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay_component.rb +234 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay/list_renderer.rb +142 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay_component.rb +185 -0
- data/lib/shoko/adapters/output/ui/components/base_component.rb +110 -0
- data/lib/shoko/adapters/output/ui/components/component_interface.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/content_component.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/enhanced_popup_menu.rb +191 -0
- data/lib/shoko/adapters/output/ui/components/footer_component.rb +120 -0
- data/lib/shoko/adapters/output/ui/components/header_component.rb +46 -0
- data/lib/shoko/adapters/output/ui/components/layouts/horizontal.rb +63 -0
- data/lib/shoko/adapters/output/ui/components/layouts/vertical.rb +73 -0
- data/lib/shoko/adapters/output/ui/components/main_menu_component.rb +103 -0
- data/lib/shoko/adapters/output/ui/components/reading/base_view_renderer.rb +199 -0
- data/lib/shoko/adapters/output/ui/components/reading/config_helpers.rb +42 -0
- data/lib/shoko/adapters/output/ui/components/reading/help_renderer.rb +62 -0
- data/lib/shoko/adapters/output/ui/components/reading/inline_segment_highlighter.rb +144 -0
- data/lib/shoko/adapters/output/ui/components/reading/kitty_image_line_renderer.rb +262 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_content_composer.rb +114 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_drawer.rb +87 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_geometry_builder.rb +41 -0
- data/lib/shoko/adapters/output/ui/components/reading/rendered_lines_recorder.rb +64 -0
- data/lib/shoko/adapters/output/ui/components/reading/single_view_renderer.rb +156 -0
- data/lib/shoko/adapters/output/ui/components/reading/split_view_renderer.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/reading/view_renderer_factory.rb +20 -0
- data/lib/shoko/adapters/output/ui/components/reading/wrapped_lines_fetcher.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/rect.rb +15 -0
- data/lib/shoko/adapters/output/ui/components/render_style.rb +84 -0
- data/lib/shoko/adapters/output/ui/components/screen_component.rb +24 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_detail_screen_component.rb +175 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_edit_screen_component.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_editor_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_rendering_helpers.rb +190 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotations_screen_component.rb +266 -0
- data/lib/shoko/adapters/output/ui/components/screens/base_screen_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/browse_screen_component.rb +319 -0
- data/lib/shoko/adapters/output/ui/components/screens/download_books_screen_component.rb +340 -0
- data/lib/shoko/adapters/output/ui/components/screens/library_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/loading_overlay_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/menu_screen_component.rb +107 -0
- data/lib/shoko/adapters/output/ui/components/screens/settings_screen_component.rb +238 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/annotations_tab_renderer.rb +159 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/bookmarks_tab_renderer.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/tab_header_component.rb +157 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_renderer.rb +111 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_support.rb +1606 -0
- data/lib/shoko/adapters/output/ui/components/sidebar_panel_component.rb +217 -0
- data/lib/shoko/adapters/output/ui/components/surface.rb +88 -0
- data/lib/shoko/adapters/output/ui/components/tooltip_overlay_component.rb +224 -0
- data/lib/shoko/adapters/output/ui/components/ui/box_drawer.rb +32 -0
- data/lib/shoko/adapters/output/ui/components/ui/list_helpers.rb +33 -0
- data/lib/shoko/adapters/output/ui/components/ui/overlay_layout.rb +79 -0
- data/lib/shoko/adapters/output/ui/components/ui/text_utils.rb +46 -0
- data/lib/shoko/adapters/output/ui/constants/highlighting.rb +21 -0
- data/lib/shoko/adapters/output/ui/constants/messages.rb +12 -0
- data/lib/shoko/adapters/output/ui/constants/themes.rb +79 -0
- data/lib/shoko/adapters/output/ui/constants/ui_constants.rb +85 -0
- data/lib/shoko/adapters/output/ui/rendering/frame_coordinator.rb +42 -0
- data/lib/shoko/adapters/output/ui/rendering/reader_render_coordinator.rb +169 -0
- data/lib/shoko/adapters/output/ui/rendering/render_pipeline.rb +55 -0
- data/lib/shoko/adapters/storage/atomic_file_writer.rb +43 -0
- data/lib/shoko/adapters/storage/background_worker.rb +66 -0
- data/lib/shoko/adapters/storage/book_cache_pipeline.rb +653 -0
- data/lib/shoko/adapters/storage/cache/epub/memory_cache.rb +99 -0
- data/lib/shoko/adapters/storage/cache/epub/persistence.rb +131 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/deserialize.rb +225 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/helpers.rb +63 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/serialize.rb +83 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer.rb +5 -0
- data/lib/shoko/adapters/storage/cache/epub/source_reference.rb +58 -0
- data/lib/shoko/adapters/storage/cache_paths.rb +21 -0
- data/lib/shoko/adapters/storage/cache_pointer_manager.rb +60 -0
- data/lib/shoko/adapters/storage/config_paths.rb +30 -0
- data/lib/shoko/adapters/storage/epub_cache.rb +195 -0
- data/lib/shoko/adapters/storage/file_writer_service.rb +47 -0
- data/lib/shoko/adapters/storage/json_cache_store/chapters.rb +141 -0
- data/lib/shoko/adapters/storage/json_cache_store/layouts.rb +67 -0
- data/lib/shoko/adapters/storage/json_cache_store/manifest.rb +42 -0
- data/lib/shoko/adapters/storage/json_cache_store/payload_helpers.rb +113 -0
- data/lib/shoko/adapters/storage/json_cache_store/resources.rb +84 -0
- data/lib/shoko/adapters/storage/json_cache_store.rb +167 -0
- data/lib/shoko/adapters/storage/lazy_file_string.rb +65 -0
- data/lib/shoko/adapters/storage/pagination_cache.rb +127 -0
- data/lib/shoko/adapters/storage/recent_files.rb +78 -0
- data/lib/shoko/adapters/storage/repositories/annotation_repository.rb +182 -0
- data/lib/shoko/adapters/storage/repositories/base_repository.rb +81 -0
- data/lib/shoko/adapters/storage/repositories/bookmark_repository.rb +132 -0
- data/lib/shoko/adapters/storage/repositories/cached_library_repository.rb +129 -0
- data/lib/shoko/adapters/storage/repositories/config_repository.rb +262 -0
- data/lib/shoko/adapters/storage/repositories/progress_repository.rb +166 -0
- data/lib/shoko/adapters/storage/repositories/storage/annotation_file_store.rb +128 -0
- data/lib/shoko/adapters/storage/repositories/storage/bookmark_file_store.rb +109 -0
- data/lib/shoko/adapters/storage/repositories/storage/file_store_utils.rb +20 -0
- data/lib/shoko/adapters/storage/repositories/storage/progress_file_store.rb +59 -0
- data/lib/shoko/application/annotation_editor_overlay_session.rb +138 -0
- data/lib/shoko/application/cli.rb +134 -0
- data/lib/shoko/application/controllers/menu/input_controller.rb +189 -0
- data/lib/shoko/application/controllers/menu/state_controller.rb +642 -0
- data/lib/shoko/application/controllers/menu_controller.rb +469 -0
- data/lib/shoko/application/controllers/mouseable_reader.rb +377 -0
- data/lib/shoko/application/controllers/reader_controller.rb +449 -0
- data/lib/shoko/application/controllers/state_controller.rb +410 -0
- data/lib/shoko/application/controllers/ui_controller.rb +782 -0
- data/lib/shoko/application/dependency_container.rb +301 -0
- data/lib/shoko/application/infrastructure/event_bus.rb +80 -0
- data/lib/shoko/application/infrastructure/observer_state_store.rb +136 -0
- data/lib/shoko/application/infrastructure/state_store.rb +413 -0
- data/lib/shoko/application/main_menu/menu_progress_presenter.rb +83 -0
- data/lib/shoko/application/pending_jump_handler.rb +122 -0
- data/lib/shoko/application/reader_lifecycle.rb +65 -0
- data/lib/shoko/application/reader_startup_orchestrator.rb +113 -0
- data/lib/shoko/application/selectors/config_selectors.rb +62 -0
- data/lib/shoko/application/selectors/menu_selectors.rb +62 -0
- data/lib/shoko/application/selectors/reader_selectors.rb +186 -0
- data/lib/shoko/application/state/actions/base_action.rb +24 -0
- data/lib/shoko/application/state/actions/quit_to_menu_action.rb +16 -0
- data/lib/shoko/application/state/actions/switch_reader_mode_action.rb +22 -0
- data/lib/shoko/application/state/actions/toggle_view_mode_action.rb +31 -0
- data/lib/shoko/application/state/actions/update_annotation_editor_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_annotations_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_annotations_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_bookmarks_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_chapter_action.rb +24 -0
- data/lib/shoko/application/state/actions/update_config_action.rb +22 -0
- data/lib/shoko/application/state/actions/update_field_helpers.rb +26 -0
- data/lib/shoko/application/state/actions/update_menu_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_message_action.rb +35 -0
- data/lib/shoko/application/state/actions/update_page_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_pagination_state_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_popup_menu_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_reader_meta_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_reader_mode_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_rendered_lines_action.rb +40 -0
- data/lib/shoko/application/state/actions/update_selection_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_selections_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_sidebar_action.rb +34 -0
- data/lib/shoko/application/state/actions/update_ui_loading_action.rb +23 -0
- data/lib/shoko/application/ui/reader_view_model_builder.rb +74 -0
- data/lib/shoko/application/ui/view_models/reader_view_model.rb +177 -0
- data/lib/shoko/application/unified_application.rb +48 -0
- data/lib/shoko/application/use_cases/catalog_service.rb +117 -0
- data/lib/shoko/application/use_cases/commands/annotation_editor_commands.rb +105 -0
- data/lib/shoko/application/use_cases/commands/application_commands.rb +208 -0
- data/lib/shoko/application/use_cases/commands/base_command.rb +166 -0
- data/lib/shoko/application/use_cases/commands/bookmark_commands.rb +114 -0
- data/lib/shoko/application/use_cases/commands/conditional_navigation_commands.rb +57 -0
- data/lib/shoko/application/use_cases/commands/menu_commands.rb +170 -0
- data/lib/shoko/application/use_cases/commands/navigation_commands.rb +183 -0
- data/lib/shoko/application/use_cases/commands/reader_commands.rb +46 -0
- data/lib/shoko/application/use_cases/commands/sidebar_commands.rb +55 -0
- data/lib/shoko/application/use_cases/settings_service.rb +123 -0
- data/lib/shoko/core/events/annotation_events.rb +94 -0
- data/lib/shoko/core/events/base_domain_event.rb +169 -0
- data/lib/shoko/core/events/bookmark_events.rb +41 -0
- data/lib/shoko/core/events/domain_event_bus.rb +163 -0
- data/lib/shoko/core/events/progress_events.rb +108 -0
- data/lib/shoko/core/models/bookmark.rb +36 -0
- data/lib/shoko/core/models/bookmark_data.rb +10 -0
- data/lib/shoko/core/models/chapter.rb +25 -0
- data/lib/shoko/core/models/content_block.rb +44 -0
- data/lib/shoko/core/models/reader_settings.rb +20 -0
- data/lib/shoko/core/models/selection_anchor.rb +73 -0
- data/lib/shoko/core/models/toc_entry.rb +14 -0
- data/lib/shoko/core/ports/annotation_repository.rb +0 -0
- data/lib/shoko/core/ports/book_repository.rb +0 -0
- data/lib/shoko/core/ports/book_source.rb +0 -0
- data/lib/shoko/core/ports/bookmark_repository.rb +0 -0
- data/lib/shoko/core/ports/cache.rb +0 -0
- data/lib/shoko/core/ports/input_handler.rb +0 -0
- data/lib/shoko/core/ports/renderer.rb +0 -0
- data/lib/shoko/core/ports/storage.rb +0 -0
- data/lib/shoko/core/services/annotation_service.rb +102 -0
- data/lib/shoko/core/services/base_service.rb +60 -0
- data/lib/shoko/core/services/bookmark_service.rb +267 -0
- data/lib/shoko/core/services/coordinate_service.rb +265 -0
- data/lib/shoko/core/services/layout_service.rb +95 -0
- data/lib/shoko/core/services/navigation/absolute_change_applier.rb +96 -0
- data/lib/shoko/core/services/navigation/absolute_layout.rb +101 -0
- data/lib/shoko/core/services/navigation/absolute_strategy.rb +179 -0
- data/lib/shoko/core/services/navigation/context_builder.rb +52 -0
- data/lib/shoko/core/services/navigation/context_helpers.rb +63 -0
- data/lib/shoko/core/services/navigation/dynamic_change_applier.rb +50 -0
- data/lib/shoko/core/services/navigation/dynamic_strategy.rb +51 -0
- data/lib/shoko/core/services/navigation/image_offset_snapper.rb +150 -0
- data/lib/shoko/core/services/navigation/nav_context.rb +27 -0
- data/lib/shoko/core/services/navigation/state_updater.rb +29 -0
- data/lib/shoko/core/services/navigation/strategy_factory.rb +20 -0
- data/lib/shoko/core/services/navigation_service.rb +150 -0
- data/lib/shoko/core/services/page_calculator_service.rb +242 -0
- data/lib/shoko/core/services/pagination/internal/absolute_page_map_builder.rb +28 -0
- data/lib/shoko/core/services/pagination/internal/chapter_cache.rb +60 -0
- data/lib/shoko/core/services/pagination/internal/dynamic_page_map_builder.rb +157 -0
- data/lib/shoko/core/services/pagination/internal/layout_metrics_calculator.rb +73 -0
- data/lib/shoko/core/services/pagination/internal/page_hydrator.rb +145 -0
- data/lib/shoko/core/services/pagination/internal/pagination_workflow.rb +152 -0
- data/lib/shoko/core/services/pagination/page_info_calculator.rb +247 -0
- data/lib/shoko/core/services/pagination/pagination_cache_preloader.rb +173 -0
- data/lib/shoko/core/services/pagination/pagination_coordinator.rb +202 -0
- data/lib/shoko/core/services/pagination/pagination_orchestrator.rb +291 -0
- data/lib/shoko/core/services/pagination.rb +10 -0
- data/lib/shoko/core/services/progress_helper.rb +22 -0
- data/lib/shoko/core/services/selection_service.rb +126 -0
- data/lib/shoko/core/validator.rb +76 -0
- data/lib/shoko/shared/errors.rb +97 -0
- data/lib/shoko/shared/version.rb +5 -0
- data/lib/shoko/test_support/terminal_double.rb +175 -0
- data/lib/shoko/test_support/test_mode.rb +78 -0
- data/lib/shoko.rb +279 -0
- data/lib/zip.rb +732 -0
- data/zip.rb +5 -0
- metadata +370 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../application/use_cases/commands/application_commands.rb'
|
|
4
|
+
require_relative '../../application/use_cases/commands/navigation_commands.rb'
|
|
5
|
+
require_relative '../../application/use_cases/commands/sidebar_commands.rb'
|
|
6
|
+
require_relative '../../application/use_cases/commands/conditional_navigation_commands.rb'
|
|
7
|
+
require_relative '../../application/use_cases/commands/menu_commands.rb'
|
|
8
|
+
require_relative '../../application/use_cases/commands/bookmark_commands.rb'
|
|
9
|
+
require_relative '../../application/use_cases/commands/reader_commands.rb'
|
|
10
|
+
|
|
11
|
+
module Shoko
|
|
12
|
+
module Adapters::Input
|
|
13
|
+
# Bridge to create Application commands for Input system usage.
|
|
14
|
+
# Provides a clean interface for the Input system to use Application commands
|
|
15
|
+
# while maintaining backward compatibility during the migration.
|
|
16
|
+
class CommandBridge
|
|
17
|
+
class << self
|
|
18
|
+
# Create navigation commands for reader movement
|
|
19
|
+
#
|
|
20
|
+
# @param action [Symbol] Navigation action (:next_page, :prev_page, etc.)
|
|
21
|
+
# @return [Application::Commands::NavigationCommand]
|
|
22
|
+
def navigation_command(action)
|
|
23
|
+
Application::Commands::NavigationCommand.new(action)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Create application lifecycle commands
|
|
27
|
+
#
|
|
28
|
+
# @param action [Symbol] Application action (:quit, :switch_mode, etc.)
|
|
29
|
+
# @return [Application::Commands::ApplicationCommand]
|
|
30
|
+
def application_command(action)
|
|
31
|
+
Application::Commands::ApplicationCommand.new(action)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Create bookmark operation commands
|
|
35
|
+
#
|
|
36
|
+
# @param action [Symbol] Bookmark action (:add, :remove, :navigate, etc.)
|
|
37
|
+
# @return [Application::Commands::BookmarkCommand]
|
|
38
|
+
def bookmark_command(action)
|
|
39
|
+
Application::Commands::BookmarkCommand.new(action)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Create sidebar navigation commands
|
|
43
|
+
#
|
|
44
|
+
# @param action [Symbol] Sidebar action (:up, :down, :select)
|
|
45
|
+
# @return [Application::Commands::SidebarCommand]
|
|
46
|
+
def sidebar_command(action)
|
|
47
|
+
Application::Commands::SidebarCommand.new(action)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Create conditional navigation commands
|
|
51
|
+
#
|
|
52
|
+
# @param type [Symbol] Type of conditional navigation
|
|
53
|
+
# @return [Application::Commands::ConditionalNavigationCommand]
|
|
54
|
+
def conditional_navigation_command(type)
|
|
55
|
+
case type
|
|
56
|
+
when :up_or_sidebar then Application::Commands::ConditionalNavigationCommand.up_or_sidebar
|
|
57
|
+
when :down_or_sidebar then Application::Commands::ConditionalNavigationCommand.down_or_sidebar
|
|
58
|
+
when :select_or_sidebar then Application::Commands::ConditionalNavigationCommand.select_or_sidebar
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "Unknown conditional navigation type: #{type}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Convert Input system symbols to appropriate Application commands
|
|
65
|
+
# This method helps during migration by automatically routing
|
|
66
|
+
# common input symbols to their Application command equivalents.
|
|
67
|
+
#
|
|
68
|
+
# @param symbol [Symbol] Input symbol
|
|
69
|
+
# @param context [Object] Execution context
|
|
70
|
+
# @return [Application::Commands::BaseCommand, nil] Command or nil if no mapping
|
|
71
|
+
def symbol_to_command(symbol, _context = nil)
|
|
72
|
+
case symbol
|
|
73
|
+
# Navigation commands - now use Application commands
|
|
74
|
+
when :next_page then navigation_command(:next_page)
|
|
75
|
+
when :prev_page then navigation_command(:prev_page)
|
|
76
|
+
when :next_chapter then navigation_command(:next_chapter)
|
|
77
|
+
when :prev_chapter then navigation_command(:prev_chapter)
|
|
78
|
+
when :scroll_up then Application::Commands::NavigationCommandFactory.scroll_up
|
|
79
|
+
when :scroll_down then Application::Commands::NavigationCommandFactory.scroll_down
|
|
80
|
+
when :go_to_start then navigation_command(:go_to_start)
|
|
81
|
+
when :go_to_end then navigation_command(:go_to_end)
|
|
82
|
+
# Application commands
|
|
83
|
+
when :show_help then application_command(:show_help)
|
|
84
|
+
when :open_toc then application_command(:show_toc)
|
|
85
|
+
when :open_bookmarks then application_command(:show_bookmarks)
|
|
86
|
+
when :open_annotations then application_command(:show_annotations)
|
|
87
|
+
when :quit_to_menu then application_command(:quit_to_menu)
|
|
88
|
+
when :add_bookmark then Application::Commands::BookmarkCommandFactory.add_bookmark
|
|
89
|
+
# Conditional navigation commands
|
|
90
|
+
when :conditional_up then conditional_navigation_command(:up_or_sidebar)
|
|
91
|
+
when :conditional_down then conditional_navigation_command(:down_or_sidebar)
|
|
92
|
+
when :conditional_select then conditional_navigation_command(:select_or_sidebar)
|
|
93
|
+
# Direct sidebar commands
|
|
94
|
+
when :sidebar_up then sidebar_command(:up)
|
|
95
|
+
when :sidebar_down then sidebar_command(:down)
|
|
96
|
+
when :sidebar_select then sidebar_command(:select)
|
|
97
|
+
# Menu commands
|
|
98
|
+
when :menu_up then Application::Commands::MenuCommand.new(:menu_up)
|
|
99
|
+
when :menu_down then Application::Commands::MenuCommand.new(:menu_down)
|
|
100
|
+
when :menu_select then Application::Commands::MenuCommand.new(:menu_select)
|
|
101
|
+
when :menu_quit then Application::Commands::MenuCommand.new(:menu_quit)
|
|
102
|
+
when :back_to_menu then Application::Commands::MenuCommand.new(:back_to_menu)
|
|
103
|
+
when :browse_up then Application::Commands::MenuCommand.new(:browse_up)
|
|
104
|
+
when :browse_down then Application::Commands::MenuCommand.new(:browse_down)
|
|
105
|
+
when :browse_select then Application::Commands::MenuCommand.new(:browse_select)
|
|
106
|
+
when :library_up then Application::Commands::MenuCommand.new(:library_up)
|
|
107
|
+
when :library_down then Application::Commands::MenuCommand.new(:library_down)
|
|
108
|
+
when :library_select then Application::Commands::MenuCommand.new(:library_select)
|
|
109
|
+
when :settings_up then Application::Commands::MenuCommand.new(:settings_up)
|
|
110
|
+
when :settings_down then Application::Commands::MenuCommand.new(:settings_down)
|
|
111
|
+
when :settings_select then Application::Commands::MenuCommand.new(:settings_select)
|
|
112
|
+
# recent_* commands removed
|
|
113
|
+
when :start_search then Application::Commands::MenuCommand.new(:start_search)
|
|
114
|
+
when :exit_search then Application::Commands::MenuCommand.new(:exit_search)
|
|
115
|
+
# Annotations-related menu commands
|
|
116
|
+
when :annotations_up then Application::Commands::MenuCommand.new(:annotations_up)
|
|
117
|
+
when :annotations_down then Application::Commands::MenuCommand.new(:annotations_down)
|
|
118
|
+
when :annotations_select then Application::Commands::MenuCommand.new(:annotations_select)
|
|
119
|
+
when :annotations_edit then Application::Commands::MenuCommand.new(:annotations_edit)
|
|
120
|
+
when :annotations_delete then Application::Commands::MenuCommand.new(:annotations_delete)
|
|
121
|
+
when :annotation_detail_open then Application::Commands::MenuCommand.new(:annotation_detail_open)
|
|
122
|
+
when :annotation_detail_edit then Application::Commands::MenuCommand.new(:annotation_detail_edit)
|
|
123
|
+
when :annotation_detail_delete then Application::Commands::MenuCommand.new(:annotation_detail_delete)
|
|
124
|
+
when :annotation_detail_back then Application::Commands::MenuCommand.new(:annotation_detail_back)
|
|
125
|
+
# Settings actions
|
|
126
|
+
when :toggle_view_mode then Application::Commands::MenuCommand.new(:toggle_view_mode)
|
|
127
|
+
when :cycle_line_spacing then Application::Commands::MenuCommand.new(:cycle_line_spacing)
|
|
128
|
+
when :toggle_page_numbers then Application::Commands::MenuCommand.new(:toggle_page_numbers)
|
|
129
|
+
when :toggle_page_numbering_mode then Application::Commands::MenuCommand.new(:toggle_page_numbering_mode)
|
|
130
|
+
when :toggle_highlight_quotes then Application::Commands::MenuCommand.new(:toggle_highlight_quotes)
|
|
131
|
+
when :toggle_kitty_images then Application::Commands::MenuCommand.new(:toggle_kitty_images)
|
|
132
|
+
when :wipe_cache then Application::Commands::MenuCommand.new(:wipe_cache)
|
|
133
|
+
# Reader mode transitions
|
|
134
|
+
when :exit_help then Application::Commands::ReaderModeCommand.new(:exit_help)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Check if a symbol can be converted to an Application command
|
|
139
|
+
#
|
|
140
|
+
# @param symbol [Symbol] Input symbol to check
|
|
141
|
+
# @return [Boolean] True if symbol has Application command equivalent
|
|
142
|
+
def command?(symbol)
|
|
143
|
+
!symbol_to_command(symbol).nil?
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'key_definitions'
|
|
4
|
+
require_relative '../output/terminal/terminal_sanitizer.rb'
|
|
5
|
+
|
|
6
|
+
module Shoko
|
|
7
|
+
module Adapters::Input
|
|
8
|
+
# Factory for creating common input command patterns.
|
|
9
|
+
module CommandFactory
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def navigation_commands(_context, selection_field, max_value_proc)
|
|
13
|
+
selection_field = selection_field.to_sym
|
|
14
|
+
action_type = case selection_field
|
|
15
|
+
when :selected, :browse_selected
|
|
16
|
+
:menu
|
|
17
|
+
when :sidebar_toc_selected, :sidebar_bookmarks_selected, :sidebar_annotations_selected
|
|
18
|
+
:sidebar
|
|
19
|
+
end
|
|
20
|
+
return {} unless action_type
|
|
21
|
+
|
|
22
|
+
commands = {}
|
|
23
|
+
register_navigation(commands, :up, -1, selection_field, action_type, max_value_proc)
|
|
24
|
+
register_navigation(commands, :down, +1, selection_field, action_type, max_value_proc)
|
|
25
|
+
commands
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def exit_commands(exit_action)
|
|
29
|
+
commands = {}
|
|
30
|
+
KeyDefinitions::ACTIONS[:cancel].each { |key| commands[key] = exit_action }
|
|
31
|
+
commands
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def menu_selection_commands
|
|
35
|
+
commands = {}
|
|
36
|
+
KeyDefinitions::ACTIONS[:confirm].each do |key|
|
|
37
|
+
commands[key] = lambda do |ctx, _|
|
|
38
|
+
ctx.handle_menu_selection
|
|
39
|
+
:handled
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
commands
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reader_navigation_commands
|
|
46
|
+
reader = KeyDefinitions::READER
|
|
47
|
+
commands = {}
|
|
48
|
+
map_keys!(commands, reader[:next_page], :next_page)
|
|
49
|
+
map_keys!(commands, reader[:prev_page], :prev_page)
|
|
50
|
+
map_keys!(commands, reader[:scroll_down], :scroll_down)
|
|
51
|
+
map_keys!(commands, reader[:scroll_up], :scroll_up)
|
|
52
|
+
map_keys!(commands, reader[:next_chapter], :next_chapter)
|
|
53
|
+
map_keys!(commands, reader[:prev_chapter], :prev_chapter)
|
|
54
|
+
map_keys!(commands, reader[:go_to_start], :go_to_start)
|
|
55
|
+
map_keys!(commands, reader[:go_to_end], :go_to_end)
|
|
56
|
+
commands
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def reader_control_commands
|
|
60
|
+
reader = KeyDefinitions::READER
|
|
61
|
+
actions = KeyDefinitions::ACTIONS
|
|
62
|
+
commands = {}
|
|
63
|
+
|
|
64
|
+
map_keys!(commands, reader[:toggle_view], :toggle_view_mode)
|
|
65
|
+
map_keys!(commands, reader[:toggle_page_mode], :toggle_page_numbering_mode)
|
|
66
|
+
map_keys!(commands, reader[:increase_spacing], :increase_line_spacing)
|
|
67
|
+
map_keys!(commands, reader[:decrease_spacing], :decrease_line_spacing)
|
|
68
|
+
map_keys!(commands, reader[:show_toc], :open_toc)
|
|
69
|
+
map_keys!(commands, reader[:add_bookmark], :add_bookmark)
|
|
70
|
+
map_keys!(commands, reader[:show_bookmarks], :open_bookmarks)
|
|
71
|
+
map_keys!(commands, reader[:show_help], :show_help)
|
|
72
|
+
|
|
73
|
+
map_keys!(commands, reader[:show_annotations], :open_annotations) if reader.key?(:show_annotations)
|
|
74
|
+
if reader.key?(:show_annotations_tab)
|
|
75
|
+
map_keys!(commands, reader[:show_annotations_tab], :open_annotations_tab)
|
|
76
|
+
end
|
|
77
|
+
map_keys!(commands, reader[:rebuild_pagination], :rebuild_pagination) if reader.key?(:rebuild_pagination)
|
|
78
|
+
if reader.key?(:invalidate_pagination)
|
|
79
|
+
map_keys!(commands, reader[:invalidate_pagination], :invalidate_pagination_cache)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
map_keys!(commands, actions[:quit], :quit_to_menu)
|
|
83
|
+
map_keys!(commands, actions[:force_quit], :quit_application)
|
|
84
|
+
commands
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def text_input_commands(input_field, context_method = nil, cursor_field: nil)
|
|
88
|
+
input_field = input_field.to_sym
|
|
89
|
+
input_path = input_path_for(input_field)
|
|
90
|
+
|
|
91
|
+
commands = {}
|
|
92
|
+
KeyDefinitions::ACTIONS[:backspace].each do |key|
|
|
93
|
+
commands[key] = lambda do |ctx, _|
|
|
94
|
+
handle_backspace(ctx, key, input_field, input_path, context_method, cursor_field)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
KeyDefinitions::ACTIONS[:delete].each do |key|
|
|
98
|
+
commands[key] = lambda do |ctx, _|
|
|
99
|
+
current, cursor = current_and_cursor(ctx, input_path, cursor_field)
|
|
100
|
+
new_value = splice_delete(current, cursor)
|
|
101
|
+
apply_value(ctx, input_field, new_value, cursor_field, cursor)
|
|
102
|
+
:handled
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
commands[:__default__] = lambda do |ctx, key|
|
|
107
|
+
char = key.to_s
|
|
108
|
+
if Shoko::Adapters::Output::Terminal::TerminalSanitizer.printable_char?(char)
|
|
109
|
+
handle_character(ctx, key, input_field, input_path, context_method, cursor_field)
|
|
110
|
+
else
|
|
111
|
+
:pass
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
commands
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def handle_backspace(ctx, key, input_field, input_path, context_method, cursor_field)
|
|
119
|
+
if context_method
|
|
120
|
+
ctx.send(context_method, key)
|
|
121
|
+
elsif input_path
|
|
122
|
+
current, cursor_pos = current_and_cursor(ctx, input_path, cursor_field)
|
|
123
|
+
new_value, new_cursor = splice_backspace(current, cursor_pos)
|
|
124
|
+
apply_value(ctx, input_field, new_value, cursor_field, new_cursor)
|
|
125
|
+
end
|
|
126
|
+
:handled
|
|
127
|
+
end
|
|
128
|
+
private_class_method :handle_backspace
|
|
129
|
+
|
|
130
|
+
def handle_character(ctx, key, input_field, input_path, context_method, cursor_field)
|
|
131
|
+
if context_method
|
|
132
|
+
ctx.send(context_method, key)
|
|
133
|
+
elsif input_path
|
|
134
|
+
current, cursor_pos = current_and_cursor(ctx, input_path, cursor_field)
|
|
135
|
+
new_value, new_cursor = splice_insert(current, cursor_pos, key.to_s)
|
|
136
|
+
apply_value(ctx, input_field, new_value, cursor_field, new_cursor)
|
|
137
|
+
end
|
|
138
|
+
:handled
|
|
139
|
+
end
|
|
140
|
+
private_class_method :handle_character
|
|
141
|
+
|
|
142
|
+
def input_path_for(input_field)
|
|
143
|
+
{
|
|
144
|
+
search_query: %i[menu search_query],
|
|
145
|
+
download_query: %i[menu download_query],
|
|
146
|
+
}[input_field]
|
|
147
|
+
end
|
|
148
|
+
private_class_method :input_path_for
|
|
149
|
+
|
|
150
|
+
def current_and_cursor(ctx, input_path, cursor_field)
|
|
151
|
+
return ['', 0] unless input_path
|
|
152
|
+
|
|
153
|
+
current = current_value(ctx, input_path)
|
|
154
|
+
cursor_pos = determine_cursor(ctx, cursor_field, current)
|
|
155
|
+
[current, cursor_pos]
|
|
156
|
+
end
|
|
157
|
+
private_class_method :current_and_cursor
|
|
158
|
+
|
|
159
|
+
def determine_cursor(ctx, cursor_field, current)
|
|
160
|
+
if cursor_field
|
|
161
|
+
(ctx.state.get([:menu, cursor_field]) || current.length).to_i
|
|
162
|
+
else
|
|
163
|
+
current.length
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
private_class_method :determine_cursor
|
|
167
|
+
|
|
168
|
+
def apply_value(ctx, input_field, new_value, cursor_field, new_cursor)
|
|
169
|
+
if cursor_field && !new_cursor.nil?
|
|
170
|
+
dispatch_menu(ctx, input_field => new_value, cursor_field => new_cursor)
|
|
171
|
+
else
|
|
172
|
+
dispatch_menu(ctx, input_field => new_value)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
private_class_method :apply_value
|
|
176
|
+
|
|
177
|
+
def register_navigation(commands, direction, step, selection_field, action_type, max_value_proc)
|
|
178
|
+
handler = navigation_handler(step, selection_field, action_type, max_value_proc)
|
|
179
|
+
Array(KeyDefinitions::NAVIGATION[direction]).each { |key| commands[key] = handler }
|
|
180
|
+
end
|
|
181
|
+
private_class_method :register_navigation
|
|
182
|
+
|
|
183
|
+
def navigation_handler(step, selection_field, action_type, max_value_proc)
|
|
184
|
+
lambda do |ctx, _|
|
|
185
|
+
current = value_at(ctx, action_type == :menu ? :menu : :reader, selection_field)
|
|
186
|
+
target = if step.negative?
|
|
187
|
+
[current + step, 0].max
|
|
188
|
+
else
|
|
189
|
+
max_val = max_value_proc.call(ctx)
|
|
190
|
+
(current + step).clamp(0, max_val)
|
|
191
|
+
end
|
|
192
|
+
dispatch_for(ctx, action_type, selection_field, target)
|
|
193
|
+
:handled
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
private_class_method :navigation_handler
|
|
197
|
+
|
|
198
|
+
def map_keys!(commands, keys, action)
|
|
199
|
+
Array(keys).each { |key| commands[key] = action }
|
|
200
|
+
commands
|
|
201
|
+
end
|
|
202
|
+
private_class_method :map_keys!
|
|
203
|
+
|
|
204
|
+
def dispatch_for(ctx, action_type, field, value)
|
|
205
|
+
case action_type
|
|
206
|
+
when :menu
|
|
207
|
+
dispatch_menu(ctx, field => value)
|
|
208
|
+
when :sidebar
|
|
209
|
+
ctx.state.dispatch(Shoko::Application::Actions::UpdateSidebarAction.new(field => value))
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
private_class_method :dispatch_for
|
|
213
|
+
|
|
214
|
+
def dispatch_menu(ctx, hash)
|
|
215
|
+
ctx.state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(hash))
|
|
216
|
+
end
|
|
217
|
+
private_class_method :dispatch_menu
|
|
218
|
+
|
|
219
|
+
def value_at(ctx, base, field)
|
|
220
|
+
ctx.state.get([base, field]) || 0
|
|
221
|
+
end
|
|
222
|
+
private_class_method :value_at
|
|
223
|
+
|
|
224
|
+
def current_value(ctx, input_path)
|
|
225
|
+
(ctx.state.get(input_path) || '').to_s
|
|
226
|
+
end
|
|
227
|
+
private_class_method :current_value
|
|
228
|
+
|
|
229
|
+
def splice_backspace(current, cursor)
|
|
230
|
+
return [current, cursor] unless cursor.positive?
|
|
231
|
+
|
|
232
|
+
before = current[0, cursor - 1] || ''
|
|
233
|
+
after = current[cursor..] || ''
|
|
234
|
+
[before + after, cursor - 1]
|
|
235
|
+
end
|
|
236
|
+
private_class_method :splice_backspace
|
|
237
|
+
|
|
238
|
+
def splice_insert(current, cursor, char)
|
|
239
|
+
before = current[0, cursor] || ''
|
|
240
|
+
after = current[cursor..] || ''
|
|
241
|
+
[before + char + after, cursor + 1]
|
|
242
|
+
end
|
|
243
|
+
private_class_method :splice_insert
|
|
244
|
+
|
|
245
|
+
def splice_delete(current, cursor)
|
|
246
|
+
return current unless cursor < current.length
|
|
247
|
+
|
|
248
|
+
before = current[0, cursor] || ''
|
|
249
|
+
after = current[(cursor + 1)..] || ''
|
|
250
|
+
before + after
|
|
251
|
+
end
|
|
252
|
+
private_class_method :splice_delete
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'command_bridge'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Adapters::Input
|
|
7
|
+
# Generic command execution helpers used by the input system.
|
|
8
|
+
module Commands
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Execute a command against the given context.
|
|
12
|
+
# Supports multiple command types:
|
|
13
|
+
# - Symbol: calls context.public_send(symbol, key) if arity allows, else without args
|
|
14
|
+
# - Proc/Lambda: calls with (context, key) if arity 2, else with (key)
|
|
15
|
+
# - Array [Symbol, *args]: calls method with args splat
|
|
16
|
+
# - BaseCommand instance: calls command.execute(context, key)
|
|
17
|
+
def execute(command, context, key = nil)
|
|
18
|
+
case command
|
|
19
|
+
when Shoko::Application::Commands::BaseCommand
|
|
20
|
+
# Support application commands with parameter conversion
|
|
21
|
+
params = { key: key, triggered_by: :input }
|
|
22
|
+
command.execute(context, params)
|
|
23
|
+
when Symbol
|
|
24
|
+
# Route all symbols through the command bridge
|
|
25
|
+
if CommandBridge.command?(command)
|
|
26
|
+
mapped_command = CommandBridge.symbol_to_command(command, context)
|
|
27
|
+
return :pass unless mapped_command
|
|
28
|
+
|
|
29
|
+
execute(mapped_command, context, key)
|
|
30
|
+
else
|
|
31
|
+
execute_symbol(command, context, key)
|
|
32
|
+
end
|
|
33
|
+
when Proc
|
|
34
|
+
ar = command.arity
|
|
35
|
+
ar_abs = ar.abs
|
|
36
|
+
return command.call(context, key) if ar_abs >= 2
|
|
37
|
+
return command.call(key) if ar_abs >= 1
|
|
38
|
+
|
|
39
|
+
command.call
|
|
40
|
+
when Array
|
|
41
|
+
sym, *args = command
|
|
42
|
+
return :pass unless sym.is_a?(Symbol) && context.respond_to?(sym)
|
|
43
|
+
|
|
44
|
+
context.public_send(sym, *args)
|
|
45
|
+
else
|
|
46
|
+
:pass
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def execute_symbol(command, context, key)
|
|
51
|
+
return :pass unless context.respond_to?(command)
|
|
52
|
+
|
|
53
|
+
method = context.method(command)
|
|
54
|
+
return context.public_send(command) if method.arity.zero?
|
|
55
|
+
|
|
56
|
+
context.public_send(command, key)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'commands'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Adapters::Input
|
|
7
|
+
# Dispatches keys through a stack of active input modes.
|
|
8
|
+
class Dispatcher
|
|
9
|
+
def initialize(context)
|
|
10
|
+
@context = context
|
|
11
|
+
@command_map = {}
|
|
12
|
+
@mode_stack = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def handle_key(key)
|
|
16
|
+
return if key.nil?
|
|
17
|
+
|
|
18
|
+
@mode_stack.reverse_each do |mode|
|
|
19
|
+
bindings = @command_map[mode] || {}
|
|
20
|
+
cmd = bindings[key] || bindings[:__default__]
|
|
21
|
+
next unless cmd
|
|
22
|
+
|
|
23
|
+
result = Commands.execute(cmd, @context, key)
|
|
24
|
+
return result if result == :handled
|
|
25
|
+
end
|
|
26
|
+
:pass
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def push_mode(mode, bindings = {})
|
|
30
|
+
@mode_stack << mode
|
|
31
|
+
@command_map[mode] = bindings if bindings && !bindings.empty?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pop_mode
|
|
35
|
+
@mode_stack.pop
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def remove_mode(mode)
|
|
39
|
+
@mode_stack.delete(mode)
|
|
40
|
+
@command_map.delete(mode)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def clear
|
|
44
|
+
@mode_stack.clear
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Register bindings for a mode without activating it
|
|
48
|
+
def register_mode(mode, bindings)
|
|
49
|
+
@command_map[mode] = bindings
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Activate a single mode (clears the stack, then pushes)
|
|
53
|
+
def activate(mode)
|
|
54
|
+
clear
|
|
55
|
+
push_mode(mode, @command_map[mode] || {})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Activate a full stack of modes in order; last wins on dispatch
|
|
59
|
+
def activate_stack(modes)
|
|
60
|
+
clear
|
|
61
|
+
modes.each { |m| push_mode(m, @command_map[m] || {}) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def mode_stack
|
|
65
|
+
@mode_stack.dup
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|