ratatui_ruby 1.2.1 → 1.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.
Files changed (268) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +54 -0
  3. data/.builds/ruby-3.3.yml +54 -0
  4. data/.builds/ruby-3.4.yml +54 -0
  5. data/.builds/ruby-4.0.0.yml +54 -0
  6. data/.pre-commit-config.yaml +16 -0
  7. data/.rubocop.yml +10 -0
  8. data/AGENTS.md +147 -0
  9. data/CHANGELOG.md +771 -0
  10. data/README.md +187 -0
  11. data/README.rdoc +302 -0
  12. data/Rakefile +11 -0
  13. data/Steepfile +50 -0
  14. data/doc/concepts/application_architecture.md +321 -0
  15. data/doc/concepts/application_testing.md +193 -0
  16. data/doc/concepts/async.md +190 -0
  17. data/doc/concepts/custom_widgets.md +247 -0
  18. data/doc/concepts/debugging.md +401 -0
  19. data/doc/concepts/event_handling.md +162 -0
  20. data/doc/concepts/interactive_design.md +146 -0
  21. data/doc/contributors/auditing/parity.md +239 -0
  22. data/doc/contributors/design/ruby_frontend.md +448 -0
  23. data/doc/contributors/design/rust_backend.md +434 -0
  24. data/doc/contributors/design.md +11 -0
  25. data/doc/contributors/developing_examples.md +400 -0
  26. data/doc/contributors/documentation_style.md +121 -0
  27. data/doc/contributors/index.md +21 -0
  28. data/doc/contributors/releasing.md +215 -0
  29. data/doc/contributors/todo/align/api_completeness_audit-finished.md +381 -0
  30. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +200 -0
  31. data/doc/contributors/todo/align/term.md +351 -0
  32. data/doc/contributors/todo/align/terminal.md +647 -0
  33. data/doc/contributors/todo/future_work.md +169 -0
  34. data/doc/contributors/upstream_requests/paragraph_span_rects.md +259 -0
  35. data/doc/contributors/upstream_requests/tab_rects.md +173 -0
  36. data/doc/contributors/upstream_requests/title_rects.md +132 -0
  37. data/doc/custom.css +22 -0
  38. data/doc/getting_started/quickstart.md +291 -0
  39. data/doc/getting_started/why.md +93 -0
  40. data/doc/images/app_all_events.png +0 -0
  41. data/doc/images/app_cli_rich_moments.gif +0 -0
  42. data/doc/images/app_color_picker.png +0 -0
  43. data/doc/images/app_debugging_showcase.gif +0 -0
  44. data/doc/images/app_debugging_showcase.png +0 -0
  45. data/doc/images/app_external_editor.gif +0 -0
  46. data/doc/images/app_login_form.png +0 -0
  47. data/doc/images/app_stateful_interaction.png +0 -0
  48. data/doc/images/verify_quickstart_dsl.png +0 -0
  49. data/doc/images/verify_quickstart_layout.png +0 -0
  50. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  51. data/doc/images/verify_readme_usage.png +0 -0
  52. data/doc/images/widget_barchart.png +0 -0
  53. data/doc/images/widget_block.png +0 -0
  54. data/doc/images/widget_box.png +0 -0
  55. data/doc/images/widget_calendar.png +0 -0
  56. data/doc/images/widget_canvas.png +0 -0
  57. data/doc/images/widget_cell.png +0 -0
  58. data/doc/images/widget_center.png +0 -0
  59. data/doc/images/widget_chart.png +0 -0
  60. data/doc/images/widget_gauge.png +0 -0
  61. data/doc/images/widget_layout_split.png +0 -0
  62. data/doc/images/widget_line_gauge.png +0 -0
  63. data/doc/images/widget_list.png +0 -0
  64. data/doc/images/widget_map.png +0 -0
  65. data/doc/images/widget_overlay.png +0 -0
  66. data/doc/images/widget_popup.png +0 -0
  67. data/doc/images/widget_ratatui_logo.png +0 -0
  68. data/doc/images/widget_ratatui_mascot.png +0 -0
  69. data/doc/images/widget_rect.png +0 -0
  70. data/doc/images/widget_render.png +0 -0
  71. data/doc/images/widget_rich_text.png +0 -0
  72. data/doc/images/widget_scroll_text.png +0 -0
  73. data/doc/images/widget_scrollbar.png +0 -0
  74. data/doc/images/widget_sparkline.png +0 -0
  75. data/doc/images/widget_style_colors.png +0 -0
  76. data/doc/images/widget_table.png +0 -0
  77. data/doc/images/widget_tabs.png +0 -0
  78. data/doc/images/widget_text_width.png +0 -0
  79. data/doc/index.md +34 -0
  80. data/doc/troubleshooting/async.md +4 -0
  81. data/doc/troubleshooting/terminal_limitations.md +131 -0
  82. data/doc/troubleshooting/tui_output.md +197 -0
  83. data/examples/app_all_events/README.md +114 -0
  84. data/examples/app_all_events/app.rb +98 -0
  85. data/examples/app_all_events/model/app_model.rb +159 -0
  86. data/examples/app_all_events/model/event_color_cycle.rb +43 -0
  87. data/examples/app_all_events/model/event_entry.rb +94 -0
  88. data/examples/app_all_events/model/msg.rb +39 -0
  89. data/examples/app_all_events/model/timestamp.rb +56 -0
  90. data/examples/app_all_events/update.rb +75 -0
  91. data/examples/app_all_events/view/app_view.rb +80 -0
  92. data/examples/app_all_events/view/controls_view.rb +54 -0
  93. data/examples/app_all_events/view/counts_view.rb +61 -0
  94. data/examples/app_all_events/view/live_view.rb +72 -0
  95. data/examples/app_all_events/view/log_view.rb +57 -0
  96. data/examples/app_all_events/view.rb +9 -0
  97. data/examples/app_cli_rich_moments/README.md +81 -0
  98. data/examples/app_cli_rich_moments/app.rb +189 -0
  99. data/examples/app_color_picker/README.md +156 -0
  100. data/examples/app_color_picker/app.rb +76 -0
  101. data/examples/app_color_picker/clipboard.rb +86 -0
  102. data/examples/app_color_picker/color.rb +193 -0
  103. data/examples/app_color_picker/controls.rb +92 -0
  104. data/examples/app_color_picker/copy_dialog.rb +168 -0
  105. data/examples/app_color_picker/export_pane.rb +128 -0
  106. data/examples/app_color_picker/harmony.rb +58 -0
  107. data/examples/app_color_picker/input.rb +176 -0
  108. data/examples/app_color_picker/main_container.rb +180 -0
  109. data/examples/app_color_picker/palette.rb +111 -0
  110. data/examples/app_debugging_showcase/README.md +119 -0
  111. data/examples/app_debugging_showcase/app.rb +318 -0
  112. data/examples/app_external_editor/README.md +62 -0
  113. data/examples/app_external_editor/app.rb +344 -0
  114. data/examples/app_login_form/README.md +58 -0
  115. data/examples/app_login_form/app.rb +109 -0
  116. data/examples/app_stateful_interaction/README.md +35 -0
  117. data/examples/app_stateful_interaction/app.rb +328 -0
  118. data/examples/timeout_demo.rb +45 -0
  119. data/examples/verify_quickstart_dsl/README.md +55 -0
  120. data/examples/verify_quickstart_dsl/app.rb +49 -0
  121. data/examples/verify_quickstart_layout/README.md +77 -0
  122. data/examples/verify_quickstart_layout/app.rb +73 -0
  123. data/examples/verify_quickstart_lifecycle/README.md +68 -0
  124. data/examples/verify_quickstart_lifecycle/app.rb +62 -0
  125. data/examples/verify_readme_usage/README.md +49 -0
  126. data/examples/verify_readme_usage/app.rb +42 -0
  127. data/examples/verify_website_managed/README.md +48 -0
  128. data/examples/verify_website_managed/app.rb +36 -0
  129. data/examples/verify_website_menu/README.md +60 -0
  130. data/examples/verify_website_menu/app.rb +84 -0
  131. data/examples/verify_website_spinner/README.md +44 -0
  132. data/examples/verify_website_spinner/app.rb +34 -0
  133. data/examples/widget_barchart/README.md +58 -0
  134. data/examples/widget_barchart/app.rb +240 -0
  135. data/examples/widget_block/README.md +44 -0
  136. data/examples/widget_block/app.rb +258 -0
  137. data/examples/widget_box/README.md +54 -0
  138. data/examples/widget_box/app.rb +255 -0
  139. data/examples/widget_calendar/README.md +48 -0
  140. data/examples/widget_calendar/app.rb +115 -0
  141. data/examples/widget_canvas/README.md +31 -0
  142. data/examples/widget_canvas/app.rb +130 -0
  143. data/examples/widget_cell/README.md +45 -0
  144. data/examples/widget_cell/app.rb +112 -0
  145. data/examples/widget_center/README.md +33 -0
  146. data/examples/widget_center/app.rb +118 -0
  147. data/examples/widget_chart/README.md +50 -0
  148. data/examples/widget_chart/app.rb +220 -0
  149. data/examples/widget_gauge/README.md +50 -0
  150. data/examples/widget_gauge/app.rb +229 -0
  151. data/examples/widget_layout_split/README.md +53 -0
  152. data/examples/widget_layout_split/app.rb +260 -0
  153. data/examples/widget_line_gauge/README.md +50 -0
  154. data/examples/widget_line_gauge/app.rb +219 -0
  155. data/examples/widget_list/README.md +58 -0
  156. data/examples/widget_list/app.rb +382 -0
  157. data/examples/widget_map/README.md +48 -0
  158. data/examples/widget_map/app.rb +95 -0
  159. data/examples/widget_overlay/README.md +45 -0
  160. data/examples/widget_overlay/app.rb +250 -0
  161. data/examples/widget_popup/README.md +45 -0
  162. data/examples/widget_popup/app.rb +106 -0
  163. data/examples/widget_ratatui_logo/README.md +43 -0
  164. data/examples/widget_ratatui_logo/app.rb +104 -0
  165. data/examples/widget_ratatui_mascot/README.md +43 -0
  166. data/examples/widget_ratatui_mascot/app.rb +95 -0
  167. data/examples/widget_rect/README.md +53 -0
  168. data/examples/widget_rect/app.rb +222 -0
  169. data/examples/widget_render/README.md +46 -0
  170. data/examples/widget_render/app.rb +186 -0
  171. data/examples/widget_render/app.rbs +41 -0
  172. data/examples/widget_rich_text/README.md +44 -0
  173. data/examples/widget_rich_text/app.rb +193 -0
  174. data/examples/widget_scroll_text/README.md +46 -0
  175. data/examples/widget_scroll_text/app.rb +109 -0
  176. data/examples/widget_scrollbar/README.md +46 -0
  177. data/examples/widget_scrollbar/app.rb +155 -0
  178. data/examples/widget_sparkline/README.md +51 -0
  179. data/examples/widget_sparkline/app.rb +277 -0
  180. data/examples/widget_style_colors/README.md +43 -0
  181. data/examples/widget_style_colors/app.rb +83 -0
  182. data/examples/widget_table/README.md +57 -0
  183. data/examples/widget_table/app.rb +285 -0
  184. data/examples/widget_tabs/README.md +50 -0
  185. data/examples/widget_tabs/app.rb +183 -0
  186. data/examples/widget_text_width/README.md +44 -0
  187. data/examples/widget_text_width/app.rb +117 -0
  188. data/ext/ratatui_ruby/Cargo.lock +1 -1
  189. data/ext/ratatui_ruby/Cargo.toml +1 -1
  190. data/lib/ratatui_ruby/event/focus_gained.rb +50 -0
  191. data/lib/ratatui_ruby/event/focus_lost.rb +51 -0
  192. data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
  193. data/lib/ratatui_ruby/event/key/modifier.rb +2 -0
  194. data/lib/ratatui_ruby/event/key.rb +9 -0
  195. data/lib/ratatui_ruby/event/mouse.rb +33 -0
  196. data/lib/ratatui_ruby/event/paste.rb +25 -0
  197. data/lib/ratatui_ruby/event/resize.rb +65 -0
  198. data/lib/ratatui_ruby/version.rb +1 -1
  199. data/migrate_to_buffer.rb +145 -0
  200. data/mise.toml +8 -0
  201. data/sig/ratatui_ruby/event.rbs +97 -0
  202. data/tasks/autodoc/examples.rb +87 -0
  203. data/tasks/autodoc/member.rb +58 -0
  204. data/tasks/autodoc/name.rb +21 -0
  205. data/tasks/autodoc.rake +21 -0
  206. data/tasks/bump/bump_workflow.rb +49 -0
  207. data/tasks/bump/cargo_lockfile.rb +21 -0
  208. data/tasks/bump/changelog.rb +104 -0
  209. data/tasks/bump/header.rb +32 -0
  210. data/tasks/bump/history.rb +32 -0
  211. data/tasks/bump/links.rb +69 -0
  212. data/tasks/bump/manifest.rb +33 -0
  213. data/tasks/bump/patch_release.rb +19 -0
  214. data/tasks/bump/release_branch.rb +17 -0
  215. data/tasks/bump/release_from_trunk.rb +49 -0
  216. data/tasks/bump/repository.rb +54 -0
  217. data/tasks/bump/ruby_gem.rb +29 -0
  218. data/tasks/bump/sem_ver.rb +44 -0
  219. data/tasks/bump/unreleased_section.rb +73 -0
  220. data/tasks/bump.rake +61 -0
  221. data/tasks/doc/documentation.rb +59 -0
  222. data/tasks/doc/link/file_url.rb +30 -0
  223. data/tasks/doc/link/relative_path.rb +61 -0
  224. data/tasks/doc/link/web_url.rb +55 -0
  225. data/tasks/doc/link.rb +52 -0
  226. data/tasks/doc/link_audit.rb +116 -0
  227. data/tasks/doc/problem.rb +40 -0
  228. data/tasks/doc/source_file.rb +93 -0
  229. data/tasks/doc.rake +905 -0
  230. data/tasks/example_viewer.html.erb +172 -0
  231. data/tasks/extension.rake +14 -0
  232. data/tasks/license/headers_md.rb +223 -0
  233. data/tasks/license/headers_rb.rb +210 -0
  234. data/tasks/license/license_utils.rb +130 -0
  235. data/tasks/license/snippets_md.rb +315 -0
  236. data/tasks/license/snippets_rdoc.rb +150 -0
  237. data/tasks/license.rake +91 -0
  238. data/tasks/lint.rake +170 -0
  239. data/tasks/rbs_predicates/predicate_catalog.rb +52 -0
  240. data/tasks/rbs_predicates/predicate_tests.rb +124 -0
  241. data/tasks/rbs_predicates/rbs_signature.rb +63 -0
  242. data/tasks/rbs_predicates.rake +31 -0
  243. data/tasks/rdoc_config.rb +29 -0
  244. data/tasks/resources/build.yml.erb +60 -0
  245. data/tasks/resources/index.html.erb +141 -0
  246. data/tasks/resources/rubies.yml +7 -0
  247. data/tasks/sourcehut.rake +122 -0
  248. data/tasks/steep.rake +11 -0
  249. data/tasks/terminal_preview/app_screenshot.rb +45 -0
  250. data/tasks/terminal_preview/crash_report.rb +54 -0
  251. data/tasks/terminal_preview/example_app.rb +27 -0
  252. data/tasks/terminal_preview/launcher_script.rb +48 -0
  253. data/tasks/terminal_preview/preview_collection.rb +60 -0
  254. data/tasks/terminal_preview/preview_timing.rb +24 -0
  255. data/tasks/terminal_preview/safety_confirmation.rb +58 -0
  256. data/tasks/terminal_preview/saved_screenshot.rb +56 -0
  257. data/tasks/terminal_preview/system_appearance.rb +13 -0
  258. data/tasks/terminal_preview/terminal_window.rb +138 -0
  259. data/tasks/terminal_preview/window_id.rb +16 -0
  260. data/tasks/terminal_preview.rake +30 -0
  261. data/tasks/test.rake +36 -0
  262. data/tasks/website/index_page.rb +30 -0
  263. data/tasks/website/version.rb +122 -0
  264. data/tasks/website/version_menu.rb +68 -0
  265. data/tasks/website/versioned_documentation.rb +83 -0
  266. data/tasks/website/website.rb +53 -0
  267. data/tasks/website.rake +28 -0
  268. metadata +256 -1
