anima-core 0.2.1 → 1.0.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 +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- data/lib/events/subscribers/action_cable_bridge.rb +0 -59
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Core Concepts
|
|
2
|
+
|
|
3
|
+
RatatuiRuby provides immediate-mode terminal rendering with managed lifecycle. This reference covers application initialization, terminal lifecycle, viewport modes, and core objects.
|
|
4
|
+
|
|
5
|
+
## RatatuiRuby.run
|
|
6
|
+
|
|
7
|
+
Entry point handling complete terminal lifecycle with exception safety.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
RatatuiRuby.run do |tui|
|
|
11
|
+
loop do
|
|
12
|
+
tui.draw do |frame|
|
|
13
|
+
frame.render_widget(tui.paragraph(text: "Hello"), frame.area)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
case tui.poll_event
|
|
17
|
+
in {type: :key, code: "q"}
|
|
18
|
+
break
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
# Terminal restored automatically
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Options
|
|
26
|
+
|
|
27
|
+
| Option | Default | Purpose |
|
|
28
|
+
|--------|---------|---------|
|
|
29
|
+
| `focus_events` | `true` | Enable focus change events |
|
|
30
|
+
| `bracketed_paste` | `true` | Enable bracketed paste mode |
|
|
31
|
+
| `viewport` | `nil` | Viewport mode (`nil`/`:fullscreen` or `:inline`) |
|
|
32
|
+
| `height` | `nil` | Lines for inline viewport (required when `viewport: :inline`) |
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
RatatuiRuby.run(viewport: :inline, height: 8) do |tui|
|
|
36
|
+
# 8-line inline viewport
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Manual Lifecycle (Advanced)
|
|
41
|
+
|
|
42
|
+
For fine-grained control, use `init_terminal` and `restore_terminal` with `ensure`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
RatatuiRuby.init_terminal
|
|
46
|
+
begin
|
|
47
|
+
RatatuiRuby.draw do |frame|
|
|
48
|
+
frame.render_widget(widget, frame.area)
|
|
49
|
+
end
|
|
50
|
+
ensure
|
|
51
|
+
RatatuiRuby.restore_terminal # Always executes
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Terminal Lifecycle
|
|
56
|
+
|
|
57
|
+
Three-phase lifecycle prevents terminal corruption.
|
|
58
|
+
|
|
59
|
+
### Phase 1: Setup (`init_terminal`)
|
|
60
|
+
|
|
61
|
+
- Enters alternate screen (fullscreen) or creates fixed region (inline)
|
|
62
|
+
- Enables raw mode for direct key capture
|
|
63
|
+
- Configures focus events and bracketed paste
|
|
64
|
+
- Sets `@tui_session_active = true`
|
|
65
|
+
|
|
66
|
+
### Phase 2: Loop
|
|
67
|
+
|
|
68
|
+
Application code executes with access to TUI instance:
|
|
69
|
+
- `draw` renders UI
|
|
70
|
+
- `poll_event` captures input
|
|
71
|
+
- Application logic manages state
|
|
72
|
+
|
|
73
|
+
### Phase 3: Teardown (`restore_terminal`)
|
|
74
|
+
|
|
75
|
+
Guaranteed cleanup via `ensure` block:
|
|
76
|
+
- Leaves alternate screen
|
|
77
|
+
- Disables raw mode
|
|
78
|
+
- Flushes warnings
|
|
79
|
+
- Sets `@tui_session_active = false`
|
|
80
|
+
|
|
81
|
+
### Signal Handling
|
|
82
|
+
|
|
83
|
+
- Most signals (SIGTERM, SIGINT) trigger stack unwinding, allowing `ensure` execution
|
|
84
|
+
- **SIGKILL cannot be caught** - may leave terminal in raw mode
|
|
85
|
+
- **Ctrl+C in raw mode** captured as key event, not SIGINT - handle explicitly:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
case tui.poll_event
|
|
89
|
+
in {type: :key, code: "c", modifiers: ["ctrl"]}
|
|
90
|
+
break
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Viewport Modes
|
|
95
|
+
|
|
96
|
+
### Fullscreen (Default)
|
|
97
|
+
|
|
98
|
+
Takes over entire terminal, clears on exit.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
RatatuiRuby.run do |tui|
|
|
102
|
+
# Fullscreen is default
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Or explicitly:
|
|
106
|
+
RatatuiRuby.run(viewport: :fullscreen) do |tui|
|
|
107
|
+
# ...
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Characteristics:**
|
|
112
|
+
- Alternate screen buffer for isolated rendering
|
|
113
|
+
- All content cleared on exit
|
|
114
|
+
- No scrollback preservation
|
|
115
|
+
- Full terminal dimensions via `frame.area`
|
|
116
|
+
- Best for complete TUI applications
|
|
117
|
+
|
|
118
|
+
### Inline Mode
|
|
119
|
+
|
|
120
|
+
Fixed-height region preserving scrollback after exit.
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
RatatuiRuby.run(viewport: :inline, height: 8) do |tui|
|
|
124
|
+
# 8-line region
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Characteristics:**
|
|
129
|
+
- Fixed height (required parameter)
|
|
130
|
+
- Content persists in scrollback
|
|
131
|
+
- Supports `insert_before` for logging above viewport
|
|
132
|
+
- Best for status displays, progress indicators, inline widgets
|
|
133
|
+
|
|
134
|
+
**Logging during inline mode:**
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
# Option 1: Pass widget directly
|
|
138
|
+
RatatuiRuby.insert_before(height, widget)
|
|
139
|
+
|
|
140
|
+
# Option 2: Use block to generate widget (block returns widget, doesn't receive frame)
|
|
141
|
+
RatatuiRuby.insert_before(height) do
|
|
142
|
+
tui.paragraph(text: "Log message") # Block returns a widget
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Frame Object
|
|
147
|
+
|
|
148
|
+
Controlled access to terminal buffer during rendering. Valid only within `draw` block.
|
|
149
|
+
|
|
150
|
+
### Methods
|
|
151
|
+
|
|
152
|
+
#### `area()`
|
|
153
|
+
|
|
154
|
+
Returns `Rect` of entire drawable region:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
tui.draw do |frame|
|
|
158
|
+
puts "Size: #{frame.area.width}x#{frame.area.height}"
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `render_widget(widget, area)`
|
|
163
|
+
|
|
164
|
+
Renders stateless widget at specified area:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
tui.draw do |frame|
|
|
168
|
+
para = tui.paragraph(text: "Content")
|
|
169
|
+
frame.render_widget(para, frame.area)
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `render_stateful_widget(widget, area, state)`
|
|
174
|
+
|
|
175
|
+
Renders widget with persistent state (scroll position, selection):
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
@list_state = tui.list_state
|
|
179
|
+
|
|
180
|
+
tui.draw do |frame|
|
|
181
|
+
list = tui.list(items: ["A", "B", "C"])
|
|
182
|
+
frame.render_stateful_widget(list, frame.area, @list_state)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# State reflects current selection/offset
|
|
186
|
+
puts "Selected: #{@list_state.selected}"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
State object is single source of truth, overriding widget configuration.
|
|
190
|
+
|
|
191
|
+
#### `set_cursor_position(x, y)`
|
|
192
|
+
|
|
193
|
+
Positions cursor at zero-indexed coordinates:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
tui.draw do |frame|
|
|
197
|
+
frame.render_widget(tui.paragraph(text: "Name: [alice]"), frame.area)
|
|
198
|
+
frame.set_cursor_position(7, 0) # After "Name: ["
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Thread Safety
|
|
203
|
+
|
|
204
|
+
Frame is not Ractor-shareable. Valid only during draw block execution.
|
|
205
|
+
|
|
206
|
+
### Layout Reuse Pattern
|
|
207
|
+
|
|
208
|
+
Store layout results for hit testing:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
tui.draw do |frame|
|
|
212
|
+
sidebar, main = tui.layout_split(frame.area, ...)
|
|
213
|
+
frame.render_widget(sidebar_widget, sidebar)
|
|
214
|
+
frame.render_widget(main_widget, main)
|
|
215
|
+
@regions = {sidebar:, main:} # Reuse for click detection
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## TUI Factory Object
|
|
220
|
+
|
|
221
|
+
Manages lifecycle and provides concise factory methods.
|
|
222
|
+
|
|
223
|
+
### Access
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
RatatuiRuby.run do |tui|
|
|
227
|
+
# tui is RatatuiRuby::TUI instance
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Factory Comparison
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# Without factories (verbose):
|
|
235
|
+
para = RatatuiRuby::Widgets::Paragraph.new(
|
|
236
|
+
text: "Hello",
|
|
237
|
+
block: RatatuiRuby::Widgets::Block.new(borders: [:all])
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# With factories (concise):
|
|
241
|
+
para = tui.paragraph(text: "Hello", block: tui.block(borders: [:all]))
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Factory Modules
|
|
245
|
+
|
|
246
|
+
| Module | Methods | Purpose |
|
|
247
|
+
|--------|---------|---------|
|
|
248
|
+
| **WidgetFactories** | `paragraph`, `block`, `list`, `table`, `gauge` | UI components |
|
|
249
|
+
| **LayoutFactories** | `rect`, `constraint_*`, `layout`, `layout_split` | Layouts |
|
|
250
|
+
| **StyleFactories** | `style` | Formatting |
|
|
251
|
+
| **TextFactories** | `text_span`, `text_line`, `text_width` | Styled text |
|
|
252
|
+
| **StateFactories** | `list_state`, `table_state`, `scrollbar_state` | Widget state |
|
|
253
|
+
| **CanvasFactories** | `shape_map`, `shape_line`, `shape_point` | Canvas shapes |
|
|
254
|
+
| **BufferFactories** | `cell` | Testing |
|
|
255
|
+
| **Core** | `draw`, `poll_event`, `get_cell_at` | Terminal ops |
|
|
256
|
+
|
|
257
|
+
### DWIM Coercion
|
|
258
|
+
|
|
259
|
+
Factories implement "Do What I Mean" argument coercion with automatic type normalization and validation.
|
|
260
|
+
|
|
261
|
+
## Module Functions
|
|
262
|
+
|
|
263
|
+
### Drawing
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
# Declarative (tree-based)
|
|
267
|
+
RatatuiRuby.draw(widget)
|
|
268
|
+
|
|
269
|
+
# Imperative (block-based)
|
|
270
|
+
RatatuiRuby.draw do |frame|
|
|
271
|
+
frame.render_widget(widget, frame.area)
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Event Polling
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
event = RatatuiRuby.poll_event # ~60 FPS default (16ms)
|
|
279
|
+
event = RatatuiRuby.poll_event(timeout: nil) # Block until event
|
|
280
|
+
event = RatatuiRuby.poll_event(timeout: 0.0) # Non-blocking
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Terminal Inspection
|
|
284
|
+
|
|
285
|
+
| Method | Returns |
|
|
286
|
+
|--------|---------|
|
|
287
|
+
| `get_terminal_size()` | Full terminal dimensions as `Rect` |
|
|
288
|
+
| `get_terminal_area()` | Alias for `get_terminal_size` |
|
|
289
|
+
| `get_viewport_area()` | Current viewport area |
|
|
290
|
+
| `get_viewport_size()` | Alias for `get_viewport_area` |
|
|
291
|
+
| `get_cell_at(x, y)` | `Buffer::Cell` at coordinates (for testing) |
|
|
292
|
+
|
|
293
|
+
## Complete Example
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
RatatuiRuby.run do |tui|
|
|
297
|
+
@list_state = tui.list_state(0)
|
|
298
|
+
items = ["Dashboard", "Settings", "Help", "Quit"]
|
|
299
|
+
|
|
300
|
+
loop do
|
|
301
|
+
tui.draw do |frame|
|
|
302
|
+
sidebar, content = tui.layout_split(
|
|
303
|
+
frame.area,
|
|
304
|
+
direction: :horizontal,
|
|
305
|
+
constraints: [tui.constraint_length(20), tui.constraint_min(0)]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Sidebar with stateful list
|
|
309
|
+
list = tui.list(
|
|
310
|
+
items:,
|
|
311
|
+
block: tui.block(title: "Menu", borders: [:all])
|
|
312
|
+
)
|
|
313
|
+
frame.render_stateful_widget(list, sidebar, @list_state)
|
|
314
|
+
|
|
315
|
+
# Content area
|
|
316
|
+
selected_item = items[@list_state.selected] || "None"
|
|
317
|
+
frame.render_widget(
|
|
318
|
+
tui.paragraph(
|
|
319
|
+
text: "Selected: #{selected_item}",
|
|
320
|
+
block: tui.block(title: "Content", borders: [:all])
|
|
321
|
+
),
|
|
322
|
+
content
|
|
323
|
+
)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
case tui.poll_event
|
|
327
|
+
in {type: :key, code: "q"}
|
|
328
|
+
break
|
|
329
|
+
in {type: :key, code: "j"} | {type: :key, code: "down"}
|
|
330
|
+
@list_state.select_next
|
|
331
|
+
in {type: :key, code: "k"} | {type: :key, code: "up"}
|
|
332
|
+
@list_state.select_previous
|
|
333
|
+
in {type: :key, code: "c", modifiers: ["ctrl"]}
|
|
334
|
+
break
|
|
335
|
+
else
|
|
336
|
+
# Continue
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
```
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# Event Handling
|
|
2
|
+
|
|
3
|
+
RatatuiRuby provides a robust event system for keyboard, mouse, resize, paste, and focus events. This reference covers polling, event types, and handling patterns.
|
|
4
|
+
|
|
5
|
+
## poll_event
|
|
6
|
+
|
|
7
|
+
Primary method for retrieving user input:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
event = RatatuiRuby.poll_event(timeout: 0.016)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Timeout Options
|
|
14
|
+
|
|
15
|
+
| Value | Behavior |
|
|
16
|
+
|-------|----------|
|
|
17
|
+
| `0.016` (default) | ~60 FPS, smooth rendering loops |
|
|
18
|
+
| `nil` | Block until event arrives |
|
|
19
|
+
| `0.0` | Non-blocking, return immediately |
|
|
20
|
+
| Positive float | Wait specified duration |
|
|
21
|
+
|
|
22
|
+
### Return Types
|
|
23
|
+
|
|
24
|
+
- `Event::Key` — Keyboard input
|
|
25
|
+
- `Event::Mouse` — Mouse interactions
|
|
26
|
+
- `Event::Resize` — Terminal dimension changes
|
|
27
|
+
- `Event::Paste` — Clipboard content
|
|
28
|
+
- `Event::FocusGained` — Terminal received focus
|
|
29
|
+
- `Event::FocusLost` — Terminal lost focus
|
|
30
|
+
- `Event::None` — No event (Null Object pattern)
|
|
31
|
+
|
|
32
|
+
## Event Base Class
|
|
33
|
+
|
|
34
|
+
Type-checking predicates:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
event.key? # Keyboard event
|
|
38
|
+
event.mouse? # Mouse activity
|
|
39
|
+
event.resize? # Terminal resize
|
|
40
|
+
event.paste? # Clipboard paste
|
|
41
|
+
event.focus_gained? # Focus acquisition
|
|
42
|
+
event.focus_lost? # Focus loss
|
|
43
|
+
event.none? # No event
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Key Events
|
|
47
|
+
|
|
48
|
+
### Attributes
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
event.code # Key identifier: "a", "enter", "up", "f1"
|
|
52
|
+
event.modifiers # Active modifiers: ["ctrl"], ["alt", "shift"]
|
|
53
|
+
event.kind # Category: :standard, :function, :media, :modifier, :system
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Comparison Methods
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# Symbol comparison
|
|
60
|
+
event == :ctrl_c
|
|
61
|
+
event == :shift_up
|
|
62
|
+
event == :q
|
|
63
|
+
|
|
64
|
+
# String comparison
|
|
65
|
+
event == "c"
|
|
66
|
+
event == "enter"
|
|
67
|
+
|
|
68
|
+
# Object comparison
|
|
69
|
+
event == Event::Key.new(code: "c", modifiers: ["ctrl"])
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Helper Predicates
|
|
73
|
+
|
|
74
|
+
Dynamic methods via `method_missing`:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
event.ctrl_c? # Ctrl+C
|
|
78
|
+
event.enter? # Enter key
|
|
79
|
+
event.shift_up? # Shift+Up
|
|
80
|
+
event.q? # "q" key
|
|
81
|
+
event.esc? # Escape
|
|
82
|
+
event.space? # Space bar
|
|
83
|
+
event.tab? # Tab key
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
DWIM (Do What I Mean) shortcuts:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
event.pause? # Matches "pause" and "media_pause"
|
|
90
|
+
event.play? # Matches "media_play"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Character Detection
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
event.text? # True for printable characters
|
|
97
|
+
event.char # Character or nil for special keys
|
|
98
|
+
event.to_s # Character or empty string
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Key Categories
|
|
102
|
+
|
|
103
|
+
**Navigation:**
|
|
104
|
+
- `up`, `down`, `left`, `right`
|
|
105
|
+
- `home`, `end`, `page_up`, `page_down`
|
|
106
|
+
- `insert`, `delete`
|
|
107
|
+
|
|
108
|
+
**System:**
|
|
109
|
+
- `print_screen`, `pause`, `menu`
|
|
110
|
+
|
|
111
|
+
**Media:**
|
|
112
|
+
- `play`, `media_pause`, `play_pause`, `stop`
|
|
113
|
+
- `track_next`, `track_previous`
|
|
114
|
+
- `mute_volume`, `lower_volume`, `raise_volume`
|
|
115
|
+
|
|
116
|
+
**Modifiers:**
|
|
117
|
+
- `left_shift`, `right_shift`
|
|
118
|
+
- `left_control`, `right_control`
|
|
119
|
+
- `left_alt`, `right_alt`
|
|
120
|
+
|
|
121
|
+
### Pattern Matching
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
case event
|
|
125
|
+
in type: :key, code: "c", modifiers: ["ctrl"]
|
|
126
|
+
break
|
|
127
|
+
in type: :key, code: "q"
|
|
128
|
+
break
|
|
129
|
+
in type: :key, kind: :media
|
|
130
|
+
handle_media_key
|
|
131
|
+
in type: :key, code: "j"
|
|
132
|
+
move_down
|
|
133
|
+
in type: :key, code: "k"
|
|
134
|
+
move_up
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Mouse Events
|
|
139
|
+
|
|
140
|
+
### Attributes
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
event.kind # "down", "up", "drag", "moved", "scroll_up", "scroll_down"
|
|
144
|
+
event.button # "left", "right", "middle", "none", nil
|
|
145
|
+
event.x # Column coordinate (zero-indexed)
|
|
146
|
+
event.y # Row coordinate (zero-indexed)
|
|
147
|
+
event.modifiers # Active keyboard modifiers
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Event Type Methods
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
event.down? # Button pressed
|
|
154
|
+
event.up? # Button released
|
|
155
|
+
event.drag? # Dragging
|
|
156
|
+
event.scroll_up? # Scroll up
|
|
157
|
+
event.scroll_down? # Scroll down
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Usage
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
if event.mouse? && event.down? && event.button == "left"
|
|
164
|
+
handle_click(event.x, event.y)
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Pattern Matching
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
case event
|
|
172
|
+
in type: :mouse, kind: "down", button: "left", x:, y:
|
|
173
|
+
handle_click(x, y)
|
|
174
|
+
in type: :mouse, kind: "drag", x:, y:
|
|
175
|
+
handle_drag(x, y)
|
|
176
|
+
in type: :mouse, kind: "scroll_up"
|
|
177
|
+
scroll_up
|
|
178
|
+
in type: :mouse, kind: "scroll_down"
|
|
179
|
+
scroll_down
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Resize Events
|
|
184
|
+
|
|
185
|
+
### Attributes
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
event.width # New terminal width in columns
|
|
189
|
+
event.height # New terminal height in rows
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Usage
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
if event.resize?
|
|
196
|
+
recalculate_layout(event.width, event.height)
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Pattern Matching
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
case event
|
|
204
|
+
in type: :resize, width:, height:
|
|
205
|
+
update_dimensions(width, height)
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Paste Events
|
|
210
|
+
|
|
211
|
+
Handles pasted text as atomic action (prevents rapid keystroke flood).
|
|
212
|
+
|
|
213
|
+
### Attributes
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
event.content # Pasted text string
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Usage
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
if event.paste?
|
|
223
|
+
insert_text(event.content)
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Pattern Matching
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
case event
|
|
231
|
+
in type: :paste, content:
|
|
232
|
+
process_pasted_content(content)
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Focus Events
|
|
237
|
+
|
|
238
|
+
Track terminal window focus state. **Limited terminal support** (iTerm2, Kitty, newer xterm).
|
|
239
|
+
|
|
240
|
+
### FocusGained
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
if event.focus_gained?
|
|
244
|
+
resume_animations
|
|
245
|
+
refresh_data
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### FocusLost
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
if event.focus_lost?
|
|
253
|
+
pause_animations
|
|
254
|
+
reduce_polling_frequency
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Pattern Matching
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
case event
|
|
262
|
+
in type: :focus_gained
|
|
263
|
+
restore_foreground_mode
|
|
264
|
+
in type: :focus_lost
|
|
265
|
+
enter_background_mode
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## None Events (Null Object)
|
|
270
|
+
|
|
271
|
+
Returns when no input available. Eliminates nil-checks:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
if event.none?
|
|
275
|
+
# Timeout expired, continue rendering
|
|
276
|
+
update_clock_display
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Safe to call predicates (all return false)
|
|
280
|
+
event.ctrl_c? # => false
|
|
281
|
+
event.key? # => false
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Complete Event Loop
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
RatatuiRuby.run do |tui|
|
|
288
|
+
loop do
|
|
289
|
+
tui.draw { |frame| render_view(model, frame) }
|
|
290
|
+
|
|
291
|
+
case tui.poll_event
|
|
292
|
+
# Exit conditions
|
|
293
|
+
in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
# Navigation
|
|
297
|
+
in type: :key, code: "up" | "k"
|
|
298
|
+
model = move_up(model)
|
|
299
|
+
in type: :key, code: "down" | "j"
|
|
300
|
+
model = move_down(model)
|
|
301
|
+
in type: :key, code: "enter"
|
|
302
|
+
model = activate(model)
|
|
303
|
+
|
|
304
|
+
# Mouse
|
|
305
|
+
in type: :mouse, kind: "down", button: "left", x:, y:
|
|
306
|
+
model = handle_click(model, x, y)
|
|
307
|
+
in type: :mouse, kind: "scroll_up"
|
|
308
|
+
model = scroll_up(model)
|
|
309
|
+
in type: :mouse, kind: "scroll_down"
|
|
310
|
+
model = scroll_down(model)
|
|
311
|
+
|
|
312
|
+
# Terminal
|
|
313
|
+
in type: :resize, width:, height:
|
|
314
|
+
model = update_layout(model, width, height)
|
|
315
|
+
in type: :paste, content:
|
|
316
|
+
model = insert_text(model, content)
|
|
317
|
+
|
|
318
|
+
# Focus
|
|
319
|
+
in type: :focus_lost
|
|
320
|
+
model = enter_background(model)
|
|
321
|
+
in type: :focus_gained
|
|
322
|
+
model = resume_foreground(model)
|
|
323
|
+
|
|
324
|
+
else
|
|
325
|
+
# Catch unmatched events (required to prevent NoMatchingPatternError)
|
|
326
|
+
nil
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Event Handling Patterns
|
|
333
|
+
|
|
334
|
+
### Helper Method Approach
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
event = tui.poll_event
|
|
338
|
+
break if event.ctrl_c?
|
|
339
|
+
@list_state.select_next if event.down? || event.j?
|
|
340
|
+
@list_state.select_previous if event.up? || event.k?
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Predicate Chain
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
event = tui.poll_event
|
|
347
|
+
case
|
|
348
|
+
when event.ctrl_c? then break
|
|
349
|
+
when event.enter? then activate_selection
|
|
350
|
+
when event.up?, event.k? then move_up
|
|
351
|
+
when event.down?, event.j? then move_down
|
|
352
|
+
end
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Mixed Pattern Matching
|
|
356
|
+
|
|
357
|
+
```ruby
|
|
358
|
+
case tui.poll_event
|
|
359
|
+
in Event::Key if _1.ctrl_c?
|
|
360
|
+
break
|
|
361
|
+
in Event::Key => e if e.text?
|
|
362
|
+
insert_character(e.char)
|
|
363
|
+
in Event::Mouse => e if e.down?
|
|
364
|
+
handle_click(e.x, e.y)
|
|
365
|
+
end
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Terminal Compatibility Notes
|
|
369
|
+
|
|
370
|
+
Some key combinations intercepted by terminal emulators:
|
|
371
|
+
- Ctrl+PageUp/PageDown (tab switching)
|
|
372
|
+
- Ctrl+Tab
|
|
373
|
+
- Cmd+key (macOS)
|
|
374
|
+
|
|
375
|
+
For broader key support, use Kitty, WezTerm, or Alacritty with enhanced key protocols.
|
|
376
|
+
|
|
377
|
+
## Quick Reference
|
|
378
|
+
|
|
379
|
+
| Event Type | Key Attributes | Common Predicates |
|
|
380
|
+
|------------|----------------|-------------------|
|
|
381
|
+
| Key | `code`, `modifiers`, `kind` | `ctrl_c?`, `enter?`, `up?`, `down?`, `text?` |
|
|
382
|
+
| Mouse | `kind`, `button`, `x`, `y`, `modifiers` | `down?`, `up?`, `drag?`, `scroll_up?` |
|
|
383
|
+
| Resize | `width`, `height` | `resize?` |
|
|
384
|
+
| Paste | `content` | `paste?` |
|
|
385
|
+
| FocusGained | — | `focus_gained?` |
|
|
386
|
+
| FocusLost | — | `focus_lost?` |
|
|
387
|
+
| None | — | `none?` |
|