ratatui_ruby 1.0.0 → 1.0.1
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/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +1 -232
- data/.builds/ruby-3.2.yml +0 -54
- data/.builds/ruby-3.3.yml +0 -54
- data/.builds/ruby-3.4.yml +0 -54
- data/.builds/ruby-4.0.0.yml +0 -54
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -10
- data/AGENTS.md +0 -146
- data/CHANGELOG.md +0 -710
- data/README.md +0 -187
- data/README.rdoc +0 -302
- data/Rakefile +0 -11
- data/Steepfile +0 -49
- data/doc/concepts/application_architecture.md +0 -321
- data/doc/concepts/application_testing.md +0 -193
- data/doc/concepts/async.md +0 -190
- data/doc/concepts/custom_widgets.md +0 -247
- data/doc/concepts/debugging.md +0 -401
- data/doc/concepts/event_handling.md +0 -162
- data/doc/concepts/interactive_design.md +0 -146
- data/doc/contributors/auditing/parity.md +0 -239
- data/doc/contributors/design/ruby_frontend.md +0 -420
- data/doc/contributors/design/rust_backend.md +0 -422
- data/doc/contributors/design.md +0 -11
- data/doc/contributors/developing_examples.md +0 -400
- data/doc/contributors/documentation_style.md +0 -121
- data/doc/contributors/index.md +0 -21
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
- data/doc/contributors/todo/align/terminal.md +0 -647
- data/doc/contributors/todo/future_work.md +0 -169
- data/doc/contributors/upstream_requests/tab_rects.md +0 -173
- data/doc/contributors/upstream_requests/title_rects.md +0 -132
- data/doc/custom.css +0 -22
- data/doc/getting_started/quickstart.md +0 -291
- data/doc/getting_started/why.md +0 -93
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_cli_rich_moments.gif +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/index.md +0 -39
- data/doc/troubleshooting/async.md +0 -4
- data/doc/troubleshooting/terminal_limitations.md +0 -131
- data/doc/troubleshooting/tui_output.md +0 -197
- data/examples/app_all_events/README.md +0 -114
- data/examples/app_all_events/app.rb +0 -98
- data/examples/app_all_events/model/app_model.rb +0 -159
- data/examples/app_all_events/model/event_color_cycle.rb +0 -43
- data/examples/app_all_events/model/event_entry.rb +0 -94
- data/examples/app_all_events/model/msg.rb +0 -39
- data/examples/app_all_events/model/timestamp.rb +0 -56
- data/examples/app_all_events/update.rb +0 -75
- data/examples/app_all_events/view/app_view.rb +0 -80
- data/examples/app_all_events/view/controls_view.rb +0 -54
- data/examples/app_all_events/view/counts_view.rb +0 -61
- data/examples/app_all_events/view/live_view.rb +0 -72
- data/examples/app_all_events/view/log_view.rb +0 -57
- data/examples/app_all_events/view.rb +0 -9
- data/examples/app_cli_rich_moments/README.md +0 -81
- data/examples/app_cli_rich_moments/app.rb +0 -189
- data/examples/app_color_picker/README.md +0 -156
- data/examples/app_color_picker/app.rb +0 -76
- data/examples/app_color_picker/clipboard.rb +0 -86
- data/examples/app_color_picker/color.rb +0 -193
- data/examples/app_color_picker/controls.rb +0 -92
- data/examples/app_color_picker/copy_dialog.rb +0 -168
- data/examples/app_color_picker/export_pane.rb +0 -128
- data/examples/app_color_picker/harmony.rb +0 -58
- data/examples/app_color_picker/input.rb +0 -176
- data/examples/app_color_picker/main_container.rb +0 -180
- data/examples/app_color_picker/palette.rb +0 -111
- data/examples/app_debugging_showcase/README.md +0 -119
- data/examples/app_debugging_showcase/app.rb +0 -318
- data/examples/app_login_form/README.md +0 -58
- data/examples/app_login_form/app.rb +0 -109
- data/examples/app_stateful_interaction/README.md +0 -35
- data/examples/app_stateful_interaction/app.rb +0 -328
- data/examples/timeout_demo.rb +0 -45
- data/examples/verify_quickstart_dsl/README.md +0 -55
- data/examples/verify_quickstart_dsl/app.rb +0 -49
- data/examples/verify_quickstart_layout/README.md +0 -77
- data/examples/verify_quickstart_layout/app.rb +0 -73
- data/examples/verify_quickstart_lifecycle/README.md +0 -68
- data/examples/verify_quickstart_lifecycle/app.rb +0 -62
- data/examples/verify_readme_usage/README.md +0 -49
- data/examples/verify_readme_usage/app.rb +0 -42
- data/examples/verify_website_managed/README.md +0 -48
- data/examples/verify_website_managed/app.rb +0 -36
- data/examples/verify_website_menu/README.md +0 -60
- data/examples/verify_website_menu/app.rb +0 -84
- data/examples/verify_website_spinner/README.md +0 -44
- data/examples/verify_website_spinner/app.rb +0 -34
- data/examples/widget_barchart/README.md +0 -58
- data/examples/widget_barchart/app.rb +0 -240
- data/examples/widget_block/README.md +0 -44
- data/examples/widget_block/app.rb +0 -258
- data/examples/widget_box/README.md +0 -54
- data/examples/widget_box/app.rb +0 -255
- data/examples/widget_calendar/README.md +0 -48
- data/examples/widget_calendar/app.rb +0 -115
- data/examples/widget_canvas/README.md +0 -31
- data/examples/widget_canvas/app.rb +0 -130
- data/examples/widget_cell/README.md +0 -45
- data/examples/widget_cell/app.rb +0 -112
- data/examples/widget_center/README.md +0 -33
- data/examples/widget_center/app.rb +0 -118
- data/examples/widget_chart/README.md +0 -50
- data/examples/widget_chart/app.rb +0 -220
- data/examples/widget_gauge/README.md +0 -50
- data/examples/widget_gauge/app.rb +0 -229
- data/examples/widget_layout_split/README.md +0 -53
- data/examples/widget_layout_split/app.rb +0 -260
- data/examples/widget_line_gauge/README.md +0 -50
- data/examples/widget_line_gauge/app.rb +0 -219
- data/examples/widget_list/README.md +0 -58
- data/examples/widget_list/app.rb +0 -384
- data/examples/widget_map/README.md +0 -48
- data/examples/widget_map/app.rb +0 -95
- data/examples/widget_overlay/README.md +0 -45
- data/examples/widget_overlay/app.rb +0 -250
- data/examples/widget_popup/README.md +0 -45
- data/examples/widget_popup/app.rb +0 -106
- data/examples/widget_ratatui_logo/README.md +0 -43
- data/examples/widget_ratatui_logo/app.rb +0 -104
- data/examples/widget_ratatui_mascot/README.md +0 -43
- data/examples/widget_ratatui_mascot/app.rb +0 -95
- data/examples/widget_rect/README.md +0 -53
- data/examples/widget_rect/app.rb +0 -222
- data/examples/widget_render/README.md +0 -46
- data/examples/widget_render/app.rb +0 -186
- data/examples/widget_render/app.rbs +0 -41
- data/examples/widget_rich_text/README.md +0 -44
- data/examples/widget_rich_text/app.rb +0 -193
- data/examples/widget_scroll_text/README.md +0 -46
- data/examples/widget_scroll_text/app.rb +0 -109
- data/examples/widget_scrollbar/README.md +0 -46
- data/examples/widget_scrollbar/app.rb +0 -155
- data/examples/widget_sparkline/README.md +0 -51
- data/examples/widget_sparkline/app.rb +0 -277
- data/examples/widget_style_colors/README.md +0 -43
- data/examples/widget_style_colors/app.rb +0 -83
- data/examples/widget_table/README.md +0 -57
- data/examples/widget_table/app.rb +0 -279
- data/examples/widget_tabs/README.md +0 -50
- data/examples/widget_tabs/app.rb +0 -183
- data/examples/widget_text_width/README.md +0 -44
- data/examples/widget_text_width/app.rb +0 -117
- data/migrate_to_buffer.rb +0 -145
- data/mise.toml +0 -8
- data/tasks/autodoc/examples.rb +0 -87
- data/tasks/autodoc/member.rb +0 -58
- data/tasks/autodoc/name.rb +0 -21
- data/tasks/autodoc.rake +0 -21
- data/tasks/bump/cargo_lockfile.rb +0 -21
- data/tasks/bump/changelog.rb +0 -47
- data/tasks/bump/header.rb +0 -32
- data/tasks/bump/history.rb +0 -32
- data/tasks/bump/links.rb +0 -69
- data/tasks/bump/manifest.rb +0 -33
- data/tasks/bump/ruby_gem.rb +0 -49
- data/tasks/bump/sem_ver.rb +0 -40
- data/tasks/bump/unreleased_section.rb +0 -56
- data/tasks/bump.rake +0 -51
- data/tasks/doc.rake +0 -887
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/extension.rake +0 -14
- data/tasks/license/headers_md.rb +0 -223
- data/tasks/license/headers_rb.rb +0 -210
- data/tasks/license/license_utils.rb +0 -130
- data/tasks/license/snippets_md.rb +0 -315
- data/tasks/license/snippets_rdoc.rb +0 -150
- data/tasks/license.rake +0 -91
- data/tasks/lint.rake +0 -170
- data/tasks/rdoc_config.rb +0 -29
- data/tasks/resources/build.yml.erb +0 -60
- data/tasks/resources/index.html.erb +0 -141
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/sourcehut.rake +0 -110
- data/tasks/steep.rake +0 -11
- data/tasks/terminal_preview/app_screenshot.rb +0 -45
- data/tasks/terminal_preview/crash_report.rb +0 -54
- data/tasks/terminal_preview/example_app.rb +0 -27
- data/tasks/terminal_preview/launcher_script.rb +0 -48
- data/tasks/terminal_preview/preview_collection.rb +0 -60
- data/tasks/terminal_preview/preview_timing.rb +0 -24
- data/tasks/terminal_preview/safety_confirmation.rb +0 -58
- data/tasks/terminal_preview/saved_screenshot.rb +0 -56
- data/tasks/terminal_preview/system_appearance.rb +0 -13
- data/tasks/terminal_preview/terminal_window.rb +0 -138
- data/tasks/terminal_preview/window_id.rb +0 -16
- data/tasks/terminal_preview.rake +0 -30
- data/tasks/test.rake +0 -33
- data/tasks/website/index_page.rb +0 -30
- data/tasks/website/version.rb +0 -127
- data/tasks/website/version_menu.rb +0 -68
- data/tasks/website/versioned_documentation.rb +0 -83
- data/tasks/website/website.rb +0 -53
- data/tasks/website.rake +0 -28
|
@@ -1,318 +0,0 @@
|
|
|
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
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Login Form Example
|
|
7
|
-
|
|
8
|
-
[](app.rb)
|
|
9
|
-
|
|
10
|
-
Demonstrates how to create a modal overlay for user input.
|
|
11
|
-
|
|
12
|
-
Many applications need to block interaction with the main UI while collecting specific information, like a login prompt or confirmation dialog. Managing the z-index and input focus for these overlays can be tricky.
|
|
13
|
-
|
|
14
|
-
This example solves this by using the `Overlay` widget to stack a centered popup on top of a base layer, conditionally rendering the popup based on state.
|
|
15
|
-
|
|
16
|
-
## Features Demonstrated
|
|
17
|
-
|
|
18
|
-
- **Overlays:** Stacking widgets on top of each other using `tui.overlay`.
|
|
19
|
-
- **Centering:** Positioning a widget in the center of the screen using `tui.center`.
|
|
20
|
-
- **State Management:** Switching between "Base" and "Popup" views.
|
|
21
|
-
- **Input Handling:** Capturing text input and handling specific keys (Enter, Esc) to trigger state changes.
|
|
22
|
-
- **Cursor Positioning:** Manually calculating cursor position within a `Paragraph`.
|
|
23
|
-
|
|
24
|
-
## Hotkeys
|
|
25
|
-
|
|
26
|
-
### Form Mode
|
|
27
|
-
- **Text Input**: Type to enter username (supports all characters including 'q').
|
|
28
|
-
- **Backspace**: Deletes the last character.
|
|
29
|
-
- **Enter**: Submits the form and opens the success popup.
|
|
30
|
-
- **Esc**: Quits the application.
|
|
31
|
-
- **Ctrl+C**: Quits the application.
|
|
32
|
-
|
|
33
|
-
### Popup Mode
|
|
34
|
-
- **q**: Closes the popup and quits the application.
|
|
35
|
-
- **Ctrl+C**: Quits the application.
|
|
36
|
-
|
|
37
|
-
## Usage
|
|
38
|
-
|
|
39
|
-
<!-- SPDX-SnippetBegin -->
|
|
40
|
-
<!--
|
|
41
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
42
|
-
SPDX-License-Identifier: MIT-0
|
|
43
|
-
-->
|
|
44
|
-
```bash
|
|
45
|
-
ruby examples/app_login_form/app.rb
|
|
46
|
-
```
|
|
47
|
-
<!-- SPDX-SnippetEnd -->
|
|
48
|
-
|
|
49
|
-
## Learning Outcomes
|
|
50
|
-
|
|
51
|
-
Use this example if you need to...
|
|
52
|
-
|
|
53
|
-
- Create a modal dialog or popup.
|
|
54
|
-
- Center a widget on the screen (vertically and horizontally).
|
|
55
|
-
- Implement a simple text input field with cursor management.
|
|
56
|
-
- layer widgets using the `Overlay` widget.
|
|
57
|
-
|
|
58
|
-
[Read the source code →](app.rb)
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
9
|
-
require "ratatui_ruby"
|
|
10
|
-
|
|
11
|
-
class AppLoginForm
|
|
12
|
-
PREFIX = "Enter Username: [ "
|
|
13
|
-
SUFFIX = " ]"
|
|
14
|
-
|
|
15
|
-
def initialize
|
|
16
|
-
@username = ""
|
|
17
|
-
@show_popup = false
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def run
|
|
21
|
-
RatatuiRuby.run do |tui|
|
|
22
|
-
@tui = tui
|
|
23
|
-
loop do
|
|
24
|
-
render
|
|
25
|
-
break if handle_input == :quit
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private def render
|
|
31
|
-
# 1. Base Layer Construction
|
|
32
|
-
# We want a cursor relative to the paragraph.
|
|
33
|
-
# So we wrap Paragraph and Cursor in an Overlay, and put that Overlay in a Center.
|
|
34
|
-
|
|
35
|
-
# Calculate cursor position
|
|
36
|
-
# Border takes 1 cell.
|
|
37
|
-
# Cursor X = 1 (border) + PREFIX.length + username.length
|
|
38
|
-
# Cursor Y = 1 (border + line 0)
|
|
39
|
-
cursor_x = 1 + PREFIX.length + @username.length
|
|
40
|
-
cursor_y = 1
|
|
41
|
-
|
|
42
|
-
# The content of the base form
|
|
43
|
-
form_content = @tui.overlay(layers: [
|
|
44
|
-
@tui.paragraph(
|
|
45
|
-
text: "#{PREFIX}#{@username}#{SUFFIX}",
|
|
46
|
-
block: @tui.block(borders: :all, title: "Login Form"),
|
|
47
|
-
alignment: :left
|
|
48
|
-
),
|
|
49
|
-
@tui.cursor(x: cursor_x, y: cursor_y),
|
|
50
|
-
])
|
|
51
|
-
|
|
52
|
-
# Center the form on screen
|
|
53
|
-
base_layer = @tui.center(
|
|
54
|
-
child: form_content,
|
|
55
|
-
width_percent: 50,
|
|
56
|
-
height_percent: 20
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# 2. Popup Layer Construction
|
|
60
|
-
final_view = if @show_popup
|
|
61
|
-
popup_message = @tui.center(
|
|
62
|
-
child: @tui.paragraph(
|
|
63
|
-
text: "Login Successful!\nPress 'q' to quit.",
|
|
64
|
-
style: @tui.style(fg: :green, bg: :black),
|
|
65
|
-
block: @tui.block(borders: :all),
|
|
66
|
-
alignment: :center,
|
|
67
|
-
wrap: true
|
|
68
|
-
),
|
|
69
|
-
width_percent: 30,
|
|
70
|
-
height_percent: 20
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
# Render Base Layer (background) THEN Popup Layer
|
|
74
|
-
@tui.overlay(layers: [base_layer, popup_message])
|
|
75
|
-
else
|
|
76
|
-
base_layer
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# 3. Draw
|
|
80
|
-
@tui.draw do |frame|
|
|
81
|
-
frame.render_widget(final_view, frame.area)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
private def handle_input
|
|
86
|
-
case @tui.poll_event
|
|
87
|
-
in { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
88
|
-
:quit
|
|
89
|
-
in { type: :key, code: "q" } if @show_popup
|
|
90
|
-
:quit
|
|
91
|
-
in { type: :key, code: "enter" }
|
|
92
|
-
@show_popup ||= true
|
|
93
|
-
nil
|
|
94
|
-
in { type: :key, code: "backspace" }
|
|
95
|
-
@username.chop! unless @show_popup
|
|
96
|
-
nil
|
|
97
|
-
in { type: :key, code: "esc" }
|
|
98
|
-
:quit unless @show_popup
|
|
99
|
-
in { type: :key, code:, modifiers: [] }
|
|
100
|
-
# Simple text input (single character, no modifiers)
|
|
101
|
-
@username += code if !@show_popup && code.length == 1
|
|
102
|
-
nil
|
|
103
|
-
else
|
|
104
|
-
nil
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
AppLoginForm.new.run if __FILE__ == $0
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
# Stateful Interaction Example
|
|
7
|
-
|
|
8
|
-
[](app.rb)
|
|
9
|
-
|
|
10
|
-
This example demonstrates High-Fidelity Interaction using **Stateful Widget Rendering**.
|
|
11
|
-
|
|
12
|
-
It showcases a "Database Viewer" layout where:
|
|
13
|
-
1. **Selection Persistence:** `ListState` and `TableState` objects persist across frames, maintaining selection without manual index tracking variables.
|
|
14
|
-
2. **Offset Read-back:** The application reads `state.offset` *after* rendering to know exactly which items were visible on screen.
|
|
15
|
-
3. **Mouse Interaction:** Using the read-back offset, we can calculate exactly which row was clicked, even when the specific item wasn't drawn at that absolute Y position due to scrolling.
|
|
16
|
-
|
|
17
|
-
## Key Concept: The "Read-back" Loop
|
|
18
|
-
|
|
19
|
-
Standard immediate-mode interaction often requires you to re-calculate layout logic to determine what was clicked.
|
|
20
|
-
|
|
21
|
-
In `ratatui_ruby`'s Stateful Rendering:
|
|
22
|
-
1. **Update**: You modify `state` (e.g., `state.select(1)`).
|
|
23
|
-
2. **Render**: You pass `state` to `render_stateful_widget`. Ratatui's Rust backend calculates layout and **updates** `state.offset` in-place if scrolling happened.
|
|
24
|
-
3. **Interact**: On the next event loop, you use `state.offset` to correctly map mouse coordinates to data indices.
|
|
25
|
-
|
|
26
|
-
## Hotkeys
|
|
27
|
-
|
|
28
|
-
| Key | Action |
|
|
29
|
-
| --- | --- |
|
|
30
|
-
| `↑` / `↓` | Scroll the active pane |
|
|
31
|
-
| `Tab` / `←` / `→` | Switch active pane (List vs Table) |
|
|
32
|
-
| `Mouse Click` | Select the clicked row (Works with scrolling!) |
|
|
33
|
-
| `q` | Quit |
|
|
34
|
-
|
|
35
|
-
[Read the source code →](app.rb)
|