@@ -70,6 +70,56 @@ module RatatuiRuby
70
70
  def ==(other)
71
71
  other.is_a?(FocusGained)
72
72
  end
73
+
74
+ # =========================================================================
75
+ # DWIM Predicates
76
+ # =========================================================================
77
+
78
+ # Returns true. The terminal window is now in focus.
79
+ #
80
+ # event.focus? # => true
81
+ def focus?
82
+ true
83
+ end
84
+ alias focused? focus?
85
+
86
+ # Returns true. The application gained focus.
87
+ #
88
+ # event.gained? # => true
89
+ def gained?
90
+ true
91
+ end
92
+
93
+ # Returns false. This is not a focus lost event.
94
+ #
95
+ # event.lost? # => false
96
+ def lost?
97
+ false
98
+ end
99
+
100
+ # Returns false. Blur is the opposite of focus gained.
101
+ #
102
+ # event.blur? # => false
103
+ def blur?
104
+ false
105
+ end
106
+ alias blurred? blur?
107
+
108
+ # Returns true. The application is active.
109
+ #
110
+ # event.active? # => true
111
+ def active?
112
+ true
113
+ end
114
+ alias foreground? active?
115
+
116
+ # Returns false. The application is not inactive.
117
+ #
118
+ # event.inactive? # => false
119
+ def inactive?
120
+ false
121
+ end
122
+ alias background? inactive?
73
123
  end
