charming 0.1.1 → 0.1.3
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/README.md +2 -2
- data/lib/charming/application.rb +14 -3
- data/lib/charming/cli.rb +23 -0
- data/lib/charming/controller/class_methods.rb +115 -0
- data/lib/charming/controller/command_palette.rb +135 -0
- data/lib/charming/controller/component_dispatching.rb +81 -0
- data/lib/charming/controller/dispatching.rb +60 -0
- data/lib/charming/controller/focus_management.rb +30 -0
- data/lib/charming/controller/rendering.rb +127 -0
- data/lib/charming/controller/session_state.rb +41 -0
- data/lib/charming/controller/sidebar_navigation.rb +111 -0
- data/lib/charming/controller.rb +35 -559
- data/lib/charming/database_commands.rb +16 -0
- data/lib/charming/database_installer.rb +27 -0
- data/lib/charming/focus.rb +58 -2
- data/lib/charming/generators/app_file_generator.rb +13 -0
- data/lib/charming/generators/app_generator.rb +123 -47
- data/lib/charming/generators/base.rb +26 -0
- data/lib/charming/generators/component_generator.rb +10 -10
- data/lib/charming/generators/controller_generator.rb +22 -11
- data/lib/charming/generators/model_generator.rb +38 -29
- data/lib/charming/generators/name.rb +10 -0
- data/lib/charming/generators/screen_generator.rb +78 -32
- data/lib/charming/generators/templates/app/Gemfile.template +5 -0
- data/lib/charming/generators/templates/app/README.md.template +9 -0
- data/lib/charming/generators/templates/app/Rakefile.template +3 -0
- data/lib/charming/generators/templates/app/application.template +13 -0
- data/lib/charming/generators/templates/app/application_controller.template +19 -0
- data/lib/charming/generators/templates/app/application_record.template +7 -0
- data/lib/charming/generators/templates/app/application_state.template +6 -0
- data/lib/charming/generators/templates/app/database_config.template +12 -0
- data/lib/charming/generators/templates/app/executable.template +7 -0
- data/lib/charming/generators/templates/app/gemspec.template +6 -0
- data/lib/charming/generators/templates/app/home_controller.template +6 -0
- data/lib/charming/generators/templates/app/home_state.template +7 -0
- data/lib/charming/generators/templates/app/keep.template +0 -0
- data/lib/charming/generators/templates/app/layout.template +110 -0
- data/lib/charming/generators/templates/app/root_file.template +20 -0
- data/lib/charming/generators/templates/app/routes.template +5 -0
- data/lib/charming/generators/templates/app/seeds.template +1 -0
- data/lib/charming/generators/templates/app/spec_controller.template +17 -0
- data/lib/charming/generators/templates/app/spec_helper.template +3 -0
- data/lib/charming/generators/templates/app/spec_state.template +17 -0
- data/lib/charming/generators/templates/app/spec_view.template +16 -0
- data/lib/charming/generators/templates/app/version.template +5 -0
- data/lib/charming/generators/templates/app/view.template +21 -0
- data/lib/charming/generators/templates/component/component.rb.template +9 -0
- data/lib/charming/generators/templates/controller/controller.rb.template +6 -0
- data/lib/charming/generators/templates/model/migration.rb.template +9 -0
- data/lib/charming/generators/templates/model/model.rb.template +6 -0
- data/lib/charming/generators/templates/model/spec.rb.template +9 -0
- data/lib/charming/generators/templates/screen/controller.rb.template +7 -0
- data/lib/charming/generators/templates/screen/spec_controller.rb.template +17 -0
- data/lib/charming/generators/templates/screen/spec_state.rb.template +17 -0
- data/lib/charming/generators/templates/screen/spec_view.rb.template +13 -0
- data/lib/charming/generators/templates/screen/state.rb.template +7 -0
- data/lib/charming/generators/templates/screen/view.rb.template +11 -0
- data/lib/charming/generators/templates/view/view.rb.template +11 -0
- data/lib/charming/generators/view_generator.rb +19 -3
- data/lib/charming/internal/renderer/differential.rb +25 -2
- data/lib/charming/internal/renderer/full_repaint.rb +6 -0
- data/lib/charming/internal/terminal/adapter.rb +29 -3
- data/lib/charming/internal/terminal/key_normalizer.rb +84 -0
- data/lib/charming/internal/terminal/memory_backend.rb +28 -1
- data/lib/charming/internal/terminal/mouse_parser.rb +81 -0
- data/lib/charming/internal/terminal/tty_backend.rb +65 -115
- data/lib/charming/presentation/component.rb +3 -5
- data/lib/charming/presentation/components/activity_indicator.rb +173 -134
- data/lib/charming/presentation/components/command_palette.rb +94 -96
- data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
- data/lib/charming/presentation/components/empty_state.rb +47 -36
- data/lib/charming/presentation/components/form/builder.rb +52 -40
- data/lib/charming/presentation/components/form/confirm.rb +50 -39
- data/lib/charming/presentation/components/form/field.rb +94 -71
- data/lib/charming/presentation/components/form/input.rb +61 -49
- data/lib/charming/presentation/components/form/note.rb +27 -20
- data/lib/charming/presentation/components/form/select.rb +84 -63
- data/lib/charming/presentation/components/form/textarea.rb +67 -53
- data/lib/charming/presentation/components/form.rb +120 -93
- data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
- data/lib/charming/presentation/components/list.rb +123 -97
- data/lib/charming/presentation/components/markdown.rb +21 -17
- data/lib/charming/presentation/components/modal.rb +55 -43
- data/lib/charming/presentation/components/progressbar.rb +61 -50
- data/lib/charming/presentation/components/spinner.rb +35 -27
- data/lib/charming/presentation/components/table.rb +108 -85
- data/lib/charming/presentation/components/text_area.rb +219 -173
- data/lib/charming/presentation/components/text_input.rb +120 -98
- data/lib/charming/presentation/components/viewport.rb +218 -168
- data/lib/charming/presentation/layout/builder.rb +84 -0
- data/lib/charming/presentation/layout/overlay.rb +55 -0
- data/lib/charming/presentation/layout/pane.rb +149 -0
- data/lib/charming/presentation/layout/rect.rb +21 -0
- data/lib/charming/presentation/layout/screen_layout.rb +58 -0
- data/lib/charming/presentation/layout/split.rb +132 -0
- data/lib/charming/presentation/layout.rb +28 -30
- data/lib/charming/presentation/markdown/block_renderers.rb +118 -0
- data/lib/charming/presentation/markdown/inline_renderers.rb +66 -0
- data/lib/charming/presentation/markdown/render_context.rb +20 -0
- data/lib/charming/presentation/markdown/renderer.rb +82 -174
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +58 -44
- data/lib/charming/presentation/markdown.rb +4 -3
- data/lib/charming/presentation/template_view.rb +22 -17
- data/lib/charming/presentation/templates/erb_handler.rb +4 -6
- data/lib/charming/presentation/templates.rb +47 -32
- data/lib/charming/presentation/ui/ansi_codes.rb +87 -0
- data/lib/charming/presentation/ui/ansi_slicer.rb +92 -0
- data/lib/charming/presentation/ui/border.rb +24 -26
- data/lib/charming/presentation/ui/border_painter.rb +56 -0
- data/lib/charming/presentation/ui/canvas.rb +80 -0
- data/lib/charming/presentation/ui/style.rb +170 -205
- data/lib/charming/presentation/ui/theme.rb +133 -135
- data/lib/charming/presentation/ui/width.rb +12 -14
- data/lib/charming/presentation/ui.rb +70 -213
- data/lib/charming/presentation/view.rb +107 -92
- data/lib/charming/runtime.rb +25 -10
- data/lib/charming/tasks/inline_executor.rb +9 -0
- data/lib/charming/tasks/task.rb +3 -0
- data/lib/charming/tasks/threaded_executor.rb +12 -0
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +16 -2
- metadata +60 -10
- data/lib/charming/generators/app_generator/app_spec_templates.rb +0 -90
- data/lib/charming/generators/app_generator/basic_templates.rb +0 -81
- data/lib/charming/generators/app_generator/component_templates.rb +0 -36
- data/lib/charming/generators/app_generator/controller_template.rb +0 -60
- data/lib/charming/generators/app_generator/database_templates.rb +0 -45
- data/lib/charming/generators/app_generator/layout_template.rb +0 -66
- data/lib/charming/generators/app_generator/screen_spec_templates.rb +0 -69
- data/lib/charming/generators/app_generator/state_templates.rb +0 -30
- data/lib/charming/generators/app_generator/view_template.rb +0 -84
|
@@ -1,118 +1,133 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
end
|
|
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
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
# Renders the view's body. Default is empty — subclasses override to return visible text.
|
|
21
|
+
def render
|
|
22
|
+
""
|
|
23
|
+
end
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
private
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
attr_reader :assigns
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
# Builds a declarative layout tree for the current terminal screen and renders it.
|
|
81
|
+
def screen_layout(background: nil, &)
|
|
82
|
+
layout = Layout::Builder.build(screen: layout_screen, view: self, background: background, &)
|
|
83
|
+
register_layout_focus(layout)
|
|
84
|
+
layout.render
|
|
85
|
+
end
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@output_buffer = []
|
|
91
|
-
result = instance_eval(&)
|
|
92
|
-
@output_buffer.empty? ? result.to_s : @output_buffer.join("\n")
|
|
93
|
-
ensure
|
|
94
|
-
@output_buffer = previous_buffer
|
|
95
|
-
end
|
|
87
|
+
# Yields the layout's `content` slot — used by view templates to inject their body into a layout wrapper (e.g., sidebar).
|
|
88
|
+
def yield_content
|
|
89
|
+
assigns.fetch(:content, "")
|
|
90
|
+
end
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
# Evaluates a block in the view's context with a clean output buffer. Captures text written via `text`/`box`
|
|
93
|
+
# and returns joined content. Resets buffer afterward for parent rendering.
|
|
94
|
+
def capture(&)
|
|
95
|
+
previous_buffer = @output_buffer
|
|
96
|
+
@output_buffer = []
|
|
97
|
+
result = instance_eval(&)
|
|
98
|
+
@output_buffer.empty? ? result.to_s : @output_buffer.join("\n")
|
|
99
|
+
ensure
|
|
100
|
+
@output_buffer = previous_buffer
|
|
101
|
+
end
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
# Appends a value to the current output buffer (if one is active). Used by rendering helpers.
|
|
104
|
+
def append_to_buffer(value)
|
|
105
|
+
@output_buffer << value if @output_buffer
|
|
106
|
+
end
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
next if respond_to?(name, true)
|
|
108
|
+
# Applies a style object's `render` method to a string, returning styled output or raw text when style is nil.
|
|
109
|
+
def apply_style(value, style_object)
|
|
110
|
+
style_object ? style_object.render(value) : value
|
|
111
|
+
end
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
# Dynamically defines read-only accessor methods for each assign key as singleton methods on self.
|
|
114
|
+
# Skips keys where the view already responds (controller methods take precedence).
|
|
115
|
+
def define_assign_readers
|
|
116
|
+
assigns.each_key do |name|
|
|
117
|
+
next if respond_to?(name, true)
|
|
118
|
+
|
|
119
|
+
define_singleton_method(name) { assigns.fetch(name) }
|
|
115
120
|
end
|
|
116
121
|
end
|
|
122
|
+
|
|
123
|
+
def layout_screen
|
|
124
|
+
assigns[:screen] || assigns[:controller]&.screen || Charming::Screen.new(width: 80, height: 24)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def register_layout_focus(layout)
|
|
128
|
+
return unless assigns[:controller]
|
|
129
|
+
|
|
130
|
+
assigns[:controller].focus.define_layout(layout.focusable_names)
|
|
131
|
+
end
|
|
117
132
|
end
|
|
118
133
|
end
|
data/lib/charming/runtime.rb
CHANGED
|
@@ -26,19 +26,21 @@ module Charming
|
|
|
26
26
|
# restores terminal state on exit.
|
|
27
27
|
def run
|
|
28
28
|
setup_terminal
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
with_raw_input do
|
|
30
|
+
render(resolve_response(dispatch(@route.action)))
|
|
31
|
+
loop do
|
|
32
|
+
event = next_task_event || next_timer_event || @backend.read_event(timeout: read_timeout)
|
|
33
|
+
next unless event
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
response = dispatch_event(event)
|
|
36
|
+
next unless response
|
|
37
|
+
break if response.quit?
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
response = resolve_response(response)
|
|
40
|
+
break if response.quit?
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
render(response)
|
|
43
|
+
end
|
|
42
44
|
end
|
|
43
45
|
ensure
|
|
44
46
|
@task_executor&.shutdown(timeout: 0.0)
|
|
@@ -75,6 +77,8 @@ module Charming
|
|
|
75
77
|
controller(event: event).dispatch_mouse
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
# Instantiates a fresh controller for the active route, passing the application, current *event*,
|
|
81
|
+
# route params, screen dimensions, and route object. Called by every dispatch path.
|
|
78
82
|
def controller(event: nil)
|
|
79
83
|
@route.controller_class.new(application: @application, event: event, params: @route.params, screen: screen, route: @route)
|
|
80
84
|
end
|
|
@@ -91,8 +95,12 @@ module Charming
|
|
|
91
95
|
end
|
|
92
96
|
|
|
93
97
|
# Dispatches a resize event: updates screen dimensions and re-renders the current action.
|
|
98
|
+
# The renderer's cached previous frame is invalidated and the backend is cleared so the
|
|
99
|
+
# new-dimension frame paints onto a clean alt-screen instead of overlaying stale rows.
|
|
94
100
|
def dispatch_resize(event)
|
|
95
101
|
@screen = Screen.new(width: event.width, height: event.height)
|
|
102
|
+
@renderer.invalidate if @renderer.respond_to?(:invalidate)
|
|
103
|
+
@backend.clear if @backend.respond_to?(:clear)
|
|
96
104
|
dispatch(@route.action, event: event)
|
|
97
105
|
end
|
|
98
106
|
|
|
@@ -181,6 +189,13 @@ module Charming
|
|
|
181
189
|
@backend.install_resize_handler if @backend.respond_to?(:install_resize_handler)
|
|
182
190
|
end
|
|
183
191
|
|
|
192
|
+
# Keeps input raw/no-echo across rendering and dispatch, not just during reads.
|
|
193
|
+
def with_raw_input(&block)
|
|
194
|
+
return yield unless @backend.respond_to?(:with_raw_input)
|
|
195
|
+
|
|
196
|
+
@backend.with_raw_input(&block)
|
|
197
|
+
end
|
|
198
|
+
|
|
184
199
|
# Restores terminal state: reinstalls any previous resize handler, shows
|
|
185
200
|
# the cursor, and leaves the alternative screen buffer.
|
|
186
201
|
def restore_terminal
|
|
@@ -2,22 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Tasks
|
|
5
|
+
# InlineExecutor runs submitted tasks synchronously on the calling thread, pushing
|
|
6
|
+
# the resulting TaskEvent directly into the runtime's *queue*. Used for testing and
|
|
7
|
+
# for environments where spawning background threads is undesirable.
|
|
5
8
|
class InlineExecutor
|
|
9
|
+
# *queue* is the thread-safe Queue (typically `runtime.@task_queue`) into which
|
|
10
|
+
# completed TaskEvents are pushed.
|
|
6
11
|
def initialize(queue)
|
|
7
12
|
@queue = queue
|
|
8
13
|
end
|
|
9
14
|
|
|
15
|
+
# Wraps *block* in a Task, invokes it immediately, and pushes the resulting
|
|
16
|
+
# TaskEvent (value or error) onto the queue. Returns nil.
|
|
10
17
|
def submit(name, &block)
|
|
11
18
|
task = Task.new(name: name.to_sym, block: block)
|
|
12
19
|
@queue << run(task)
|
|
13
20
|
nil
|
|
14
21
|
end
|
|
15
22
|
|
|
23
|
+
# No-op stub for the shutdown contract; nothing to join since tasks run on the caller.
|
|
16
24
|
def shutdown(timeout: 0.0)
|
|
17
25
|
end
|
|
18
26
|
|
|
19
27
|
private
|
|
20
28
|
|
|
29
|
+
# Invokes the task's block and wraps the result (or raised exception) in a TaskEvent.
|
|
21
30
|
def run(task)
|
|
22
31
|
Events::TaskEvent.new(name: task.name, value: task.call)
|
|
23
32
|
rescue StandardError, ScriptError => e
|
data/lib/charming/tasks/task.rb
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Tasks
|
|
5
|
+
# Task is the unit of work submitted to a task executor. It pairs a *name* (used by
|
|
6
|
+
# `on_task` handlers to route the result) with a *block* to invoke on the executor.
|
|
5
7
|
Task = Data.define(:name, :block) do
|
|
8
|
+
# Invokes the task's block in the executor's thread and returns its value (or raises).
|
|
6
9
|
def call = block.call
|
|
7
10
|
end
|
|
8
11
|
end
|
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Tasks
|
|
5
|
+
# ThreadedExecutor runs submitted tasks on background Ruby threads. Each submission
|
|
6
|
+
# creates a new thread that invokes the block and pushes the resulting TaskEvent
|
|
7
|
+
# onto the shared *queue*. Threads are tracked so `shutdown` can wait (or kill)
|
|
8
|
+
# in-flight work.
|
|
5
9
|
class ThreadedExecutor
|
|
10
|
+
# *queue* is the thread-safe Queue (typically `runtime.@task_queue`) into which
|
|
11
|
+
# completed TaskEvents are pushed.
|
|
6
12
|
def initialize(queue)
|
|
7
13
|
@queue = queue
|
|
8
14
|
@threads = []
|
|
9
15
|
@mutex = Mutex.new
|
|
10
16
|
end
|
|
11
17
|
|
|
18
|
+
# Wraps *block* in a Task and spawns a new thread to invoke it. The thread's
|
|
19
|
+
# return value (or rescued exception) is pushed onto the queue as a TaskEvent.
|
|
20
|
+
# Returns nil immediately.
|
|
12
21
|
def submit(name, &block)
|
|
13
22
|
task = Task.new(name: name.to_sym, block: block)
|
|
14
23
|
thread = Thread.new(task) { |t| @queue << run(t) }
|
|
@@ -16,6 +25,8 @@ module Charming
|
|
|
16
25
|
nil
|
|
17
26
|
end
|
|
18
27
|
|
|
28
|
+
# Waits up to *timeout* seconds for in-flight threads to finish, then kills any
|
|
29
|
+
# remaining live threads. Used by Runtime during teardown.
|
|
19
30
|
def shutdown(timeout: 0.0)
|
|
20
31
|
threads = @mutex.synchronize { @threads.dup }
|
|
21
32
|
threads.each { |thread| thread.join(timeout) }
|
|
@@ -29,6 +40,7 @@ module Charming
|
|
|
29
40
|
|
|
30
41
|
private
|
|
31
42
|
|
|
43
|
+
# Invokes the task's block and wraps the result (or rescued exception) in a TaskEvent.
|
|
32
44
|
def run(task)
|
|
33
45
|
Events::TaskEvent.new(name: task.name, value: task.call)
|
|
34
46
|
rescue StandardError, ScriptError => e
|
data/lib/charming/version.rb
CHANGED
data/lib/charming.rb
CHANGED
|
@@ -6,23 +6,37 @@ loader = Zeitwerk::Loader.for_gem
|
|
|
6
6
|
loader.inflector.inflect(
|
|
7
7
|
"cli" => "CLI",
|
|
8
8
|
"ui" => "UI",
|
|
9
|
+
"ansi_codes" => "ANSICodes",
|
|
10
|
+
"ansi_slicer" => "ANSISlicer",
|
|
11
|
+
"border_painter" => "BorderPainter",
|
|
12
|
+
"block_renderers" => "BlockRenderer",
|
|
13
|
+
"inline_renderers" => "InlineRenderer",
|
|
14
|
+
"render_context" => "RenderContext",
|
|
9
15
|
"erb_handler" => "ErbHandler",
|
|
16
|
+
"key_normalizer" => "KeyNormalizer",
|
|
17
|
+
"mouse_parser" => "MouseParser",
|
|
10
18
|
"tty_backend" => "TTYBackend"
|
|
11
19
|
)
|
|
20
|
+
loader.collapse("#{__dir__}/charming/presentation")
|
|
12
21
|
loader.setup
|
|
13
22
|
|
|
14
23
|
module Charming
|
|
24
|
+
# Base error class for all Charming-specific exceptions (used by templates, generators, runtime, etc.).
|
|
15
25
|
class Error < StandardError; end
|
|
16
26
|
|
|
27
|
+
# Entry point for running a Charming application. Instantiates a Runtime for *application* and starts
|
|
28
|
+
# the event loop. *backend* defaults to TTYBackend; tests pass MemoryBackend directly via `Charming::Runtime.new`.
|
|
17
29
|
def self.run(application, backend: nil)
|
|
18
30
|
Runtime.new(application, backend: backend).run
|
|
19
31
|
end
|
|
20
32
|
|
|
33
|
+
# Returns the normalized key symbol for an event-like object — `event.key` when the object responds
|
|
34
|
+
# to it, otherwise `event.to_sym`. Lets components treat raw strings and KeyEvent objects uniformly.
|
|
21
35
|
def self.key_of(event)
|
|
22
36
|
key = event.respond_to?(:key) ? event.key : event
|
|
23
37
|
key.to_sym
|
|
24
38
|
end
|
|
25
39
|
end
|
|
26
40
|
|
|
27
|
-
Charming::
|
|
28
|
-
Charming::
|
|
41
|
+
Charming::Templates.register ".tui.erb", Charming::Templates::ErbHandler
|
|
42
|
+
Charming::Templates.register ".txt.erb", Charming::Templates::ErbHandler
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: charming
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- pando
|
|
@@ -206,6 +206,14 @@ files:
|
|
|
206
206
|
- lib/charming/application_state.rb
|
|
207
207
|
- lib/charming/cli.rb
|
|
208
208
|
- lib/charming/controller.rb
|
|
209
|
+
- lib/charming/controller/class_methods.rb
|
|
210
|
+
- lib/charming/controller/command_palette.rb
|
|
211
|
+
- lib/charming/controller/component_dispatching.rb
|
|
212
|
+
- lib/charming/controller/dispatching.rb
|
|
213
|
+
- lib/charming/controller/focus_management.rb
|
|
214
|
+
- lib/charming/controller/rendering.rb
|
|
215
|
+
- lib/charming/controller/session_state.rb
|
|
216
|
+
- lib/charming/controller/sidebar_navigation.rb
|
|
209
217
|
- lib/charming/database_commands.rb
|
|
210
218
|
- lib/charming/database_installer.rb
|
|
211
219
|
- lib/charming/events/key_event.rb
|
|
@@ -216,15 +224,6 @@ files:
|
|
|
216
224
|
- lib/charming/focus.rb
|
|
217
225
|
- lib/charming/generators/app_file_generator.rb
|
|
218
226
|
- lib/charming/generators/app_generator.rb
|
|
219
|
-
- lib/charming/generators/app_generator/app_spec_templates.rb
|
|
220
|
-
- lib/charming/generators/app_generator/basic_templates.rb
|
|
221
|
-
- lib/charming/generators/app_generator/component_templates.rb
|
|
222
|
-
- lib/charming/generators/app_generator/controller_template.rb
|
|
223
|
-
- lib/charming/generators/app_generator/database_templates.rb
|
|
224
|
-
- lib/charming/generators/app_generator/layout_template.rb
|
|
225
|
-
- lib/charming/generators/app_generator/screen_spec_templates.rb
|
|
226
|
-
- lib/charming/generators/app_generator/state_templates.rb
|
|
227
|
-
- lib/charming/generators/app_generator/view_template.rb
|
|
228
227
|
- lib/charming/generators/base.rb
|
|
229
228
|
- lib/charming/generators/component_generator.rb
|
|
230
229
|
- lib/charming/generators/controller_generator.rb
|
|
@@ -232,15 +231,53 @@ files:
|
|
|
232
231
|
- lib/charming/generators/model_generator.rb
|
|
233
232
|
- lib/charming/generators/name.rb
|
|
234
233
|
- lib/charming/generators/screen_generator.rb
|
|
234
|
+
- lib/charming/generators/templates/app/Gemfile.template
|
|
235
|
+
- lib/charming/generators/templates/app/README.md.template
|
|
236
|
+
- lib/charming/generators/templates/app/Rakefile.template
|
|
237
|
+
- lib/charming/generators/templates/app/application.template
|
|
238
|
+
- lib/charming/generators/templates/app/application_controller.template
|
|
239
|
+
- lib/charming/generators/templates/app/application_record.template
|
|
240
|
+
- lib/charming/generators/templates/app/application_state.template
|
|
241
|
+
- lib/charming/generators/templates/app/database_config.template
|
|
242
|
+
- lib/charming/generators/templates/app/executable.template
|
|
243
|
+
- lib/charming/generators/templates/app/gemspec.template
|
|
244
|
+
- lib/charming/generators/templates/app/home_controller.template
|
|
245
|
+
- lib/charming/generators/templates/app/home_state.template
|
|
246
|
+
- lib/charming/generators/templates/app/keep.template
|
|
247
|
+
- lib/charming/generators/templates/app/layout.template
|
|
248
|
+
- lib/charming/generators/templates/app/root_file.template
|
|
249
|
+
- lib/charming/generators/templates/app/routes.template
|
|
250
|
+
- lib/charming/generators/templates/app/seeds.template
|
|
251
|
+
- lib/charming/generators/templates/app/spec_controller.template
|
|
252
|
+
- lib/charming/generators/templates/app/spec_helper.template
|
|
253
|
+
- lib/charming/generators/templates/app/spec_state.template
|
|
254
|
+
- lib/charming/generators/templates/app/spec_view.template
|
|
255
|
+
- lib/charming/generators/templates/app/version.template
|
|
256
|
+
- lib/charming/generators/templates/app/view.template
|
|
257
|
+
- lib/charming/generators/templates/component/component.rb.template
|
|
258
|
+
- lib/charming/generators/templates/controller/controller.rb.template
|
|
259
|
+
- lib/charming/generators/templates/model/migration.rb.template
|
|
260
|
+
- lib/charming/generators/templates/model/model.rb.template
|
|
261
|
+
- lib/charming/generators/templates/model/spec.rb.template
|
|
262
|
+
- lib/charming/generators/templates/screen/controller.rb.template
|
|
263
|
+
- lib/charming/generators/templates/screen/spec_controller.rb.template
|
|
264
|
+
- lib/charming/generators/templates/screen/spec_state.rb.template
|
|
265
|
+
- lib/charming/generators/templates/screen/spec_view.rb.template
|
|
266
|
+
- lib/charming/generators/templates/screen/state.rb.template
|
|
267
|
+
- lib/charming/generators/templates/screen/view.rb.template
|
|
268
|
+
- lib/charming/generators/templates/view/view.rb.template
|
|
235
269
|
- lib/charming/generators/view_generator.rb
|
|
236
270
|
- lib/charming/internal/renderer/differential.rb
|
|
237
271
|
- lib/charming/internal/renderer/full_repaint.rb
|
|
238
272
|
- lib/charming/internal/terminal/adapter.rb
|
|
273
|
+
- lib/charming/internal/terminal/key_normalizer.rb
|
|
239
274
|
- lib/charming/internal/terminal/memory_backend.rb
|
|
275
|
+
- lib/charming/internal/terminal/mouse_parser.rb
|
|
240
276
|
- lib/charming/internal/terminal/tty_backend.rb
|
|
241
277
|
- lib/charming/presentation/component.rb
|
|
242
278
|
- lib/charming/presentation/components/activity_indicator.rb
|
|
243
279
|
- lib/charming/presentation/components/command_palette.rb
|
|
280
|
+
- lib/charming/presentation/components/command_palette_modal.rb
|
|
244
281
|
- lib/charming/presentation/components/empty_state.rb
|
|
245
282
|
- lib/charming/presentation/components/form.rb
|
|
246
283
|
- lib/charming/presentation/components/form/builder.rb
|
|
@@ -261,14 +298,27 @@ files:
|
|
|
261
298
|
- lib/charming/presentation/components/text_input.rb
|
|
262
299
|
- lib/charming/presentation/components/viewport.rb
|
|
263
300
|
- lib/charming/presentation/layout.rb
|
|
301
|
+
- lib/charming/presentation/layout/builder.rb
|
|
302
|
+
- lib/charming/presentation/layout/overlay.rb
|
|
303
|
+
- lib/charming/presentation/layout/pane.rb
|
|
304
|
+
- lib/charming/presentation/layout/rect.rb
|
|
305
|
+
- lib/charming/presentation/layout/screen_layout.rb
|
|
306
|
+
- lib/charming/presentation/layout/split.rb
|
|
264
307
|
- lib/charming/presentation/markdown.rb
|
|
308
|
+
- lib/charming/presentation/markdown/block_renderers.rb
|
|
309
|
+
- lib/charming/presentation/markdown/inline_renderers.rb
|
|
310
|
+
- lib/charming/presentation/markdown/render_context.rb
|
|
265
311
|
- lib/charming/presentation/markdown/renderer.rb
|
|
266
312
|
- lib/charming/presentation/markdown/syntax_highlighter.rb
|
|
267
313
|
- lib/charming/presentation/template_view.rb
|
|
268
314
|
- lib/charming/presentation/templates.rb
|
|
269
315
|
- lib/charming/presentation/templates/erb_handler.rb
|
|
270
316
|
- lib/charming/presentation/ui.rb
|
|
317
|
+
- lib/charming/presentation/ui/ansi_codes.rb
|
|
318
|
+
- lib/charming/presentation/ui/ansi_slicer.rb
|
|
271
319
|
- lib/charming/presentation/ui/border.rb
|
|
320
|
+
- lib/charming/presentation/ui/border_painter.rb
|
|
321
|
+
- lib/charming/presentation/ui/canvas.rb
|
|
272
322
|
- lib/charming/presentation/ui/style.rb
|
|
273
323
|
- lib/charming/presentation/ui/theme.rb
|
|
274
324
|
- lib/charming/presentation/ui/themes/phosphor.json
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Charming
|
|
4
|
-
module Generators
|
|
5
|
-
class AppGenerator
|
|
6
|
-
module AppSpecTemplates
|
|
7
|
-
def spec_state
|
|
8
|
-
%(# frozen_string_literal: true
|
|
9
|
-
|
|
10
|
-
require "#{app_name.snake_name}"
|
|
11
|
-
|
|
12
|
-
RSpec.describe #{app_name.class_name}::HomeState do
|
|
13
|
-
describe "#title" do
|
|
14
|
-
it "has the correct default string value" do
|
|
15
|
-
instance = described_class.new
|
|
16
|
-
expect(instance.title).to eq("#{app_name.class_name}")
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "accepts overridden title values" do
|
|
20
|
-
instance = described_class.new(title: "Alternative")
|
|
21
|
-
expect(instance.title).to eq("Alternative")
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def spec_controller
|
|
29
|
-
%(# frozen_string_literal: true
|
|
30
|
-
|
|
31
|
-
require "#{app_name.snake_name}"
|
|
32
|
-
|
|
33
|
-
RSpec.describe #{app_name.class_name}::HomeController do
|
|
34
|
-
let(:application) { #{app_name.class_name}::Application.new }
|
|
35
|
-
|
|
36
|
-
subject(:controller) { described_class.new(application: application) }
|
|
37
|
-
|
|
38
|
-
describe "#show" do
|
|
39
|
-
it "renders the view with the state" do
|
|
40
|
-
response = controller.dispatch(:show)
|
|
41
|
-
|
|
42
|
-
expect(response).to respond_to(:body)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def spec_view
|
|
50
|
-
%(# frozen_string_literal: true
|
|
51
|
-
|
|
52
|
-
require "#{app_name.snake_name}"
|
|
53
|
-
|
|
54
|
-
RSpec.describe "home/show template" do
|
|
55
|
-
describe "#render" do
|
|
56
|
-
it "renders the state title" do
|
|
57
|
-
template = Charming::Presentation::Templates.resolve("home/show", root: #{app_name.class_name}::Application.root)
|
|
58
|
-
view = Charming::Presentation::TemplateView.new(
|
|
59
|
-
template: template,
|
|
60
|
-
namespace: #{app_name.class_name},
|
|
61
|
-
home: double(title: "#{app_name.class_name}"),
|
|
62
|
-
theme: #{app_name.class_name}::Application.new.theme
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
expect(view.render).to include("#{app_name.class_name}")
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def spec_component
|
|
73
|
-
%(# frozen_string_literal: true
|
|
74
|
-
|
|
75
|
-
require "#{app_name.snake_name}"
|
|
76
|
-
|
|
77
|
-
RSpec.describe #{app_name.class_name}::AppFrameComponent do
|
|
78
|
-
describe "#render" do
|
|
79
|
-
it "returns a string" do
|
|
80
|
-
component = described_class.new(title: "#{app_name.class_name}")
|
|
81
|
-
expect(component.render).to be_a(String)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|