ratatui_ruby 1.0.0 → 1.1.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 +3 -2
- data/CHANGELOG.md +33 -7
- data/Steepfile +1 -0
- data/doc/concepts/application_testing.md +5 -5
- data/doc/concepts/event_handling.md +1 -1
- data/doc/contributors/design/ruby_frontend.md +40 -12
- data/doc/contributors/design/rust_backend.md +13 -1
- data/doc/contributors/releasing.md +215 -0
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +6 -0
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +1 -7
- data/doc/contributors/todo/align/term.md +351 -0
- data/doc/contributors/upstream_requests/paragraph_span_rects.md +259 -0
- data/doc/getting_started/quickstart.md +1 -1
- data/doc/getting_started/why.md +3 -3
- data/doc/images/app_external_editor.gif +0 -0
- data/doc/index.md +1 -6
- data/examples/app_external_editor/README.md +62 -0
- data/examples/app_external_editor/app.rb +344 -0
- data/examples/widget_list/app.rb +2 -4
- data/examples/widget_table/app.rb +8 -2
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/events.rs +171 -203
- data/ext/ratatui_ruby/src/lib.rs +36 -0
- data/ext/ratatui_ruby/src/lib_header.rs +11 -0
- data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
- data/ext/ratatui_ruby/src/terminal/init.rs +92 -0
- data/ext/ratatui_ruby/src/terminal/mod.rs +12 -3
- data/ext/ratatui_ruby/src/terminal/queries.rs +15 -0
- data/ext/ratatui_ruby/src/terminal/query.rs +64 -2
- data/lib/ratatui_ruby/backend/window_size.rb +50 -0
- data/lib/ratatui_ruby/backend.rb +59 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +10 -1
- data/lib/ratatui_ruby/event/key.rb +84 -0
- data/lib/ratatui_ruby/event/mouse.rb +95 -3
- data/lib/ratatui_ruby/event/resize.rb +45 -3
- data/lib/ratatui_ruby/layout/alignment.rb +91 -0
- data/lib/ratatui_ruby/layout/layout.rb +1 -2
- data/lib/ratatui_ruby/layout/size.rb +10 -3
- data/lib/ratatui_ruby/layout.rb +4 -0
- data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
- data/lib/ratatui_ruby/terminal/viewport.rb +1 -1
- data/lib/ratatui_ruby/terminal.rb +66 -0
- data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
- data/lib/ratatui_ruby/test_helper.rb +3 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/table.rb +2 -2
- data/lib/ratatui_ruby.rb +25 -4
- data/sig/examples/app_external_editor/app.rbs +12 -0
- data/sig/generated/event_key_predicates.rbs +1348 -0
- data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
- data/sig/ratatui_ruby/backend.rbs +12 -0
- data/sig/ratatui_ruby/event.rbs +7 -0
- data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -0
- data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
- data/sig/ratatui_ruby/terminal/viewport.rbs +15 -1
- data/tasks/bump/bump_workflow.rb +49 -0
- data/tasks/bump/changelog.rb +57 -0
- data/tasks/bump/patch_release.rb +19 -0
- data/tasks/bump/release_branch.rb +17 -0
- data/tasks/bump/release_from_trunk.rb +49 -0
- data/tasks/bump/repository.rb +54 -0
- data/tasks/bump/ruby_gem.rb +6 -26
- data/tasks/bump/sem_ver.rb +4 -0
- data/tasks/bump/unreleased_section.rb +17 -0
- data/tasks/bump.rake +21 -11
- data/tasks/doc/documentation.rb +59 -0
- data/tasks/doc/link/file_url.rb +30 -0
- data/tasks/doc/link/relative_path.rb +61 -0
- data/tasks/doc/link/web_url.rb +55 -0
- data/tasks/doc/link.rb +52 -0
- data/tasks/doc/link_audit.rb +116 -0
- data/tasks/doc/problem.rb +40 -0
- data/tasks/doc/source_file.rb +93 -0
- data/tasks/doc.rake +18 -0
- data/tasks/rbs_predicates/predicate_catalog.rb +52 -0
- data/tasks/rbs_predicates/predicate_tests.rb +124 -0
- data/tasks/rbs_predicates/rbs_signature.rb +63 -0
- data/tasks/rbs_predicates.rake +31 -0
- data/tasks/test.rake +3 -0
- data/tasks/website/version.rb +23 -28
- metadata +38 -1
|
@@ -0,0 +1,91 @@
|
|
|
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 Layout
|
|
10
|
+
# Horizontal content alignment within a layout area.
|
|
11
|
+
#
|
|
12
|
+
# Use these constants for discoverability, or pass symbols directly
|
|
13
|
+
# (<tt>:left</tt>, <tt>:center</tt>, <tt>:right</tt>).
|
|
14
|
+
#
|
|
15
|
+
# Mirrors +ratatui::layout::HorizontalAlignment+ from upstream Ratatui.
|
|
16
|
+
#
|
|
17
|
+
# === Example
|
|
18
|
+
#
|
|
19
|
+
#--
|
|
20
|
+
# SPDX-SnippetBegin
|
|
21
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
22
|
+
# SPDX-License-Identifier: MIT-0
|
|
23
|
+
#++
|
|
24
|
+
# # Using constants (discoverable)
|
|
25
|
+
# paragraph = Paragraph.new(
|
|
26
|
+
# text: "Hello",
|
|
27
|
+
# alignment: HorizontalAlignment::CENTER
|
|
28
|
+
# )
|
|
29
|
+
#
|
|
30
|
+
# # Using symbols directly (idiomatic Ruby)
|
|
31
|
+
# paragraph = Paragraph.new(text: "Hello", alignment: :center)
|
|
32
|
+
#--
|
|
33
|
+
# SPDX-SnippetEnd
|
|
34
|
+
#++
|
|
35
|
+
module HorizontalAlignment
|
|
36
|
+
# Align content to the left edge.
|
|
37
|
+
LEFT = :left
|
|
38
|
+
|
|
39
|
+
# Align content to the center.
|
|
40
|
+
CENTER = :center
|
|
41
|
+
|
|
42
|
+
# Align content to the right edge.
|
|
43
|
+
RIGHT = :right
|
|
44
|
+
|
|
45
|
+
# All valid alignment values.
|
|
46
|
+
ALL = [LEFT, CENTER, RIGHT].freeze
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Vertical content alignment within a layout area.
|
|
50
|
+
#
|
|
51
|
+
# Use these constants for discoverability, or pass symbols directly
|
|
52
|
+
# (<tt>:top</tt>, <tt>:center</tt>, <tt>:bottom</tt>).
|
|
53
|
+
#
|
|
54
|
+
# Mirrors +ratatui::layout::VerticalAlignment+ from upstream Ratatui.
|
|
55
|
+
#
|
|
56
|
+
# === Example
|
|
57
|
+
#
|
|
58
|
+
#--
|
|
59
|
+
# SPDX-SnippetBegin
|
|
60
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
61
|
+
# SPDX-License-Identifier: MIT-0
|
|
62
|
+
#++
|
|
63
|
+
# # Using constants (discoverable)
|
|
64
|
+
# widget.vertical_alignment = VerticalAlignment::CENTER
|
|
65
|
+
#
|
|
66
|
+
# # Using symbols directly (idiomatic Ruby)
|
|
67
|
+
# widget.vertical_alignment = :center
|
|
68
|
+
#--
|
|
69
|
+
# SPDX-SnippetEnd
|
|
70
|
+
#++
|
|
71
|
+
module VerticalAlignment
|
|
72
|
+
# Align content to the top edge.
|
|
73
|
+
TOP = :top
|
|
74
|
+
|
|
75
|
+
# Align content to the center.
|
|
76
|
+
CENTER = :center
|
|
77
|
+
|
|
78
|
+
# Align content to the bottom edge.
|
|
79
|
+
BOTTOM = :bottom
|
|
80
|
+
|
|
81
|
+
# All valid alignment values.
|
|
82
|
+
ALL = [TOP, CENTER, BOTTOM].freeze
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Type alias for HorizontalAlignment.
|
|
86
|
+
#
|
|
87
|
+
# Provided for upstream API parity. In Ratatui, +Alignment+ was the
|
|
88
|
+
# original name before +HorizontalAlignment+ was introduced in v0.30.0.
|
|
89
|
+
Alignment = HorizontalAlignment
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -49,8 +49,7 @@ module RatatuiRuby
|
|
|
49
49
|
#
|
|
50
50
|
# One of <tt>:legacy</tt>, <tt>:start</tt>, <tt>:center</tt>, <tt>:end</tt>, <tt>:space_between</tt>, <tt>:space_around</tt>.
|
|
51
51
|
|
|
52
|
-
# :nodoc:
|
|
53
|
-
FLEX_MODES = %i[legacy start center end space_between space_around space_evenly].freeze
|
|
52
|
+
FLEX_MODES = %i[legacy start center end space_between space_around space_evenly].freeze # :nodoc:
|
|
54
53
|
|
|
55
54
|
##
|
|
56
55
|
# Direction: split vertically (top to bottom).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
module RatatuiRuby
|
|
9
9
|
module Layout
|
|
10
|
-
#
|
|
10
|
+
# Generic dimensions as width and height.
|
|
11
11
|
#
|
|
12
12
|
# Layout calculations need sizes. Passing width and height
|
|
13
13
|
# as separate arguments is verbose and easy to swap by mistake.
|
|
@@ -15,6 +15,13 @@ module RatatuiRuby
|
|
|
15
15
|
# This class bundles dimensions into a single immutable object.
|
|
16
16
|
# Extract it from a Rect or create it directly for sizing operations.
|
|
17
17
|
#
|
|
18
|
+
# Following upstream Ratatui's design, the same Size type represents
|
|
19
|
+
# both character-grid dimensions (columns × rows) and pixel dimensions.
|
|
20
|
+
# The semantic meaning depends on the source:
|
|
21
|
+
#
|
|
22
|
+
# - From <tt>Terminal.size</tt> or <tt>WindowSize#columns_rows</tt>: columns and rows
|
|
23
|
+
# - From <tt>WindowSize#pixels</tt>: pixel width and height
|
|
24
|
+
#
|
|
18
25
|
# Use it for terminal dimensions, widget sizing constraints,
|
|
19
26
|
# or anywhere you need width/height without position.
|
|
20
27
|
#
|
|
@@ -37,11 +44,11 @@ module RatatuiRuby
|
|
|
37
44
|
class Size < Data.define(:width, :height)
|
|
38
45
|
##
|
|
39
46
|
# :attr_reader: width
|
|
40
|
-
# Width
|
|
47
|
+
# Width dimension (columns for character grids, pixels for pixel sizes).
|
|
41
48
|
|
|
42
49
|
##
|
|
43
50
|
# :attr_reader: height
|
|
44
|
-
# Height
|
|
51
|
+
# Height dimension (rows for character grids, pixels for pixel sizes).
|
|
45
52
|
|
|
46
53
|
# Creates a new Size.
|
|
47
54
|
#
|
data/lib/ratatui_ruby/layout.rb
CHANGED
|
@@ -14,10 +14,14 @@ module RatatuiRuby
|
|
|
14
14
|
# - {Size} — Terminal dimensions
|
|
15
15
|
# - {Constraint} — Sizing rules
|
|
16
16
|
# - {Layout} — Space distribution
|
|
17
|
+
# - {HorizontalAlignment} — Horizontal alignment constants
|
|
18
|
+
# - {VerticalAlignment} — Vertical alignment constants
|
|
19
|
+
# - {Alignment} — Alias for HorizontalAlignment
|
|
17
20
|
module Layout
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
require_relative "layout/alignment"
|
|
21
25
|
require_relative "layout/rect"
|
|
22
26
|
require_relative "layout/position"
|
|
23
27
|
require_relative "layout/size"
|
|
@@ -0,0 +1,316 @@
|
|
|
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
|
+
require "timeout"
|
|
9
|
+
|
|
10
|
+
module RatatuiRuby
|
|
11
|
+
class Terminal
|
|
12
|
+
# Environment-based terminal capability detection.
|
|
13
|
+
#
|
|
14
|
+
# TUI applications need to know what the terminal supports. Color depth
|
|
15
|
+
# varies. Some terminals lack escape sequence support entirely. Users
|
|
16
|
+
# set environment variables like <tt>NO_COLOR</tt> to express preferences.
|
|
17
|
+
#
|
|
18
|
+
# This module detects terminal capabilities from environment variables.
|
|
19
|
+
# It checks <tt>TERM</tt>, <tt>COLORTERM</tt>, <tt>NO_COLOR</tt>, and
|
|
20
|
+
# <tt>FORCE_COLOR</tt> to determine what the terminal supports.
|
|
21
|
+
#
|
|
22
|
+
# Use these methods before initializing a Terminal instance to decide
|
|
23
|
+
# whether TUI mode is appropriate for the current environment.
|
|
24
|
+
#
|
|
25
|
+
# === Example
|
|
26
|
+
#
|
|
27
|
+
#--
|
|
28
|
+
# SPDX-SnippetBegin
|
|
29
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
30
|
+
# SPDX-License-Identifier: MIT-0
|
|
31
|
+
#++
|
|
32
|
+
# if RatatuiRuby::Terminal.interactive?
|
|
33
|
+
# RatatuiRuby.run { |tui| ... }
|
|
34
|
+
# else
|
|
35
|
+
# puts "TUI mode not available"
|
|
36
|
+
# end
|
|
37
|
+
#--
|
|
38
|
+
# SPDX-SnippetEnd
|
|
39
|
+
#++
|
|
40
|
+
module Capabilities
|
|
41
|
+
# Checks if stdout connects to a terminal device.
|
|
42
|
+
#
|
|
43
|
+
# Terminal apps render escape sequences. Piped output or log files
|
|
44
|
+
# cannot interpret them. If your app writes ANSI codes to a non-TTY,
|
|
45
|
+
# logs fill with garbage like <tt>[32m</tt> instead of green text.
|
|
46
|
+
#
|
|
47
|
+
# This method checks <tt>$stdout.tty?</tt>. Use it to skip TUI mode
|
|
48
|
+
# when output is redirected. Print plain text instead.
|
|
49
|
+
#
|
|
50
|
+
# === Example
|
|
51
|
+
#
|
|
52
|
+
#--
|
|
53
|
+
# SPDX-SnippetBegin
|
|
54
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
55
|
+
# SPDX-License-Identifier: MIT-0
|
|
56
|
+
#++
|
|
57
|
+
# if RatatuiRuby::Terminal.tty?
|
|
58
|
+
# start_tui
|
|
59
|
+
# else
|
|
60
|
+
# print_plain_output
|
|
61
|
+
# end
|
|
62
|
+
#--
|
|
63
|
+
# SPDX-SnippetEnd
|
|
64
|
+
#++
|
|
65
|
+
def tty?
|
|
66
|
+
$stdout.tty?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Checks if the terminal declared itself "dumb."
|
|
70
|
+
#
|
|
71
|
+
# Dumb terminals exist. Emacs shell-mode sets <tt>TERM=dumb</tt>.
|
|
72
|
+
# Serial consoles do too. These terminals cannot interpret escape
|
|
73
|
+
# sequences. If your app sends cursor movements or colors, output
|
|
74
|
+
# becomes unreadable.
|
|
75
|
+
#
|
|
76
|
+
# This method checks for explicit <tt>TERM=dumb</tt>. Empty or unset
|
|
77
|
+
# <tt>TERM</tt> means "unknown," not "dumb." Use it to fall back to
|
|
78
|
+
# plain text rendering.
|
|
79
|
+
#
|
|
80
|
+
# === Example
|
|
81
|
+
#
|
|
82
|
+
#--
|
|
83
|
+
# SPDX-SnippetBegin
|
|
84
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
85
|
+
# SPDX-License-Identifier: MIT-0
|
|
86
|
+
#++
|
|
87
|
+
# if RatatuiRuby::Terminal.dumb?
|
|
88
|
+
# render_plain_table(data)
|
|
89
|
+
# else
|
|
90
|
+
# render_styled_table(data)
|
|
91
|
+
# end
|
|
92
|
+
#--
|
|
93
|
+
# SPDX-SnippetEnd
|
|
94
|
+
#++
|
|
95
|
+
def dumb?
|
|
96
|
+
ENV["TERM"] == "dumb"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Checks if the user disabled color output.
|
|
100
|
+
#
|
|
101
|
+
# Users with visual impairments or screen readers often disable
|
|
102
|
+
# colors. The NO_COLOR standard (no-color.org) provides a universal
|
|
103
|
+
# way to request this. Ignoring it frustrates accessibility-conscious
|
|
104
|
+
# users.
|
|
105
|
+
#
|
|
106
|
+
# This method checks for <tt>NO_COLOR</tt> in the environment. The
|
|
107
|
+
# value does not matter; presence alone disables color. Respect it.
|
|
108
|
+
#
|
|
109
|
+
# === Example
|
|
110
|
+
#
|
|
111
|
+
#--
|
|
112
|
+
# SPDX-SnippetBegin
|
|
113
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
114
|
+
# SPDX-License-Identifier: MIT-0
|
|
115
|
+
#++
|
|
116
|
+
# style = RatatuiRuby::Terminal.no_color? ? :plain : :colored
|
|
117
|
+
#--
|
|
118
|
+
# SPDX-SnippetEnd
|
|
119
|
+
#++
|
|
120
|
+
def no_color?
|
|
121
|
+
ENV.key?("NO_COLOR")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Checks if color output is explicitly forced.
|
|
125
|
+
#
|
|
126
|
+
# Some CI systems and logging pipelines detect non-TTY and strip
|
|
127
|
+
# colors. Users want colors anyway for readability. <tt>FORCE_COLOR</tt>
|
|
128
|
+
# overrides the TTY check.
|
|
129
|
+
#
|
|
130
|
+
# This method checks for <tt>FORCE_COLOR</tt> in the environment.
|
|
131
|
+
# When set, your app should emit colors even when <tt>tty?</tt>
|
|
132
|
+
# returns false.
|
|
133
|
+
#
|
|
134
|
+
# === Example
|
|
135
|
+
#
|
|
136
|
+
#--
|
|
137
|
+
# SPDX-SnippetBegin
|
|
138
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
139
|
+
# SPDX-License-Identifier: MIT-0
|
|
140
|
+
#++
|
|
141
|
+
# use_color = RatatuiRuby::Terminal.tty? ||
|
|
142
|
+
# RatatuiRuby::Terminal.force_color?
|
|
143
|
+
#--
|
|
144
|
+
# SPDX-SnippetEnd
|
|
145
|
+
#++
|
|
146
|
+
def force_color?
|
|
147
|
+
ENV.key?("FORCE_COLOR")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Checks if the terminal supports interactive TUI mode.
|
|
151
|
+
#
|
|
152
|
+
# A TUI needs a real terminal. Piped output breaks cursor control.
|
|
153
|
+
# Dumb terminals corrupt escape sequences. Starting TUI mode in
|
|
154
|
+
# these environments wastes resources and confuses users.
|
|
155
|
+
#
|
|
156
|
+
# This method combines <tt>tty?</tt> and <tt>dumb?</tt> checks.
|
|
157
|
+
# Returns +true+ only when both conditions allow TUI operation.
|
|
158
|
+
# Use it as the gatekeeper before calling <tt>run</tt>.
|
|
159
|
+
#
|
|
160
|
+
# === Example
|
|
161
|
+
#
|
|
162
|
+
#--
|
|
163
|
+
# SPDX-SnippetBegin
|
|
164
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
165
|
+
# SPDX-License-Identifier: MIT-0
|
|
166
|
+
#++
|
|
167
|
+
# if RatatuiRuby::Terminal.interactive?
|
|
168
|
+
# RatatuiRuby.run { |tui| main_loop(tui) }
|
|
169
|
+
# else
|
|
170
|
+
# abort "Interactive terminal required"
|
|
171
|
+
# end
|
|
172
|
+
#--
|
|
173
|
+
# SPDX-SnippetEnd
|
|
174
|
+
#++
|
|
175
|
+
def interactive?
|
|
176
|
+
tty? && !dumb?
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Queries how many colors the terminal can display.
|
|
180
|
+
#
|
|
181
|
+
# Modern terminals vary wildly in capability. Some only support 8 ANSI
|
|
182
|
+
# colors. Others display 256. High-end terminals render 16 million
|
|
183
|
+
# truecolor shades. If your app uses rich color palettes without
|
|
184
|
+
# checking, users on basic terminals see garbled output or crashes.
|
|
185
|
+
#
|
|
186
|
+
# This method queries crossterm (which checks <tt>COLORTERM</tt> and
|
|
187
|
+
# <tt>TERM</tt>) and returns the raw count. Use it to select color
|
|
188
|
+
# palettes or degrade gracefully.
|
|
189
|
+
#
|
|
190
|
+
# Returns 8, 256, or 65535 (truecolor).
|
|
191
|
+
#
|
|
192
|
+
# === Example
|
|
193
|
+
#
|
|
194
|
+
#--
|
|
195
|
+
# SPDX-SnippetBegin
|
|
196
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
197
|
+
# SPDX-License-Identifier: MIT-0
|
|
198
|
+
#++
|
|
199
|
+
# colors = RatatuiRuby::Terminal.available_color_count
|
|
200
|
+
# palette = colors >= 256 ? :rich : :basic
|
|
201
|
+
#--
|
|
202
|
+
# SPDX-SnippetEnd
|
|
203
|
+
#++
|
|
204
|
+
def available_color_count
|
|
205
|
+
_available_color_count
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Returns the terminal's color capability as a symbol.
|
|
209
|
+
#
|
|
210
|
+
# Comparing integers is annoying. You want to know: can I use
|
|
211
|
+
# gradients? Do I need a fallback palette? This method translates
|
|
212
|
+
# the raw count into semantic symbols.
|
|
213
|
+
#
|
|
214
|
+
# Returns <tt>:none</tt> when <tt>NO_COLOR</tt> is set or terminal is
|
|
215
|
+
# dumb. Returns <tt>:basic</tt> (8 colors), <tt>:ansi256</tt> (256),
|
|
216
|
+
# or <tt>:truecolor</tt> (16M) based on capability.
|
|
217
|
+
#
|
|
218
|
+
# Use it to switch rendering strategies or skip color entirely.
|
|
219
|
+
#
|
|
220
|
+
# === Example
|
|
221
|
+
#
|
|
222
|
+
#--
|
|
223
|
+
# SPDX-SnippetBegin
|
|
224
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
225
|
+
# SPDX-License-Identifier: MIT-0
|
|
226
|
+
#++
|
|
227
|
+
# case RatatuiRuby::Terminal.color_support
|
|
228
|
+
# when :truecolor then use_gradient_theme
|
|
229
|
+
# when :ansi256 then use_256_palette
|
|
230
|
+
# when :basic then use_ansi_colors
|
|
231
|
+
# when :none then use_monochrome
|
|
232
|
+
# end
|
|
233
|
+
#--
|
|
234
|
+
# SPDX-SnippetEnd
|
|
235
|
+
#++
|
|
236
|
+
def color_support
|
|
237
|
+
return :none if no_color?
|
|
238
|
+
return :none if dumb?
|
|
239
|
+
|
|
240
|
+
count = available_color_count
|
|
241
|
+
return :truecolor if count >= 65_535
|
|
242
|
+
return :ansi256 if count >= 256
|
|
243
|
+
|
|
244
|
+
:basic
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Checks for Kitty keyboard protocol support.
|
|
248
|
+
#
|
|
249
|
+
# Standard terminal input is ambiguous. Escape key and arrow keys
|
|
250
|
+
# share prefixes. Modifier keys get lost. Applications that need
|
|
251
|
+
# precise key handling (editors, games) struggle with the limitations.
|
|
252
|
+
#
|
|
253
|
+
# The Kitty keyboard protocol solves this. Terminals that support it
|
|
254
|
+
# report key presses unambiguously, with full modifier information.
|
|
255
|
+
# This method queries support so you can enable enhanced input or
|
|
256
|
+
# fall back gracefully.
|
|
257
|
+
#
|
|
258
|
+
# Returns <tt>false</tt> immediately if <tt>tty?</tt> returns false.
|
|
259
|
+
# Otherwise queries crossterm with a 0.5s timeout.
|
|
260
|
+
# Returns <tt>true</tt> only if the terminal responds affirmatively.
|
|
261
|
+
#
|
|
262
|
+
# === Example
|
|
263
|
+
#
|
|
264
|
+
#--
|
|
265
|
+
# SPDX-SnippetBegin
|
|
266
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
267
|
+
# SPDX-License-Identifier: MIT-0
|
|
268
|
+
#++
|
|
269
|
+
# if RatatuiRuby::Terminal.supports_keyboard_enhancement?
|
|
270
|
+
# enable_vim_style_keybindings
|
|
271
|
+
# else
|
|
272
|
+
# use_simple_navigation
|
|
273
|
+
# end
|
|
274
|
+
#--
|
|
275
|
+
# SPDX-SnippetEnd
|
|
276
|
+
#++
|
|
277
|
+
def supports_keyboard_enhancement?
|
|
278
|
+
return false unless tty?
|
|
279
|
+
|
|
280
|
+
Timeout.timeout(0.5) { _supports_keyboard_enhancement }
|
|
281
|
+
rescue
|
|
282
|
+
false
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Globally override NO_COLOR detection.
|
|
286
|
+
#
|
|
287
|
+
# The NO_COLOR environment variable tells applications to disable color.
|
|
288
|
+
# Sometimes users want colors anyway, like in CI pipelines that log to
|
|
289
|
+
# files but display logs with color. Passing +true+ forces color output
|
|
290
|
+
# regardless of NO_COLOR.
|
|
291
|
+
#
|
|
292
|
+
# This method calls crossterm's global override. The effect is immediate
|
|
293
|
+
# and affects all subsequent color detection queries. Pass +false+ to
|
|
294
|
+
# restore normal NO_COLOR behavior.
|
|
295
|
+
#
|
|
296
|
+
# === Example
|
|
297
|
+
#
|
|
298
|
+
#--
|
|
299
|
+
# SPDX-SnippetBegin
|
|
300
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
301
|
+
# SPDX-License-Identifier: MIT-0
|
|
302
|
+
#++
|
|
303
|
+
# if options[:color] == :always
|
|
304
|
+
# RatatuiRuby::Terminal.force_color_output(true)
|
|
305
|
+
# end
|
|
306
|
+
#--
|
|
307
|
+
# SPDX-SnippetEnd
|
|
308
|
+
#++
|
|
309
|
+
def force_color_output(enable)
|
|
310
|
+
_force_color_output(enable)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
extend Capabilities
|
|
315
|
+
end
|
|
316
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
require_relative "terminal/capabilities"
|
|
9
|
+
|
|
10
|
+
module RatatuiRuby
|
|
11
|
+
# Terminal object for managing terminal lifecycle and rendering.
|
|
12
|
+
#
|
|
13
|
+
# Instance-based API aligned with upstream Ratatui Terminal struct.
|
|
14
|
+
#
|
|
15
|
+
# === Example
|
|
16
|
+
#
|
|
17
|
+
#--
|
|
18
|
+
# SPDX-SnippetBegin
|
|
19
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
20
|
+
# SPDX-License-Identifier: MIT-0
|
|
21
|
+
#++
|
|
22
|
+
# terminal = RatatuiRuby::Terminal.new
|
|
23
|
+
# terminal.draw { |frame| ... }
|
|
24
|
+
# terminal.restore
|
|
25
|
+
#--
|
|
26
|
+
# SPDX-SnippetEnd
|
|
27
|
+
#++
|
|
28
|
+
class Terminal
|
|
29
|
+
##
|
|
30
|
+
# :attr_reader: terminal_id
|
|
31
|
+
# Unique identifier for this terminal instance in Rust (Integer).
|
|
32
|
+
attr_reader :terminal_id
|
|
33
|
+
|
|
34
|
+
# Creates a new Terminal instance.
|
|
35
|
+
#
|
|
36
|
+
# [viewport] Symbol or Viewport object (:fullscreen or :inline)
|
|
37
|
+
# [height] Integer height for inline viewports
|
|
38
|
+
def initialize(viewport: :fullscreen, height: nil)
|
|
39
|
+
@viewport = resolve_viewport(viewport, height)
|
|
40
|
+
|
|
41
|
+
# Call Rust FFI to create instance and get ID
|
|
42
|
+
# For now, only test backend is supported (real crossterm coming later)
|
|
43
|
+
@terminal_id = self.class._init_test_terminal_instance(
|
|
44
|
+
80, # default width for test
|
|
45
|
+
24, # default height for test
|
|
46
|
+
@viewport.type.to_s,
|
|
47
|
+
@viewport.height
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the terminal size as a Layout::Rect
|
|
52
|
+
# Rust constructs the Rect object directly (not a hash!)
|
|
53
|
+
def size
|
|
54
|
+
self.class._get_terminal_size_instance(@terminal_id)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private def resolve_viewport(viewport, height)
|
|
58
|
+
case viewport
|
|
59
|
+
when nil, :fullscreen then Terminal::Viewport.fullscreen
|
|
60
|
+
when :inline then Terminal::Viewport.inline(height || 8)
|
|
61
|
+
when Terminal::Viewport then viewport
|
|
62
|
+
else raise ArgumentError, "Unknown viewport: #{viewport.inspect}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
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 TestHelper
|
|
10
|
+
##
|
|
11
|
+
# Helpers for testing code that reads global state.
|
|
12
|
+
#
|
|
13
|
+
# Applications often read +ARGV+ and +ENV+ at startup. Testing these code
|
|
14
|
+
# paths requires stubbing those constants. Doing it manually is brittle —
|
|
15
|
+
# tests that forget to restore the original values leak state.
|
|
16
|
+
#
|
|
17
|
+
# These helpers swap the constants, yield to the block, and restore the
|
|
18
|
+
# originals automatically.
|
|
19
|
+
#
|
|
20
|
+
# == Example
|
|
21
|
+
#
|
|
22
|
+
#--
|
|
23
|
+
# SPDX-SnippetBegin
|
|
24
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
25
|
+
# SPDX-License-Identifier: MIT-0
|
|
26
|
+
#++
|
|
27
|
+
# def test_parses_command_line_flags
|
|
28
|
+
# with_argv(["--verbose", "--log=debug.log"]) do
|
|
29
|
+
# app = MyApp.new
|
|
30
|
+
# assert app.verbose?
|
|
31
|
+
# assert_equal "debug.log", app.log_path
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# def test_reads_api_key_from_environment
|
|
36
|
+
# with_env("API_KEY", "test-key-1234") do
|
|
37
|
+
# client = APIClient.new
|
|
38
|
+
# assert_equal "test-key-1234", client.api_key
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
#--
|
|
42
|
+
# SPDX-SnippetEnd
|
|
43
|
+
#++
|
|
44
|
+
module GlobalState
|
|
45
|
+
# Temporarily replaces ARGV for the duration of the block.
|
|
46
|
+
#
|
|
47
|
+
# [argv] An array of strings to use as ARGV.
|
|
48
|
+
#
|
|
49
|
+
# === Example
|
|
50
|
+
#
|
|
51
|
+
#--
|
|
52
|
+
# SPDX-SnippetBegin
|
|
53
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
54
|
+
# SPDX-License-Identifier: MIT-0
|
|
55
|
+
#++
|
|
56
|
+
# with_argv(["--help"]) do
|
|
57
|
+
# # Code here sees ARGV as ["--help"]
|
|
58
|
+
# end
|
|
59
|
+
#--
|
|
60
|
+
# SPDX-SnippetEnd
|
|
61
|
+
#++
|
|
62
|
+
def with_argv(argv)
|
|
63
|
+
original_argv = ARGV.dup
|
|
64
|
+
ARGV.replace(argv)
|
|
65
|
+
yield
|
|
66
|
+
ensure
|
|
67
|
+
ARGV.replace(original_argv)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Temporarily replaces an environment variable for the duration of the block.
|
|
71
|
+
#
|
|
72
|
+
# If +value+ is +nil+, the variable is deleted for the block's duration.
|
|
73
|
+
#
|
|
74
|
+
# [key] The environment variable name (String).
|
|
75
|
+
# [value] The value to set, or +nil+ to delete.
|
|
76
|
+
#
|
|
77
|
+
# === Example
|
|
78
|
+
#
|
|
79
|
+
#--
|
|
80
|
+
# SPDX-SnippetBegin
|
|
81
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
82
|
+
# SPDX-License-Identifier: MIT-0
|
|
83
|
+
#++
|
|
84
|
+
# with_env("DEBUG", "1") do
|
|
85
|
+
# # Code here sees ENV["DEBUG"] as "1"
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# with_env("DEBUG", nil) do
|
|
89
|
+
# # Code here does not see ENV["DEBUG"]
|
|
90
|
+
# end
|
|
91
|
+
#--
|
|
92
|
+
# SPDX-SnippetEnd
|
|
93
|
+
#++
|
|
94
|
+
def with_env(key, value)
|
|
95
|
+
original_value = ENV[key]
|
|
96
|
+
if value.nil?
|
|
97
|
+
ENV.delete(key)
|
|
98
|
+
else
|
|
99
|
+
ENV[key] = value
|
|
100
|
+
end
|
|
101
|
+
yield
|
|
102
|
+
ensure
|
|
103
|
+
if original_value.nil?
|
|
104
|
+
ENV.delete(key)
|
|
105
|
+
else
|
|
106
|
+
ENV[key] = original_value
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -11,6 +11,7 @@ require_relative "test_helper/snapshot"
|
|
|
11
11
|
require_relative "test_helper/event_injection"
|
|
12
12
|
require_relative "test_helper/style_assertions"
|
|
13
13
|
require_relative "test_helper/test_doubles"
|
|
14
|
+
require_relative "test_helper/global_state"
|
|
14
15
|
|
|
15
16
|
module RatatuiRuby
|
|
16
17
|
##
|
|
@@ -30,6 +31,7 @@ module RatatuiRuby
|
|
|
30
31
|
# [EventInjection] Simulates keypresses, mouse clicks, and resize events.
|
|
31
32
|
# [StyleAssertions] Checks foreground color, background color, and text modifiers.
|
|
32
33
|
# [TestDoubles] Provides mocks and stubs for testing views in isolation.
|
|
34
|
+
# [GlobalState] Provides with_argv and with_env helpers for testing global state access.
|
|
33
35
|
#
|
|
34
36
|
# == Example
|
|
35
37
|
#
|
|
@@ -106,5 +108,6 @@ module RatatuiRuby
|
|
|
106
108
|
include EventInjection
|
|
107
109
|
include StyleAssertions
|
|
108
110
|
include TestDoubles
|
|
111
|
+
include GlobalState
|
|
109
112
|
end
|
|
110
113
|
end
|
data/lib/ratatui_ruby/version.rb
CHANGED