74
124
  end
75
125
  end
@@ -71,6 +71,57 @@ module RatatuiRuby
71
71
  def ==(other)
72
72
  other.is_a?(FocusLost)
73
73
  end
74
+
75
+ # =========================================================================
76
+ # DWIM Predicates
77
+ # =========================================================================
78
+
79
+ # Returns true. The terminal has lost focus (blur).
80
+ #
81
+ # event.blur? # => true
82
+ def blur?
83
+ true
84
+ end
85
+ alias blurred? blur?
86
+
87
+ # Returns true. The application lost focus.
88
+ #
89
+ # event.lost? # => true
90
+ def lost?
91
+ true
92
+ end
93
+ alias unfocused? lost?
94
+
95
+ # Returns false. This is not a focus gained event.
96
+ #
97
+ # event.focus? # => false
98
+ def focus?
99
+ false
100
+ end
101
+ alias focused? focus?
102
+
103
+ # Returns false. This is not a gained event.
104
+ #
105
+ # event.gained? # => false
106
+ def gained?
107
+ false
108
+ end
109
+
110
+ # Returns true. The application is inactive.
111
+ #
112
+ # event.inactive? # => true
113
+ def inactive?
114
+ true
115
+ end
116
+ alias background? inactive?
117
+
118
+ # Returns false. The application is not active.
119
+ #
120
+ # event.active? # => false
121
+ def active?
122
+ false
123
+ end
124
+ alias foreground? active?
74
125
  end
