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
|
@@ -258,20 +258,25 @@ These larger examples combine widgets into complete applications, demonstrating
|
|
|
258
258
|
|-------------|--------------|-------------------|
|
|
259
259
|
| [All Events](../examples/app_all_events/app.rb) | Model-View-Update | Event handling, unidirectional data flow, scalable structure |
|
|
260
260
|
| [Color Picker](../examples/app_color_picker/app.rb) | Component-Based | Hit testing, modal dialogs, encapsulated state |
|
|
261
|
+
| [Debugging Showcase](../examples/app_debugging_showcase/app.rb) | Simple Loop | Remote debugging, Rust backtraces, improved error messages |
|
|
261
262
|
| [Login Form](../examples/app_login_form/app.rb) | Overlay + Center | Modal forms, cursor positioning, text input |
|
|
262
263
|
| [Stateful Interaction](../examples/app_stateful_interaction/app.rb) | State Objects | ListState/TableState, offset read-back, mouse click-to-row |
|
|
263
264
|
|
|
264
265
|
#### All Events
|
|
265
266
|
|
|
266
|
-
[](../examples/app_all_events/README.md)
|
|
267
268
|
|
|
268
269
|
#### Color Picker
|
|
269
270
|
|
|
270
|
-
[](../examples/app_color_picker/README.md)
|
|
272
|
+
|
|
273
|
+
#### Debugging Showcase
|
|
274
|
+
|
|
275
|
+
[](../examples/app_debugging_showcase/README.md)
|
|
271
276
|
|
|
272
277
|
#### Login Form
|
|
273
278
|
|
|
274
|
-
[](../examples/app_login_form/README.md)
|
|
275
280
|
|
|
276
281
|
|
|
277
282
|
## Next Steps
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/doc/images/widget_block.png
CHANGED
|
Binary file
|
data/doc/images/widget_box.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/doc/images/widget_cell.png
CHANGED
|
Binary file
|
|
Binary file
|
data/doc/images/widget_chart.png
CHANGED
|
Binary file
|
data/doc/images/widget_gauge.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/doc/images/widget_list.png
CHANGED
|
Binary file
|
data/doc/images/widget_map.png
CHANGED
|
Binary file
|
|
Binary file
|
data/doc/images/widget_popup.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/doc/images/widget_rect.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/doc/images/widget_table.png
CHANGED
|
Binary file
|
data/doc/images/widget_tabs.png
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Debugging Showcase
|
|
7
|
+
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
10
|
+
Interactive demonstration of RatatuiRuby's debugging features.
|
|
11
|
+
|
|
12
|
+
For comprehensive documentation, see the [Debugging Guide](../../doc/concepts/debugging.md).
|
|
13
|
+
|
|
14
|
+
## What This Example Does
|
|
15
|
+
|
|
16
|
+
This app lets you trigger each debugging feature with a hotkey. Test your setup before encountering a real bug.
|
|
17
|
+
|
|
18
|
+
| Key | Action |
|
|
19
|
+
|-----|--------|
|
|
20
|
+
| `d` | Enable `debug_mode!` programmatically |
|
|
21
|
+
| `p` | Trigger a Rust panic |
|
|
22
|
+
| `t` | Trigger a Rust `TypeError` |
|
|
23
|
+
| `b` | Refresh debug status |
|
|
24
|
+
| `q` | Quit |
|
|
25
|
+
|
|
26
|
+
## Library Features Showcased
|
|
27
|
+
|
|
28
|
+
Reading this code will teach you how to:
|
|
29
|
+
|
|
30
|
+
* **Enable Remote Debugging**: Call `RatatuiRuby.debug_mode!` and receive the socket path
|
|
31
|
+
* **Detect Debug State**: Query `Debug.enabled?`, `Debug.rust_backtrace_enabled?`, and `Debug.remote_debugging_mode`
|
|
32
|
+
* **Trigger Test Panics**: Use `Debug.test_panic!` to verify Rust backtrace visibility
|
|
33
|
+
* **See Improved Error Messages**: Bypass DWIM coercion to see `TypeError` messages with value context
|
|
34
|
+
|
|
35
|
+
**Note:** Rust backtraces only appear for panics (`[p]`). Exceptions (`[t]`) show Ruby backtraces with improved error messages from Rust.
|
|
36
|
+
|
|
37
|
+
## Environment Variables
|
|
38
|
+
|
|
39
|
+
The example behaves differently depending on which environment variables you set.
|
|
40
|
+
|
|
41
|
+
### No Environment Variables
|
|
42
|
+
|
|
43
|
+
<!-- SPDX-SnippetBegin -->
|
|
44
|
+
<!--
|
|
45
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
46
|
+
SPDX-License-Identifier: MIT-0
|
|
47
|
+
-->
|
|
48
|
+
```bash
|
|
49
|
+
ruby examples/app_debugging_showcase/app.rb
|
|
50
|
+
```
|
|
51
|
+
<!-- SPDX-SnippetEnd -->
|
|
52
|
+
|
|
53
|
+
The TUI starts normally. Rust backtraces are disabled. Press `[d]` to enable remote debugging mid-session.
|
|
54
|
+
|
|
55
|
+
### `RUST_BACKTRACE=1`
|
|
56
|
+
|
|
57
|
+
<!-- SPDX-SnippetBegin -->
|
|
58
|
+
<!--
|
|
59
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
60
|
+
SPDX-License-Identifier: MIT-0
|
|
61
|
+
-->
|
|
62
|
+
```bash
|
|
63
|
+
RUST_BACKTRACE=1 ruby examples/app_debugging_showcase/app.rb
|
|
64
|
+
```
|
|
65
|
+
<!-- SPDX-SnippetEnd -->
|
|
66
|
+
|
|
67
|
+
Enables Rust backtraces. Press `[p]` to see the full Rust stack trace with file names and line numbers.
|
|
68
|
+
|
|
69
|
+
### `RR_DEBUG=1`
|
|
70
|
+
|
|
71
|
+
<!-- SPDX-SnippetBegin -->
|
|
72
|
+
<!--
|
|
73
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
74
|
+
SPDX-License-Identifier: MIT-0
|
|
75
|
+
-->
|
|
76
|
+
```bash
|
|
77
|
+
RR_DEBUG=1 ruby examples/app_debugging_showcase/app.rb
|
|
78
|
+
```
|
|
79
|
+
<!-- SPDX-SnippetEnd -->
|
|
80
|
+
|
|
81
|
+
Full debug mode at startup. The debugger opens a socket and **waits for a connection** before the TUI starts. You'll see the socket path printed before the app runs.
|
|
82
|
+
|
|
83
|
+
In another terminal:
|
|
84
|
+
|
|
85
|
+
<!-- SPDX-SnippetBegin -->
|
|
86
|
+
<!--
|
|
87
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
88
|
+
SPDX-License-Identifier: MIT-0
|
|
89
|
+
-->
|
|
90
|
+
```bash
|
|
91
|
+
rdbg --attach
|
|
92
|
+
```
|
|
93
|
+
<!-- SPDX-SnippetEnd -->
|
|
94
|
+
|
|
95
|
+
When you attach, you'll see "Stop by SIGURG" — that's normal! SIGURG is how the debug gem signals the app to pause. Type `continue` to resume.
|
|
96
|
+
|
|
97
|
+
This uses the [ruby/debug](https://github.com/ruby/debug?tab=readme-ov-file#readme) gem for remote debugging.
|
|
98
|
+
|
|
99
|
+
### When to Use Which
|
|
100
|
+
|
|
101
|
+
`RR_DEBUG=1` includes Rust backtraces automatically (it calls `enable_rust_backtrace!` internally).
|
|
102
|
+
|
|
103
|
+
Use `RUST_BACKTRACE=1` alone when you want backtraces but **don't** want the debugger to stop and wait for a connection at startup.
|
|
104
|
+
|
|
105
|
+
## What Problems Does This Solve?
|
|
106
|
+
|
|
107
|
+
### "Is my Rust backtrace setup working?"
|
|
108
|
+
|
|
109
|
+
Press `[p]`. If you see a stack trace with file names and line numbers, you're good. If not, you need `RUST_BACKTRACE=1`.
|
|
110
|
+
|
|
111
|
+
### "How do I attach a debugger to a TUI?"
|
|
112
|
+
|
|
113
|
+
TUIs run in raw mode — you can't type into a REPL. Press `[d]` to enable remote debugging. The socket path appears in the UI. Run `rdbg --attach` from another terminal.
|
|
114
|
+
|
|
115
|
+
### "What does a Rust TypeError look like?"
|
|
116
|
+
|
|
117
|
+
Press `[t]`. The error message shows the expected type and the actual value you passed (e.g., `expected array for rows, got 42`). Note: This shows Ruby's backtrace — only panics (`[p]`) show Rust backtraces.
|
|
118
|
+
|
|
119
|
+
[Read the source code →](app.rb)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: MIT-0
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
+
|
|
10
|
+
require "ratatui_ruby"
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Interactive demonstration of RatatuiRuby debugging features.
|
|
14
|
+
#
|
|
15
|
+
# This example lets you trigger each debugging feature with a hotkey to verify
|
|
16
|
+
# your setup works before encountering a real bug.
|
|
17
|
+
#
|
|
18
|
+
# == Hotkeys
|
|
19
|
+
#
|
|
20
|
+
# [d] Enable debug_mode! — Shows the debug socket path for remote attachment
|
|
21
|
+
# [p] Trigger test_panic! — Deliberately crashes to verify Rust backtrace visibility
|
|
22
|
+
# [t] Cause TypeError — Passes wrong type to widget factory to show Rust stack frames
|
|
23
|
+
# [b] Show backtrace status — Displays current debug configuration
|
|
24
|
+
# [q] Quit
|
|
25
|
+
#
|
|
26
|
+
# == Usage
|
|
27
|
+
#
|
|
28
|
+
# # Normal mode (no backtraces):
|
|
29
|
+
# ruby examples/verify_debugging_usage/app.rb
|
|
30
|
+
#
|
|
31
|
+
# # With Rust backtraces only:
|
|
32
|
+
# RUST_BACKTRACE=1 ruby examples/verify_debugging_usage/app.rb
|
|
33
|
+
#
|
|
34
|
+
# # Full debug mode (stops at startup for debugger attachment):
|
|
35
|
+
# RR_DEBUG=1 ruby examples/verify_debugging_usage/app.rb
|
|
36
|
+
#
|
|
37
|
+
# == Remote Debugging
|
|
38
|
+
#
|
|
39
|
+
# When you press [d] to enable debug_mode!, the app continues running but
|
|
40
|
+
# prints a socket path. From another terminal:
|
|
41
|
+
#
|
|
42
|
+
# rdbg --attach
|
|
43
|
+
#
|
|
44
|
+
# This gives you a full debugger REPL while the TUI keeps running.
|
|
45
|
+
class VerifyDebuggingUsage
|
|
46
|
+
def initialize
|
|
47
|
+
@status_message = "Press a key to test debugging features"
|
|
48
|
+
@show_debug_info = false
|
|
49
|
+
@quit = false
|
|
50
|
+
|
|
51
|
+
# If debug mode was enabled via RR_DEBUG=1 at startup, capture the socket path
|
|
52
|
+
if RatatuiRuby::Debug.enabled?
|
|
53
|
+
@socket_path = begin
|
|
54
|
+
::DEBUGGER__.create_unix_domain_socket_name
|
|
55
|
+
rescue NameError
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
@show_debug_info = true
|
|
59
|
+
@status_message = "RR_DEBUG=1 detected — debug mode active"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def run
|
|
64
|
+
RatatuiRuby.run do |tui|
|
|
65
|
+
@tui = tui
|
|
66
|
+
@loop_count = 0
|
|
67
|
+
|
|
68
|
+
loop do
|
|
69
|
+
@loop_count += 1
|
|
70
|
+
|
|
71
|
+
# 🎯 Breakpoint every 250 loops. Try: p @status_message
|
|
72
|
+
if RatatuiRuby::Debug.enabled? && (@loop_count % 250).zero?
|
|
73
|
+
you_found_me = "🎉 You found me! Loop ##{@loop_count}"
|
|
74
|
+
# rubocop:disable Lint/Debugger
|
|
75
|
+
debugger
|
|
76
|
+
# rubocop:enable Lint/Debugger
|
|
77
|
+
_ = you_found_me # Suppress unused variable warning
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
render
|
|
81
|
+
break if @quit || handle_input == :quit
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private def render
|
|
87
|
+
@tui.draw do |frame|
|
|
88
|
+
constraints = [
|
|
89
|
+
@tui.constraint_length(3), # Status
|
|
90
|
+
@tui.constraint_length(5), # Config
|
|
91
|
+
@tui.constraint_length(6), # Actions
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
if @show_debug_info
|
|
95
|
+
constraints << @tui.constraint_length(6) # Debug info
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
constraints << @tui.constraint_fill(1) # Spacer
|
|
99
|
+
constraints << @tui.constraint_length(3) # Help
|
|
100
|
+
|
|
101
|
+
chunks = @tui.layout_split(frame.area, direction: :vertical, constraints:)
|
|
102
|
+
|
|
103
|
+
idx = 0
|
|
104
|
+
render_status(frame, chunks[idx])
|
|
105
|
+
idx += 1
|
|
106
|
+
render_config(frame, chunks[idx])
|
|
107
|
+
idx += 1
|
|
108
|
+
render_actions(frame, chunks[idx])
|
|
109
|
+
idx += 1
|
|
110
|
+
|
|
111
|
+
if @show_debug_info
|
|
112
|
+
render_debug_info(frame, chunks[idx])
|
|
113
|
+
idx += 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Skip spacer
|
|
117
|
+
idx += 1
|
|
118
|
+
render_help(frame, chunks[idx])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private def render_status(frame, area)
|
|
123
|
+
frame.render_widget(
|
|
124
|
+
@tui.paragraph(
|
|
125
|
+
text: @status_message,
|
|
126
|
+
alignment: :center,
|
|
127
|
+
block: @tui.block(
|
|
128
|
+
title: " Status ",
|
|
129
|
+
title_alignment: :center,
|
|
130
|
+
borders: [:all],
|
|
131
|
+
border_style: { fg: :yellow }
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
area
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private def render_config(frame, area)
|
|
139
|
+
config_lines = [
|
|
140
|
+
"Rust Backtraces: #{flag(RatatuiRuby::Debug.rust_backtrace_enabled?)}",
|
|
141
|
+
"Full Debug Mode: #{flag(RatatuiRuby::Debug.enabled?)}",
|
|
142
|
+
"Remote Debugging: #{remote_mode_description}",
|
|
143
|
+
].join("\n")
|
|
144
|
+
|
|
145
|
+
frame.render_widget(
|
|
146
|
+
@tui.paragraph(
|
|
147
|
+
text: config_lines,
|
|
148
|
+
block: @tui.block(
|
|
149
|
+
title: " Current Debug Configuration ",
|
|
150
|
+
borders: [:all],
|
|
151
|
+
border_style: { fg: :cyan }
|
|
152
|
+
)
|
|
153
|
+
),
|
|
154
|
+
area
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private def render_actions(frame, area)
|
|
159
|
+
actions_lines = [
|
|
160
|
+
"[d] Enable debug_mode! and show socket info",
|
|
161
|
+
"[p] Trigger test_panic! to verify backtrace visibility",
|
|
162
|
+
"[t] Cause TypeError (pass wrong type to widget)",
|
|
163
|
+
"[b] Refresh debug status",
|
|
164
|
+
].join("\n")
|
|
165
|
+
|
|
166
|
+
frame.render_widget(
|
|
167
|
+
@tui.paragraph(
|
|
168
|
+
text: actions_lines,
|
|
169
|
+
block: @tui.block(
|
|
170
|
+
title: " Available Actions ",
|
|
171
|
+
borders: [:all],
|
|
172
|
+
border_style: { fg: :green }
|
|
173
|
+
)
|
|
174
|
+
),
|
|
175
|
+
area
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private def render_debug_info(frame, area)
|
|
180
|
+
socket_display = @socket_path || "(socket not available)"
|
|
181
|
+
info_lines = [
|
|
182
|
+
"Socket: #{socket_display}",
|
|
183
|
+
"Attach: rdbg --attach",
|
|
184
|
+
"Hint: type 'continue' if you see SIGURG",
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
frame.render_widget(
|
|
188
|
+
@tui.paragraph(
|
|
189
|
+
text: info_lines.join("\n"),
|
|
190
|
+
block: @tui.block(
|
|
191
|
+
title: " Remote Debugging ",
|
|
192
|
+
borders: [:all],
|
|
193
|
+
border_style: { fg: :magenta }
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
area
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private def render_help(frame, area)
|
|
201
|
+
frame.render_widget(
|
|
202
|
+
@tui.paragraph(
|
|
203
|
+
text: "[d] debug_mode! [p] test_panic! [t] TypeError [b] status [q] quit",
|
|
204
|
+
alignment: :center,
|
|
205
|
+
block: @tui.block(
|
|
206
|
+
borders: [:all],
|
|
207
|
+
border_style: { fg: :dark_gray }
|
|
208
|
+
)
|
|
209
|
+
),
|
|
210
|
+
area
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private def flag(value)
|
|
215
|
+
value ? "✓ enabled" : "✗ disabled"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private def remote_mode_description
|
|
219
|
+
case RatatuiRuby::Debug.remote_debugging_mode
|
|
220
|
+
when :open
|
|
221
|
+
attached = debugger_attached? ? " — ATTACHED" : " — waiting"
|
|
222
|
+
"✓ open#{attached}"
|
|
223
|
+
when :open_nonstop
|
|
224
|
+
attached = debugger_attached? ? " — ATTACHED" : ""
|
|
225
|
+
"✓ open_nonstop#{attached}"
|
|
226
|
+
else
|
|
227
|
+
"✗ not configured"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# ☣️ FRAGILE: This pokes at debug gem internals.
|
|
232
|
+
#
|
|
233
|
+
# Private instance variables can change between gem versions. This code
|
|
234
|
+
# may silently break. We accept that risk here because this showcase
|
|
235
|
+
# exists specifically to demonstrate debugger attachment status.
|
|
236
|
+
#
|
|
237
|
+
# For production apps, checking Debug.enabled? is sufficient — knowing
|
|
238
|
+
# whether a client has attached rarely matters.
|
|
239
|
+
private def debugger_attached?
|
|
240
|
+
return false unless defined?(::DEBUGGER__::SESSION)
|
|
241
|
+
|
|
242
|
+
ui = ::DEBUGGER__::SESSION.instance_variable_get(:@ui)
|
|
243
|
+
return false unless ui
|
|
244
|
+
|
|
245
|
+
# The @sock instance variable is set when a client connects
|
|
246
|
+
sock = ui.instance_variable_get(:@sock)
|
|
247
|
+
!sock.nil?
|
|
248
|
+
rescue
|
|
249
|
+
false
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private def handle_input
|
|
253
|
+
case @tui.poll_event
|
|
254
|
+
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
255
|
+
:quit
|
|
256
|
+
|
|
257
|
+
in { type: :key, code: "d" }
|
|
258
|
+
enable_debug_mode!
|
|
259
|
+
|
|
260
|
+
in { type: :key, code: "p" }
|
|
261
|
+
trigger_test_panic!
|
|
262
|
+
|
|
263
|
+
in { type: :key, code: "t" }
|
|
264
|
+
trigger_type_error!
|
|
265
|
+
|
|
266
|
+
in { type: :key, code: "b" }
|
|
267
|
+
@status_message = "Debug status refreshed at #{Time.now.strftime('%H:%M:%S')}"
|
|
268
|
+
|
|
269
|
+
else
|
|
270
|
+
nil
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
private def enable_debug_mode!
|
|
275
|
+
if RatatuiRuby::Debug.enabled?
|
|
276
|
+
@status_message = "Debug mode already enabled!"
|
|
277
|
+
else
|
|
278
|
+
# debug_mode! returns the socket path and suppresses the debug gem's output
|
|
279
|
+
@socket_path = RatatuiRuby.debug_mode!
|
|
280
|
+
@status_message = "debug_mode! enabled"
|
|
281
|
+
@show_debug_info = true
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
private def trigger_test_panic!
|
|
286
|
+
if RatatuiRuby::Debug.rust_backtrace_enabled?
|
|
287
|
+
@status_message = "Triggering test_panic! — check stderr for backtrace..."
|
|
288
|
+
else
|
|
289
|
+
@status_message = "Triggering test_panic! — backtrace hidden (set RUST_BACKTRACE=1)"
|
|
290
|
+
end
|
|
291
|
+
render # Show the message before crashing
|
|
292
|
+
|
|
293
|
+
# Give a moment for the render to complete
|
|
294
|
+
sleep 0.1
|
|
295
|
+
|
|
296
|
+
# This will crash the app with a Rust panic. If RUST_BACKTRACE=1 or
|
|
297
|
+
# debug mode is enabled, you'll see the full Rust stack trace after
|
|
298
|
+
# the terminal is restored.
|
|
299
|
+
RatatuiRuby::Debug.test_panic!
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
private def trigger_type_error!
|
|
303
|
+
if RatatuiRuby::Debug.rust_backtrace_enabled?
|
|
304
|
+
@status_message = "Triggering TypeError — check stderr for error message..."
|
|
305
|
+
else
|
|
306
|
+
@status_message = "Triggering TypeError — set RUST_BACKTRACE=1 for stack trace"
|
|
307
|
+
end
|
|
308
|
+
render # Show the message before crashing
|
|
309
|
+
sleep 0.1
|
|
310
|
+
|
|
311
|
+
# Bypass the factory's DWIM coercion to trigger a real Rust TypeError.
|
|
312
|
+
# Uses Widgets::Table.new directly with invalid rows type.
|
|
313
|
+
bad_table = RatatuiRuby::Widgets::Table.new(rows: 42, widths: [])
|
|
314
|
+
@tui.draw { |f| f.render_widget(bad_table, f.area) }
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
VerifyDebuggingUsage.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -37,24 +37,26 @@ class WidgetCanvas
|
|
|
37
37
|
|
|
38
38
|
private def render
|
|
39
39
|
@tui.draw do |frame|
|
|
40
|
-
# Define shapes
|
|
40
|
+
# Define shapes using terse aliases (circle, rectangle, point, map, label)
|
|
41
|
+
# These are shorter forms of shape_circle, shape_rectangle, etc.
|
|
41
42
|
shapes = []
|
|
42
43
|
|
|
43
|
-
# 1. Static Grid (Lines)
|
|
44
|
+
# 1. Static Grid (Lines) - using shape_line (no terse alias for line)
|
|
44
45
|
(-100..100).step(20) do |i|
|
|
45
46
|
shapes << @tui.shape_line(x1: i.to_f, y1: -100.0, x2: i.to_f, y2: 100.0, color: :gray)
|
|
46
47
|
shapes << @tui.shape_line(x1: -100.0, y1: i.to_f, x2: 100.0, y2: i.to_f, color: :gray)
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
# 2. Moving Circle (The "Player")
|
|
50
|
-
shapes << @tui.
|
|
50
|
+
# 2. Moving Circle (The "Player") - using terse 'circle' alias
|
|
51
|
+
shapes << @tui.circle(
|
|
51
52
|
x: @x_offset,
|
|
52
53
|
y: @y_offset,
|
|
53
54
|
radius: 10.0,
|
|
54
55
|
color: :green
|
|
55
56
|
)
|
|
56
57
|
|
|
57
|
-
# 3. Static Rectangle (Target)
|
|
58
|
+
# 3. Static Rectangle (Target) - using shape_rectangle (no 'rectangle' alias
|
|
59
|
+
# to avoid confusion with Layout::Rect)
|
|
58
60
|
shapes << @tui.shape_rectangle(
|
|
59
61
|
x: 30.0,
|
|
60
62
|
y: 30.0,
|
|
@@ -63,16 +65,16 @@ class WidgetCanvas
|
|
|
63
65
|
color: :red
|
|
64
66
|
)
|
|
65
67
|
|
|
66
|
-
# 4. Points (Starfield)
|
|
68
|
+
# 4. Points (Starfield) - using terse 'point' alias
|
|
67
69
|
# Deterministic "random" points
|
|
68
70
|
10.times do |i|
|
|
69
|
-
shapes << @tui.
|
|
71
|
+
shapes << @tui.point(
|
|
70
72
|
x: ((i * 37) % 200) - 100.0,
|
|
71
73
|
y: ((i * 19) % 200) - 100.0
|
|
72
74
|
)
|
|
73
75
|
end
|
|
74
76
|
|
|
75
|
-
# 5.
|
|
77
|
+
# 5. Connecting line from origin to player position
|
|
76
78
|
shapes << @tui.shape_line(x1: 0.0, y1: 0.0, x2: @x_offset, y2: @y_offset, color: :yellow)
|
|
77
79
|
|
|
78
80
|
canvas = @tui.canvas(
|
|
@@ -89,21 +91,24 @@ class WidgetCanvas
|
|
|
89
91
|
direction: :vertical,
|
|
90
92
|
constraints: [
|
|
91
93
|
@tui.constraint_fill(1),
|
|
92
|
-
@tui.constraint_length(
|
|
94
|
+
@tui.constraint_length(2),
|
|
93
95
|
]
|
|
94
96
|
)
|
|
95
97
|
|
|
96
98
|
frame.render_widget(canvas, layout[0])
|
|
97
99
|
|
|
98
|
-
#
|
|
100
|
+
# Query: Canvas#get_point maps canvas coordinates to normalized [0.0, 1.0] grid
|
|
101
|
+
normalized = canvas.get_point(@x_offset, @y_offset)
|
|
102
|
+
norm_str = normalized ? format("[%.2f, %.2f]", normalized[0], normalized[1]) : "nil"
|
|
103
|
+
|
|
104
|
+
# Controls showing query method demonstration (single concise line)
|
|
99
105
|
controls = @tui.paragraph(
|
|
100
106
|
text: [
|
|
101
|
-
@tui.text_line(spans: [
|
|
102
|
-
@tui.text_span(content: "Canvas auto-animates.", style: @tui.style(fg: :yellow)),
|
|
103
|
-
]),
|
|
104
107
|
@tui.text_line(spans: [
|
|
105
108
|
@tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])),
|
|
106
|
-
@tui.text_span(content: ": Quit"),
|
|
109
|
+
@tui.text_span(content: ": Quit "),
|
|
110
|
+
@tui.text_span(content: "get_point → ", style: @tui.style(fg: :dark_gray)),
|
|
111
|
+
@tui.text_span(content: norm_str, style: @tui.style(fg: :cyan)),
|
|
107
112
|
]),
|
|
108
113
|
],
|
|
109
114
|
block: @tui.block(borders: [:top])
|