ratatui_ruby 0.5.0 → 0.6.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 +6 -0
- data/CHANGELOG.md +44 -7
- data/README.md +11 -4
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +84 -10
- data/doc/application_testing.md +75 -29
- data/doc/contributors/design/ruby_frontend.md +39 -3
- data/doc/contributors/design/rust_backend.md +1 -0
- data/doc/contributors/developing_examples.md +129 -44
- data/doc/contributors/examples_audit/p1_high.md +21 -0
- data/doc/contributors/examples_audit/p2_moderate.md +81 -0
- data/doc/contributors/examples_audit.md +41 -0
- data/doc/event_handling.md +11 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.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_demo.png +0 -0
- data/doc/images/widget_block_demo.png +0 -0
- data/doc/images/widget_canvas_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_center_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_overlay_demo.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_sparkline_demo.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/quickstart.md +69 -76
- data/doc/terminal_limitations.md +92 -0
- data/examples/app_all_events/README.md +45 -27
- data/examples/app_all_events/app.rb +38 -35
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_entry.rb +17 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +8 -8
- data/examples/app_all_events/view/controls_view.rb +8 -6
- data/examples/app_all_events/view/counts_view.rb +12 -8
- data/examples/app_all_events/view/live_view.rb +8 -7
- data/examples/app_all_events/view/log_view.rb +10 -15
- data/examples/app_color_picker/README.md +84 -44
- data/examples/app_color_picker/app.rb +24 -62
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +45 -49
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/input.rb +99 -67
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +55 -26
- data/examples/app_login_form/README.md +47 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +31 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +48 -0
- data/examples/verify_quickstart_dsl/app.rb +2 -0
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +2 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +8 -2
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +8 -2
- data/examples/widget_barchart_demo/README.md +49 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +34 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_canvas_demo/README.md +27 -0
- data/examples/widget_canvas_demo/app.rb +123 -0
- data/examples/widget_cell_demo/README.md +36 -0
- data/examples/widget_cell_demo/app.rb +31 -24
- data/examples/widget_center_demo/README.md +29 -0
- data/examples/widget_center_demo/app.rb +116 -0
- data/examples/widget_chart_demo/README.md +41 -0
- data/examples/widget_chart_demo/app.rb +7 -2
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +91 -107
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{app_map_demo → widget_map_demo}/app.rb +2 -2
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +62 -33
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +0 -1
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +4 -3
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/{app_table_select → widget_table_demo}/app.rb +46 -8
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +15 -1
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +106 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +238 -26
- data/ext/ratatui_ruby/src/frame.rs +113 -1
- data/ext/ratatui_ruby/src/lib.rs +34 -4
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/terminal.rs +39 -15
- data/ext/ratatui_ruby/src/text.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
- data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +113 -1
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/cell.rb +4 -4
- data/lib/ratatui_ruby/event/key/character.rb +35 -0
- data/lib/ratatui_ruby/event/key/media.rb +44 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
- data/lib/ratatui_ruby/event/key/system.rb +45 -0
- data/lib/ratatui_ruby/event/key.rb +111 -51
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/frame.rb +96 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/cursor.rb +5 -0
- data/lib/ratatui_ruby/schema/gauge.rb +3 -1
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +25 -4
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/rect.rb +43 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +21 -3
- data/lib/ratatui_ruby/schema/text.rb +69 -1
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/session/autodoc.rb +65 -0
- data/lib/ratatui_ruby/session.rb +22 -7
- data/lib/ratatui_ruby/table_state.rb +90 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
- data/lib/ratatui_ruby/test_helper.rb +65 -358
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +42 -19
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
- data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
- data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +2 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/list.rbs +4 -2
- data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
- data/sig/ratatui_ruby/schema/rect.rbs +3 -0
- data/sig/ratatui_ruby/schema/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +8 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +13 -0
- data/sig/ratatui_ruby/table_state.rbs +15 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
- data/sig/ratatui_ruby/test_helper.rbs +5 -4
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc/inventory.rb +9 -7
- data/tasks/autodoc.rake +11 -5
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +61 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +111 -37
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/examples/app_all_events/model/events.rb +0 -180
- data/examples/app_all_events/model/highlight.rb +0 -57
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
- data/examples/app_all_events/view_state.rb +0 -42
- data/examples/app_color_picker/scene.rb +0 -201
- data/examples/widget_block_padding/app.rb +0 -67
- data/examples/widget_block_titles/app.rb +0 -69
- data/examples/widget_list_styles/app.rb +0 -141
- data/examples/widget_table_flex/app.rb +0 -95
- data/sig/examples/widget_block_padding/app.rbs +0 -11
- data/sig/examples/widget_block_titles/app.rbs +0 -11
- data/sig/examples/widget_list_styles/app.rbs +0 -11
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Scrollbar Widget Example
|
|
7
|
+
|
|
8
|
+
Demonstrates explicit scrollbars for navigation feedback.
|
|
9
|
+
|
|
10
|
+
Content overflows. Users get lost in long lists. Scrollbars provide essential spatial awareness ("How far down am I?") and navigation controls.
|
|
11
|
+
|
|
12
|
+
## Features Demonstrated
|
|
13
|
+
|
|
14
|
+
- **Orientation**: Vertical, Horizontal, and variation modes (Right/Left, Top/Bottom).
|
|
15
|
+
- **Styling**: Custom characters for Track, Thumb, and arrows.
|
|
16
|
+
- **State Integration**: Linking the scrollbar `position` to the content view state.
|
|
17
|
+
|
|
18
|
+
## Hotkeys
|
|
19
|
+
|
|
20
|
+
- **Mouse Wheel**: Scroll content (`position`)
|
|
21
|
+
- **s**: Cycle Scrollbar Theme (Standard, Rounded, ASCII, Minimal)
|
|
22
|
+
- **o**: Cycle Orientation (`orientation`)
|
|
23
|
+
- **q**: Quit
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ruby examples/widget_scrollbar_demo/app.rb
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Learning Outcomes
|
|
32
|
+
|
|
33
|
+
Use this example if you need to...
|
|
34
|
+
- Add visual scroll indicators to Lists or Tables.
|
|
35
|
+
- Implement specialized inputs like sliders or volume controls.
|
|
36
|
+
|
|
37
|
+