75
126
  end
76
127
  end
@@ -0,0 +1,301 @@
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
+ class Event
10
+ class Key < Event
11
+ # DWIM predicates for common key patterns.
12
+ #
13
+ # These predicates anticipate what developers intuitively try. Space bars,
14
+ # character categories, Unix signals, and Vim-style navigation.
15
+ module Dwim
16
+ # Returns true if the key is a space character.
17
+ #
18
+ # event.space? # => true for " "
19
+ def space?
20
+ @code == " " && @modifiers.empty?
21
+ end
22
+
23
+ alias spacebar? space?
24
+
25
+ # Returns true if the key is Enter. Alias for carriage return.
26
+ #
27
+ # event.cr? # => true for enter
28
+ def cr?
29
+ @code == "enter" && @modifiers.empty?
30
+ end
31
+
32
+ alias carriagereturn? cr?
33
+ alias linefeed? cr?
34
+ alias newline? cr?
35
+ alias lf? cr?
36
+
37
+ # Returns true if the key is a single letter (a-z, A-Z).
38
+ #
39
+ # Event::Key.new(code: "a").letter? # => true
40
+ def letter?
41
+ @code.length == 1 && @code.match?(/\A[A-Za-z]\z/)
42
+ end
43
+
44
+ # Returns true if the key is a digit (0-9).
45
+ #
46
+ # Event::Key.new(code: "5").digit? # => true
47
+ def digit?
48
+ @code.length == 1 && @code.match?(/\A[0-9]\z/)
49
+ end
50
+
51
+ # Returns true if the key is alphanumeric.
52
+ #
53
+ # Event::Key.new(code: "a").alphanumeric? # => true
54
+ def alphanumeric?
55
+ letter? || digit?
56
+ end
57
+
58
+ # Returns true if the key is punctuation.
59
+ #
60
+ # Event::Key.new(code: "@", modifiers: ["shift"]).punctuation? # => true
61
+ def punctuation?
62
+ return false unless @code.length == 1
63
+ !letter? && !digit? && !whitespace?
64
+ end
65
+
66
+ # Returns true if the key is whitespace (space, enter, tab).
67
+ #
68
+ # Event::Key.new(code: " ").whitespace? # => true
69
+ def whitespace?
70
+ @code == " " || @code == "enter" || @code == "tab"
71
+ end
72
+
73
+ # Returns true for interrupt (Ctrl+C).
74
+ #
75
+ # event.interrupt? # => true for Ctrl+C
76
+ def interrupt?
77
+ @code == "c" && @modifiers == ["ctrl"]
78
+ end
79
+
80
+ # Returns true for end-of-file (Ctrl+D).
81
+ #
82
+ # event.eof? # => true for Ctrl+D
83
+ def eof?
84
+ @code == "d" && @modifiers == ["ctrl"]
85
+ end
86
+
87
+ # Returns true for cancel (Esc or Ctrl+C).
88
+ #
89
+ # event.cancel? # => true for Esc or Ctrl+C
90
+ def cancel?
91
+ (@code == "esc" && @modifiers.empty?) || interrupt?
92
+ end
93
+
94
+ # Returns true for SIGINT (Ctrl+C).
95
+ #
96
+ # event.sigint? # => true for Ctrl+C
97
+ def sigint?
98
+ interrupt?
99
+ end
100
+
101
+ alias int? sigint?
102
+
103
+ # Returns true for SIGTSTP (Ctrl+Z) - suspend/stop.
104
+ #
105
+ # event.suspend? # => true for Ctrl+Z
106
+ def suspend?
107
+ @code == "z" && @modifiers == ["ctrl"]
108
+ end
109
+
110
+ alias sigtstp? suspend?
111
+ alias tstp? suspend?
112
+
113
+ # Returns true for SIGQUIT (Ctrl+\).
114
+ #
115
+ # event.quit? # => true for Ctrl+\
116
+ def quit?
117
+ @code == "\\" && @modifiers == ["ctrl"]
118
+ end
119
+
120
+ alias sigquit? quit?
121
+
122
+ NAVIGATION_KEYS = %w[up down left right home end page_up page_down].freeze # :nodoc:
123
+
124
+ # Returns true if key is a navigation key.
125
+ #
126
+ # Event::Key.new(code: "up").navigation? # => true
127
+ def navigation?
128
+ NAVIGATION_KEYS.include?(@code) && @modifiers.empty?
129
+ end
130
+
131
+ ARROW_KEYS = %w[up down left right].freeze # :nodoc:
132
+
133
+ # Returns true if key is an arrow key.
134
+ #
135
+ # Event::Key.new(code: "up").arrow? # => true
136
+ def arrow?
137
+ ARROW_KEYS.include?(@code) && @modifiers.empty?
138
+ end
139
+
140
+ VIM_MOVEMENT_KEYS = %w[h j k l w b g G].freeze # :nodoc:
141
+
142
+ # Returns true if key is a Vim movement key.
143
+ #
144
+ # Event::Key.new(code: "j").vim? # => true
145
+ def vim?
146
+ return true if VIM_MOVEMENT_KEYS.include?(@code) && @modifiers.empty?
147
+ @code == "G" && @modifiers == ["shift"]
148
+ end
149
+
150
+ # Returns true for Vim left (h).
151
+ def vim_left?
152
+ @code == "h" && @modifiers.empty?
153
+ end
154
+
155
+ # Returns true for Vim down (j).
156
+ def vim_down?
157
+ @code == "j" && @modifiers.empty?
158
+ end
159
+
160
+ # Returns true for Vim up (k).
161
+ def vim_up?
162
+ @code == "k" && @modifiers.empty?
163
+ end
164
+
165
+ # Returns true for Vim right (l).
166
+ def vim_right?
167
+ @code == "l" && @modifiers.empty?
168
+ end
169
+
170
+ # Returns true for Vim word forward (w).
171
+ def vim_word_forward?
172
+ @code == "w" && @modifiers.empty?
173
+ end
174
+
175
+ # Returns true for Vim word backward (b).
176
+ def vim_word_backward?
177
+ @code == "b" && @modifiers.empty?
178
+ end
179
+
180
+ # Returns true for Vim go to top (gg pattern, here just g).
181
+ def vim_top?
182
+ @code == "g" && @modifiers.empty?
183
+ end
184
+
185
+ # Returns true for Vim go to bottom (G).
186
+ def vim_bottom?
187
+ @code == "G" && @modifiers == ["shift"]
188
+ end
189
+
190
+ # Punctuation name predicates - generated at load time for performance.
191
+ # Maps intuitive names to their symbol characters.
192
+ # :nodoc:
193
+ PUNCTUATION_NAMES = {
194
+ # Navigation shortcuts (the original use case!)
195
+ tilde: "~",
196
+ slash: "/",
197
+ forwardslash: "/",
198
+ backslash: "\\",
199
+
200
+ # Common punctuation
201
+ comma: ",",
202
+ period: ".",
203
+ dot: ".",
204
+ colon: ":",
205
+ semicolon: ";",
206
+
207
+ # Question and exclamation
208
+ question: "?",
209
+ questionmark: "?",
210
+ exclamation: "!",
211
+ exclamationmark: "!",
212
+ exclamationpoint: "!",
213
+ bang: "!",
214
+
215
+ # Programming symbols
216
+ at: "@",
217
+ atsign: "@",
218
+ hash: "#",
219
+ pound: "#",
220
+ numbersign: "#",
221
+ dollar: "$",
222
+ dollarsign: "$",
223
+ percent: "%",
224
+ caret: "^",
225
+ circumflex: "^",
226
+ ampersand: "&",
227
+ asterisk: "*",
228
+ star: "*",
229
+
230
+ # Arithmetic and comparison
231
+ underscore: "_",
232
+ hyphen: "-",
233
+ dash: "-",
234
+ minus: "-",
235
+ plus: "+",
236
+ equals: "=",
237
+ equalsign: "=",
238
+ pipe: "|",
239
+ bar: "|",
240
+ lessthan: "<",
241
+ lt: "<",
242
+ greaterthan: ">",
243
+ gt: ">",
244
+
245
+ # Brackets and parens
246
+ lparen: "(",
247
+ leftparen: "(",
248
+ openparen: "(",
249
+ leftparenthesis: "(",
250
+ openparenthesis: "(",
251
+ rparen: ")",
252
+ rightparen: ")",
253
+ closeparen: ")",
254
+ rightparenthesis: ")",
255
+ closeparenthesis: ")",
256
+ lbracket: "[",
257
+ leftbracket: "[",
258
+ openbracket: "[",
259
+ leftsquarebracket: "[",
260
+ opensquarebracket: "[",
261
+ rbracket: "]",
262
+ rightbracket: "]",
263
+ closebracket: "]",
264
+ rightsquarebracket: "]",
265
+ closesquarebracket: "]",
266
+ lbrace: "{",
267
+ leftbrace: "{",
268
+ openbrace: "{",
269
+ leftcurlybrace: "{",
270
+ opencurlybrace: "{",
271
+ rbrace: "}",
272
+ rightbrace: "}",
273
+ closebrace: "}",
274
+ rightcurlybrace: "}",
275
+ closecurlybrace: "}",
276
+
277
+ # Quotes
278
+ backtick: "`",
279
+ grave: "`",
280
+ singlequote: "'",
281
+ apostrophe: "'",
282
+ doublequote: "\"",
283
+ }.freeze
284
+
285
+ # Generate predicate methods at load time (faster than method_missing)
286
+ PUNCTUATION_NAMES.each do |name, char|
287
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
288
+ def #{name}?
289
+ @code == #{char.inspect}
290
+ end
291
+ RUBY
292
+ end
293
+
294
+ # quote? matches both single and double quotes
295
+ def quote?
296
+ @code == "'" || @code == "\""
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
@@ -40,6 +40,8 @@ module RatatuiRuby
40
40
  # Alias for {#super?}.
