charming 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +421 -0
  4. data/exe/charming +6 -0
  5. data/lib/charming/application.rb +90 -0
  6. data/lib/charming/application_model.rb +13 -0
  7. data/lib/charming/cli.rb +60 -0
  8. data/lib/charming/component.rb +8 -0
  9. data/lib/charming/components/activity_indicator.rb +158 -0
  10. data/lib/charming/components/command_palette.rb +118 -0
  11. data/lib/charming/components/keyboard_handler.rb +22 -0
  12. data/lib/charming/components/list.rb +105 -0
  13. data/lib/charming/components/modal.rb +48 -0
  14. data/lib/charming/components/progressbar.rb +55 -0
  15. data/lib/charming/components/spinner.rb +37 -0
  16. data/lib/charming/components/table.rb +115 -0
  17. data/lib/charming/components/text_input.rb +103 -0
  18. data/lib/charming/components/viewport.rb +191 -0
  19. data/lib/charming/controller.rb +523 -0
  20. data/lib/charming/focus.rb +65 -0
  21. data/lib/charming/generators/app_file_generator.rb +28 -0
  22. data/lib/charming/generators/app_generator/app_spec_templates.rb +86 -0
  23. data/lib/charming/generators/app_generator/basic_templates.rb +69 -0
  24. data/lib/charming/generators/app_generator/component_templates.rb +36 -0
  25. data/lib/charming/generators/app_generator/controller_template.rb +69 -0
  26. data/lib/charming/generators/app_generator/layout_template.rb +160 -0
  27. data/lib/charming/generators/app_generator/model_templates.rb +30 -0
  28. data/lib/charming/generators/app_generator/screen_spec_templates.rb +70 -0
  29. data/lib/charming/generators/app_generator/view_template.rb +90 -0
  30. data/lib/charming/generators/app_generator.rb +76 -0
  31. data/lib/charming/generators/base.rb +29 -0
  32. data/lib/charming/generators/component_generator.rb +30 -0
  33. data/lib/charming/generators/controller_generator.rb +50 -0
  34. data/lib/charming/generators/name.rb +32 -0
  35. data/lib/charming/generators/screen_generator.rb +154 -0
  36. data/lib/charming/generators/view_generator.rb +34 -0
  37. data/lib/charming/generators.rb +7 -0
  38. data/lib/charming/internal/renderer/differential.rb +53 -0
  39. data/lib/charming/internal/renderer/full_repaint.rb +19 -0
  40. data/lib/charming/internal/terminal/adapter.rb +52 -0
  41. data/lib/charming/internal/terminal/memory_backend.rb +91 -0
  42. data/lib/charming/internal/terminal/tty_backend.rb +250 -0
  43. data/lib/charming/key_event.rb +13 -0
  44. data/lib/charming/mouse_event.rb +40 -0
  45. data/lib/charming/resize_event.rb +7 -0
  46. data/lib/charming/response.rb +33 -0
  47. data/lib/charming/router.rb +137 -0
  48. data/lib/charming/runtime.rb +192 -0
  49. data/lib/charming/screen.rb +8 -0
  50. data/lib/charming/task.rb +7 -0
  51. data/lib/charming/task_event.rb +17 -0
  52. data/lib/charming/task_executor.rb +62 -0
  53. data/lib/charming/timer_event.rb +7 -0
  54. data/lib/charming/ui/border.rb +33 -0
  55. data/lib/charming/ui/style.rb +244 -0
  56. data/lib/charming/ui/theme.rb +178 -0
  57. data/lib/charming/ui/themes/phosphor.json +100 -0
  58. data/lib/charming/ui/width.rb +24 -0
  59. data/lib/charming/ui.rb +230 -0
  60. data/lib/charming/version.rb +5 -0
  61. data/lib/charming/view.rb +116 -0
  62. data/lib/charming.rb +24 -0
  63. data/sig/charming.rbs +3 -0
  64. metadata +225 -0
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charming
4
+ # UI is a module of layout primitives for composing and positioning ANSI-styled
5
+ # terminal text. It provides functions to join blocks horizontally or vertically,
6
+ # place content on fixed-size canvases, overlay elements, and slice strings that
7
+ # contain ANSI escape sequences while preserving their styling.
8
+ module UI
9
+ module_function
10
+
11
+ # Builds a new {Style} instance for chaining color, padding, alignment, and other visual properties.
12
+ def style
13
+ Style.new
14
+ end
15
+
16
+ # Horizontally concatenates *blocks* into a single multi-line string, padding each block's
17
+ # rows to match the widest row. A *gap* argument (in spaces) can separate adjacent columns.
18
+ def join_horizontal(*blocks, gap: 0)
19
+ normalized = normalize_blocks(blocks)
20
+ widths = block_widths(normalized)
21
+ separator = " " * gap
22
+
23
+ Array.new(block_height(normalized)) do |index|
24
+ horizontal_line(normalized, widths, index).join(separator)
25
+ end.join("\n")
26
+ end
27
+
28
+ # Stacks *blocks* vertically separated by one or more blank lines. A *gap* of N inserts N
29
+ # extra newline characters between blocks (1 gap = 1 blank line, 2 gaps = 2 blank lines, etc.).
30
+ def join_vertical(*blocks, gap: 0)
31
+ blocks.join("\n" * (gap + 1))
32
+ end
33
+
34
+ # Centers a *block* within a canvas of the given *width* and *height*, then returns the result.
35
+ def center(block, width:, height:, background: nil)
36
+ place(block, width: width, height: height, top: :center, left: :center, background: background)
37
+ end
38
+
39
+ # Draws *overlay* on top of a base at the specified *top* (row) and *left* (column) coordinates,
40
+ # defaulting to center in both directions. ANSI styling on the base content is preserved underneath.
41
+ def overlay(base, overlay, top: :center, left: :center)
42
+ base_lines = base.to_s.lines(chomp: true)
43
+ overlay_lines = overlay.to_s.lines(chomp: true)
44
+ width = block_width(base_lines)
45
+ row = offset(top, base_lines.length, overlay_lines.length)
46
+ column = offset(left, width, block_width(overlay_lines))
47
+
48
+ draw_lines(base_lines, overlay_lines, row: row, column: column, width: width)
49
+ end
50
+
51
+ # Places a *block* onto a blank canvas of *width* × *height* at an offset determined by *top* (row)
52
+ # and *left* (column). Non-:center values are treated as absolute positions. When *background* is
53
+ # given, the assembled frame is wrapped so the theme bg paints the entire canvas — overlay content
54
+ # with its own bg overrides per-cell; resets re-apply the canvas bg.
55
+ def place(block, width:, height:, top: 0, left: 0, background: nil)
56
+ lines = block.to_s.lines(chomp: true)
57
+ row = offset(top, height, lines.length)
58
+ column = offset(left, width, block_width(lines))
59
+ canvas = Array.new(height) { " " * width }
60
+ composed = draw_lines(canvas, lines, row: row, column: column, width: width)
61
+ return composed unless background
62
+
63
+ Style.new.background(background).render(composed)
64
+ end
65
+
66
+ # Normalizes an array of mixed objects into arrays of lines by calling `#to_s` on each element.
67
+ def normalize_blocks(blocks)
68
+ blocks.map { |block| block.to_s.lines(chomp: true) }
69
+ end
70
+
71
+ # Measures the displayed (visual) width of each normalised block, returning an array of integer widths.
72
+ def block_widths(blocks)
73
+ blocks.map { |lines| lines.map { |line| Width.measure(line) }.max || 0 }
74
+ end
75
+
76
+ # Returns the maximum visual character width across all *lines*, accounting for multi-column characters
77
+ # (e.g., full-width CJK glyphs) and invisible ANSI escape sequences.
78
+ def block_width(lines)
79
+ lines.map { |line| Width.measure(line) }.max || 0
80
+ end
81
+
82
+ # Returns the height in rows of each normalised block, taking the maximum across all blocks.
83
+ def block_height(blocks)
84
+ blocks.map(&:length).max || 0
85
+ end
86
+
87
+ # Builds a single horizontal row by concatenating one line from each *block* at index *index*, padding
88
+ # every segment to its corresponding *width* in spaces. Returns the assembled array of padded segments.
89
+ def horizontal_line(blocks, widths, index)
90
+ blocks.each_with_index.map do |lines, block_index|
91
+ line = lines[index] || ""
92
+ line + (" " * (widths[block_index] - Width.measure(line)))
93
+ end
94
+ end
95
+
96
+ # Computes a placement coordinate: if *value* is `:center` the result centres the *size* within *available*;
97
+ # otherwise *value* is returned verbatim as an absolute integer position.
98
+ def offset(value, available, size)
99
+ return [(available - size) / 2, 0].max if value == :center
100
+
101
+ value
102
+ end
103
+
104
+ # Merges an *overlay_line* into a *base_line* at the given *column*, returning the combined string. The
105
+ # overlay replaces (covers) underlying characters; anything to the right that exceeds *width* is truncated.
106
+ def composed_overlay_line(base_line, overlay_line, column, width)
107
+ return visible_slice(base_line, 0, width) if column >= width
108
+ return visible_slice(base_line, 0, width) if column + Width.measure(overlay_line) <= 0
109
+
110
+ target_column = [column, 0].max
111
+ overlay_start = [0 - column, 0].max
112
+ overlay = visible_slice(overlay_line, overlay_start, width - target_column)
113
+ overlay_width = Width.measure(overlay)
114
+ return visible_slice(base_line, 0, width) if overlay_width.zero?
115
+
116
+ right_column = target_column + overlay_width
117
+
118
+ visible_slice(base_line, 0, target_column) +
119
+ overlay +
120
+ visible_slice(base_line, right_column, [width - right_column, 0].max)
121
+ end
122
+
123
+ # Returns a visible-slice of *line* starting at *start_column* spanning *width* characters, preserving any
124
+ # ANSI escape sequences that were active at the start of the slice. Non-positive widths return `""`.
125
+ def visible_slice(line, start_column, width)
126
+ return "" unless width.positive?
127
+
128
+ slice_visible_text(line.to_s, start_column, start_column + width)
129
+ end
130
+
131
+ # Slices a string by visible terminal columns while preserving ANSI style state.
132
+ def slice_visible_text(line, start_column, end_column)
133
+ state = {column: 0, output: +"", active: [], started: false, styled: false}
134
+
135
+ each_ansi_or_char(line) do |token, ansi|
136
+ if ansi
137
+ slice_ansi(token, state, start_column, end_column)
138
+ else
139
+ slice_char(token, state, start_column, end_column)
140
+ end
141
+ end
142
+
143
+ terminate_slice(state)
144
+ end
145
+
146
+ # Splits a *line* into token-range pieces bounded by *start_column* and *end_column*, preserving ANSI escapes
147
+ # that fall within the visible range. Yields each character or escape sequence along with whether it is ANSI.
148
+ def each_ansi_or_char(line)
149
+ index = 0
150
+ while index < line.length
151
+ match = line.match(Width::ANSI_PATTERN, index)
152
+ if match&.begin(0) == index
153
+ yield match[0], true
154
+ index = match.end(0)
155
+ else
156
+ char = line[index]
157
+ yield char, false
158
+ index += 1
159
+ end
160
+ end
161
+ end
162
+
163
+ # Slices an ANSI *token* (escape sequence) into *state*, writing active markers to the output if the current
164
+ # *column* falls within the [start_column, end_column) range. Resets styles on `[0m` sequences.
165
+ def slice_ansi(token, state, start_column, end_column)
166
+ started = state[:started]
167
+ update_active_styles(state[:active], token)
168
+ return unless state[:column].between?(start_column, end_column - 1)
169
+
170
+ start_slice(state)
171
+ if started
172
+ state[:output] << token
173
+ state[:styled] = !token.include?("[0m")
174
+ end
175
+ end
176
+
177
+ # Slices a plain *char* into *state*, advancing the column tracker by the character's visual width. If the
178
+ # character overlaps with the [start_column, end_column) range it is appended to the output.
179
+ def slice_char(char, state, start_column, end_column)
180
+ char_width = Width.measure(char)
181
+ char_start = state[:column]
182
+ char_end = char_start + char_width
183
+ state[:column] = char_end
184
+ return unless char_end > start_column && char_start < end_column
185
+
186
+ start_slice(state)
187
+ state[:output] << char
188
+ end
189
+
190
+ # Starts writing to the output buffer, flushing any active ANSI markers if this is the first character placed.
191
+ def start_slice(state)
192
+ return if state[:started]
193
+
194
+ state[:output] << state[:active].join
195
+ state[:styled] = true unless state[:active].empty?
196
+ state[:started] = true
197
+ end
198
+
199
+ # Closes the slice by appending a final `[0m` reset escape to the output unless no active styling exists or
200
+ # nothing was written. Returns the fully constructed output string with trailing reset applied.
201
+ def terminate_slice(state)
202
+ return state[:output] if !state[:styled] || state[:output].empty?
203
+
204
+ "#{state[:output]}\e[0m"
205
+ end
206
+
207
+ # Updates *state*[:active] with an ANSI *token*: resets all active styles on `[0m` or appends the token as a
208
+ # new active marker otherwise. Called during each_ansi_or_char iteration.
209
+ def update_active_styles(active, token)
210
+ if token.include?("[0m")
211
+ active.clear
212
+ else
213
+ active << token
214
+ end
215
+ end
216
+
217
+ # Overlays *lines* onto a *canvas* starting at (*row*, *column*), writing each overlaid line into the canvas
218
+ # via `composed_overlay_line`. Returns the final canvas joined by newlines.
219
+ def draw_lines(canvas, lines, row:, column:, width:)
220
+ lines.each_with_index do |line, index|
221
+ line_index = row + index
222
+ next if line_index.negative? || line_index >= canvas.length
223
+
224
+ canvas[line_index] = composed_overlay_line(canvas[line_index], line, column, width)
225
+ end
226
+
227
+ canvas.join("\n")
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charming
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charming
4
+ # View is the base class for all screen view implementations. It provides assign injection (via `initialize`),
5
+ # rendering hooks, layout composition helpers (`row`, `column`, `render_component`, `yield_content`),
6
+ # and access to controller theme, style, and focus state from within views.
7
+ class View
8
+ # Initializes the view with named assigns injected as instance-local accessor methods via
9
+ # `define_singleton_method`. Called when a controller instantiates a view for rendering.
10
+ def initialize(**assigns)
11
+ @assigns = assigns
12
+ define_assign_readers
13
+ end
14
+
15
+ # Returns all view assigns as a hash, used by layouts to compose the full template (content + screen + controller).
16
+ def layout_assigns
17
+ assigns
18
+ end
19
+
20
+ # Renders the view's body. Default is empty — subclasses override to return visible text.
21
+ def render
22
+ ""
23
+ end
24
+
25
+ # Delegates focus checking to the controller in assigns, allowing views to determine which slot (sidebar, content) has focus.
26
+ def focused?(slot)
27
+ ctrl = assigns[:focus_controller] || assigns[:controller]
28
+ ctrl ? ctrl.focused?(slot) : false
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :assigns
34
+
35
+ # Returns the shared UI style configuration used by components and views for visual rendering (colors, borders).
36
+ def style
37
+ UI.style
38
+ end
39
+
40
+ # Returns the active theme: uses `theme` from assigns or controller, falling back to `UI::Theme.default`.
41
+ def theme
42
+ assigns[:theme] || assigns[:controller]&.theme || UI::Theme.default
43
+ end
44
+
45
+ # Outputs styled text through the view's rendering pipeline. Accepts a named `style:` for inline formatting.
46
+ # Appends the rendered value to the output buffer and returns it.
47
+ def text(value, style: nil)
48
+ rendered = apply_style(value.to_s, style)
49
+ append_to_buffer(rendered)
50
+ rendered
51
+ end
52
+
53
+ # Renders a box with optional styling. Accepts an inline block for complex content or a plain value.
54
+ # Used for bordered containers and field groups in views.
55
+ def box(value = nil, style: nil, &)
56
+ content = block_given? ? capture(&) : value.to_s
57
+ apply_style(content, style)
58
+ end
59
+
60
+ # Joins items horizontally (side-by-side) using the UI rendering engine. Supports a `gap:` parameter.
61
+ def row(*items, gap: 0)
62
+ UI.join_horizontal(*items, gap: gap)
63
+ end
64
+
65
+ # Stacks items vertically using the UI rendering engine. Supports a `gap:` parameter for spacing.
66
+ def column(*items, gap: 0)
67
+ UI.join_vertical(*items, gap: gap)
68
+ end
69
+
70
+ # Renders a component (e.g., a ProgressBar, Spinner, Modal) and returns its string output.
71
+ def render_component(component)
72
+ component.render.to_s
73
+ end
74
+
75
+ # Renders a partial view component. An alias for `render_component` used in layout templates.
76
+ def render_partial(partial)
77
+ render_component(partial)
78
+ end
79
+
80
+ # Yields the layout's `content` slot — used by view templates to inject their body into a layout wrapper (e.g., sidebar).
81
+ def yield_content
82
+ assigns.fetch(:content, "")
83
+ end
84
+
85
+ # Evaluates a block in the view's context with a clean output buffer. Captures text written via `text`/`box`
86
+ # and returns joined content. Resets buffer afterward for parent rendering.
87
+ def capture(&)
88
+ previous_buffer = @output_buffer
89
+ @output_buffer = []
90
+ result = instance_eval(&)
91
+ @output_buffer.empty? ? result.to_s : @output_buffer.join("\n")
92
+ ensure
93
+ @output_buffer = previous_buffer
94
+ end
95
+
96
+ # Appends a value to the current output buffer (if one is active). Used by rendering helpers.
97
+ def append_to_buffer(value)
98
+ @output_buffer << value if @output_buffer
99
+ end
100
+
101
+ # Applies a style object's `render` method to a string, returning styled output or raw text when style is nil.
102
+ def apply_style(value, style_object)
103
+ style_object ? style_object.render(value) : value
104
+ end
105
+
106
+ # Dynamically defines read-only accessor methods for each assign key as singleton methods on self.
107
+ # Skips keys where the view already responds (controller methods take precedence).
108
+ def define_assign_readers
109
+ assigns.each_key do |name|
110
+ next if respond_to?(name, true)
111
+
112
+ define_singleton_method(name) { assigns.fetch(name) }
113
+ end
114
+ end
115
+ end
116
+ end
data/lib/charming.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+
5
+ loader = Zeitwerk::Loader.for_gem
6
+ loader.inflector.inflect(
7
+ "cli" => "CLI",
8
+ "ui" => "UI",
9
+ "tty_backend" => "TTYBackend"
10
+ )
11
+ loader.setup
12
+
13
+ module Charming
14
+ class Error < StandardError; end
15
+
16
+ def self.run(application, backend: nil)
17
+ Runtime.new(application, backend: backend).run
18
+ end
19
+
20
+ def self.key_of(event)
21
+ key = event.respond_to?(:key) ? event.key : event
22
+ key.to_sym
23
+ end
24
+ end
data/sig/charming.rbs ADDED
@@ -0,0 +1,3 @@
1
+ module Charming
2
+ VERSION: String
3
+ end
metadata ADDED
@@ -0,0 +1,225 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: charming
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - pando
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activemodel
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.1'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 8.1.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '8.1'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 8.1.2
32
+ - !ruby/object:Gem::Dependency
33
+ name: tty-cursor
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '0.7'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '0.7'
46
+ - !ruby/object:Gem::Dependency
47
+ name: zeitwerk
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.6'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.6'
60
+ - !ruby/object:Gem::Dependency
61
+ name: tty-progressbar
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.18'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.18'
74
+ - !ruby/object:Gem::Dependency
75
+ name: tty-table
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.12'
81
+ type: :runtime
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '0.12'
88
+ - !ruby/object:Gem::Dependency
89
+ name: tty-reader
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '0.9'
95
+ type: :runtime
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '0.9'
102
+ - !ruby/object:Gem::Dependency
103
+ name: tty-screen
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '0.8'
109
+ type: :runtime
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '0.8'
116
+ - !ruby/object:Gem::Dependency
117
+ name: unicode-display_width
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '2.6'
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '2.6'
130
+ description: Charming brings Rails-like application, routing, controller, and rendering
131
+ conventions to Ruby terminal user interfaces.
132
+ email:
133
+ - pandorocks@proton.me
134
+ executables:
135
+ - charming
136
+ extensions: []
137
+ extra_rdoc_files: []
138
+ files:
139
+ - LICENSE.txt
140
+ - README.md
141
+ - exe/charming
142
+ - lib/charming.rb
143
+ - lib/charming/application.rb
144
+ - lib/charming/application_model.rb
145
+ - lib/charming/cli.rb
146
+ - lib/charming/component.rb
147
+ - lib/charming/components/activity_indicator.rb
148
+ - lib/charming/components/command_palette.rb
149
+ - lib/charming/components/keyboard_handler.rb
150
+ - lib/charming/components/list.rb
151
+ - lib/charming/components/modal.rb
152
+ - lib/charming/components/progressbar.rb
153
+ - lib/charming/components/spinner.rb
154
+ - lib/charming/components/table.rb
155
+ - lib/charming/components/text_input.rb
156
+ - lib/charming/components/viewport.rb
157
+ - lib/charming/controller.rb
158
+ - lib/charming/focus.rb
159
+ - lib/charming/generators.rb
160
+ - lib/charming/generators/app_file_generator.rb
161
+ - lib/charming/generators/app_generator.rb
162
+ - lib/charming/generators/app_generator/app_spec_templates.rb
163
+ - lib/charming/generators/app_generator/basic_templates.rb
164
+ - lib/charming/generators/app_generator/component_templates.rb
165
+ - lib/charming/generators/app_generator/controller_template.rb
166
+ - lib/charming/generators/app_generator/layout_template.rb
167
+ - lib/charming/generators/app_generator/model_templates.rb
168
+ - lib/charming/generators/app_generator/screen_spec_templates.rb
169
+ - lib/charming/generators/app_generator/view_template.rb
170
+ - lib/charming/generators/base.rb
171
+ - lib/charming/generators/component_generator.rb
172
+ - lib/charming/generators/controller_generator.rb
173
+ - lib/charming/generators/name.rb
174
+ - lib/charming/generators/screen_generator.rb
175
+ - lib/charming/generators/view_generator.rb
176
+ - lib/charming/internal/renderer/differential.rb
177
+ - lib/charming/internal/renderer/full_repaint.rb
178
+ - lib/charming/internal/terminal/adapter.rb
179
+ - lib/charming/internal/terminal/memory_backend.rb
180
+ - lib/charming/internal/terminal/tty_backend.rb
181
+ - lib/charming/key_event.rb
182
+ - lib/charming/mouse_event.rb
183
+ - lib/charming/resize_event.rb
184
+ - lib/charming/response.rb
185
+ - lib/charming/router.rb
186
+ - lib/charming/runtime.rb
187
+ - lib/charming/screen.rb
188
+ - lib/charming/task.rb
189
+ - lib/charming/task_event.rb
190
+ - lib/charming/task_executor.rb
191
+ - lib/charming/timer_event.rb
192
+ - lib/charming/ui.rb
193
+ - lib/charming/ui/border.rb
194
+ - lib/charming/ui/style.rb
195
+ - lib/charming/ui/theme.rb
196
+ - lib/charming/ui/themes/phosphor.json
197
+ - lib/charming/ui/width.rb
198
+ - lib/charming/version.rb
199
+ - lib/charming/view.rb
200
+ - sig/charming.rbs
201
+ homepage: https://github.com/pandorocks/charming
202
+ licenses:
203
+ - MIT
204
+ metadata:
205
+ homepage_uri: https://github.com/pandorocks/charming
206
+ source_code_uri: https://github.com/pandorocks/charming
207
+ rubygems_mfa_required: 'true'
208
+ rdoc_options: []
209
+ require_paths:
210
+ - lib
211
+ required_ruby_version: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 4.0.0
216
+ required_rubygems_version: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ requirements: []
222
+ rubygems_version: 4.0.12
223
+ specification_version: 4
224
+ summary: A Rails-inspired TUI framework for Ruby.
225
+ test_files: []