ratatui_ruby 0.1.0 → 0.3.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 +52 -0
- data/.builds/ruby-3.3.yml +52 -0
- data/.builds/ruby-3.4.yml +52 -0
- data/.builds/ruby-4.0.0.yml +53 -0
- data/.pre-commit-config.yaml +9 -2
- data/AGENTS.md +53 -5
- data/CHANGELOG.md +51 -1
- data/README.md +38 -18
- data/REUSE.toml +5 -0
- data/Rakefile +3 -100
- data/{docs → doc}/contributors/index.md +2 -1
- data/doc/custom.css +8 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/{docs → doc}/index.md +1 -1
- data/{docs → doc}/quickstart.md +81 -11
- data/examples/analytics.rb +2 -1
- data/examples/calendar_demo.rb +55 -0
- data/examples/chart_demo.rb +84 -0
- data/examples/custom_widget.rb +43 -0
- data/examples/list_styles.rb +66 -0
- data/examples/login_form.rb +2 -1
- data/examples/popup_demo.rb +105 -0
- data/examples/quickstart_dsl.rb +30 -0
- data/examples/quickstart_lifecycle.rb +40 -0
- data/examples/readme_usage.rb +21 -0
- data/examples/scroll_text.rb +74 -0
- data/examples/stock_ticker.rb +13 -5
- data/examples/system_monitor.rb +2 -1
- data/examples/table_select.rb +70 -0
- data/examples/test_calendar_demo.rb +66 -0
- data/examples/test_list_styles.rb +61 -0
- data/examples/test_popup_demo.rb +62 -0
- data/examples/test_scroll_text.rb +130 -0
- data/examples/test_table_select.rb +37 -0
- data/ext/ratatui_ruby/.cargo/config.toml +5 -0
- data/ext/ratatui_ruby/Cargo.lock +260 -50
- data/ext/ratatui_ruby/Cargo.toml +5 -4
- data/ext/ratatui_ruby/extconf.rb +1 -1
- data/ext/ratatui_ruby/src/buffer.rs +54 -0
- data/ext/ratatui_ruby/src/events.rs +115 -107
- data/ext/ratatui_ruby/src/lib.rs +15 -6
- data/ext/ratatui_ruby/src/rendering.rs +18 -1
- data/ext/ratatui_ruby/src/style.rs +2 -1
- data/ext/ratatui_ruby/src/terminal.rs +27 -24
- data/ext/ratatui_ruby/src/widgets/calendar.rs +82 -0
- data/ext/ratatui_ruby/src/widgets/canvas.rs +1 -2
- data/ext/ratatui_ruby/src/widgets/center.rs +0 -2
- data/ext/ratatui_ruby/src/widgets/chart.rs +260 -0
- data/ext/ratatui_ruby/src/widgets/clear.rs +37 -0
- data/ext/ratatui_ruby/src/widgets/cursor.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/layout.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/list.rs +44 -5
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +10 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +25 -6
- data/ext/ratatui_ruby/src/widgets/tabs.rs +2 -1
- data/lib/ratatui_ruby/dsl.rb +64 -0
- data/lib/ratatui_ruby/schema/calendar.rb +26 -0
- data/lib/ratatui_ruby/schema/chart.rb +81 -0
- data/lib/ratatui_ruby/schema/clear.rb +83 -0
- data/lib/ratatui_ruby/schema/list.rb +8 -2
- data/lib/ratatui_ruby/schema/paragraph.rb +7 -4
- data/lib/ratatui_ruby/schema/rect.rb +24 -0
- data/lib/ratatui_ruby/schema/table.rb +8 -2
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +24 -2
- data/mise.toml +8 -0
- data/sig/ratatui_ruby/buffer.rbs +11 -0
- data/sig/ratatui_ruby/schema/calendar.rbs +13 -0
- data/sig/ratatui_ruby/schema/{line_chart.rbs → chart.rbs} +20 -1
- data/sig/ratatui_ruby/schema/list.rbs +4 -1
- data/sig/ratatui_ruby/schema/rect.rbs +14 -0
- data/tasks/bump/cargo_lockfile.rb +19 -0
- data/tasks/bump/changelog.rb +37 -0
- data/tasks/bump/comparison_links.rb +41 -0
- data/tasks/bump/header.rb +30 -0
- data/tasks/bump/history.rb +30 -0
- data/tasks/bump/manifest.rb +31 -0
- data/tasks/bump/ruby_gem.rb +35 -0
- data/tasks/bump/sem_ver.rb +34 -0
- data/tasks/bump/unreleased_section.rb +38 -0
- data/tasks/bump.rake +49 -0
- data/tasks/doc.rake +25 -0
- data/tasks/extension.rake +12 -0
- data/tasks/lint.rake +49 -0
- data/tasks/rdoc_config.rb +15 -0
- data/tasks/resources/build.yml.erb +65 -0
- data/tasks/resources/index.html.erb +38 -0
- data/tasks/resources/rubies.yml +7 -0
- data/tasks/sourcehut.rake +38 -0
- data/tasks/test.rake +31 -0
- data/tasks/website/index_page.rb +28 -0
- data/tasks/website/version.rb +117 -0
- data/tasks/website/version_menu.rb +68 -0
- data/tasks/website/versioned_documentation.rb +49 -0
- data/tasks/website/website.rb +53 -0
- data/tasks/website.rake +26 -0
- metadata +119 -28
- data/.build.yml +0 -34
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -30
- data/CONTRIBUTING.md +0 -40
- data/docs/images/examples-stock_ticker.rb.png +0 -0
- data/ext/ratatui_ruby/src/widgets/linechart.rs +0 -154
- data/lib/ratatui_ruby/schema/line_chart.rb +0 -41
- /data/{docs → doc}/application_testing.md +0 -0
- /data/{docs → doc}/contributors/design/ruby_frontend.md +0 -0
- /data/{docs → doc}/contributors/design/rust_backend.md +0 -0
- /data/{docs → doc}/contributors/design.md +0 -0
- /data/{docs → doc}/images/examples-analytics.rb.png +0 -0
- /data/{docs → doc}/images/examples-box_demo.rb.png +0 -0
- /data/{docs → doc}/images/examples-dashboard.rb.png +0 -0
- /data/{docs → doc}/images/examples-login_form.rb.png +0 -0
- /data/{docs → doc}/images/examples-map_demo.rb.png +0 -0
- /data/{docs → doc}/images/examples-mouse_events.rb.png +0 -0
- /data/{docs → doc}/images/examples-scrollbar_demo.rb.png +0 -0
- /data/{docs → doc}/images/examples-system_monitor.rb.png +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
##
|
|
8
|
+
# A wrapper class that provides a concise DSL for creating widgets and interacting
|
|
9
|
+
# with the terminal within the +main_loop+.
|
|
10
|
+
#
|
|
11
|
+
# This class is yielded to the block provided to {RatatuiRuby.main_loop}.
|
|
12
|
+
# It uses metaprogramming to delegate method calls to {RatatuiRuby} module functions
|
|
13
|
+
# and to act as a factory for {RatatuiRuby} widget classes.
|
|
14
|
+
#
|
|
15
|
+
# == Features
|
|
16
|
+
#
|
|
17
|
+
# 1. **Widget Shorthand**: Provides factory methods for every widget class.
|
|
18
|
+
# Converts snake_case method calls (e.g., +paragraph+) into CamelCase class instantiations
|
|
19
|
+
# (e.g., +RatatuiRuby::Paragraph.new+).
|
|
20
|
+
#
|
|
21
|
+
# 2. **Method Shorthand**: Aliases module functions of {RatatuiRuby}, allowing you
|
|
22
|
+
# to call methods like +draw+ and +poll_event+ directly on the DSL object.
|
|
23
|
+
#
|
|
24
|
+
# == Example
|
|
25
|
+
#
|
|
26
|
+
# RatatuiRuby.main_loop do |tui|
|
|
27
|
+
# # Create UI using shorthand methods
|
|
28
|
+
# view = tui.paragraph(
|
|
29
|
+
# text: "Hello World",
|
|
30
|
+
# block: tui.block(borders: [:all])
|
|
31
|
+
# )
|
|
32
|
+
#
|
|
33
|
+
# # Use module aliases to draw and handle events
|
|
34
|
+
# tui.draw(view)
|
|
35
|
+
# event = tui.poll_event
|
|
36
|
+
#
|
|
37
|
+
# break if event && event[:code] == "q"
|
|
38
|
+
# end
|
|
39
|
+
class DSL
|
|
40
|
+
# Wrap methods directly
|
|
41
|
+
RatatuiRuby.singleton_methods(false).each do |method_name|
|
|
42
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
43
|
+
RatatuiRuby.public_send(method_name, *args, **kwargs, &block)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Wrap classes as snake_case factories
|
|
48
|
+
RatatuiRuby.constants.each do |const_name|
|
|
49
|
+
next if const_name == :Buffer
|
|
50
|
+
|
|
51
|
+
klass = RatatuiRuby.const_get(const_name)
|
|
52
|
+
next unless klass.is_a?(Class)
|
|
53
|
+
|
|
54
|
+
method_name = const_name.to_s
|
|
55
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
56
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
57
|
+
.downcase
|
|
58
|
+
|
|
59
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
60
|
+
klass.new(*args, **kwargs, &block)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
# A Monthly Calendar widget.
|
|
8
|
+
#
|
|
9
|
+
# [year] Integer (e.g., 2025)
|
|
10
|
+
# [month] Integer (1-12)
|
|
11
|
+
# [day_style] Style (Style for regular days)
|
|
12
|
+
# [header_style] Style (Style for the month title)
|
|
13
|
+
# [block] Block
|
|
14
|
+
class Calendar < Data.define(:year, :month, :day_style, :header_style, :block)
|
|
15
|
+
# Creates a new Calendar.
|
|
16
|
+
#
|
|
17
|
+
# [year] Integer (e.g., 2025)
|
|
18
|
+
# [month] Integer (1-12)
|
|
19
|
+
# [day_style] Style (Style for regular days)
|
|
20
|
+
# [header_style] Style (Style for the month title)
|
|
21
|
+
# [block] Block
|
|
22
|
+
def initialize(year:, month:, day_style: nil, header_style: nil, block: nil)
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
# Defines an Axis for a Chart
|
|
8
|
+
# [title] String
|
|
9
|
+
# [bounds] Array<Float> [min, max]
|
|
10
|
+
# [labels] Array<String>
|
|
11
|
+
# [style] Style
|
|
12
|
+
class Axis < Data.define(:title, :bounds, :labels, :style)
|
|
13
|
+
# Creates a new Axis.
|
|
14
|
+
#
|
|
15
|
+
# [title] String
|
|
16
|
+
# [bounds] Array<Float> [min, max]
|
|
17
|
+
# [labels] Array<String>
|
|
18
|
+
# [style] Style
|
|
19
|
+
def initialize(title: "", bounds: [0.0, 10.0], labels: [], style: nil)
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Defines a Dataset for a Chart.
|
|
25
|
+
# [name] The name of the dataset.
|
|
26
|
+
# [data] Array of arrays [[x, y], [x, y]] (Floats).
|
|
27
|
+
# [color] The color of the line.
|
|
28
|
+
# [marker] Symbol (:dot, :braille, :block, :bar)
|
|
29
|
+
# [graph_type] Symbol (:line, :scatter)
|
|
30
|
+
class Dataset < Data.define(:name, :data, :color, :marker, :graph_type)
|
|
31
|
+
# Creates a new Dataset.
|
|
32
|
+
#
|
|
33
|
+
# [name] The name of the dataset.
|
|
34
|
+
# [data] Array of arrays [[x, y], [x, y]] (Floats).
|
|
35
|
+
# [color] The color of the line.
|
|
36
|
+
# [marker] Symbol (:dot, :braille, :block, :bar)
|
|
37
|
+
# [graph_type] Symbol (:line, :scatter)
|
|
38
|
+
def initialize(name:, data:, color: "reset", marker: :dot, graph_type: :line)
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A generic Cartesian chart.
|
|
44
|
+
# [datasets] Array<Dataset>
|
|
45
|
+
# [x_axis] Axis
|
|
46
|
+
# [y_axis] Axis
|
|
47
|
+
# [block] Block
|
|
48
|
+
# [style] Style (base style)
|
|
49
|
+
class Chart < Data.define(:datasets, :x_axis, :y_axis, :block, :style)
|
|
50
|
+
# Creates a new Chart widget.
|
|
51
|
+
#
|
|
52
|
+
# [datasets] Array<Dataset>
|
|
53
|
+
# [x_axis] Axis
|
|
54
|
+
# [y_axis] Axis
|
|
55
|
+
# [block] Block
|
|
56
|
+
# [style] Style (base style)
|
|
57
|
+
def initialize(datasets:, x_axis:, y_axis:, block: nil, style: nil)
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# A complex chart widget. (Legacy/Alias for Chart)
|
|
63
|
+
#
|
|
64
|
+
# [datasets] Array of Dataset objects.
|
|
65
|
+
# [x_labels] Array of Strings for the X-axis labels.
|
|
66
|
+
# [y_labels] Array of Strings for the Y-axis labels.
|
|
67
|
+
# [y_bounds] Array of two Floats [min, max] for the Y-axis.
|
|
68
|
+
# [block] Optional block widget to wrap the chart.
|
|
69
|
+
class LineChart < Data.define(:datasets, :x_labels, :y_labels, :y_bounds, :block)
|
|
70
|
+
# Creates a new LineChart widget.
|
|
71
|
+
#
|
|
72
|
+
# [datasets] Array of Dataset objects.
|
|
73
|
+
# [x_labels] Array of Strings for the X-axis labels.
|
|
74
|
+
# [y_labels] Array of Strings for the Y-axis labels.
|
|
75
|
+
# [y_bounds] Array of two Floats [min, max] for the Y-axis.
|
|
76
|
+
# [block] Optional block widget to wrap the chart.
|
|
77
|
+
def initialize(datasets:, x_labels: [], y_labels: [], y_bounds: [0.0, 100.0], block: nil)
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
# A widget that clears (resets) the terminal buffer in the area it is rendered into.
|
|
8
|
+
#
|
|
9
|
+
# The Clear widget is essential for creating opaque popups and modals. Without it,
|
|
10
|
+
# background content or styles (like background colors) will "bleed through"
|
|
11
|
+
# empty spaces or transparent widgets.
|
|
12
|
+
#
|
|
13
|
+
# > [!TIP]
|
|
14
|
+
# > Use `Clear` to prevent "Style Bleed". If a widget rendered behind the popup
|
|
15
|
+
# > has a background color, widgets rendered on top with `Style.default` will
|
|
16
|
+
# > inherit that background color unless you `Clear` the area first.
|
|
17
|
+
#
|
|
18
|
+
# == Usage with Overlay
|
|
19
|
+
#
|
|
20
|
+
# Because RatatuiRuby uses an immediate-mode UI pattern, you must use {Overlay} to
|
|
21
|
+
# layer widgets properly. The typical pattern for creating an opaque popup is:
|
|
22
|
+
#
|
|
23
|
+
# background = Paragraph.new(text: "Background content...")
|
|
24
|
+
# popup = Paragraph.new(
|
|
25
|
+
# text: "Popup content",
|
|
26
|
+
# block: Block.new(title: "Popup", borders: [:all])
|
|
27
|
+
# )
|
|
28
|
+
#
|
|
29
|
+
# # Create an opaque popup by layering: background -> Clear -> popup
|
|
30
|
+
# ui = Overlay.new(
|
|
31
|
+
# layers: [
|
|
32
|
+
# background,
|
|
33
|
+
# Center.new(
|
|
34
|
+
# child: Overlay.new(
|
|
35
|
+
# layers: [
|
|
36
|
+
# Clear.new, # Erases background in this area
|
|
37
|
+
# popup # Draws on top of cleared area
|
|
38
|
+
# ]
|
|
39
|
+
# ),
|
|
40
|
+
# width_percent: 50,
|
|
41
|
+
# height_percent: 40
|
|
42
|
+
# )
|
|
43
|
+
# ]
|
|
44
|
+
# )
|
|
45
|
+
#
|
|
46
|
+
# Without the Clear widget, the background text would be visible through the
|
|
47
|
+
# empty spaces in the popup.
|
|
48
|
+
#
|
|
49
|
+
# == Optional Block Parameter
|
|
50
|
+
#
|
|
51
|
+
# You can optionally provide a {Block} to draw borders around the cleared area:
|
|
52
|
+
#
|
|
53
|
+
# Clear.new(block: Block.new(title: "Cleared Area", borders: [:all]))
|
|
54
|
+
#
|
|
55
|
+
# This is equivalent to:
|
|
56
|
+
#
|
|
57
|
+
# Overlay.new(
|
|
58
|
+
# layers: [
|
|
59
|
+
# Clear.new,
|
|
60
|
+
# Block.new(title: "Cleared Area", borders: [:all])
|
|
61
|
+
# ]
|
|
62
|
+
# )
|
|
63
|
+
#
|
|
64
|
+
# [block] Optional {Block} widget to render on top of the cleared area.
|
|
65
|
+
#
|
|
66
|
+
# @see Overlay
|
|
67
|
+
# @see Center
|
|
68
|
+
# @see Block
|
|
69
|
+
class Clear < Data.define(:block)
|
|
70
|
+
# Creates a new Clear widget.
|
|
71
|
+
#
|
|
72
|
+
# @param block [Block, nil] Optional block widget to render on top of the cleared area.
|
|
73
|
+
#
|
|
74
|
+
# @example Basic usage
|
|
75
|
+
# Clear.new
|
|
76
|
+
#
|
|
77
|
+
# @example With a border
|
|
78
|
+
# Clear.new(block: Block.new(title: "Modal", borders: [:all]))
|
|
79
|
+
def initialize(block: nil)
|
|
80
|
+
super
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -8,14 +8,20 @@ module RatatuiRuby
|
|
|
8
8
|
#
|
|
9
9
|
# [items] An array of strings to display in the list.
|
|
10
10
|
# [selected_index] The index of the currently selected item, or nil if none.
|
|
11
|
+
# [style] The base style for all items.
|
|
12
|
+
# [highlight_style] The style for the selected item.
|
|
13
|
+
# [highlight_symbol] The symbol to display in front of the selected item.
|
|
11
14
|
# [block] An optional Block widget to wrap the list.
|
|
12
|
-
class List < Data.define(:items, :selected_index, :block)
|
|
15
|
+
class List < Data.define(:items, :selected_index, :style, :highlight_style, :highlight_symbol, :block)
|
|
13
16
|
# Creates a new List.
|
|
14
17
|
#
|
|
15
18
|
# [items] An array of strings to display in the list.
|
|
16
19
|
# [selected_index] The index of the currently selected item, or nil if none.
|
|
20
|
+
# [style] The base style for all items.
|
|
21
|
+
# [highlight_style] The style for the selected item.
|
|
22
|
+
# [highlight_symbol] The symbol to display in front of the selected item.
|
|
17
23
|
# [block] An optional Block widget to wrap the list.
|
|
18
|
-
def initialize(items: [], selected_index: nil, block: nil)
|
|
24
|
+
def initialize(items: [], selected_index: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", block: nil)
|
|
19
25
|
super
|
|
20
26
|
end
|
|
21
27
|
end
|
|
@@ -9,7 +9,8 @@ module RatatuiRuby
|
|
|
9
9
|
# [text] the text to display.
|
|
10
10
|
# [style] the style to apply (Style object).
|
|
11
11
|
# [block] an optional Block widget to wrap the paragraph.
|
|
12
|
-
|
|
12
|
+
# [scroll] scroll offset as (y, x) array matching ratatui convention.
|
|
13
|
+
class Paragraph < Data.define(:text, :style, :block, :wrap, :align, :scroll)
|
|
13
14
|
# Creates a new Paragraph.
|
|
14
15
|
#
|
|
15
16
|
# [text] the text to display.
|
|
@@ -17,7 +18,8 @@ module RatatuiRuby
|
|
|
17
18
|
# [block] the block to wrap the paragraph.
|
|
18
19
|
# [wrap] whether to wrap text at width.
|
|
19
20
|
# [align] alignment (:left, :center, :right).
|
|
20
|
-
|
|
21
|
+
# [scroll] scroll offset as (y, x) array (default: [0, 0]).
|
|
22
|
+
def initialize(text:, style: Style.default, block: nil, wrap: false, align: :left, scroll: [0, 0])
|
|
21
23
|
super
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -29,9 +31,10 @@ module RatatuiRuby
|
|
|
29
31
|
# [block] the block to wrap the paragraph.
|
|
30
32
|
# [wrap] whether to wrap text at width.
|
|
31
33
|
# [align] alignment (:left, :center, :right).
|
|
32
|
-
|
|
34
|
+
# [scroll] scroll offset as (y, x) array (default: [0, 0]).
|
|
35
|
+
def self.new(text:, style: nil, fg: nil, bg: nil, block: nil, wrap: false, align: :left, scroll: [0, 0])
|
|
33
36
|
style ||= Style.new(fg:, bg:)
|
|
34
|
-
super(text:, style:, block:, wrap:, align:)
|
|
37
|
+
super(text:, style:, block:, wrap:, align:, scroll:)
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
# A rectangle in the terminal grid.
|
|
8
|
+
#
|
|
9
|
+
# [x] The x-coordinate of the top-left corner.
|
|
10
|
+
# [y] The y-coordinate of the top-left corner.
|
|
11
|
+
# [width] The width of the rectangle.
|
|
12
|
+
# [height] The height of the rectangle.
|
|
13
|
+
class Rect < Data.define(:x, :y, :width, :height)
|
|
14
|
+
# Creates a new Rect.
|
|
15
|
+
#
|
|
16
|
+
# [x] The x-coordinate of the top-left corner.
|
|
17
|
+
# [y] The y-coordinate of the top-left corner.
|
|
18
|
+
# [width] The width of the rectangle.
|
|
19
|
+
# [height] The height of the rectangle.
|
|
20
|
+
def initialize(x: 0, y: 0, width: 0, height: 0)
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -9,15 +9,21 @@ module RatatuiRuby
|
|
|
9
9
|
# [header] An array of strings or Paragraphs representing the header row.
|
|
10
10
|
# [rows] An array of arrays of strings or Paragraphs representing the data rows.
|
|
11
11
|
# [widths] An array of Constraint objects defining column widths.
|
|
12
|
+
# [highlight_style] The style for the selected row.
|
|
13
|
+
# [highlight_symbol] The symbol to display in front of the selected row.
|
|
14
|
+
# [selected_row] The index of the currently selected row, or nil if none.
|
|
12
15
|
# [block] An optional Block widget to wrap the table.
|
|
13
|
-
class Table < Data.define(:header, :rows, :widths, :block)
|
|
16
|
+
class Table < Data.define(:header, :rows, :widths, :highlight_style, :highlight_symbol, :selected_row, :block)
|
|
14
17
|
# Creates a new Table.
|
|
15
18
|
#
|
|
16
19
|
# [header] An array of strings or Paragraphs representing the header row.
|
|
17
20
|
# [rows] An array of arrays of strings or Paragraphs representing the data rows.
|
|
18
21
|
# [widths] An array of Constraint objects defining column widths.
|
|
22
|
+
# [highlight_style] The style for the selected row.
|
|
23
|
+
# [highlight_symbol] The symbol to display in front of the selected row.
|
|
24
|
+
# [selected_row] The index of the currently selected row, or nil if none.
|
|
19
25
|
# [block] An optional Block widget to wrap the table.
|
|
20
|
-
def initialize(header: nil, rows: [], widths: [], block: nil)
|
|
26
|
+
def initialize(header: nil, rows: [], widths: [], highlight_style: nil, highlight_symbol: "> ", selected_row: nil, block: nil)
|
|
21
27
|
super
|
|
22
28
|
end
|
|
23
29
|
end
|
data/lib/ratatui_ruby/version.rb
CHANGED
data/lib/ratatui_ruby.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
|
|
6
6
|
require_relative "ratatui_ruby/version"
|
|
7
|
+
require_relative "ratatui_ruby/schema/rect"
|
|
7
8
|
require_relative "ratatui_ruby/schema/paragraph"
|
|
8
9
|
require_relative "ratatui_ruby/schema/layout"
|
|
9
10
|
require_relative "ratatui_ruby/schema/block"
|
|
@@ -15,12 +16,14 @@ require_relative "ratatui_ruby/schema/table"
|
|
|
15
16
|
require_relative "ratatui_ruby/schema/tabs"
|
|
16
17
|
require_relative "ratatui_ruby/schema/bar_chart"
|
|
17
18
|
require_relative "ratatui_ruby/schema/sparkline"
|
|
18
|
-
require_relative "ratatui_ruby/schema/
|
|
19
|
+
require_relative "ratatui_ruby/schema/chart"
|
|
20
|
+
require_relative "ratatui_ruby/schema/clear"
|
|
19
21
|
require_relative "ratatui_ruby/schema/cursor"
|
|
20
22
|
require_relative "ratatui_ruby/schema/overlay"
|
|
21
23
|
require_relative "ratatui_ruby/schema/center"
|
|
22
24
|
require_relative "ratatui_ruby/schema/scrollbar"
|
|
23
25
|
require_relative "ratatui_ruby/schema/canvas"
|
|
26
|
+
require_relative "ratatui_ruby/schema/calendar"
|
|
24
27
|
|
|
25
28
|
begin
|
|
26
29
|
require "ratatui_ruby/ratatui_ruby"
|
|
@@ -69,7 +72,7 @@ module RatatuiRuby
|
|
|
69
72
|
# Polls for a keyboard event.
|
|
70
73
|
#
|
|
71
74
|
# poll_event
|
|
72
|
-
# # => { type:
|
|
75
|
+
# # => { type: :key, code: "a", modifiers: ["ctrl"] }
|
|
73
76
|
#
|
|
74
77
|
# (Native method implemented in Rust)
|
|
75
78
|
|
|
@@ -84,4 +87,23 @@ module RatatuiRuby
|
|
|
84
87
|
# inject_test_event("key", { code: "a" })
|
|
85
88
|
#
|
|
86
89
|
# (Native method implemented in Rust)
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Provides a convenience wrapper for the main TUI loop.
|
|
93
|
+
# Initializes the terminal, runs the loop, and ensures the terminal is restored.
|
|
94
|
+
#
|
|
95
|
+
# RatatuiRuby.main_loop do
|
|
96
|
+
# draw RatatuiRuby::Paragraph.new(text: "Hello")
|
|
97
|
+
# event = RatatuiRuby::poll_event
|
|
98
|
+
# break if event && event[:type] == :key && event[:code] == "q"
|
|
99
|
+
# end
|
|
100
|
+
def self.main_loop
|
|
101
|
+
require_relative "ratatui_ruby/dsl"
|
|
102
|
+
init_terminal
|
|
103
|
+
loop do
|
|
104
|
+
yield DSL.new
|
|
105
|
+
end
|
|
106
|
+
ensure
|
|
107
|
+
restore_terminal
|
|
108
|
+
end
|
|
87
109
|
end
|
data/mise.toml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
class Buffer
|
|
8
|
+
def set_string: (Integer x, Integer y, String string, Style style) -> void
|
|
9
|
+
def area: () -> Rect
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
module RatatuiRuby
|
|
5
|
+
class Calendar < Data
|
|
6
|
+
attr_reader year: Integer
|
|
7
|
+
attr_reader month: Integer
|
|
8
|
+
attr_reader day_style: Style?
|
|
9
|
+
attr_reader header_style: Style?
|
|
10
|
+
attr_reader block: Block?
|
|
11
|
+
def self.new: (year: Integer, month: Integer, ?day_style: Style?, ?header_style: Style?, ?block: Block?) -> Calendar
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -2,11 +2,30 @@
|
|
|
2
2
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
module RatatuiRuby
|
|
5
|
+
class Axis < Data
|
|
6
|
+
attr_reader title: String
|
|
7
|
+
attr_reader bounds: [Float, Float]
|
|
8
|
+
attr_reader labels: Array[String]
|
|
9
|
+
attr_reader style: Style?
|
|
10
|
+
def self.new: (?title: String, ?bounds: [Float, Float], ?labels: Array[String], ?style: Style?) -> Axis
|
|
11
|
+
end
|
|
12
|
+
|
|
5
13
|
class Dataset < Data
|
|
6
14
|
attr_reader name: String | Symbol
|
|
7
15
|
attr_reader data: Array[[Float, Float]]
|
|
8
16
|
attr_reader color: String | Symbol
|
|
9
|
-
|
|
17
|
+
attr_reader marker: Symbol
|
|
18
|
+
attr_reader graph_type: Symbol
|
|
19
|
+
def self.new: (name: String | Symbol, data: Array[[Float, Float]], ?color: String | Symbol, ?marker: Symbol, ?graph_type: Symbol) -> Dataset
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Chart < Data
|
|
23
|
+
attr_reader datasets: Array[Dataset]
|
|
24
|
+
attr_reader x_axis: Axis
|
|
25
|
+
attr_reader y_axis: Axis
|
|
26
|
+
attr_reader block: Block?
|
|
27
|
+
attr_reader style: Style?
|
|
28
|
+
def self.new: (datasets: Array[Dataset], x_axis: Axis, y_axis: Axis, ?block: Block?, ?style: Style?) -> Chart
|
|
10
29
|
end
|
|
11
30
|
|
|
12
31
|
class LineChart < Data
|
|
@@ -5,7 +5,10 @@ module RatatuiRuby
|
|
|
5
5
|
class List < Data
|
|
6
6
|
attr_reader items: Array[String]
|
|
7
7
|
attr_reader selected_index: Integer?
|
|
8
|
+
attr_reader style: Style?
|
|
9
|
+
attr_reader highlight_style: Style?
|
|
10
|
+
attr_reader highlight_symbol: String?
|
|
8
11
|
attr_reader block: Block?
|
|
9
|
-
def self.new: (?items: Array[String], ?selected_index: Integer?, ?block: Block?) -> List
|
|
12
|
+
def self.new: (?items: Array[String], ?selected_index: Integer?, ?style: Style?, ?highlight_style: Style?, ?highlight_symbol: String?, ?block: Block?) -> List
|
|
10
13
|
end
|
|
11
14
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
class Rect < Data
|
|
8
|
+
attr_reader x: Integer
|
|
9
|
+
attr_reader y: Integer
|
|
10
|
+
attr_reader width: Integer
|
|
11
|
+
attr_reader height: Integer
|
|
12
|
+
def self.new: (x: Integer, y: Integer, width: Integer, height: Integer) -> instance
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
# Lockfiles need to be refreshed by a tool after Manifests are changed.
|
|
7
|
+
class CargoLockfile < Data.define(:path, :dir, :name)
|
|
8
|
+
def exists?
|
|
9
|
+
File.exist?(path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def refresh
|
|
13
|
+
return unless exists?
|
|
14
|
+
|
|
15
|
+
Dir.chdir(dir) do
|
|
16
|
+
system("cargo update -p #{name} --offline")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require_relative "comparison_links"
|
|
7
|
+
require_relative "unreleased_section"
|
|
8
|
+
require_relative "history"
|
|
9
|
+
require_relative "header"
|
|
10
|
+
|
|
11
|
+
# Changelog manages the project's CHANGELOG.md file.
|
|
12
|
+
class Changelog
|
|
13
|
+
# Creates a new Changelog for the file at the given path.
|
|
14
|
+
def initialize(path: "CHANGELOG.md")
|
|
15
|
+
@path = path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Releases a new version in the changelog.
|
|
19
|
+
# This moves the unreleased changes to a new version heading and resets the unreleased section.
|
|
20
|
+
def release(new_version)
|
|
21
|
+
content = File.read(@path)
|
|
22
|
+
|
|
23
|
+
header = Header.parse(content)
|
|
24
|
+
unreleased = UnreleasedSection.parse(content)
|
|
25
|
+
links = ComparisonLinks.parse(content)
|
|
26
|
+
|
|
27
|
+
raise "Could not parse CHANGELOG.md" unless header && unreleased && links
|
|
28
|
+
|
|
29
|
+
history = History.parse(content, header.length, unreleased.to_s.length, links.to_s)
|
|
30
|
+
|
|
31
|
+
links.update(new_version)
|
|
32
|
+
history.add(unreleased.as_version(new_version))
|
|
33
|
+
|
|
34
|
+
File.write(@path, "#{header}#{UnreleasedSection.fresh}\n\n#{history}\n#{links}")
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
# ComparisonLinks manages the git comparison links at the bottom of the changelog.
|
|
7
|
+
class ComparisonLinks
|
|
8
|
+
PATTERN = /^(\[Unreleased\]: .*)$/m
|
|
9
|
+
|
|
10
|
+
# Extracts the comparison links from the given content.
|
|
11
|
+
def self.parse(content)
|
|
12
|
+
match = content.match(PATTERN)
|
|
13
|
+
new(match[1].strip) if match
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Creates a new ComparisonLinks from the given links text.
|
|
17
|
+
def initialize(links)
|
|
18
|
+
@links = links.dup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Updates the comparison links for the new version.
|
|
22
|
+
def update(new_version)
|
|
23
|
+
pattern = %r{^\[Unreleased\]: (.*?/compare/)v(.*)\.\.\.HEAD$}
|
|
24
|
+
match = @links.match(pattern)
|
|
25
|
+
return unless match
|
|
26
|
+
|
|
27
|
+
base_url = match[1]
|
|
28
|
+
prev_version = match[2]
|
|
29
|
+
|
|
30
|
+
new_unreleased = "[Unreleased]: #{base_url}v#{new_version}...HEAD"
|
|
31
|
+
new_version_link = "[#{new_version}]: #{base_url}v#{prev_version}...v#{new_version}"
|
|
32
|
+
|
|
33
|
+
@links.sub!(pattern, "#{new_unreleased}\n#{new_version_link}")
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the current state of the links as a string.
|
|
38
|
+
def to_s
|
|
39
|
+
@links
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
# Header manages the header section of the changelog.
|
|
7
|
+
class Header
|
|
8
|
+
PATTERN = /^(.*?)(?=## \[Unreleased\])/m
|
|
9
|
+
|
|
10
|
+
# Extracts the header section from the given content.
|
|
11
|
+
def self.parse(content)
|
|
12
|
+
match = content.match(PATTERN)
|
|
13
|
+
new(match[1]) if match
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Creates a new Header from the given content.
|
|
17
|
+
def initialize(content)
|
|
18
|
+
@content = content.dup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns the length of the header content.
|
|
22
|
+
def length
|
|
23
|
+
@content.length
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the current state of the header as a string.
|
|
27
|
+
def to_s
|
|
28
|
+
@content
|
|
29
|
+
end
|
|
30
|
+
end
|