charming 0.1.2 → 0.1.4
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/lib/charming/application.rb +22 -5
- data/lib/charming/cli.rb +3 -3
- data/lib/charming/controller/class_methods.rb +2 -2
- data/lib/charming/controller/command_palette.rb +2 -2
- data/lib/charming/controller/component_dispatching.rb +47 -3
- data/lib/charming/controller/focus.rb +123 -0
- data/lib/charming/controller/focus_management.rb +1 -1
- data/lib/charming/controller/rendering.rb +6 -17
- data/lib/charming/controller/session_state.rb +12 -1
- data/lib/charming/controller.rb +11 -2
- data/lib/charming/database/commands.rb +106 -0
- data/lib/charming/generators/component_generator.rb +1 -1
- data/lib/charming/generators/database_installer.rb +154 -0
- data/lib/charming/generators/model_generator.rb +2 -10
- data/lib/charming/generators/name.rb +1 -1
- data/lib/charming/generators/templates/app/application.template +1 -1
- data/lib/charming/generators/templates/app/layout.template +3 -6
- data/lib/charming/generators/templates/app/view.template +1 -1
- data/lib/charming/generators/templates/component/component.rb.template +1 -1
- data/lib/charming/generators/templates/screen/view.rb.template +1 -1
- data/lib/charming/generators/templates/view/view.rb.template +1 -1
- data/lib/charming/generators/view_generator.rb +1 -1
- data/lib/charming/internal/renderer/differential.rb +13 -5
- data/lib/charming/internal/terminal/tty_backend.rb +22 -2
- 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 -49
- data/lib/charming/presentation/components/form/builder.rb +52 -54
- data/lib/charming/presentation/components/form/confirm.rb +49 -51
- data/lib/charming/presentation/components/form/field.rb +94 -96
- data/lib/charming/presentation/components/form/input.rb +53 -55
- data/lib/charming/presentation/components/form/note.rb +27 -29
- data/lib/charming/presentation/components/form/select.rb +84 -86
- data/lib/charming/presentation/components/form/textarea.rb +67 -69
- data/lib/charming/presentation/components/form.rb +120 -122
- data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
- data/lib/charming/presentation/components/list.rb +123 -125
- data/lib/charming/presentation/components/markdown.rb +21 -23
- data/lib/charming/presentation/components/modal.rb +46 -48
- data/lib/charming/presentation/components/progressbar.rb +51 -53
- data/lib/charming/presentation/components/spinner.rb +40 -42
- data/lib/charming/presentation/components/table.rb +109 -111
- data/lib/charming/presentation/components/text_area.rb +219 -221
- data/lib/charming/presentation/components/text_input.rb +120 -122
- data/lib/charming/presentation/components/viewport.rb +218 -220
- data/lib/charming/presentation/layout/builder.rb +64 -66
- data/lib/charming/presentation/layout/overlay.rb +48 -50
- data/lib/charming/presentation/layout/pane.rb +129 -118
- data/lib/charming/presentation/layout/rect.rb +19 -16
- data/lib/charming/presentation/layout/screen_layout.rb +47 -42
- data/lib/charming/presentation/layout/split.rb +107 -102
- data/lib/charming/presentation/layout.rb +28 -30
- data/lib/charming/presentation/markdown/render_context.rb +30 -14
- data/lib/charming/presentation/markdown/renderer.rb +302 -79
- data/lib/charming/presentation/markdown/style_config.rb +215 -0
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +58 -59
- data/lib/charming/presentation/markdown.rb +4 -6
- data/lib/charming/presentation/template_view.rb +22 -24
- data/lib/charming/presentation/templates/erb_handler.rb +4 -6
- data/lib/charming/presentation/templates.rb +47 -49
- data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
- data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
- data/lib/charming/presentation/ui/border.rb +24 -26
- data/lib/charming/presentation/ui/border_painter.rb +37 -39
- data/lib/charming/presentation/ui/canvas.rb +59 -61
- data/lib/charming/presentation/ui/style.rb +173 -175
- data/lib/charming/presentation/ui/theme.rb +133 -135
- data/lib/charming/presentation/ui/width.rb +12 -14
- data/lib/charming/presentation/ui.rb +69 -71
- data/lib/charming/presentation/view.rb +110 -105
- data/lib/charming/router.rb +3 -8
- data/lib/charming/runtime.rb +25 -10
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +5 -4
- metadata +43 -9
- data/lib/charming/database_commands.rb +0 -103
- data/lib/charming/database_installer.rb +0 -152
- data/lib/charming/focus.rb +0 -121
- data/lib/charming/presentation/markdown/block_renderers.rb +0 -120
- data/lib/charming/presentation/markdown/inline_renderers.rb +0 -68
|
@@ -1,133 +1,138 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end
|
|
4
|
+
module Layout
|
|
5
|
+
# Split divides a parent Rect among its child nodes horizontally or vertically.
|
|
6
|
+
# Children with a configured `width`/`height` are placed at that fixed size; children
|
|
7
|
+
# without a fixed size share the remaining space according to their `grow` weight.
|
|
8
|
+
class Split
|
|
9
|
+
# The fixed width/height of the split (when set) and the grow weight for the split itself.
|
|
10
|
+
attr_reader :width, :height, :grow
|
|
11
|
+
|
|
12
|
+
# *direction* is `:horizontal` or `:vertical`. *gap* (in cells) separates children.
|
|
13
|
+
# *width*/*height* are optional fixed dimensions for the split as a whole.
|
|
14
|
+
# *grow* is the weight for distributing remaining space (used when this Split is a
|
|
15
|
+
# child of another Split).
|
|
16
|
+
def initialize(direction:, gap: 0, width: nil, height: nil, grow: nil)
|
|
17
|
+
@direction = direction.to_sym
|
|
18
|
+
@gap = gap.to_i
|
|
19
|
+
@width = width
|
|
20
|
+
@height = height
|
|
21
|
+
@grow = grow
|
|
22
|
+
@children = []
|
|
23
|
+
end
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
# Appends *node* (a child Split or Pane) to this Split.
|
|
26
|
+
def add_child(node)
|
|
27
|
+
children << node
|
|
28
|
+
end
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
# Returns the flattened list of focusable names from all child nodes.
|
|
31
|
+
def focusable_names
|
|
32
|
+
children.flat_map(&:focusable_names)
|
|
33
|
+
end
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
# Returns all named pane mouse targets in this split, preserving declaration order.
|
|
36
|
+
def mouse_targets(rect)
|
|
37
|
+
child_rects(rect).zip(children).flat_map do |child_rect, child|
|
|
38
|
+
child.respond_to?(:mouse_targets) ? child.mouse_targets(child_rect) : []
|
|
39
|
+
end
|
|
40
|
+
end
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# Renders each child into its own sub-rect, then overlays them on a blank canvas
|
|
43
|
+
# of the parent's dimensions.
|
|
44
|
+
def render(rect)
|
|
45
|
+
frame = UI.place("", width: rect.width, height: rect.height)
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
child_rects(rect).zip(children).each do |child_rect, child|
|
|
48
|
+
frame = UI.overlay(frame, child.render(child_rect), top: child_rect.y - rect.y, left: child_rect.x - rect.x)
|
|
46
49
|
end
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# The split direction (`:horizontal` or `:vertical`) and the inter-child gap.
|
|
51
|
-
attr_reader :direction, :gap, :children
|
|
51
|
+
frame
|
|
52
|
+
end
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
# and grow weights. Raises ArgumentError when *direction* is neither horizontal nor vertical.
|
|
55
|
-
def child_rects(rect)
|
|
56
|
-
return horizontal_rects(rect) if direction == :horizontal
|
|
57
|
-
return vertical_rects(rect) if direction == :vertical
|
|
54
|
+
private
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
# The split direction (`:horizontal` or `:vertical`) and the inter-child gap.
|
|
57
|
+
attr_reader :direction, :gap, :children
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
# Returns an array of child rects sized according to each child's fixed dimensions
|
|
60
|
+
# and grow weights. Raises ArgumentError when *direction* is neither horizontal nor vertical.
|
|
61
|
+
def child_rects(rect)
|
|
62
|
+
return horizontal_rects(rect) if direction == :horizontal
|
|
63
|
+
return vertical_rects(rect) if direction == :vertical
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
left += width + gap
|
|
70
|
-
child_rect
|
|
71
|
-
end
|
|
72
|
-
end
|
|
65
|
+
raise ArgumentError, "unknown split direction: #{direction.inspect}"
|
|
66
|
+
end
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
# Computes per-child rects for a horizontal split.
|
|
69
|
+
def horizontal_rects(rect)
|
|
70
|
+
sizes = child_sizes(axis: :horizontal, available: rect.width)
|
|
71
|
+
left = rect.x
|
|
78
72
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
73
|
+
sizes.map do |width|
|
|
74
|
+
child_rect = Rect.new(x: left, y: rect.y, width: width, height: rect.height)
|
|
75
|
+
left += width + gap
|
|
76
|
+
child_rect
|
|
84
77
|
end
|
|
78
|
+
end
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
gap_size = gap * [children.length - 1, 0].max
|
|
91
|
-
available_for_children = [available - gap_size, 0].max
|
|
92
|
-
fixed = children.map { |child| fixed_size(child, axis) }
|
|
93
|
-
flexible_indexes = fixed.each_index.select { |index| fixed[index].nil? }
|
|
94
|
-
sizes = fixed.map { |size| size&.to_i }
|
|
95
|
-
remaining = [available_for_children - sizes.compact.sum, 0].max
|
|
96
|
-
|
|
97
|
-
distribute_remaining(sizes, flexible_indexes, remaining)
|
|
98
|
-
end
|
|
80
|
+
# Computes per-child rects for a vertical split.
|
|
81
|
+
def vertical_rects(rect)
|
|
82
|
+
sizes = child_sizes(axis: :vertical, available: rect.height)
|
|
83
|
+
top = rect.y
|
|
99
84
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
sizes.map do |height|
|
|
86
|
+
child_rect = Rect.new(x: rect.x, y: top, width: rect.width, height: height)
|
|
87
|
+
top += height + gap
|
|
88
|
+
child_rect
|
|
103
89
|
end
|
|
90
|
+
end
|
|
104
91
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
92
|
+
# Computes the size of each child along the *axis* given the *available* cells.
|
|
93
|
+
# Subtracts the total gap, allocates fixed sizes first, and distributes the remainder
|
|
94
|
+
# among flexible (non-fixed) children by their grow weights.
|
|
95
|
+
def child_sizes(axis:, available:)
|
|
96
|
+
gap_size = gap * [children.length - 1, 0].max
|
|
97
|
+
available_for_children = [available - gap_size, 0].max
|
|
98
|
+
fixed = children.map { |child| fixed_size(child, axis) }
|
|
99
|
+
flexible_indexes = fixed.each_index.select { |index| fixed[index].nil? }
|
|
100
|
+
sizes = fixed.map { |size| size&.to_i }
|
|
101
|
+
remaining = [available_for_children - sizes.compact.sum, 0].max
|
|
102
|
+
|
|
103
|
+
distribute_remaining(sizes, flexible_indexes, remaining)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the fixed size of *child* along *axis* (`:horizontal` reads width, `:vertical` reads height).
|
|
107
|
+
def fixed_size(child, axis)
|
|
108
|
+
(axis == :horizontal) ? child.width : child.height
|
|
109
|
+
end
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
# Distributes the *remaining* cells across *flexible_indexes* by grow weight, with the
|
|
112
|
+
# last flexible child absorbing any rounding remainder.
|
|
113
|
+
def distribute_remaining(sizes, flexible_indexes, remaining)
|
|
114
|
+
return sizes.map { |size| size || 0 } if flexible_indexes.empty?
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
remaining - used
|
|
116
|
-
else
|
|
117
|
-
(remaining * grow_weight(children[index]) / total_grow).floor
|
|
118
|
-
end
|
|
116
|
+
total_grow = flexible_indexes.sum { |index| grow_weight(children[index]) }
|
|
117
|
+
used = 0
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
flexible_indexes.each_with_index do |index, flexible_index|
|
|
120
|
+
size = if flexible_index == flexible_indexes.length - 1
|
|
121
|
+
remaining - used
|
|
122
|
+
else
|
|
123
|
+
(remaining * grow_weight(children[index]) / total_grow).floor
|
|
122
124
|
end
|
|
123
125
|
|
|
124
|
-
sizes
|
|
126
|
+
sizes[index] = size
|
|
127
|
+
used += size
|
|
125
128
|
end
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
sizes
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns the grow weight of *child*, defaulting to 1 when unset.
|
|
134
|
+
def grow_weight(child)
|
|
135
|
+
[child.grow.to_i, 1].max
|
|
131
136
|
end
|
|
132
137
|
end
|
|
133
138
|
end
|
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
module_function
|
|
4
|
+
# Layout contains generic screen-size math and composition helpers. It is
|
|
5
|
+
# intentionally unaware of application shells such as sidebars or nav panes.
|
|
6
|
+
module Layout
|
|
7
|
+
module_function
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
def clamp_size(value, min: nil, max: nil)
|
|
10
|
+
size = value.to_i
|
|
11
|
+
size = [size, min].max if min
|
|
12
|
+
size = [size, max].min if max
|
|
13
|
+
size
|
|
14
|
+
end
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
def available_width(screen, reserved: 0, min: nil, max: nil)
|
|
17
|
+
clamp_size(screen.width - reserved, min: min, max: max)
|
|
18
|
+
end
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
def available_height(screen, reserved: 0, min: nil, max: nil)
|
|
21
|
+
clamp_size(screen.height - reserved, min: min, max: max)
|
|
22
|
+
end
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
24
|
+
def stack_or_row(*blocks, narrow:, gap: 0)
|
|
25
|
+
if narrow
|
|
26
|
+
UI.join_vertical(*blocks, gap: gap)
|
|
27
|
+
else
|
|
28
|
+
UI.join_horizontal(*blocks, gap: gap)
|
|
31
29
|
end
|
|
30
|
+
end
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
def selected_window_start(selected_index:, item_count:, window_size:)
|
|
33
|
+
count = item_count.to_i
|
|
34
|
+
size = [window_size.to_i, 1].max
|
|
35
|
+
selected = selected_index.to_i.clamp(0, [count - 1, 0].max)
|
|
36
|
+
max_start = [count - size, 0].max
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
end
|
|
38
|
+
(selected - size + 1).clamp(0, max_start)
|
|
41
39
|
end
|
|
42
40
|
end
|
|
43
41
|
end
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
module Markdown
|
|
5
|
+
# RenderContext carries render-time state that needs to flow down the Markdown AST.
|
|
6
|
+
RenderContext = Data.define(:width, :list_depth, :style, :current_style, :base_url, :source_lines) do
|
|
7
|
+
def self.from(width:, style:, base_url: nil, source_lines: [], list_depth: 0, current_style: nil)
|
|
8
|
+
new(
|
|
9
|
+
width: width,
|
|
10
|
+
list_depth: list_depth,
|
|
11
|
+
style: style,
|
|
12
|
+
current_style: current_style || style[:document],
|
|
13
|
+
base_url: base_url,
|
|
14
|
+
source_lines: source_lines
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def with(width: self.width, list_depth: self.list_depth, current_style: self.current_style)
|
|
19
|
+
self.class.new(
|
|
20
|
+
width: width,
|
|
21
|
+
list_depth: list_depth,
|
|
22
|
+
style: style,
|
|
23
|
+
current_style: current_style,
|
|
24
|
+
base_url: base_url,
|
|
25
|
+
source_lines: source_lines
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def nested_list(width: self.width)
|
|
30
|
+
with(width: width, list_depth: list_depth + 1)
|
|
31
|
+
end
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def nested(depth_increment: 0, width: self.width)
|
|
17
|
-
self.class.new(list_depth: list_depth + depth_increment, width: width)
|
|
18
|
-
end
|
|
33
|
+
def inherit(style_name)
|
|
34
|
+
current_style.inherit_visual(style[style_name])
|
|
19
35
|
end
|
|
20
36
|
end
|
|
21
37
|
end
|