|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Sparkline Widget Example
|
|
7
|
+
|
|
8
|
+
Demonstrates high-density data visualization in a condensed footprint.
|
|
9
|
+
|
|
10
|
+
Users need context. A single number ("90% CPU") tells you status, but not the trend. Full charts take up too much space. Sparklines condense history into a single line, perfect for headers and dashboards.
|
|
11
|
+
|
|
12
|
+
## Features Demonstrated
|
|
13
|
+
|
|
14
|
+
- **High Density**: Showing dozens of data points in a small area.
|
|
15
|
+
- **Direction**: Rendering Left-to-Right (standard) or Right-to-Left (like a scrolling ticker).
|
|
16
|
+
- **Gaps**: Handling `nil` values with "absent symbols" to indicate missing data.
|
|
17
|
+
- **Styling**: Using colors and custom characters to indicate severity or type.
|
|
18
|
+
|
|
19
|
+
## Hotkeys
|
|
20
|
+
|
|
21
|
+
- **Up/Down (↑/↓)**: Cycle Data Set (`data`)
|
|
22
|
+
- **d**: Cycle Direction (`direction`)
|
|
23
|
+
- **c**: Cycle Color (`style`)
|
|
24
|
+
- **m**: Cycle Absent Value Marker Symbol (`absent_value_symbol`)
|
|
25
|
+
- **s**: Cycle Absent Value Marker Style (`absent_value_style`)
|
|
26
|
+
- **b**: Cycle Bar Character Set (`bar_set`)
|
|
27
|
+
- **q**: Quit
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ruby examples/widget_sparkline_demo/app.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Learning Outcomes
|
|
36
|
+
|
|
37
|
+
Use this example if you need to...
|
|
38
|
+
- Add a "CPU Load" graph to your header.
|
|
39
|
+
- Visualize stock price trends in a list row.
|
|
40
|
+
- Monitor memory usage over the last 60 seconds.
|
|
41
|
+
|
|
42
|
+