41
41
  alias win? super?
42
42
  # Alias for {#super?}.
43
+ alias windows? super?
44
+ # Alias for {#super?}.
43
45
  alias command? super?
44
46
  # Alias for {#super?}.
45
47
  alias cmd? super?
@@ -6,6 +6,7 @@
6
6
  #++
7
7
 
8
8
  require_relative "key/character"
9
+ require_relative "key/dwim"
9
10
  require_relative "key/media"
10
11
  require_relative "key/modifier"
11
12
  require_relative "key/navigation"
@@ -88,6 +89,7 @@ module RatatuiRuby
88
89
  # These keys will not work in Terminal.app, iTerm2, or GNOME Terminal.
89
90
  class Key < Event
90
91
  include Character
92
+ include Dwim
91
93
  include Media
92
94
  include Modifier
93
95
  include Navigation
@@ -435,6 +437,13 @@ module RatatuiRuby
435
437
  normalized_code = @code.delete("_")
436
438
  return true if normalized_predicate == normalized_code && @modifiers.empty?
437
439
 
440
+ # DWIM: Underscore variants delegate to existing methods
441
+ # space_bar? → spacebar? → space?, sig_int? → sigint?
442
+ normalized_method = :"#{normalized_predicate}?"
443
+ if normalized_method != name && respond_to?(normalized_method)
444
+ return public_send(normalized_method)
445
+ end
446
+
438
447
  false
