ratatui_ruby 0.9.1 → 0.10.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/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +2 -1
- data/CHANGELOG.md +98 -0
- data/REUSE.toml +5 -0
- data/Rakefile +1 -1
- data/Steepfile +49 -0
- data/doc/concepts/debugging.md +401 -0
- data/doc/getting_started/quickstart.md +8 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.png +0 -0
- data/doc/images/widget_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/troubleshooting/async.md +4 -0
- data/examples/app_debugging_showcase/README.md +119 -0
- data/examples/app_debugging_showcase/app.rb +318 -0
- data/examples/widget_canvas/app.rb +19 -14
- data/examples/widget_gauge/app.rb +18 -3
- data/examples/widget_layout_split/app.rb +10 -4
- data/examples/widget_list/app.rb +22 -6
- data/examples/widget_rect/app.rb +7 -6
- data/examples/widget_rich_text/app.rb +62 -37
- data/examples/widget_style_colors/app.rb +26 -47
- data/examples/widget_table/app.rb +28 -5
- data/examples/widget_text_width/app.rb +6 -4
- data/ext/ratatui_ruby/Cargo.lock +48 -1
- data/ext/ratatui_ruby/Cargo.toml +6 -2
- data/ext/ratatui_ruby/src/color.rs +82 -0
- data/ext/ratatui_ruby/src/errors.rs +28 -0
- data/ext/ratatui_ruby/src/events.rs +15 -14
- data/ext/ratatui_ruby/src/lib.rs +56 -0
- data/ext/ratatui_ruby/src/rendering.rs +3 -1
- data/ext/ratatui_ruby/src/style.rs +48 -21
- data/ext/ratatui_ruby/src/terminal.rs +40 -9
- data/ext/ratatui_ruby/src/text.rs +21 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
- data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
- data/lib/ratatui_ruby/buffer/cell.rb +25 -15
- data/lib/ratatui_ruby/buffer.rb +134 -2
- data/lib/ratatui_ruby/cell.rb +13 -5
- data/lib/ratatui_ruby/debug.rb +215 -0
- data/lib/ratatui_ruby/event/key.rb +3 -2
- data/lib/ratatui_ruby/event.rb +1 -1
- data/lib/ratatui_ruby/layout/constraint.rb +49 -0
- data/lib/ratatui_ruby/layout/layout.rb +119 -13
- data/lib/ratatui_ruby/layout/position.rb +55 -0
- data/lib/ratatui_ruby/layout/rect.rb +188 -0
- data/lib/ratatui_ruby/layout/size.rb +55 -0
- data/lib/ratatui_ruby/layout.rb +4 -0
- data/lib/ratatui_ruby/style/color.rb +149 -0
- data/lib/ratatui_ruby/style/style.rb +51 -4
- data/lib/ratatui_ruby/style.rb +2 -0
- data/lib/ratatui_ruby/symbols.rb +435 -0
- data/lib/ratatui_ruby/synthetic_events.rb +1 -1
- data/lib/ratatui_ruby/table_state.rb +51 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
- data/lib/ratatui_ruby/test_helper/event_injection.rb +6 -1
- data/lib/ratatui_ruby/test_helper.rb +9 -0
- data/lib/ratatui_ruby/text/line.rb +245 -0
- data/lib/ratatui_ruby/text/span.rb +158 -0
- data/lib/ratatui_ruby/text.rb +99 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
- data/lib/ratatui_ruby/tui/core.rb +13 -2
- data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
- data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
- data/lib/ratatui_ruby/tui.rb +22 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
- data/lib/ratatui_ruby/widgets/block.rb +14 -6
- data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
- data/lib/ratatui_ruby/widgets/cell.rb +2 -0
- data/lib/ratatui_ruby/widgets/center.rb +2 -0
- data/lib/ratatui_ruby/widgets/chart.rb +6 -0
- data/lib/ratatui_ruby/widgets/clear.rb +2 -0
- data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
- data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
- data/lib/ratatui_ruby/widgets/list.rb +87 -3
- data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
- data/lib/ratatui_ruby/widgets/row.rb +45 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
- data/lib/ratatui_ruby/widgets/table.rb +13 -3
- data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
- data/lib/ratatui_ruby/widgets.rb +1 -0
- data/lib/ratatui_ruby.rb +40 -9
- data/sig/examples/app_all_events/model/app_model.rbs +23 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
- data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_stateful_interaction/app.rbs +5 -5
- data/sig/examples/widget_block_demo/app.rbs +6 -6
- data/sig/manifest.yaml +5 -0
- data/sig/patches/data.rbs +26 -0
- data/sig/patches/debugger__.rbs +8 -0
- data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
- data/sig/ratatui_ruby/buffer.rbs +18 -0
- data/sig/ratatui_ruby/cell.rbs +44 -0
- data/sig/ratatui_ruby/clear.rbs +18 -0
- data/sig/ratatui_ruby/constraint.rbs +26 -0
- data/sig/ratatui_ruby/debug.rbs +45 -0
- data/sig/ratatui_ruby/draw.rbs +30 -0
- data/sig/ratatui_ruby/event.rbs +68 -8
- data/sig/ratatui_ruby/frame.rbs +4 -4
- data/sig/ratatui_ruby/interfaces.rbs +25 -0
- data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
- data/sig/ratatui_ruby/layout/layout.rbs +45 -0
- data/sig/ratatui_ruby/layout/position.rbs +18 -0
- data/sig/ratatui_ruby/layout/rect.rbs +64 -0
- data/sig/ratatui_ruby/layout/size.rbs +18 -0
- data/sig/ratatui_ruby/output_guard.rbs +23 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +83 -4
- data/sig/ratatui_ruby/rect.rbs +17 -0
- data/sig/ratatui_ruby/style/color.rbs +22 -0
- data/sig/ratatui_ruby/style/style.rbs +29 -0
- data/sig/ratatui_ruby/symbols.rbs +141 -0
- data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
- data/sig/ratatui_ruby/table_state.rbs +6 -0
- data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
- data/sig/ratatui_ruby/text/line.rbs +27 -0
- data/sig/ratatui_ruby/text/span.rbs +23 -0
- data/sig/ratatui_ruby/text.rbs +12 -0
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
- data/sig/ratatui_ruby/tui/core.rbs +2 -2
- data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
- data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
- data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
- data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
- data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
- data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
- data/sig/ratatui_ruby/widgets/block.rbs +51 -0
- data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
- data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
- data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
- data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
- data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
- data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
- data/sig/ratatui_ruby/widgets/list.rbs +63 -0
- data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
- data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
- data/sig/ratatui_ruby/widgets/row.rbs +43 -0
- data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
- data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
- data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
- data/sig/ratatui_ruby/widgets/table.rbs +78 -0
- data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
- data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
- data/tasks/steep.rake +11 -0
- metadata +80 -63
- data/doc/contributors/v1.0.0_blockers.md +0 -870
- data/doc/troubleshooting/debugging.md +0 -101
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
- data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
- data/lib/ratatui_ruby/schema/block.rb +0 -198
- data/lib/ratatui_ruby/schema/calendar.rb +0 -84
- data/lib/ratatui_ruby/schema/canvas.rb +0 -239
- data/lib/ratatui_ruby/schema/center.rb +0 -67
- data/lib/ratatui_ruby/schema/chart.rb +0 -159
- data/lib/ratatui_ruby/schema/clear.rb +0 -62
- data/lib/ratatui_ruby/schema/constraint.rb +0 -151
- data/lib/ratatui_ruby/schema/cursor.rb +0 -50
- data/lib/ratatui_ruby/schema/gauge.rb +0 -72
- data/lib/ratatui_ruby/schema/layout.rb +0 -122
- data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
- data/lib/ratatui_ruby/schema/list.rb +0 -135
- data/lib/ratatui_ruby/schema/list_item.rb +0 -51
- data/lib/ratatui_ruby/schema/overlay.rb +0 -51
- data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
- data/lib/ratatui_ruby/schema/rect.rb +0 -174
- data/lib/ratatui_ruby/schema/row.rb +0 -76
- data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
- data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
- data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
- data/lib/ratatui_ruby/schema/style.rb +0 -97
- data/lib/ratatui_ruby/schema/table.rb +0 -141
- data/lib/ratatui_ruby/schema/tabs.rb +0 -85
- data/lib/ratatui_ruby/schema/text.rb +0 -217
- data/sig/examples/app_all_events/model/events.rbs +0 -15
- data/sig/examples/app_all_events/view_state.rbs +0 -21
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
- data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
- data/sig/ratatui_ruby/schema/block.rbs +0 -18
- data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
- data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
- data/sig/ratatui_ruby/schema/center.rbs +0 -17
- data/sig/ratatui_ruby/schema/chart.rbs +0 -39
- data/sig/ratatui_ruby/schema/constraint.rbs +0 -30
- data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
- data/sig/ratatui_ruby/schema/draw.rbs +0 -33
- data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
- data/sig/ratatui_ruby/schema/layout.rbs +0 -27
- data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
- data/sig/ratatui_ruby/schema/list.rbs +0 -28
- data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
- data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
- data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
- data/sig/ratatui_ruby/schema/rect.rbs +0 -48
- data/sig/ratatui_ruby/schema/row.rbs +0 -28
- data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
- data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
- data/sig/ratatui_ruby/schema/style.rbs +0 -19
- data/sig/ratatui_ruby/schema/table.rbs +0 -32
- data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
- data/sig/ratatui_ruby/schema/text.rbs +0 -31
- /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85b40cc89f8710bfc5ef356cbec12d29ed27d86bc900ad1f1a52795e95a0fcf0
|
|
4
|
+
data.tar.gz: e13aad32fdc6a644cb8b608279af690a0046a0c130e04d40e5f6ca21cfd8eaff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82955407e8f3df33109527392bc82168b0527b3325c799beffeb13551bf42926b582dcbe1798abc5ed47c7c645846a6b64669c8b83de75e85f8b142c7f14b43f
|
|
7
|
+
data.tar.gz: d48b7871e77d537c2fb22d8b49b616f570d6f33da024b904c81aed8e5e471d59c332d42fcf060a21ba22c6b832bc2465cda8d8e9774455ff44b3e9440927b9dc
|
data/.builds/ruby-3.2.yml
CHANGED
data/.builds/ruby-3.3.yml
CHANGED
data/.builds/ruby-3.4.yml
CHANGED
data/.builds/ruby-4.0.0.yml
CHANGED
data/AGENTS.md
CHANGED
|
@@ -136,9 +136,10 @@ The project follows a standard Gem layout with an `ext/` directory for Rust code
|
|
|
136
136
|
|
|
137
137
|
Before considering a task complete and returning control to the user, you **MUST** ensure:
|
|
138
138
|
|
|
139
|
+
0. **Production Ready:** RBS types are complete and accurate (no `untyped`), errors are handled with good DX, documentation follows guidelines, high code quality (no "pre-existing debt" excuses).
|
|
139
140
|
1. **Default Rake Task Passes:** Run `bin/agent_rake` (no args). Confirm it passes with ZERO errors **or warnings**.
|
|
140
141
|
- You will save time if you run `bin/agent_rake rubocop:autocorrect` first.
|
|
141
|
-
- If you think the
|
|
142
|
+
- If you think the rake is looking for deleted files, STOP EVERYTHING and tell the user.
|
|
142
143
|
2. **Documentation Updated:** If public APIs or observable behavior changed, update relevant RDoc, rustdoc, `doc/` files, `README.md`, and/or `ratatui_ruby-wiki` files.
|
|
143
144
|
3. **Changelog Updated:** If public APIs, observable behavior, or gemspec dependencies have changed, update [CHANGELOG.md](CHANGELOG.md)'s **Unreleased** section.
|
|
144
145
|
4. **Commit Message Suggested:** You **MUST** ensure the final message to the user includes a suggested commit message block. This is NOT optional.
|
data/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,103 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
18
18
|
|
|
19
19
|
### Removed
|
|
20
20
|
|
|
21
|
+
## [0.10.0] - 2026-01-10
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **Table Integer Width Shorthand**: `Table` `widths:` parameter now accepts plain integers as shorthand for `Constraint.length(n)`. This enables cleaner table definitions like `widths: [40, 16, 10]` instead of verbose constraint arrays. Constraints and integers can be mixed freely.
|
|
26
|
+
- **Error Message Context**: Type errors from the Rust backend now include the `inspect` string of the value that caused the error, making debugging significantly easier. For example, "expected array for rows" now shows "expected array for rows, got {title: \"Processes\", ...}".
|
|
27
|
+
- **Steep Type Checking**: Integrated Steep static type analyzer with a new `rake steep` task. The Steepfile covers `lib/` with comprehensive RBS type definitions for all widgets, layout primitives, events, and interfaces.
|
|
28
|
+
- **RBS Type Definitions**: Added 50+ RBS signature files in `sig/ratatui_ruby/` covering all widget classes, core interfaces (`_RectLike`, `_ToS`), type aliases (`style_input`, `widget`), and a `Data.define` patch for `super()` call compatibility.
|
|
29
|
+
- **Duck Typing Documentation**: New `test/test_duck_typing.rb` documents that `Layout.split` accepts any object responding to `x`, `y`, `width`, `height` (Struct, Data.define, custom classes), not just `Rect`.
|
|
30
|
+
- **Debug Mode**: New `RatatuiRuby::Debug` module controls Rust backtrace visibility for easier debugging. Activate via:
|
|
31
|
+
- `RUST_BACKTRACE=1` — Rust backtraces only
|
|
32
|
+
- `RR_DEBUG=1` — full debug mode (backtraces + future Ruby-side features)
|
|
33
|
+
- `include RatatuiRuby::TestHelper` — auto-enables debug mode in tests
|
|
34
|
+
- `RatatuiRuby.debug_mode!` — programmatic activation
|
|
35
|
+
- **Debug.test_panic!**: New method that intentionally triggers a Rust panic, allowing developers to verify their backtrace setup is working correctly before encountering a real bug.
|
|
36
|
+
- **Deferred Panic Backtraces**: Rust backtraces during TUI sessions are now stored and printed after terminal restoration, preventing output from being lost on the alternate screen. Previously, panic output in raw terminal mode was invisible.
|
|
37
|
+
- **Remote Debugging**: Debug mode now integrates with Ruby's `debug` gem for remote debugging. `RR_DEBUG=1` stops at startup and waits for debugger attachment. `RatatuiRuby.debug_mode!` continues running in nonstop mode. Attach from another terminal with `rdbg --attach`.
|
|
38
|
+
- **Debug.suppress_debug_mode**: New block method temporarily suppresses Ruby-side debug checks within its block. Rust backtraces remain enabled. Useful for testing production behavior in debug mode environments.
|
|
39
|
+
- **DWIM Hash Coercion**: All widget factory methods now accept both `tui.table(hash)` and `tui.table(**hash)` calling styles. When a bare Hash is passed as the first positional argument, it is automatically splatted into keyword arguments. Unknown keys are silently ignored in production mode; in debug mode (`RR_DEBUG=1`), they raise `ArgumentError` for early typo detection.
|
|
40
|
+
- **Ratatui-Aligned Text Methods**: New methods on `Text::Span` and `Text::Line` matching Ratatui's API for style manipulation:
|
|
41
|
+
- `Span#width` — display width in terminal cells (unicode-aware)
|
|
42
|
+
- `Span.raw(content)` — factory for unstyled spans
|
|
43
|
+
- `Span#patch_style(style)` — merge style onto existing style
|
|
44
|
+
- `Span#reset_style` — clear all styling
|
|
45
|
+
- `Line#left_aligned`, `Line#centered`, `Line#right_aligned` — fluent alignment setters
|
|
46
|
+
- `Line#push_span(span)` — append span (returns new Line, immutable)
|
|
47
|
+
- `Line#patch_style(style)`, `Line#reset_style` — style manipulation for all spans
|
|
48
|
+
- **List Query Methods**: New methods on `List` matching Ratatui's API:
|
|
49
|
+
- `List#len` — number of items (with Ruby aliases `length`, `size`)
|
|
50
|
+
- **TableState Navigation Methods**: New methods on `TableState` matching Ratatui's API for column navigation:
|
|
51
|
+
- `selected_cell` — returns `[row, column]` tuple when both are selected
|
|
52
|
+
- `with_selected_cell(cell)` — constructor to create state with both row and column selected
|
|
53
|
+
- `select_next_column` — select the next column (or first if none selected)
|
|
54
|
+
- `select_previous_column` — select the previous column (saturates at 0)
|
|
55
|
+
- `select_first_column` — select column 0
|
|
56
|
+
- `select_last_column` — select the last column (clamped during rendering)
|
|
57
|
+
- **Buffer Query Methods**: New module methods on `Buffer` matching Ratatui's API for buffer inspection:
|
|
58
|
+
- `Buffer.content` — returns all cells as an array
|
|
59
|
+
- `Buffer.get(x, y)` — returns the Cell at the specified position
|
|
60
|
+
- `Buffer.index_of(x, y)` — converts position to linear buffer index
|
|
61
|
+
- `Buffer.pos_of(index)` — converts linear index to position coordinates
|
|
62
|
+
- **Rect Conversion Methods**: New methods on `Rect` for extracting geometry components:
|
|
63
|
+
- `Rect#as_position` — returns a `Position` object containing x and y coordinates
|
|
64
|
+
- `Rect#as_size` — returns a `Size` object containing width and height
|
|
65
|
+
- **Position and Size Classes**: New layout primitives matching Ratatui's API:
|
|
66
|
+
- `Layout::Position` — represents terminal coordinates (x, y)
|
|
67
|
+
- `Layout::Size` — represents terminal dimensions (width, height)
|
|
68
|
+
- **Constraint#apply**: Computes the constrained size for a given available space. For example, `Constraint.percentage(50).apply(100)` returns `50`. Also aliased as `call` for proc-like invocation (`constraint.(100)`).
|
|
69
|
+
- **Color Module**: New `Style::Color` module with constructors matching Ratatui's API:
|
|
70
|
+
- `Color.from_u32(0xRRGGBB)` — creates a color from a hex integer (aliased as `Color.hex`)
|
|
71
|
+
- `Color.from_hsl(h, s, l)` — creates a color from HSL values (aliased as `Color.hsl`)
|
|
72
|
+
- **Ruby-Idiomatic Aliases**: All new APIs include shorter, more Ruby-ish aliases following TIMTOWTDI:
|
|
73
|
+
- `Rect#position` (alias for `as_position`), `Rect#size` (alias for `as_size`)
|
|
74
|
+
- `Buffer[x, y]` (alias for `Buffer.get`)
|
|
75
|
+
- `Constraint#call` (alias for `apply`, enables `constraint.(n)` syntax)
|
|
76
|
+
- **Layout Margin and Spacing**: `Layout` now supports `margin:` and `spacing:` parameters for edge insets and gaps between segments, matching Ratatui's Layout API.
|
|
77
|
+
- **Layout.split_with_spacers**: New class method returns both content segments and spacer Rects, enabling custom rendering of dividers or separators between layout sections.
|
|
78
|
+
- **Canvas#get_point**: Converts canvas coordinates to normalized [0.0, 1.0] grid coordinates for hit testing. Returns `nil` for out-of-bounds coordinates. Also aliased as `point` and `[]` for Ruby-idiomatic access.
|
|
79
|
+
- **Row#enable_strikethrough**: Returns a new Row with `:crossed_out` modifier for indicating cancelled or deleted items. Also aliased as `strikethrough`. Note: Strikethrough (SGR 9) is not supported by all terminals; macOS Terminal.app notably lacks support while Kitty, iTerm2, Alacritty, and WezTerm render it correctly.
|
|
80
|
+
- **Rect Geometry Methods**: New methods on `Rect` for geometry manipulation:
|
|
81
|
+
- `Rect#outer(margin)` — expands a rectangle by a margin (inverse of `inner`)
|
|
82
|
+
- `Rect#resize(size)` — changes dimensions while preserving top-left position
|
|
83
|
+
- `Rect#centered_horizontally(constraint)` — centers horizontally using Layout
|
|
84
|
+
- `Rect#centered_vertically(constraint)` — centers vertically using Layout
|
|
85
|
+
- `Rect#centered(h, v)` — centers on both axes
|
|
86
|
+
- **TUI Shape Aliases (DWIM)**: Canvas shape factories now have terse and bidirectional aliases:
|
|
87
|
+
- Terse: `circle()`, `point()`, `map()`, `label()` (shorter forms of `shape_*`)
|
|
88
|
+
- Bidirectional: `circle_shape()`, `point_shape()`, `rectangle_shape()`, `map_shape()`, `label_shape()` (same as `shape_*`)
|
|
89
|
+
- Note: Terse `rectangle` is intentionally excluded to avoid confusion with `Layout::Rect`; use `shape_rectangle()` or `rectangle_shape()`.
|
|
90
|
+
- **TUI `item` Alias**: `tui.item(...)` is now an alias for `tui.list_item(...)`, providing a terser API when building lists.
|
|
91
|
+
- **Gauge and LineGauge `percent`**: Both widgets now have a `percent` reader that returns the ratio as an integer percentage (0-100). `LineGauge` also now accepts a `percent:` constructor parameter matching `Gauge`.
|
|
92
|
+
- **List#selected_item**: Returns the item at the selected index (or nil if nothing is selected).
|
|
93
|
+
- **Style `underline_color`**: New optional parameter for `Style` that sets a distinct underline color independent of the foreground color. Useful for styling like "white text with red underline". Terminals must support the underline color extension (SGR 58).
|
|
94
|
+
- **Style `remove_modifiers`**: New optional parameter for `Style` that explicitly removes modifiers when styles are patched/inherited. Corresponds to Ratatui's `sub_modifier` field. Use it to prevent inherited bold, italic, or other modifiers from propagating.
|
|
95
|
+
- **Symbols::Shade Constants**: New `RatatuiRuby::Symbols::Shade` module exposes Ratatui's shade block characters as named constants: `EMPTY` (" "), `LIGHT` ("░"), `MEDIUM` ("▒"), `DARK` ("▓"), `FULL` ("█"). Use them for gradients, density fills, or custom progress indicators.
|
|
96
|
+
- **Symbols::Line Constants and Sets**: New `RatatuiRuby::Symbols::Line` module exposes Ratatui's box-drawing characters with 36 individual constants (VERTICAL, HORIZONTAL, corners, T-junctions, cross) and 4 predefined sets: `NORMAL` (standard corners), `ROUNDED` (rounded corners), `DOUBLE` (double-line), `THICK` (heavy lines). Use them for custom borders or drawing.
|
|
97
|
+
- **Symbols::Bar Constants and Sets**: New `RatatuiRuby::Symbols::Bar` module exposes Ratatui's vertical bar characters (lower blocks) with 8 individual constants and 2 predefined sets: `NINE_LEVELS` (full resolution) and `THREE_LEVELS` (simplified). Used by Sparkline widget.
|
|
98
|
+
- **Symbols::Block Constants and Sets**: New `RatatuiRuby::Symbols::Block` module exposes Ratatui's horizontal block characters (left blocks) with 8 individual constants and 2 predefined sets: `NINE_LEVELS` (full resolution) and `THREE_LEVELS` (simplified). Used by Gauge widget.
|
|
99
|
+
- **Symbols::Scrollbar Sets**: New `RatatuiRuby::Symbols::Scrollbar` module exposes 4 predefined scrollbar symbol sets: `VERTICAL`, `DOUBLE_VERTICAL`, `HORIZONTAL`, `DOUBLE_HORIZONTAL`. Each set contains `track`, `thumb`, `begin_char`, and `end_char` symbols.
|
|
100
|
+
- **Cell `underline_color`**: `Buffer::Cell` and top-level `Cell` now expose `underline_color` attribute for introspecting styled underline colors during testing.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **`tui.draw` Argument Validation (Breaking)**: `tui.draw` now validates its arguments. Calling `draw` with neither a tree nor a block raises `ArgumentError`, as does calling it with both. Previously this produced undefined behavior.
|
|
105
|
+
- **`Layout.split` Stricter Type Checking (Breaking)**: `Layout.split` now rejects invalid `area` arguments with a clear `ArgumentError` instead of silently misbehaving. Objects must be a `Rect`, `Hash` with `:x/:y/:width/:height` keys, or respond to all four geometry methods.
|
|
106
|
+
- **Schema Directory Removed (Breaking)**: The legacy `lib/ratatui_ruby/schema/` directory has been removed. All widget classes now live exclusively in their proper namespaces (`Widgets::`, `Layout::`, `Text::`, etc.). Direct usage like `RatatuiRuby::Paragraph` (without `Widgets::`) is no longer supported. Use `RatatuiRuby::Widgets::Paragraph` or the TUI facade (`tui.paragraph`).
|
|
107
|
+
- **Buffer::Cell Modifiers (Breaking)**: `Buffer::Cell#modifiers` and `Cell#modifiers` now return an array of `Symbol`s (e.g., `[:bold, :italic]`) instead of `String`s. This unifies the API, as `Style` modifiers were already symbols. Code expecting strings (e.g. `cell.modifiers.include?("bold")`) must be updated to use symbols. This changes behavior established in v0.9.1.
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- **Tabs Padding Coercion**: `Tabs` `padding_left` and `padding_right` now correctly coerce duck-typed integer values (objects responding to `to_int`/`to_i`) via `Integer()`. Previously these parameters were passed through without coercion, inconsistent with other integer parameters. `Text::Line` values are still accepted as-is for styled padding.
|
|
112
|
+
|
|
113
|
+
### Removed
|
|
114
|
+
|
|
115
|
+
- **NullIO Re-export (Breaking)**: Removed `RatatuiRuby::NullIO` constant. Use `RatatuiRuby::OutputGuard::NullIO` if you were relying on this internal class.
|
|
116
|
+
- **Legacy Media Keys (Breaking)**: Removed support for legacy unprefixed media keys (e.g. `play`, `stop`) in event parsing. You must now use the canonical `media_`-prefixed keys (e.g. `media_play`, `media_stop`). The `play?`/`stop?` predicates still work for both, checking the correct canonical codes.
|
|
117
|
+
|
|
21
118
|
## [0.9.1] - 2026-01-08
|
|
22
119
|
|
|
23
120
|
### Added
|
|
@@ -515,6 +612,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
515
612
|
- **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
|
|
516
613
|
|
|
517
614
|
[Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/HEAD
|
|
615
|
+
[0.10.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.10.0
|
|
518
616
|
[0.9.1]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.9.1
|
|
519
617
|
[0.9.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.9.0
|
|
520
618
|
[0.8.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.8.0
|
data/REUSE.toml
CHANGED
|
@@ -16,6 +16,11 @@ path = 'doc/images/*.png'
|
|
|
16
16
|
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
17
17
|
SPDX-License-Identifier = "CC-BY-SA-4.0"
|
|
18
18
|
|
|
19
|
+
[[annotations]]
|
|
20
|
+
path = 'doc/images/*.gif'
|
|
21
|
+
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
22
|
+
SPDX-License-Identifier = "CC-BY-SA-4.0"
|
|
23
|
+
|
|
19
24
|
[[annotations]]
|
|
20
25
|
path = 'test/fixtures/*.txt'
|
|
21
26
|
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
data/Rakefile
CHANGED
data/Steepfile
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
target :lib do
|
|
7
|
+
signature "sig"
|
|
8
|
+
check "lib"
|
|
9
|
+
|
|
10
|
+
# Legacy schema/ files pending migration - exclude from checking
|
|
11
|
+
# Only schema/text.rb and schema/draw.rb are loaded by the main gem
|
|
12
|
+
# See doc/contributors/v1.0.0_blockers.md
|
|
13
|
+
ignore "lib/ratatui_ruby/schema/bar_chart.rb"
|
|
14
|
+
ignore "lib/ratatui_ruby/schema/bar_chart/"
|
|
15
|
+
ignore "lib/ratatui_ruby/schema/block.rb"
|
|
16
|
+
ignore "lib/ratatui_ruby/schema/calendar.rb"
|
|
17
|
+
ignore "lib/ratatui_ruby/schema/canvas.rb"
|
|
18
|
+
ignore "lib/ratatui_ruby/schema/center.rb"
|
|
19
|
+
ignore "lib/ratatui_ruby/schema/chart.rb"
|
|
20
|
+
ignore "lib/ratatui_ruby/schema/clear.rb"
|
|
21
|
+
ignore "lib/ratatui_ruby/schema/constraint.rb"
|
|
22
|
+
ignore "lib/ratatui_ruby/schema/cursor.rb"
|
|
23
|
+
ignore "lib/ratatui_ruby/schema/gauge.rb"
|
|
24
|
+
ignore "lib/ratatui_ruby/schema/layout.rb"
|
|
25
|
+
ignore "lib/ratatui_ruby/schema/line_gauge.rb"
|
|
26
|
+
ignore "lib/ratatui_ruby/schema/list.rb"
|
|
27
|
+
ignore "lib/ratatui_ruby/schema/list_item.rb"
|
|
28
|
+
ignore "lib/ratatui_ruby/schema/overlay.rb"
|
|
29
|
+
ignore "lib/ratatui_ruby/schema/paragraph.rb"
|
|
30
|
+
ignore "lib/ratatui_ruby/schema/ratatui_logo.rb"
|
|
31
|
+
ignore "lib/ratatui_ruby/schema/ratatui_mascot.rb"
|
|
32
|
+
ignore "lib/ratatui_ruby/schema/rect.rb"
|
|
33
|
+
ignore "lib/ratatui_ruby/schema/row.rb"
|
|
34
|
+
ignore "lib/ratatui_ruby/schema/scrollbar.rb"
|
|
35
|
+
ignore "lib/ratatui_ruby/schema/shape/"
|
|
36
|
+
ignore "lib/ratatui_ruby/schema/sparkline.rb"
|
|
37
|
+
ignore "lib/ratatui_ruby/schema/style.rb"
|
|
38
|
+
ignore "lib/ratatui_ruby/schema/table.rb"
|
|
39
|
+
ignore "lib/ratatui_ruby/schema/tabs.rb"
|
|
40
|
+
|
|
41
|
+
# ClassMethods mixin pattern cannot be typed in RBS
|
|
42
|
+
# (self in ClassMethods refers to the including Class)
|
|
43
|
+
ignore "lib/ratatui_ruby/widgets/coerceable_widget.rb"
|
|
44
|
+
|
|
45
|
+
library "pathname"
|
|
46
|
+
library "fileutils"
|
|
47
|
+
library "minitest"
|
|
48
|
+
library "date"
|
|
49
|
+
end
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Debugging Guide
|
|
7
|
+
|
|
8
|
+
TUI applications are harder to debug than typical Ruby programs. The terminal is in raw mode. Standard output corrupts the display. Debuggers that rely on REPL input conflict with the event loop. Rust panics produce cryptic stack traces without symbols.
|
|
9
|
+
|
|
10
|
+
This guide covers what RatatuiRuby offers and what works (and what does not) when debugging TUI apps.
|
|
11
|
+
|
|
12
|
+
## Debug Mode
|
|
13
|
+
|
|
14
|
+
RatatuiRuby ships with debug symbols in release builds. Call `RatatuiRuby::Debug.enable!` to get Rust backtraces with meaningful stack frames.
|
|
15
|
+
|
|
16
|
+
### Activation Methods
|
|
17
|
+
|
|
18
|
+
You can turn on debug features in three ways.
|
|
19
|
+
|
|
20
|
+
1. **Environment variable (Rust only):** `RUST_BACKTRACE=1` turns on Rust backtraces without Ruby-side debug features.
|
|
21
|
+
|
|
22
|
+
2. **Environment variable (full):** `RR_DEBUG=1` turns on full debug mode at process startup.
|
|
23
|
+
|
|
24
|
+
3. **Programmatic:** Call `RatatuiRuby.debug_mode!` or `RatatuiRuby::Debug.enable!`.
|
|
25
|
+
|
|
26
|
+
> [!WARNING]
|
|
27
|
+
> Debug mode opens a remote debugging socket. This is a **security vulnerability**. Do not use it in production. See [Remote Debugging](#remote-debugging) for details.
|
|
28
|
+
|
|
29
|
+
Including `RatatuiRuby::TestHelper` auto-enables debug mode. Test authors get backtraces automatically.
|
|
30
|
+
|
|
31
|
+
<!-- SPDX-SnippetBegin -->
|
|
32
|
+
<!--
|
|
33
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
34
|
+
SPDX-License-Identifier: MIT-0
|
|
35
|
+
-->
|
|
36
|
+
```ruby
|
|
37
|
+
# Option 1: Environment variable
|
|
38
|
+
# $ RR_DEBUG=1 ruby my_app.rb
|
|
39
|
+
|
|
40
|
+
# Option 2: Programmatic
|
|
41
|
+
RatatuiRuby.debug_mode!
|
|
42
|
+
|
|
43
|
+
# Now Rust panics show meaningful stack traces
|
|
44
|
+
```
|
|
45
|
+
<!-- SPDX-SnippetEnd -->
|
|
46
|
+
|
|
47
|
+
### Panics vs. Exceptions
|
|
48
|
+
|
|
49
|
+
Rust backtraces only appear for **panics** (unrecoverable crashes). When Rust code raises a Ruby exception (like `TypeError`), Ruby handles the backtrace. Rust provides the error message.
|
|
50
|
+
|
|
51
|
+
| Error Type | Backtrace | When It Happens |
|
|
52
|
+
|------------|-----------|-----------------|
|
|
53
|
+
| **Panic** | Rust stack trace | Internal Rust bug, `Debug.test_panic!` |
|
|
54
|
+
| **Exception** | Ruby stack trace | Type mismatch, invalid arguments |
|
|
55
|
+
|
|
56
|
+
The `RUST_BACKTRACE=1` environment variable and `Debug.enable!` affect panic backtraces. Exceptions always show Ruby backtraces, but RatatuiRuby includes **contextual error messages** showing the actual value that caused the error:
|
|
57
|
+
|
|
58
|
+
<!-- SPDX-SnippetBegin -->
|
|
59
|
+
<!--
|
|
60
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
61
|
+
SPDX-License-Identifier: MIT-0
|
|
62
|
+
-->
|
|
63
|
+
```
|
|
64
|
+
# Without context (generic):
|
|
65
|
+
expected array for rows
|
|
66
|
+
|
|
67
|
+
# With context (RatatuiRuby):
|
|
68
|
+
expected array for rows, got 42
|
|
69
|
+
```
|
|
70
|
+
<!-- SPDX-SnippetEnd -->
|
|
71
|
+
|
|
72
|
+
## Inspecting the Buffer
|
|
73
|
+
|
|
74
|
+
The following methods help you debug rendering issues from tests or scripts.
|
|
75
|
+
|
|
76
|
+
### print_buffer
|
|
77
|
+
|
|
78
|
+
Outputs the current terminal buffer to STDOUT with full ANSI colors. Call it inside `with_test_terminal` to see exactly what would render.
|
|
79
|
+
|
|
80
|
+
<!-- SPDX-SnippetBegin -->
|
|
81
|
+
<!--
|
|
82
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
83
|
+
SPDX-License-Identifier: MIT-0
|
|
84
|
+
-->
|
|
85
|
+
```ruby
|
|
86
|
+
with_test_terminal do
|
|
87
|
+
MyApp.new.render
|
|
88
|
+
print_buffer # Outputs the screen with colors
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
<!-- SPDX-SnippetEnd -->
|
|
92
|
+
|
|
93
|
+
### buffer_content
|
|
94
|
+
|
|
95
|
+
Returns the terminal buffer as an array of strings (one per row). Use it for programmatic inspection.
|
|
96
|
+
|
|
97
|
+
<!-- SPDX-SnippetBegin -->
|
|
98
|
+
<!--
|
|
99
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
100
|
+
SPDX-License-Identifier: MIT-0
|
|
101
|
+
-->
|
|
102
|
+
```ruby
|
|
103
|
+
with_test_terminal do
|
|
104
|
+
MyApp.new.render
|
|
105
|
+
pp buffer_content # ["Line 1: ...", "Line 2: ...", ...]
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
<!-- SPDX-SnippetEnd -->
|
|
109
|
+
|
|
110
|
+
### get_cell
|
|
111
|
+
|
|
112
|
+
Returns a `Buffer::Cell` with the character, foreground color, background color, and modifiers at specific coordinates.
|
|
113
|
+
|
|
114
|
+
<!-- SPDX-SnippetBegin -->
|
|
115
|
+
<!--
|
|
116
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
117
|
+
SPDX-License-Identifier: MIT-0
|
|
118
|
+
-->
|
|
119
|
+
```ruby
|
|
120
|
+
with_test_terminal do
|
|
121
|
+
MyApp.new.render
|
|
122
|
+
cell = get_cell(0, 0)
|
|
123
|
+
pp cell.symbol # "H"
|
|
124
|
+
pp cell.fg # :red
|
|
125
|
+
pp cell.bold? # true
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
<!-- SPDX-SnippetEnd -->
|
|
129
|
+
|
|
130
|
+
## Protecting Output
|
|
131
|
+
|
|
132
|
+
During a TUI session, writes to `$stdout` or `$stderr` corrupt the display. Third-party gems often print warnings or debug output unexpectedly.
|
|
133
|
+
|
|
134
|
+
Use `guard_io` to temporarily swallow output from chatty code.
|
|
135
|
+
|
|
136
|
+
<!-- SPDX-SnippetBegin -->
|
|
137
|
+
<!--
|
|
138
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
139
|
+
SPDX-License-Identifier: MIT-0
|
|
140
|
+
-->
|
|
141
|
+
```ruby
|
|
142
|
+
RatatuiRuby.run do |tui|
|
|
143
|
+
RatatuiRuby.guard_io do
|
|
144
|
+
SomeChattyGem.process # Any puts/warn calls are swallowed
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
<!-- SPDX-SnippetEnd -->
|
|
149
|
+
|
|
150
|
+
## Interactive Debuggers
|
|
151
|
+
|
|
152
|
+
> [!WARNING]
|
|
153
|
+
> This section has not been verified by a human.
|
|
154
|
+
|
|
155
|
+
> [!CAUTION]
|
|
156
|
+
> Traditional interactive debuggers (Pry, IRB, debug.gem) do not work inside an active TUI session. They require terminal input and output, which conflicts with raw mode.
|
|
157
|
+
|
|
158
|
+
### Workarounds
|
|
159
|
+
|
|
160
|
+
**Temporarily exit TUI mode.** Restore the terminal, run your debugger, then re-initialize.
|
|
161
|
+
|
|
162
|
+
<!-- SPDX-SnippetBegin -->
|
|
163
|
+
<!--
|
|
164
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
165
|
+
SPDX-License-Identifier: MIT-0
|
|
166
|
+
-->
|
|
167
|
+
```ruby
|
|
168
|
+
RatatuiRuby.restore_terminal
|
|
169
|
+
binding.pry # Now Pry works normally
|
|
170
|
+
RatatuiRuby.init_terminal
|
|
171
|
+
```
|
|
172
|
+
<!-- SPDX-SnippetEnd -->
|
|
173
|
+
|
|
174
|
+
**Use test mode.** Debug rendering logic inside `with_test_terminal` where there is no real terminal conflict.
|
|
175
|
+
|
|
176
|
+
<!-- SPDX-SnippetBegin -->
|
|
177
|
+
<!--
|
|
178
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
179
|
+
SPDX-License-Identifier: MIT-0
|
|
180
|
+
-->
|
|
181
|
+
```ruby
|
|
182
|
+
with_test_terminal do
|
|
183
|
+
binding.pry # Works fine in test mode
|
|
184
|
+
MyApp.new.render
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
<!-- SPDX-SnippetEnd -->
|
|
188
|
+
|
|
189
|
+
## Remote Debugging
|
|
190
|
+
|
|
191
|
+
Debug mode uses [Ruby's `debug` gem](https://rubygems.org/gems/debug) for [remote debugging](https://github.com/ruby/debug?tab=readme-ov-file#readme). Attach from another terminal (or IDE or Chrome DevTools) while the TUI runs.
|
|
192
|
+
|
|
193
|
+

|
|
194
|
+
|
|
195
|
+
For a hands-on demo, see the [Debugging Showcase](../../examples/app_debugging_showcase/README.md) example.
|
|
196
|
+
|
|
197
|
+
Debug mode loads the `debug` gem and creates a UNIX domain socket. Debuggers attach from another terminal. This works well for TUI apps since the main terminal is in raw mode.
|
|
198
|
+
|
|
199
|
+
### How It Works
|
|
200
|
+
|
|
201
|
+
- **`RR_DEBUG=1`**: Loads `debug/open`. The app stops at startup and waits for a debugger to attach.
|
|
202
|
+
- **`RatatuiRuby.debug_mode!`**: Loads `debug/open_nonstop`. The app continues running. Attach whenever you want.
|
|
203
|
+
|
|
204
|
+
Attach from another terminal with `rdbg --attach`.
|
|
205
|
+
|
|
206
|
+
<!-- SPDX-SnippetBegin -->
|
|
207
|
+
<!--
|
|
208
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
209
|
+
SPDX-License-Identifier: MIT-0
|
|
210
|
+
-->
|
|
211
|
+
```sh
|
|
212
|
+
$ rdbg --attach
|
|
213
|
+
```
|
|
214
|
+
<!-- SPDX-SnippetEnd -->
|
|
215
|
+
|
|
216
|
+
> [!CAUTION]
|
|
217
|
+
> Remote debugging opens a backdoor to your application. This is a **security vulnerability**. The `debug/open_nonstop` mode is particularly dangerous because it allows attachment at any time. Do not run debug mode in production. Anyone who can access the socket can execute arbitrary code.
|
|
218
|
+
|
|
219
|
+
### Example: Debugging a Running TUI
|
|
220
|
+
|
|
221
|
+
Terminal 1 (your app):
|
|
222
|
+
<!-- SPDX-SnippetBegin -->
|
|
223
|
+
<!--
|
|
224
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
225
|
+
SPDX-License-Identifier: MIT-0
|
|
226
|
+
-->
|
|
227
|
+
```sh
|
|
228
|
+
$ ruby my_tui_app.rb
|
|
229
|
+
# App starts, TUI is running
|
|
230
|
+
# In your code: RatatuiRuby.debug_mode!
|
|
231
|
+
# Console shows: DEBUGGER: Debugger can attach via UNIX domain socket (...)
|
|
232
|
+
```
|
|
233
|
+
<!-- SPDX-SnippetEnd -->
|
|
234
|
+
|
|
235
|
+
Terminal 2 (debugger):
|
|
236
|
+
<!-- SPDX-SnippetBegin -->
|
|
237
|
+
<!--
|
|
238
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
239
|
+
SPDX-License-Identifier: MIT-0
|
|
240
|
+
-->
|
|
241
|
+
```sh
|
|
242
|
+
$ rdbg --attach
|
|
243
|
+
# Now you have a full debugger REPL
|
|
244
|
+
(rdbg) info locals
|
|
245
|
+
(rdbg) break MyApp#handle_key
|
|
246
|
+
(rdbg) continue
|
|
247
|
+
```
|
|
248
|
+
<!-- SPDX-SnippetEnd -->
|
|
249
|
+
|
|
250
|
+
### Requirements
|
|
251
|
+
|
|
252
|
+
Add the `debug` gem to your Gemfile:
|
|
253
|
+
|
|
254
|
+
<!-- SPDX-SnippetBegin -->
|
|
255
|
+
<!--
|
|
256
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
257
|
+
SPDX-License-Identifier: MIT-0
|
|
258
|
+
-->
|
|
259
|
+
```ruby
|
|
260
|
+
gem "debug", ">= 1.0"
|
|
261
|
+
```
|
|
262
|
+
<!-- SPDX-SnippetEnd -->
|
|
263
|
+
|
|
264
|
+
If `RR_DEBUG=1` is set but the debug gem is missing, RatatuiRuby raises a `LoadError` with installation instructions.
|
|
265
|
+
|
|
266
|
+
## File Logging
|
|
267
|
+
|
|
268
|
+
You can write debug output to a log file instead of stdout.
|
|
269
|
+
|
|
270
|
+
### Basic Logging
|
|
271
|
+
|
|
272
|
+
<!-- SPDX-SnippetBegin -->
|
|
273
|
+
<!--
|
|
274
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
275
|
+
SPDX-License-Identifier: MIT-0
|
|
276
|
+
-->
|
|
277
|
+
```ruby
|
|
278
|
+
DEBUG_LOG = File.open("debug.log", "a")
|
|
279
|
+
|
|
280
|
+
def debug(msg)
|
|
281
|
+
DEBUG_LOG.puts("[#{Time.now}] #{msg}")
|
|
282
|
+
DEBUG_LOG.flush
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
<!-- SPDX-SnippetEnd -->
|
|
286
|
+
|
|
287
|
+
Then tail the log in a separate terminal.
|
|
288
|
+
|
|
289
|
+
<!-- SPDX-SnippetBegin -->
|
|
290
|
+
<!--
|
|
291
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
292
|
+
SPDX-License-Identifier: MIT-0
|
|
293
|
+
-->
|
|
294
|
+
```bash
|
|
295
|
+
tail -f debug.log
|
|
296
|
+
```
|
|
297
|
+
<!-- SPDX-SnippetEnd -->
|
|
298
|
+
|
|
299
|
+
### Timestamped Logging
|
|
300
|
+
|
|
301
|
+
For high-frequency logging (like inside a render loop), use timestamped files to avoid overwrites:
|
|
302
|
+
|
|
303
|
+
<!-- SPDX-SnippetBegin -->
|
|
304
|
+
<!--
|
|
305
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
306
|
+
SPDX-License-Identifier: MIT-0
|
|
307
|
+
-->
|
|
308
|
+
```ruby
|
|
309
|
+
FileUtils.mkdir_p(File.join(Dir.tmpdir, "my_debug"))
|
|
310
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
|
|
311
|
+
File.write(
|
|
312
|
+
File.join(Dir.tmpdir, "my_debug", "#{timestamp}.log"),
|
|
313
|
+
"variable=#{value.inspect}\n"
|
|
314
|
+
)
|
|
315
|
+
```
|
|
316
|
+
<!-- SPDX-SnippetEnd -->
|
|
317
|
+
|
|
318
|
+
Then tail the directory.
|
|
319
|
+
|
|
320
|
+
<!-- SPDX-SnippetBegin -->
|
|
321
|
+
<!--
|
|
322
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
323
|
+
SPDX-License-Identifier: MIT-0
|
|
324
|
+
-->
|
|
325
|
+
```bash
|
|
326
|
+
watch -n 0.5 'ls -la /tmp/my_debug/ && cat /tmp/my_debug/*.log'
|
|
327
|
+
```
|
|
328
|
+
<!-- SPDX-SnippetEnd -->
|
|
329
|
+
|
|
330
|
+
## REPL Without the TUI
|
|
331
|
+
|
|
332
|
+
Unit tests verify correctness, but sometimes you want to poke at objects interactively. Wrap your main execution in a guard:
|
|
333
|
+
|
|
334
|
+
<!-- SPDX-SnippetBegin -->
|
|
335
|
+
<!--
|
|
336
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
337
|
+
SPDX-License-Identifier: MIT-0
|
|
338
|
+
-->
|
|
339
|
+
```ruby
|
|
340
|
+
if __FILE__ == $PROGRAM_NAME
|
|
341
|
+
MyApp.new.run
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
<!-- SPDX-SnippetEnd -->
|
|
345
|
+
|
|
346
|
+
Then load the file without entering raw mode.
|
|
347
|
+
|
|
348
|
+
<!-- SPDX-SnippetBegin -->
|
|
349
|
+
<!--
|
|
350
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
351
|
+
SPDX-License-Identifier: MIT-0
|
|
352
|
+
-->
|
|
353
|
+
```bash
|
|
354
|
+
ruby -e 'load "./bin/my_tui"; obj = MyClass.new; puts obj.result'
|
|
355
|
+
```
|
|
356
|
+
<!-- SPDX-SnippetEnd -->
|
|
357
|
+
|
|
358
|
+
This exercises domain logic without the terminal conflict. Use it for exploration. Write tests with [TestHelper](application_testing.md) for regression coverage.
|
|
359
|
+
|
|
360
|
+
## Isolating Terminal Issues
|
|
361
|
+
|
|
362
|
+
Sometimes code works in a `ruby -e` script but fails in the TUI. Here are common causes.
|
|
363
|
+
|
|
364
|
+
1. **Thread context.** Ruby threads share the process's terminal state.
|
|
365
|
+
2. **Raw mode.** External commands fail when stdin/stdout are reconfigured.
|
|
366
|
+
3. **SSH/Git auth.** Commands that prompt for credentials hang or return empty.
|
|
367
|
+
|
|
368
|
+
See [Async Operations](./async.md) for solutions.
|
|
369
|
+
|
|
370
|
+
## Error Classes
|
|
371
|
+
|
|
372
|
+
RatatuiRuby has semantic exception classes for different failure modes:
|
|
373
|
+
|
|
374
|
+
| Class | Meaning |
|
|
375
|
+
|-------|---------|
|
|
376
|
+
| `RatatuiRuby::Error::Terminal` | I/O failure (backend crashed, terminal unavailable) |
|
|
377
|
+
| `RatatuiRuby::Error::Safety` | Lifetime violation (using Frame after draw block exits) |
|
|
378
|
+
| `RatatuiRuby::Error::Invariant` | Contract violation (double init, headless mode conflict) |
|
|
379
|
+
|
|
380
|
+
Catch these specifically instead of rescuing `StandardError` broadly.
|
|
381
|
+
|
|
382
|
+
<!-- SPDX-SnippetBegin -->
|
|
383
|
+
<!--
|
|
384
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
385
|
+
SPDX-License-Identifier: MIT-0
|
|
386
|
+
-->
|
|
387
|
+
```ruby
|
|
388
|
+
begin
|
|
389
|
+
RatatuiRuby.run { |tui| ... }
|
|
390
|
+
rescue RatatuiRuby::Error::Terminal => e
|
|
391
|
+
puts "Terminal I/O failed: #{e.message}"
|
|
392
|
+
rescue RatatuiRuby::Error::Safety => e
|
|
393
|
+
puts "API misuse: #{e.message}"
|
|
394
|
+
end
|
|
395
|
+
```
|
|
396
|
+
<!-- SPDX-SnippetEnd -->
|
|
397
|
+
|
|
398
|
+
## Further Reading
|
|
399
|
+
|
|
400
|
+
- [Application Testing Guide](application_testing.md) — Test helpers, snapshots, event injection
|
|
401
|
+
- [RatatuiRuby::Debug](../../lib/ratatui_ruby/debug.rb) — Debug module source
|