|
|
@@ -57,7 +57,8 @@ class WidgetSparklineDemo
|
|
|
57
57
|
data: [1, 5, 1, 8, 1, 6, 1, 9, 1, 7, 1, 10],
|
|
58
58
|
},
|
|
59
59
|
]
|
|
60
|
-
@data_index =
|
|
60
|
+
@data_index = 2
|
|
61
|
+
srand(12345) # Ensure reproducible "Random" data for snapshots
|
|
61
62
|
|
|
62
63
|
@directions = [
|
|
63
64
|
{ name: "Left to Right", direction: :left_to_right },
|
|
@@ -72,7 +73,7 @@ class WidgetSparklineDemo
|
|
|
72
73
|
{ name: "Cyan", style: @tui.style(fg: :cyan) },
|
|
73
74
|
{ name: "Magenta", style: @tui.style(fg: :magenta) },
|
|
74
75
|
]
|
|
75
|
-
@style_index =
|
|
76
|
+
@style_index = 3
|
|
76
77
|
|
|
77
78
|
@absent_symbols = [
|
|
78
79
|
{ name: "None", symbol: nil },
|
|
@@ -89,7 +90,7 @@ class WidgetSparklineDemo
|
|
|
89
90
|
{ name: "Dim Red", style: @tui.style(fg: :red, modifiers: [:dim]) },
|
|
90
91
|
{ name: "Dim Yellow", style: @tui.style(fg: :yellow, modifiers: [:dim]) },
|
|
91
92
|
]
|
|
92
|
-
@absent_style_index =
|
|
93
|
+
@absent_style_index = 2
|
|
93
94
|
|
|
94
95
|
@bar_sets = [
|
|
95
96
|
{ name: "Default (Block)", set: nil },
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Style Colors Example
|
|
7
|
+
|
|
8
|
+
Demonstrates high-fidelity color support.
|
|
9
|
+
|
|
10
|
+
Terminals support millions of colors. This example generates a mathematically precise HSL gradient to prove the rendering engine's color fidelity.
|
|
11
|
+
|
|
12
|
+
## Features Demonstrated
|
|
13
|
+
|
|
14
|
+
- **HSL to RGB Conversion**: generating smooth color gradients programmatically.
|
|
15
|
+
- **TrueColor Support**: Rendering arbitrary HEX colors.
|
|
16
|
+
|
|
17
|
+
## Hotkeys
|
|
18
|
+
|
|
19
|
+
- **q** / **Ctrl+c**: Quit
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ruby examples/widget_style_colors/app.rb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Learning Outcomes
|
|
28
|
+
|
|
29
|
+
Use this example if you need to...
|
|
30
|
+
- Create meaningful heatmaps.
|
|
31
|
+
- Generate color palettes dynamically.
|
|
32
|
+
- Test your terminal's color support capabilities.
|
|
33
|
+
|
|
34
|
+

|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Table Widget Example
|
|
7
|
+
|
|
8
|
+
Demonstrates advanced options for the `Table` widget, including selection, row-level highlighting, and column-level highlighting.
|
|
9
|
+
|
|
10
|
+
Data grids are complex. Users expect to navigate them with keys, select rows, and clearly see which cell is active. The `Table` widget provides these features out of the box efficiently.
|
|
11
|
+
|
|
12
|
+
## Features Demonstrated
|
|
13
|
+
|
|
14
|
+
- **Selection State**: Managing `selected_row` and `selected_column` to track user focus.
|
|
15
|
+
- **Complex Highlighting**:
|
|
16
|
+
- **Row**: Highlight the entire active row.
|
|
17
|
+
- **Highlight Symbol:** Adding a visual indicator (like `> `) to the selected row.
|
|
18
|
+
- **Spacing:** Adjusting `column_spacing` and `highlight_spacing` to control layout density.
|
|
19
|
+
- **Flex Layout:** Switching between different column distribution modes (`legacy`, `start`, `space_between`, etc.).
|
|
20
|
+
- **Offset Control:** Manually controlling the scroll position using `offset`.
|
|
21
|
+
|
|
22
|
+
## Hotkeys
|
|
23
|
+
|
|
24
|
+
- **Arrows (↑/↓)**: Navigate Rows (`selected_row`)
|
|
25
|
+
- **Arrows (←/→)**: Navigate Columns (`selected_column`)
|
|
26
|
+
- **x**: Toggle Row Selection (`selected_row` = nil)
|
|
27
|
+
- **s**: Cycle Table Style (`style`)
|
|
28
|
+
- **p**: Cycle Spacing (`highlight_spacing`)
|
|
29
|
+
- **c**: Toggle Column Highlight (`column_highlight_style`)
|
|
30
|
+
- **z**: Toggle Cell Highlight (`cell_highlight_style`)
|
|
31
|
+
- **o**: Cycle Offset Mode (`offset`)
|
|
32
|
+
- **f**: Cycle Flex Mode (`flex`)
|
|
33
|
+
- **q**: Quit
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ruby examples/widget_table_demo/app.rb
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Learning Outcomes
|
|
42
|
+
|
|
43
|
+
Use this example if you need to...
|
|
44
|
+
- Build a file explorer or process list.
|
|
45
|
+
- Create a data-heavy dashboard.
|
|
46
|
+
- Handle conflicting style requirements (e.g., "Highlight this row, but make this error cell red").
|
|
47
|
+
|
|
48
|
+

|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
@@ -19,7 +18,7 @@ PROCESSES = [
|
|
|
19
18
|
{ pid: 6789, name: "node", cpu: 18.9 },
|
|
20
19
|
].freeze
|
|
21
20
|
|
|
22
|
-
class
|
|
21
|
+
class WidgetTableDemo
|
|
23
22
|
attr_reader :selected_index, :selected_col, :current_style_index, :column_spacing, :highlight_spacing, :column_highlight_style, :cell_highlight_style
|
|
24
23
|
|
|
25
24
|
HIGHLIGHT_SPACINGS = [
|
|
@@ -28,6 +27,22 @@ class AppTableSelect
|
|
|
28
27
|
{ name: "Never", spacing: :never },
|
|
29
28
|
].freeze
|
|
30
29
|
|
|
30
|
+
OFFSET_MODES = [
|
|
31
|
+
{ name: "Auto (No Offset)", offset: nil, allow_selection: true },
|
|
32
|
+
{ name: "Offset Only (row 3)", offset: 3, allow_selection: false },
|
|
33
|
+
{ name: "Selection + Offset (Conflict)", offset: 0, allow_selection: true },
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
FLEX_MODES = [
|
|
37
|
+
{ name: "Legacy (Default)", flex: :legacy },
|
|
38
|
+
{ name: "Start", flex: :start },
|
|
39
|
+
{ name: "Center", flex: :center },
|
|
40
|
+
{ name: "End", flex: :end },
|
|
41
|
+
{ name: "Space Between", flex: :space_between },
|
|
42
|
+
{ name: "Space Around", flex: :space_around },
|
|
43
|
+
{ name: "Space Evenly", flex: :space_evenly },
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
31
46
|
def initialize
|
|
32
47
|
@selected_index = 1
|
|
33
48
|
@selected_col = 1
|
|
@@ -36,6 +51,8 @@ class AppTableSelect
|
|
|
36
51
|
@highlight_spacing_index = 0
|
|
37
52
|
@show_column_highlight = true
|
|
38
53
|
@show_cell_highlight = true
|
|
54
|
+
@offset_mode_index = 0
|
|
55
|
+
@flex_mode_index = 0
|
|
39
56
|
end
|
|
40
57
|
|
|
41
58
|
def run
|
|
@@ -80,15 +97,23 @@ class AppTableSelect
|
|
|
80
97
|
|
|
81
98
|
current_style_entry = @styles[@current_style_index]
|
|
82
99
|
current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
|
|
83
|
-
|
|
100
|
+
offset_mode_entry = OFFSET_MODES[@offset_mode_index]
|
|
101
|
+
flex_mode_entry = FLEX_MODES[@flex_mode_index]
|
|
102
|
+
|
|
103
|
+
# Determine selection/offset based on mode
|
|
104
|
+
effective_selection = offset_mode_entry[:allow_selection] ? @selected_index : nil
|
|
105
|
+
effective_offset = offset_mode_entry[:offset]
|
|
106
|
+
selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
|
|
107
|
+
offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
|
|
84
108
|
|
|
85
109
|
# Main table
|
|
86
110
|
table = @tui.table(
|
|
87
111
|
header: ["PID", "Name", "CPU"],
|
|
88
112
|
rows:,
|
|
89
113
|
widths:,
|
|
90
|
-
selected_row:
|
|
114
|
+
selected_row: effective_selection,
|
|
91
115
|
selected_column: @selected_col,
|
|
116
|
+
offset: effective_offset,
|
|
92
117
|
highlight_style:,
|
|
93
118
|
highlight_symbol: "> ",
|
|
94
119
|
highlight_spacing: current_spacing_entry[:spacing],
|
|
@@ -96,8 +121,9 @@ class AppTableSelect
|
|
|
96
121
|
cell_highlight_style: @show_cell_highlight ? @cell_highlight_style : nil,
|
|
97
122
|
style: current_style_entry[:style],
|
|
98
123
|
column_spacing: @column_spacing,
|
|
124
|
+
flex: flex_mode_entry[:flex],
|
|
99
125
|
block: @tui.block(
|
|
100
|
-
title: "Processes",
|
|
126
|
+
title: "Processes | Sel: #{selection_label} | Offset: #{offset_label} | Flex: #{flex_mode_entry[:name]}",
|
|
101
127
|
borders: :all
|
|
102
128
|
),
|
|
103
129
|
footer: ["Total: #{PROCESSES.length}", "Total CPU: #{PROCESSES.sum { |p| p[:cpu] }}%", ""]
|
|
@@ -130,11 +156,19 @@ class AppTableSelect
|
|
|
130
156
|
]),
|
|
131
157
|
# Line 3: More Controls
|
|
132
158
|
@tui.text_line(spans: [
|
|
159
|
+
@tui.text_span(content: "+/-", style: @hotkey_style),
|
|
133
160
|
@tui.text_span(content: ": Col Space (#{@column_spacing}) "),
|
|
134
161
|
@tui.text_span(content: "c", style: @hotkey_style),
|
|
135
162
|
@tui.text_span(content: ": Col Highlight (#{@show_column_highlight ? 'On' : 'Off'}) "),
|
|
163
|
+
@tui.text_span(content: "f", style: @hotkey_style),
|
|
164
|
+
@tui.text_span(content: ": Flex Mode (#{flex_mode_entry[:name]})"),
|
|
165
|
+
]),
|
|
166
|
+
# Line 4: Offset Mode
|
|
167
|
+
@tui.text_line(spans: [
|
|
136
168
|
@tui.text_span(content: "z", style: @hotkey_style),
|
|
137
|
-
@tui.text_span(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'})"),
|
|
169
|
+
@tui.text_span(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'}) "),
|
|
170
|
+
@tui.text_span(content: "o", style: @hotkey_style),
|
|
171
|
+
@tui.text_span(content: ": Offset Mode (#{offset_mode_entry[:name]})"),
|
|
138
172
|
]),
|
|
139
173
|
]
|
|
140
174
|
),
|
|
@@ -147,7 +181,7 @@ class AppTableSelect
|
|
|
147
181
|
direction: :vertical,
|
|
148
182
|
constraints: [
|
|
149
183
|
@tui.constraint_fill(1),
|
|
150
|
-
@tui.constraint_length(
|
|
184
|
+
@tui.constraint_length(6),
|
|
151
185
|
]
|
|
152
186
|
)
|
|
153
187
|
|
|
@@ -190,6 +224,10 @@ class AppTableSelect
|
|
|
190
224
|
@show_column_highlight = !@show_column_highlight
|
|
191
225
|
in type: :key, code: "z"
|
|
192
226
|
@show_cell_highlight = !@show_cell_highlight
|
|
227
|
+
in type: :key, code: "o"
|
|
228
|
+
@offset_mode_index = (@offset_mode_index + 1) % OFFSET_MODES.length
|
|
229
|
+
in type: :key, code: "f"
|
|
230
|
+
@flex_mode_index = (@flex_mode_index + 1) % FLEX_MODES.length
|
|
193
231
|
else
|
|
194
232
|
nil
|
|
195
233
|
end
|
|
@@ -197,5 +235,5 @@ class AppTableSelect
|
|
|
197
235
|
end
|
|
198
236
|
|
|
199
237
|
if __FILE__ == $0
|
|
200
|
-
|
|
238
|
+
WidgetTableDemo.new.run
|
|
201
239
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Tabs Widget Example
|
|
7
|
+
|
|
8
|
+
Demonstrates view segregation with interactive navigation.
|
|
9
|
+
|
|
10
|
+
Screen real estate is limited. You cannot show everything at once. Tabs segregate content into specialized views (modes), allowing users to switch contexts easily.
|
|
11
|
+
|
|
12
|
+
## Features Demonstrated
|
|
13
|
+
|
|
14
|
+
- **Condition Rendering**: Changing the *content* of the screen based on the selected tab (Revenue vs Traffic vs Errors).
|
|
15
|
+
- **Styling**: Configurable highlight styles, dividers, and padding.
|
|
16
|
+
- **Interaction**: Keyboard navigation to cycle through tabs.
|
|
17
|
+
|
|
18
|
+
## Hotkeys
|
|
19
|
+
|
|
20
|
+
- **Left/Right (←/→)**: Select Tab (`selected_index`)
|
|
21
|
+
- **d**: Cycle Divider Character (`divider`)
|
|
22
|
+
- **s**: Cycle Highlight Style (`highlight_style`)
|
|
23
|
+
- **b**: Cycle Base Style (`style`)
|
|
24
|
+
- **h/l**: Adjust Left Padding (`padding_left`)
|
|
25
|
+
- **j/k**: Adjust Right Padding (`padding_right`)
|
|
26
|
+
- **q**: Quit
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ruby examples/widget_tabs_demo/app.rb
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Learning Outcomes
|
|
35
|
+
|
|
36
|
+
Use this example if you need to...
|
|
37
|
+
- Build a multi-pane dashboard.
|
|
38
|
+
- Create a "Settings" screen with different categories.
|
|
39
|
+
- Implement a "wizard" interface with steps.
|
|
40
|
+
|
|
41
|
+

|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
7
|
require "ratatui_ruby"
|
|
8
|
+
require "faker"
|
|
8
9
|
|
|
9
10
|
# Demonstrates view segregation with interactive tab navigation.
|
|
10
11
|
#
|
|
@@ -35,6 +36,9 @@ class WidgetTabsDemo
|
|
|
35
36
|
@padding_right = 0
|
|
36
37
|
@width_constraint_index = 0
|
|
37
38
|
@hotkey_style = nil
|
|
39
|
+
|
|
40
|
+
# Generate the content once, not on every frame
|
|
41
|
+
@tab_text = 4.times.map { |it| Faker::Lorem.paragraph(sentence_count: 10 + it) }
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def run
|
|
@@ -77,11 +81,12 @@ class WidgetTabsDemo
|
|
|
77
81
|
)
|
|
78
82
|
|
|
79
83
|
# Center the tabs vertically in the main area
|
|
80
|
-
tabs_area, = @tui.layout_split(
|
|
84
|
+
tabs_area, content_area = @tui.layout_split(
|
|
81
85
|
main_area,
|
|
82
86
|
direction: :vertical,
|
|
83
87
|
constraints: [
|
|
84
88
|
@tui.constraint_length(3),
|
|
89
|
+
@tui.constraint_fill(1),
|
|
85
90
|
]
|
|
86
91
|
)
|
|
87
92
|
|
|
@@ -96,6 +101,7 @@ class WidgetTabsDemo
|
|
|
96
101
|
padding_right: @padding_right
|
|
97
102
|
)
|
|
98
103
|
frame.render_widget(tabs, tabs_area)
|
|
104
|
+
frame.render_widget(tab_contents, content_area)
|
|
99
105
|
|
|
100
106
|
render_controls(frame, controls_area, tabs.width)
|
|
101
107
|
end
|
|
@@ -162,6 +168,14 @@ class WidgetTabsDemo
|
|
|
162
168
|
# Ignore other events
|
|
163
169
|
end
|
|
164
170
|
end
|
|
171
|
+
|
|
172
|
+
private def tab_contents
|
|
173
|
+
@tui.paragraph(
|
|
174
|
+
text: @tab_text[@selected_tab],
|
|
175
|
+
wrap: true,
|
|
176
|
+
block: @tui.block(borders: [:all], title: @tabs[@selected_tab])
|
|
177
|
+
)
|
|
178
|
+
end
|
|
165
179
|
end
|
|
166
180
|
|
|
167
181
|
WidgetTabsDemo.new.run if __FILE__ == $0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Text Width Calculator
|
|
7
|
+
|
|
8
|
+
Demonstrates string width calculation in a terminal environment.
|
|
9
|
+
|
|
10
|
+
Not all characters are created equal. In a TUI, "Width" means cell count, not string length. Emoji (`👍`) take 2 cells. Chinese characters (`你`) take 2 cells. The `tui.text_width` helper tells you the visual width of a string.
|
|
11
|
+
|
|
12
|
+
## Features Demonstrated
|
|
13
|
+
|
|
14
|
+
- **Unicode Width**: Rendering ASCII (1 cell), CJK (2 cells), and Emoji (2 cells).
|
|
15
|
+
- **Calculation**: Comparing `string.length` vs `tui.text_width(string)`.
|
|
16
|
+
|
|
17
|
+
## Hotkeys
|
|
18
|
+
|
|
19
|
+
- **Up/Down (↑/↓)**: Cycle Text Sample
|
|
20
|
+
- **q**: Quit
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
ruby examples/widget_text_width/app.rb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Learning Outcomes
|
|
29
|
+
|
|
30
|
+
Use this example if you need to...
|
|
31
|
+
- Align text correctly in columns.
|
|
32
|
+
- Truncate strings that are too long for a widget.
|
|
33
|
+
- Build your own custom layout engine.
|
|
34
|
+
|
|
35
|
+

|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
8
|
+
|
|
9
|
+
class WidgetTextWidth
|
|
10
|
+
def initialize
|
|
11
|
+
@text_samples = [
|
|
12
|
+
{ label: "ASCII", text: "Hello, World!", desc: "Simple English text" },
|
|
13
|
+
{ label: "CJK", text: "你好世界", desc: "Chinese (full-width characters)" },
|
|
14
|
+
{ label: "Emoji", text: "Hello 👍 World 🌍", desc: "Mixed text with emoji (2 cells each)" },
|
|
15
|
+
{ label: "Mixed", text: "Hi 你好 👍", desc: "ASCII + CJK + emoji" },
|
|
16
|
+
{ label: "Empty", text: "", desc: "Empty string" },
|
|
17
|
+
]
|
|
18
|
+
@selected_index = 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
RatatuiRuby.run do |tui|
|
|
23
|
+
@tui = tui
|
|
24
|
+
loop do
|
|
25
|
+
render
|
|
26
|
+
break if handle_input == :quit
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private def render
|
|
32
|
+
@tui.draw do |frame|
|
|
33
|
+
# Layout: main content above, controls below
|
|
34
|
+
areas = @tui.layout_split(
|
|
35
|
+
frame.area,
|
|
36
|
+
direction: :vertical,
|
|
37
|
+
constraints: [@tui.constraint_fill(1), @tui.constraint_length(7)]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Main content area with sample text
|
|
41
|
+
render_content(frame, areas[0])
|
|
42
|
+
|
|
43
|
+
# Controls footer
|
|
44
|
+
render_controls(frame, areas[1])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private def render_content(frame, area)
|
|
49
|
+
sample = @text_samples[@selected_index]
|
|
50
|
+
measured_width = @tui.text_width(sample[:text])
|
|
51
|
+
|
|
52
|
+
# Build content text with newlines
|
|
53
|
+
content = []
|
|
54
|
+
content << "Sample: #{sample[:text]}"
|
|
55
|
+
content << ""
|
|
56
|
+
content << "Display Width: #{measured_width} cells"
|
|
57
|
+
content << "Character Count: #{sample[:text].length}"
|
|
58
|
+
content << ""
|
|
59
|
+
content << sample[:desc]
|
|
60
|
+
text = content.join("\n")
|
|
61
|
+
|
|
62
|
+
widget = @tui.paragraph(
|
|
63
|
+
text:,
|
|
64
|
+
block: @tui.block(
|
|
65
|
+
title: "Text Width Calculator",
|
|
66
|
+
borders: [:all],
|
|
67
|
+
border_color: "cyan"
|
|
68
|
+
),
|
|
69
|
+
alignment: :left
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
frame.render_widget(widget, area)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private def render_controls(frame, area)
|
|
76
|
+
info = "Sample #{@selected_index + 1}/#{@text_samples.length}: #{@text_samples[@selected_index][:label]}"
|
|
77
|
+
controls = "↑/↓ Select q Quit"
|
|
78
|
+
text = "#{info}\n#{controls}"
|
|
79
|
+
|
|
80
|
+
widget = @tui.paragraph(
|
|
81
|
+
text:,
|
|
82
|
+
block: @tui.block(borders: [:top], border_color: "gray"),
|
|
83
|
+
alignment: :center
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
frame.render_widget(widget, area)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private def handle_input
|
|
90
|
+
event = @tui.poll_event
|
|
91
|
+
case event
|
|
92
|
+
in { type: :key, code: "q" }
|
|
93
|
+
:quit
|
|
94
|
+
in { type: :key, code: "up" }
|
|
95
|
+
@selected_index = (@selected_index - 1) % @text_samples.length
|
|
96
|
+
nil
|
|
97
|
+
in { type: :key, code: "down" }
|
|
98
|
+
@selected_index = (@selected_index + 1) % @text_samples.length
|
|
99
|
+
nil
|
|
100
|
+
else
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
WidgetTextWidth.new.run if __FILE__ == $PROGRAM_NAME
|
data/exe/.gitkeep
ADDED
|
File without changes
|
data/ext/ratatui_ruby/Cargo.lock
CHANGED
|
@@ -956,7 +956,7 @@ dependencies = [
|
|
|
956
956
|
"thiserror 2.0.17",
|
|
957
957
|
"unicode-segmentation",
|
|
958
958
|
"unicode-truncate",
|
|
959
|
-
"unicode-width",
|
|
959
|
+
"unicode-width 0.2.0",
|
|
960
960
|
]
|
|
961
961
|
|
|
962
962
|
[[package]]
|
|
@@ -1007,18 +1007,19 @@ dependencies = [
|
|
|
1007
1007
|
"strum",
|
|
1008
1008
|
"time",
|
|
1009
1009
|
"unicode-segmentation",
|
|
1010
|
-
"unicode-width",
|
|
1010
|
+
"unicode-width 0.2.0",
|
|
1011
1011
|
]
|
|
1012
1012
|
|
|
1013
1013
|
[[package]]
|
|
1014
1014
|
name = "ratatui_ruby"
|
|
1015
|
-
version = "0.
|
|
1015
|
+
version = "0.6.0"
|
|
1016
1016
|
dependencies = [
|
|
1017
1017
|
"bumpalo",
|
|
1018
1018
|
"lazy_static",
|
|
1019
1019
|
"magnus",
|
|
1020
1020
|
"ratatui",
|
|
1021
1021
|
"time",
|
|
1022
|
+
"unicode-width 0.1.14",
|
|
1022
1023
|
]
|
|
1023
1024
|
|
|
1024
1025
|
[[package]]
|
|
@@ -1464,9 +1465,15 @@ checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330"
|
|
|
1464
1465
|
dependencies = [
|
|
1465
1466
|
"itertools 0.13.0",
|
|
1466
1467
|
"unicode-segmentation",
|
|
1467
|
-
"unicode-width",
|
|
1468
|
+
"unicode-width 0.2.0",
|
|
1468
1469
|
]
|
|
1469
1470
|
|
|
1471
|
+
[[package]]
|
|
1472
|
+
name = "unicode-width"
|
|
1473
|
+
version = "0.1.14"
|
|
1474
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1475
|
+
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
|
1476
|
+
|
|
1470
1477
|
[[package]]
|
|
1471
1478
|
name = "unicode-width"
|
|
1472
1479
|
version = "0.2.0"
|
data/ext/ratatui_ruby/Cargo.toml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[package]
|
|
5
5
|
name = "ratatui_ruby"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.6.0"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
|
|
9
9
|
[lib]
|
|
@@ -12,6 +12,7 @@ crate-type = ["cdylib", "staticlib"]
|
|
|
12
12
|
[dependencies]
|
|
13
13
|
magnus = "0.8.2"
|
|
14
14
|
ratatui = { version = "0.30", features = ["widget-calendar", "layout-cache", "unstable-rendered-line-info"] }
|
|
15
|
+
unicode-width = "0.1"
|
|
15
16
|
|
|
16
17
|
bumpalo = "3.16"
|
|
17
18
|
lazy_static = "1.4"
|