439
448
  else
440
449
  super
@@ -253,6 +253,39 @@ module RatatuiRuby
253
253
  else false
254
254
  end
255
255
  end
256
+
257
+ alias wheel_up? scroll_up?
258
+ alias wheel_down? scroll_down?
259
+
260
+ # Returns true for any scroll event.
261
+ #
262
+ # event.scroll? # => true for scroll_up or scroll_down
263
+ def scroll?
264
+ scroll_up? || scroll_down?
265
+ end
266
+
267
+ alias primary? left?
268
+ alias secondary? right?
269
+ alias context_menu? right?
270
+ alias aux? middle?
271
+ alias auxiliary? aux?
272
+
273
+ # Returns true for mouse movement without button press.
274
+ #
275
+ # event.moved? # => true for moved (no button)
276
+ def moved?
277
+ @kind == "moved"
278
+ end
279
+
280
+ alias hover? moved?
281
+ alias hovering? moved?
282
+ alias move? moved?
283
+ alias dragging? drag?
284
+
285
+ alias release? up?
286
+ alias released? up?
287
+ alias press? down?
288
+ alias pressed? down?
256
289
  end
257
290
  end
258
291
  end
@@ -67,6 +67,9 @@ module RatatuiRuby
67
67
  def paste?
68
68
  true
