ratatui_ruby 0.9.1 → 0.10.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/.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 +113 -0
- data/README.md +17 -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 +42 -11
- 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 +84 -5
- 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
|
@@ -96,5 +96,56 @@ module RatatuiRuby
|
|
|
96
96
|
# Scrolls up by +n+ rows.
|
|
97
97
|
#
|
|
98
98
|
# (Native method implemented in Rust)
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# :method: selected_cell
|
|
102
|
+
# :call-seq: selected_cell() -> Array or nil
|
|
103
|
+
#
|
|
104
|
+
# Returns the currently selected cell as <tt>[row, column]</tt>.
|
|
105
|
+
# Returns +nil+ if either row or column is not selected.
|
|
106
|
+
#
|
|
107
|
+
# (Native method implemented in Rust)
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# :method: select_next_column
|
|
111
|
+
# :call-seq: select_next_column() -> nil
|
|
112
|
+
#
|
|
113
|
+
# Selects the next column, or column 0 if none selected.
|
|
114
|
+
#
|
|
115
|
+
# (Native method implemented in Rust)
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# :method: select_previous_column
|
|
119
|
+
# :call-seq: select_previous_column() -> nil
|
|
120
|
+
#
|
|
121
|
+
# Selects the previous column. Saturates at 0.
|
|
122
|
+
#
|
|
123
|
+
# (Native method implemented in Rust)
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# :method: select_first_column
|
|
127
|
+
# :call-seq: select_first_column() -> nil
|
|
128
|
+
#
|
|
129
|
+
# Selects column 0.
|
|
130
|
+
#
|
|
131
|
+
# (Native method implemented in Rust)
|
|
132
|
+
|
|
133
|
+
##
|
|
134
|
+
# :method: select_last_column
|
|
135
|
+
# :call-seq: select_last_column() -> nil
|
|
136
|
+
#
|
|
137
|
+
# Selects the last column. The index is clamped during rendering.
|
|
138
|
+
#
|
|
139
|
+
# (Native method implemented in Rust)
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# :singleton-method: with_selected_cell
|
|
143
|
+
# :call-seq: with_selected_cell(cell) -> TableState
|
|
144
|
+
#
|
|
145
|
+
# Creates a new TableState with both row and column selected.
|
|
146
|
+
#
|
|
147
|
+
# [cell] <tt>[row, column]</tt> array, or +nil+.
|
|
148
|
+
#
|
|
149
|
+
# (Native method implemented in Rust)
|
|
99
150
|
end
|
|
100
151
|
end
|
|
@@ -90,7 +90,7 @@ module RatatuiRuby
|
|
|
90
90
|
##
|
|
91
91
|
# Restores the terminal to its original state.
|
|
92
92
|
# Leaves alternate screen and disables raw mode.
|
|
93
|
-
# Also flushes any deferred warnings that were queued during the session.
|
|
93
|
+
# Also flushes any deferred warnings and panic info that were queued during the session.
|
|
94
94
|
#
|
|
95
95
|
# In headless mode ({headless!}), this method is a silent no-op since
|
|
96
96
|
# no terminal was ever initialized.
|
|
@@ -103,6 +103,7 @@ module RatatuiRuby
|
|
|
103
103
|
ensure
|
|
104
104
|
@tui_session_active = false
|
|
105
105
|
flush_warnings
|
|
106
|
+
flush_panic_info
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
##
|
|
@@ -64,7 +64,7 @@ module RatatuiRuby
|
|
|
64
64
|
# [event] A <tt>RatatuiRuby::Event</tt> object.
|
|
65
65
|
def inject_event(event)
|
|
66
66
|
unless @_ratatui_test_terminal_active
|
|
67
|
-
raise "Events must be injected inside a `with_test_terminal` block. " \
|
|
67
|
+
raise RatatuiRuby::Error::Invariant, "Events must be injected inside a `with_test_terminal` block. " \
|
|
68
68
|
"Calling this method outside the block causes a race condition where the event " \
|
|
69
69
|
"is flushed before the application starts."
|
|
70
70
|
end
|
|
@@ -209,6 +209,11 @@ module RatatuiRuby
|
|
|
209
209
|
# pending async operations to complete before processing the next event.
|
|
210
210
|
# This enables deterministic testing of async behavior.
|
|
211
211
|
#
|
|
212
|
+
# *Important*: Sync waits for commands to _complete_. Do not use it
|
|
213
|
+
# with long-running commands that wait indefinitely (e.g., for
|
|
214
|
+
# cancellation). Those commands will block forever, causing a timeout.
|
|
215
|
+
# For cancellation tests, dispatch the cancel command without Sync.
|
|
216
|
+
#
|
|
212
217
|
# === Example
|
|
213
218
|
#
|
|
214
219
|
#--
|
|
@@ -92,6 +92,15 @@ module RatatuiRuby
|
|
|
92
92
|
# SPDX-SnippetEnd
|
|
93
93
|
#++
|
|
94
94
|
module TestHelper
|
|
95
|
+
##
|
|
96
|
+
# Auto-enables debug mode when TestHelper is included.
|
|
97
|
+
#
|
|
98
|
+
# This ensures Rust backtraces are available in tests.
|
|
99
|
+
# Skips remote debugging since tests don't need it.
|
|
100
|
+
def self.included(base)
|
|
101
|
+
RatatuiRuby::Debug.enable!(source: :test)
|
|
102
|
+
end
|
|
103
|
+
|
|
95
104
|
include Terminal
|
|
96
105
|
include Snapshot
|
|
97
106
|
include EventInjection
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
module Text
|
|
10
|
+
# A sequence of styled spans.
|
|
11
|
+
#
|
|
12
|
+
# Words form sentences. Spans form lines.
|
|
13
|
+
#
|
|
14
|
+
# This class composes multiple {Span} objects into a single horizontal row of text.
|
|
15
|
+
# It handles the layout of rich text fragments within the flow of a paragraph.
|
|
16
|
+
#
|
|
17
|
+
# Use it to build multi-colored headers, status messages, or log entries.
|
|
18
|
+
#
|
|
19
|
+
# === Examples
|
|
20
|
+
#
|
|
21
|
+
#--
|
|
22
|
+
# SPDX-SnippetBegin
|
|
23
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
24
|
+
# SPDX-License-Identifier: MIT-0
|
|
25
|
+
#++
|
|
26
|
+
# Text::Line.new(
|
|
27
|
+
# spans: [
|
|
28
|
+
# Text::Span.styled("User: ", Style.new(modifiers: [:bold])),
|
|
29
|
+
# Text::Span.styled("kerrick", Style.new(fg: :blue))
|
|
30
|
+
# ]
|
|
31
|
+
# )
|
|
32
|
+
#--
|
|
33
|
+
# SPDX-SnippetEnd
|
|
34
|
+
#++
|
|
35
|
+
class Line < Data.define(:spans, :alignment, :style)
|
|
36
|
+
##
|
|
37
|
+
# :attr_reader: spans
|
|
38
|
+
# Array of Span objects.
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# :attr_reader: alignment
|
|
42
|
+
# Alignment within the container.
|
|
43
|
+
#
|
|
44
|
+
# <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# :attr_reader: style
|
|
48
|
+
# Line-level style applied to all spans.
|
|
49
|
+
#
|
|
50
|
+
# A Style object that sets colors/modifiers for the entire line.
|
|
51
|
+
|
|
52
|
+
# Creates a new Line.
|
|
53
|
+
#
|
|
54
|
+
# [spans] Array of Span objects (or Strings).
|
|
55
|
+
# [alignment] Symbol (optional).
|
|
56
|
+
# [style] Style object (optional).
|
|
57
|
+
def initialize(spans: [], alignment: nil, style: nil)
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Creates a simple line from a string.
|
|
62
|
+
#
|
|
63
|
+
# Text::Line.from_string("Hello")
|
|
64
|
+
def self.from_string(content, alignment: nil)
|
|
65
|
+
new(spans: [Span.new(content:, style: nil)], alignment:)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Calculates the display width of this line in terminal cells.
|
|
69
|
+
#
|
|
70
|
+
# Sums the widths of all span contents using the same unicode-aware
|
|
71
|
+
# algorithm as Text.width. Useful for layout calculations.
|
|
72
|
+
#
|
|
73
|
+
# === Examples
|
|
74
|
+
#
|
|
75
|
+
#--
|
|
76
|
+
# SPDX-SnippetBegin
|
|
77
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
78
|
+
# SPDX-License-Identifier: MIT-0
|
|
79
|
+
#++
|
|
80
|
+
# line = Text::Line.new(spans: [
|
|
81
|
+
# Text::Span.new(content: "Hello "),
|
|
82
|
+
# Text::Span.new(content: "世界")
|
|
83
|
+
# ])
|
|
84
|
+
# line.width # => 10 (6 ASCII + 4 CJK)
|
|
85
|
+
#
|
|
86
|
+
#--
|
|
87
|
+
# SPDX-SnippetEnd
|
|
88
|
+
#++
|
|
89
|
+
# Returns: Integer (number of terminal cells).
|
|
90
|
+
def width
|
|
91
|
+
RatatuiRuby::Text.width(spans.map { |s| s.content.to_s }.join)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Left-aligns this line of text.
|
|
95
|
+
#
|
|
96
|
+
# Convenience shortcut for <tt>alignment: :left</tt>. Setting the alignment of a Line
|
|
97
|
+
# overrides the alignment of its parent Text or Widget.
|
|
98
|
+
#
|
|
99
|
+
# === Example
|
|
100
|
+
#
|
|
101
|
+
#--
|
|
102
|
+
# SPDX-SnippetBegin
|
|
103
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
104
|
+
# SPDX-License-Identifier: MIT-0
|
|
105
|
+
#++
|
|
106
|
+
# line = Text::Line.new(spans: [Text::Span.new(content: "Hello")])
|
|
107
|
+
# aligned = line.left_aligned
|
|
108
|
+
# aligned.alignment # => :left
|
|
109
|
+
#
|
|
110
|
+
#--
|
|
111
|
+
# SPDX-SnippetEnd
|
|
112
|
+
#++
|
|
113
|
+
# Returns: Line.
|
|
114
|
+
def left_aligned
|
|
115
|
+
with(alignment: :left)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Center-aligns this line of text.
|
|
119
|
+
#
|
|
120
|
+
# Convenience shortcut for <tt>alignment: :center</tt>. Setting the alignment of a Line
|
|
121
|
+
# overrides the alignment of its parent Text or Widget.
|
|
122
|
+
#
|
|
123
|
+
# === Example
|
|
124
|
+
#
|
|
125
|
+
#--
|
|
126
|
+
# SPDX-SnippetBegin
|
|
127
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
128
|
+
# SPDX-License-Identifier: MIT-0
|
|
129
|
+
#++
|
|
130
|
+
# line = Text::Line.new(spans: [Text::Span.new(content: "Hello")])
|
|
131
|
+
# centered = line.centered
|
|
132
|
+
# centered.alignment # => :center
|
|
133
|
+
#
|
|
134
|
+
#--
|
|
135
|
+
# SPDX-SnippetEnd
|
|
136
|
+
#++
|
|
137
|
+
# Returns: Line.
|
|
138
|
+
def centered
|
|
139
|
+
with(alignment: :center)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Right-aligns this line of text.
|
|
143
|
+
#
|
|
144
|
+
# Convenience shortcut for <tt>alignment: :right</tt>. Setting the alignment of a Line
|
|
145
|
+
# overrides the alignment of its parent Text or Widget.
|
|
146
|
+
#
|
|
147
|
+
# === Example
|
|
148
|
+
#
|
|
149
|
+
#--
|
|
150
|
+
# SPDX-SnippetBegin
|
|
151
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
152
|
+
# SPDX-License-Identifier: MIT-0
|
|
153
|
+
#++
|
|
154
|
+
# line = Text::Line.new(spans: [Text::Span.new(content: "Hello")])
|
|
155
|
+
# aligned = line.right_aligned
|
|
156
|
+
# aligned.alignment # => :right
|
|
157
|
+
#
|
|
158
|
+
#--
|
|
159
|
+
# SPDX-SnippetEnd
|
|
160
|
+
#++
|
|
161
|
+
# Returns: Line.
|
|
162
|
+
def right_aligned
|
|
163
|
+
with(alignment: :right)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Adds a span to the line.
|
|
167
|
+
#
|
|
168
|
+
# Since Line is immutable (a Data subclass), this returns a new Line with the span appended.
|
|
169
|
+
# The original line remains unchanged.
|
|
170
|
+
#
|
|
171
|
+
# === Example
|
|
172
|
+
#
|
|
173
|
+
#--
|
|
174
|
+
# SPDX-SnippetBegin
|
|
175
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
176
|
+
# SPDX-License-Identifier: MIT-0
|
|
177
|
+
#++
|
|
178
|
+
# line = Text::Line.new(spans: [Text::Span.new(content: "Hello, ")])
|
|
179
|
+
# extended = line.push_span(Text::Span.new(content: "world!"))
|
|
180
|
+
# extended.spans.size # => 2
|
|
181
|
+
# line.spans.size # => 1 (original unchanged)
|
|
182
|
+
#
|
|
183
|
+
#--
|
|
184
|
+
# SPDX-SnippetEnd
|
|
185
|
+
#++
|
|
186
|
+
# [span] Span to append.
|
|
187
|
+
#
|
|
188
|
+
# Returns: Line.
|
|
189
|
+
def push_span(span)
|
|
190
|
+
with(spans: spans + [span])
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Patches the style of this line, adding modifiers from the given style.
|
|
194
|
+
#
|
|
195
|
+
# Applies <tt>patch_style</tt> to each span in the line. Use this when you want to layer
|
|
196
|
+
# styles on all spans without replacing their existing styles.
|
|
197
|
+
#
|
|
198
|
+
# === Example
|
|
199
|
+
#
|
|
200
|
+
#--
|
|
201
|
+
# SPDX-SnippetBegin
|
|
202
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
203
|
+
# SPDX-License-Identifier: MIT-0
|
|
204
|
+
#++
|
|
205
|
+
# line = Text::Line.new(spans: [Text::Span.new(content: "Hello")])
|
|
206
|
+
# styled = line.patch_style(Style::Style.new(fg: :red))
|
|
207
|
+
# styled.spans.first.style.fg # => :red
|
|
208
|
+
#
|
|
209
|
+
#--
|
|
210
|
+
# SPDX-SnippetEnd
|
|
211
|
+
#++
|
|
212
|
+
# [patch] Style::Style to merge onto each span.
|
|
213
|
+
#
|
|
214
|
+
# Returns: Line.
|
|
215
|
+
def patch_style(patch)
|
|
216
|
+
with(spans: spans.map { |s| s.patch_style(patch) })
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Resets the style of this line.
|
|
220
|
+
#
|
|
221
|
+
# Applies <tt>reset_style</tt> to each span in the line, clearing all styling.
|
|
222
|
+
#
|
|
223
|
+
# === Example
|
|
224
|
+
#
|
|
225
|
+
#--
|
|
226
|
+
# SPDX-SnippetBegin
|
|
227
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
228
|
+
# SPDX-License-Identifier: MIT-0
|
|
229
|
+
#++
|
|
230
|
+
# line = Text::Line.new(spans: [
|
|
231
|
+
# Text::Span.new(content: "styled", style: Style::Style.new(fg: :red))
|
|
232
|
+
# ])
|
|
233
|
+
# reset = line.reset_style
|
|
234
|
+
# reset.spans.first.style # => nil
|
|
235
|
+
#
|
|
236
|
+
#--
|
|
237
|
+
# SPDX-SnippetEnd
|
|
238
|
+
#++
|
|
239
|
+
# Returns: Line.
|
|
240
|
+
def reset_style
|
|
241
|
+
with(spans: spans.map(&:reset_style))
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
module Text
|
|
10
|
+
# A styled string fragment.
|
|
11
|
+
#
|
|
12
|
+
# Text is rarely uniform. You need to bold a keyword, colorize an error, or dim a timestamp.
|
|
13
|
+
#
|
|
14
|
+
# This class attaches style to content. It pairs a string with visual attributes.
|
|
15
|
+
#
|
|
16
|
+
# combine spans into a {Line} to create rich text.
|
|
17
|
+
#
|
|
18
|
+
# === Examples
|
|
19
|
+
#
|
|
20
|
+
# Text::Span.new(content: "Error", style: Style.new(fg: :red, modifiers: [:bold]))
|
|
21
|
+
class Span < Data.define(:content, :style)
|
|
22
|
+
##
|
|
23
|
+
# :attr_reader: content
|
|
24
|
+
# The text content.
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# :attr_reader: style
|
|
28
|
+
# The style to apply.
|
|
29
|
+
|
|
30
|
+
# Creates a new Span.
|
|
31
|
+
#
|
|
32
|
+
# [content] String.
|
|
33
|
+
# [style] Style object (optional).
|
|
34
|
+
def initialize(content:, style: nil)
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Concise helper for styling.
|
|
39
|
+
#
|
|
40
|
+
# === Example
|
|
41
|
+
#
|
|
42
|
+
# Text::Span.styled("Bold", Style::Style.new(modifiers: [:bold]))
|
|
43
|
+
def self.styled(content, style = nil)
|
|
44
|
+
new(content:, style:)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the unicode display width of the content in terminal cells.
|
|
48
|
+
#
|
|
49
|
+
# CJK characters and emoji count as 2 cells. ASCII characters count as 1 cell.
|
|
50
|
+
# Use this to measure how much horizontal space a span will occupy.
|
|
51
|
+
#
|
|
52
|
+
# === Example
|
|
53
|
+
#
|
|
54
|
+
#--
|
|
55
|
+
# SPDX-SnippetBegin
|
|
56
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
57
|
+
# SPDX-License-Identifier: MIT-0
|
|
58
|
+
#++
|
|
59
|
+
# span = Text::Span.new(content: "Hello")
|
|
60
|
+
# span.width # => 5
|
|
61
|
+
#
|
|
62
|
+
# span = Text::Span.new(content: "你好") # Chinese characters
|
|
63
|
+
# span.width # => 4
|
|
64
|
+
#
|
|
65
|
+
#--
|
|
66
|
+
# SPDX-SnippetEnd
|
|
67
|
+
#++
|
|
68
|
+
# Returns: Integer.
|
|
69
|
+
def width
|
|
70
|
+
RatatuiRuby::Text.width(content.to_s)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Creates a span with the default style.
|
|
74
|
+
#
|
|
75
|
+
# Use this factory method when you want unstyled text. It mirrors the Ratatui API.
|
|
76
|
+
#
|
|
77
|
+
# === Example
|
|
78
|
+
#
|
|
79
|
+
#--
|
|
80
|
+
# SPDX-SnippetBegin
|
|
81
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
82
|
+
# SPDX-License-Identifier: MIT-0
|
|
83
|
+
#++
|
|
84
|
+
# span = Text::Span.raw("test content")
|
|
85
|
+
# span.content # => "test content"
|
|
86
|
+
# span.style # => nil
|
|
87
|
+
#
|
|
88
|
+
#--
|
|
89
|
+
# SPDX-SnippetEnd
|
|
90
|
+
#++
|
|
91
|
+
# [content] String.
|
|
92
|
+
#
|
|
93
|
+
# Returns: Span.
|
|
94
|
+
def self.raw(content)
|
|
95
|
+
new(content:)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Patches the style of the span, merging modifiers from the given style.
|
|
99
|
+
#
|
|
100
|
+
# Non-nil values from the patch style override the existing style. Use this when you want to
|
|
101
|
+
# layer styles without replacing the entire style. Colors in the patch take precedence over
|
|
102
|
+
# existing colors. Modifiers are combined.
|
|
103
|
+
#
|
|
104
|
+
# === Example
|
|
105
|
+
#
|
|
106
|
+
#--
|
|
107
|
+
# SPDX-SnippetBegin
|
|
108
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
109
|
+
# SPDX-License-Identifier: MIT-0
|
|
110
|
+
#++
|
|
111
|
+
# span = Text::Span.new(content: "test", style: Style::Style.new(fg: :green))
|
|
112
|
+
# patched = span.patch_style(Style::Style.new(bg: :yellow, modifiers: [:bold]))
|
|
113
|
+
# patched.style.fg # => :green (preserved)
|
|
114
|
+
# patched.style.bg # => :yellow (added)
|
|
115
|
+
#
|
|
116
|
+
#--
|
|
117
|
+
# SPDX-SnippetEnd
|
|
118
|
+
#++
|
|
119
|
+
# [patch] Style::Style to merge.
|
|
120
|
+
#
|
|
121
|
+
# Returns: Span.
|
|
122
|
+
def patch_style(patch)
|
|
123
|
+
return self if patch.nil?
|
|
124
|
+
return with(style: patch) if style.nil?
|
|
125
|
+
|
|
126
|
+
merged = Style::Style.new(
|
|
127
|
+
fg: patch.fg.nil? ? style.fg : patch.fg,
|
|
128
|
+
bg: patch.bg.nil? ? style.bg : patch.bg,
|
|
129
|
+
modifiers: patch.modifiers.empty? ? style.modifiers : (style.modifiers + patch.modifiers).uniq
|
|
130
|
+
)
|
|
131
|
+
with(style: merged)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Resets the style of the span.
|
|
135
|
+
#
|
|
136
|
+
# Returns a new span with no style applied. Use this to strip all styling.
|
|
137
|
+
#
|
|
138
|
+
# === Example
|
|
139
|
+
#
|
|
140
|
+
#--
|
|
141
|
+
# SPDX-SnippetBegin
|
|
142
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
143
|
+
# SPDX-License-Identifier: MIT-0
|
|
144
|
+
#++
|
|
145
|
+
# span = Text::Span.new(content: "styled", style: Style::Style.new(fg: :red))
|
|
146
|
+
# reset = span.reset_style
|
|
147
|
+
# reset.style # => nil
|
|
148
|
+
#
|
|
149
|
+
#--
|
|
150
|
+
# SPDX-SnippetEnd
|
|
151
|
+
#++
|
|
152
|
+
# Returns: Span.
|
|
153
|
+
def reset_style
|
|
154
|
+
with(style: nil)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
# Namespace for rich text components (Span, Line) and text utilities.
|
|
10
|
+
# Distinct from canvas shapes and other Line usages.
|
|
11
|
+
#
|
|
12
|
+
# == Text Measurement
|
|
13
|
+
#
|
|
14
|
+
# The Text module provides a utility method for calculating the display width
|
|
15
|
+
# of strings in terminal cells. This accounts for unicode complexity:
|
|
16
|
+
#
|
|
17
|
+
# - ASCII characters: 1 cell each
|
|
18
|
+
# - CJK (Chinese, Japanese, Korean) characters: 2 cells each (full-width)
|
|
19
|
+
# - Emoji: typically 2 cells each (varies by terminal)
|
|
20
|
+
# - Combining marks: 0 cells (zero-width)
|
|
21
|
+
#
|
|
22
|
+
# This is essential for layout calculations in TUI applications, where you need to know
|
|
23
|
+
# how much space a string will occupy on the screen, not just its byte or character length.
|
|
24
|
+
#
|
|
25
|
+
# === Use Cases
|
|
26
|
+
#
|
|
27
|
+
# - Auto-sizing widgets (Button, Badge) that fit their content
|
|
28
|
+
# - Calculating padding or centering for text alignment
|
|
29
|
+
# - Building responsive layouts that adapt to content width
|
|
30
|
+
# - Measuring text for scrolling or truncation logic
|
|
31
|
+
#
|
|
32
|
+
# === Examples
|
|
33
|
+
#
|
|
34
|
+
#--
|
|
35
|
+
# SPDX-SnippetBegin
|
|
36
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
37
|
+
# SPDX-License-Identifier: MIT-0
|
|
38
|
+
#++
|
|
39
|
+
# # Simple ASCII text
|
|
40
|
+
# RatatuiRuby::Text.width("Hello") # => 5
|
|
41
|
+
#
|
|
42
|
+
# # With emoji
|
|
43
|
+
# RatatuiRuby::Text.width("Hello 👍") # => 8 (5 + space + 2-width emoji)
|
|
44
|
+
#
|
|
45
|
+
# # With CJK characters
|
|
46
|
+
# RatatuiRuby::Text.width("你好") # => 4 (each CJK char is 2 cells)
|
|
47
|
+
#
|
|
48
|
+
# # Mixed content
|
|
49
|
+
# RatatuiRuby::Text.width("Hi 你好 👍") # => 11 (2 + space + 4 + space + 2)
|
|
50
|
+
#--
|
|
51
|
+
# SPDX-SnippetEnd
|
|
52
|
+
#++
|
|
53
|
+
module Text
|
|
54
|
+
##
|
|
55
|
+
# :method: width
|
|
56
|
+
# :call-seq: width(string) -> Integer
|
|
57
|
+
#
|
|
58
|
+
# Calculates the display width of a string in terminal cells.
|
|
59
|
+
#
|
|
60
|
+
# Layout demands precision. Terminals measure space in cells, not characters. An ASCII letter occupies one cell. A Chinese character occupies two. An emoji occupies two. Combining marks occupy zero.
|
|
61
|
+
#
|
|
62
|
+
# Measuring width manually is error-prone. You can count <tt>string.length</tt>, but that counts characters, not cells. A string with one emoji counts as 1 character but occupies 2 cells.
|
|
63
|
+
#
|
|
64
|
+
# This method returns the true display width. Use it to auto-size widgets, calculate padding, center text, or build responsive layouts.
|
|
65
|
+
#
|
|
66
|
+
# === Examples
|
|
67
|
+
#
|
|
68
|
+
#--
|
|
69
|
+
# SPDX-SnippetBegin
|
|
70
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
71
|
+
# SPDX-License-Identifier: MIT-0
|
|
72
|
+
#++
|
|
73
|
+
# RatatuiRuby::Text.width("Hello") # => 5 (5 ASCII chars × 1 cell)
|
|
74
|
+
#
|
|
75
|
+
# RatatuiRuby::Text.width("你好") # => 4 (2 CJK chars × 2 cells)
|
|
76
|
+
#
|
|
77
|
+
# RatatuiRuby::Text.width("Hello 👍") # => 8 (5 ASCII + 1 space + 1 emoji × 2)
|
|
78
|
+
#
|
|
79
|
+
# # In the Session DSL (easier)
|
|
80
|
+
# RatatuiRuby.run do |tui|
|
|
81
|
+
# width = tui.text_width("Hello 👍")
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
#--
|
|
85
|
+
# SPDX-SnippetEnd
|
|
86
|
+
#++
|
|
87
|
+
# [string] String to measure (String or object convertible to String)
|
|
88
|
+
# Returns: Integer (number of terminal cells the string occupies)
|
|
89
|
+
# Raises: TypeError if the argument is not a String
|
|
90
|
+
#
|
|
91
|
+
# (Native method implemented in Rust)
|
|
92
|
+
def self.width(string)
|
|
93
|
+
RatatuiRuby._text_width(string)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
require_relative "text/span"
|
|
99
|
+
require_relative "text/line"
|