69
69
  end
70
+ alias clipboard? paste?
71
+ alias pasteboard? paste?
72
+ alias pasted? paste?
70
73
 
71
74
  # Creates a new Paste event.
72
75
  #
@@ -100,6 +103,28 @@ module RatatuiRuby
100
103
  return false unless other.is_a?(Paste)
101
104
  content == other.content
102
105
  end
106
+
107
+ # Returns true if the pasted content is empty.
108
+ def empty?
109
+ @content.empty?
110
+ end
111
+
112
+ # Returns true if the pasted content is empty or whitespace-only.
113
+ def blank?
114
+ @content.strip.empty?
115
+ end
116
+
117
+ # Returns true if the pasted content spans multiple lines.
118
+ def multiline?
119
+ @content.include?("\n")
120
+ end
121
+ alias multi_line? multiline?
122
+
123
+ # Returns true if the pasted content is a single line.
124
+ def single_line?
125
+ !multiline?
126
+ end
127
+ alias singleline? single_line?
103
128
  end
104
129
  end
105
130
  end
@@ -151,6 +151,71 @@ module RatatuiRuby
151
151
  else false
152
152
  end
153
153
  end
154
+
155
+ # Returns true. Unix <tt>SIGWINCH</tt> triggers terminal resize.
156
+ #
157
+ # event.sigwinch? # => true
158
+ def sigwinch?
159
+ true
160
+ end
161
+
162
+ alias winch? sigwinch?
163
+ alias sig_winch? sigwinch?
164
+
165
+ alias terminal_resize? resize?
166
+ alias window_resize? resize?
167
+ alias window_change? resize?
168
+ alias viewport_resize? resize?
169
+ alias viewport_change? resize?
170
+ alias size_change? resize?
171
+ alias resized? resize?
172
+
173
+ VT100_WIDTH = 80 # :nodoc:
174
+ VT100_HEIGHT = 24 # :nodoc:
175
+
176
+ # Returns true if width exceeds height.
177
+ #
178
+ # Event::Resize.new(width: 120, height: 24).landscape? # => true
179
+ def landscape?
180
+ @width > @height
181
+ end
182
+
183
+ # Returns true if height exceeds or equals width.
184
+ #
185
+ # Event::Resize.new(width: 40, height: 80).portrait? # => true
186
+ def portrait?
187
+ @height >= @width
188
+ end
189
+
190
+ # Returns true if dimensions are exactly 80x24.
191
+ #
192
+ # Event::Resize.new(width: 80, height: 24).vt100? # => true
193
+ def vt100?
194
+ @width == VT100_WIDTH && @height == VT100_HEIGHT
195
+ end
196
+
197
+ # Returns true if both dimensions meet or exceed 80x24.
198
+ #
199
+ # Event::Resize.new(width: 120, height: 40).at_least_vt100? # => true
200
+ def at_least_vt100?
201
+ @width >= VT100_WIDTH && @height >= VT100_HEIGHT
202
+ end
203
+
204
+ # Returns true if both dimensions exceed 80x24.
205
+ #
206
+ # Event::Resize.new(width: 81, height: 25).over_vt100? # => true
207
+ def over_vt100?
208
+ @width > VT100_WIDTH && @height > VT100_HEIGHT
209
+ end
210
+
211
+ # Returns true if either dimension falls below VT100 standard.
212
+ #
213
+ # Event::Resize.new(width: 60, height: 24).cramped? # => true
214
+ def cramped?
215
+ @width < VT100_WIDTH || @height < VT100_HEIGHT
216
+ end
217
+
218
+ alias constrained? cramped?
154
219
  end
155
220
  end
156
221
  end
@@ -8,5 +8,5 @@
8
8
  module RatatuiRuby
9
9
  # The version of the ratatui_ruby gem.
10
10
  # See https://semver.org/spec/v2.0.0.html
11
- VERSION = "1.2.1"
11
+ VERSION = "1.3.0"
12
12
  end