charming 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -14
- data/lib/charming/application.rb +19 -2
- data/lib/charming/cli.rb +3 -3
- 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 +4 -15
- data/lib/charming/controller/session_state.rb +11 -0
- data/lib/charming/controller.rb +11 -2
- data/lib/charming/database/commands.rb +106 -0
- 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/view_generator.rb +1 -1
- data/lib/charming/presentation/components/form/field.rb +1 -1
- data/lib/charming/presentation/components/markdown.rb +7 -7
- data/lib/charming/presentation/layout/builder.rb +8 -2
- data/lib/charming/presentation/layout/pane.rb +40 -108
- data/lib/charming/presentation/layout/pane_behavior.rb +39 -0
- data/lib/charming/presentation/layout/pane_geometry.rb +71 -0
- data/lib/charming/presentation/layout/pane_style.rb +39 -0
- data/lib/charming/presentation/layout/rect.rb +5 -0
- data/lib/charming/presentation/layout/screen_layout.rb +7 -0
- data/lib/charming/presentation/layout/split.rb +7 -0
- data/lib/charming/presentation/markdown/render_context.rb +28 -10
- data/lib/charming/presentation/markdown/renderer.rb +264 -39
- data/lib/charming/presentation/markdown/style_config.rb +215 -0
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +3 -2
- data/lib/charming/presentation/markdown.rb +2 -2
- data/lib/charming/presentation/view.rb +7 -0
- data/lib/charming/router.rb +3 -8
- data/lib/charming/runtime.rb +2 -0
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +2 -2
- metadata +45 -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 -118
- data/lib/charming/presentation/markdown/inline_renderers.rb +0 -66
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Components
|
|
5
|
-
# Markdown renders
|
|
6
|
-
# `Charming::Markdown::Renderer`; set *syntax_highlighting* to false to disable
|
|
7
|
-
# Rouge-backed code block highlighting.
|
|
5
|
+
# Markdown renders CommonMark/GFM source as ANSI-styled terminal text.
|
|
8
6
|
class Markdown < Component
|
|
9
|
-
|
|
10
|
-
# *syntax_highlighting* enables Rouge for code blocks (defaults to true).
|
|
11
|
-
def initialize(content:, width: nil, theme: nil, syntax_highlighting: true)
|
|
7
|
+
def initialize(content:, width: nil, theme: nil, syntax_highlighting: true, style: :dark, base_url: nil)
|
|
12
8
|
super(theme: theme)
|
|
13
9
|
@content = content
|
|
14
10
|
@width = width
|
|
15
11
|
@syntax_highlighting = syntax_highlighting
|
|
12
|
+
@style = style
|
|
13
|
+
@base_url = base_url
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
# Renders the Markdown body to a styled, terminal-safe string.
|
|
@@ -21,7 +19,9 @@ module Charming
|
|
|
21
19
|
content: @content,
|
|
22
20
|
width: @width,
|
|
23
21
|
theme: theme,
|
|
24
|
-
syntax_highlighting: @syntax_highlighting
|
|
22
|
+
syntax_highlighting: @syntax_highlighting,
|
|
23
|
+
style: @style,
|
|
24
|
+
base_url: @base_url
|
|
25
25
|
).render
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -36,9 +36,15 @@ module Charming
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Adds a Pane leaf node to the current scope. *name* (optional) is the focus slot name;
|
|
39
|
-
# *content* (or a *block*) is the body. *options* are
|
|
39
|
+
# *content* (or a *block*) is the body. *options* are split into PaneGeometry,
|
|
40
|
+
# PaneStyle, and PaneBehavior keyword args.
|
|
40
41
|
def pane(name = nil, content = nil, **options, &block)
|
|
41
|
-
node = Pane.new(
|
|
42
|
+
node = Pane.new(
|
|
43
|
+
name: name, content: content, block: block, view: view,
|
|
44
|
+
geometry: PaneGeometry.build(**options.slice(:width, :height, :grow, :border, :padding)),
|
|
45
|
+
style: PaneStyle.build(**options.slice(:style, :focused_style)),
|
|
46
|
+
behavior: PaneBehavior.build(**options.slice(:focus, :scroll, :clip, :wrap))
|
|
47
|
+
)
|
|
42
48
|
append(node)
|
|
43
49
|
node
|
|
44
50
|
end
|
|
@@ -4,34 +4,20 @@ module Charming
|
|
|
4
4
|
module Layout
|
|
5
5
|
# Pane is a leaf layout node: a single rectangle with optional border, padding, and
|
|
6
6
|
# styling, containing a piece of content (a string, a View, or a block evaluated in the
|
|
7
|
-
# view's context). Panes with a `name` and `focus: true` are registered as
|
|
8
|
-
# slots in the controller's focus ring.
|
|
7
|
+
# view's context). Panes with a `name` and `behavior.focus: true` are registered as
|
|
8
|
+
# focusable slots in the controller's focus ring.
|
|
9
9
|
class Pane
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# *name* is the focus slot identifier
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@
|
|
21
|
-
@content = content
|
|
22
|
-
@block = block
|
|
23
|
-
@view = view
|
|
24
|
-
@width = width
|
|
25
|
-
@height = height
|
|
26
|
-
@grow = grow
|
|
27
|
-
@border = border
|
|
28
|
-
@padding = padding
|
|
29
|
-
@style = style
|
|
30
|
-
@focused_style = focused_style
|
|
31
|
-
@focus = focus
|
|
32
|
-
@scroll = scroll
|
|
33
|
-
@clip = clip
|
|
34
|
-
@wrap = wrap
|
|
10
|
+
attr_reader :name
|
|
11
|
+
delegate :width, :height, :grow, to: :geometry
|
|
12
|
+
|
|
13
|
+
# *name* is the focus slot identifier. *content* (or a *block*) is the body; *view*
|
|
14
|
+
# is the view used for instance_exec when the block is given. *geometry*, *style*, and
|
|
15
|
+
# *behavior* are value objects that own sizing, styling, and render-time flags.
|
|
16
|
+
def initialize(name: nil, content: nil, block: nil, view: nil,
|
|
17
|
+
geometry: PaneGeometry.new, style: PaneStyle.new,
|
|
18
|
+
behavior: PaneBehavior.new)
|
|
19
|
+
@name, @content, @block, @view = name, content, block, view
|
|
20
|
+
@geometry, @style, @behavior = geometry, style, behavior
|
|
35
21
|
end
|
|
36
22
|
|
|
37
23
|
# Raises ArgumentError — panes are leaves and cannot contain layout children.
|
|
@@ -39,110 +25,56 @@ module Charming
|
|
|
39
25
|
raise ArgumentError, "pane cannot contain layout children"
|
|
40
26
|
end
|
|
41
27
|
|
|
42
|
-
# Returns [name] when the pane is
|
|
28
|
+
# Returns [name] when the pane is focusable and named, otherwise [].
|
|
43
29
|
def focusable_names
|
|
44
|
-
(focus && name) ? [name] : []
|
|
30
|
+
(@behavior.focus && name) ? [name] : []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns the mouse target represented by this pane, if it has a name.
|
|
34
|
+
def mouse_targets(rect)
|
|
35
|
+
return [] unless name
|
|
36
|
+
|
|
37
|
+
[{name: name, rect: rect, inner_rect: @geometry.inset(rect)}]
|
|
45
38
|
end
|
|
46
39
|
|
|
47
40
|
# Renders the pane into *rect*, applying the configured style, border, and padding
|
|
48
41
|
# around the evaluated content.
|
|
49
42
|
def render(rect)
|
|
50
|
-
|
|
43
|
+
inner = @geometry.inset(rect)
|
|
44
|
+
outer_style(inner).render(rendered_content(inner))
|
|
51
45
|
end
|
|
52
46
|
|
|
53
47
|
private
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
attr_reader :content, :block, :view, :border, :padding, :style, :focused_style, :focus, :scroll, :clip, :wrap
|
|
57
|
-
|
|
58
|
-
# Returns the content string for *rect*, optionally clipped/scrolled by an embedded Viewport.
|
|
59
|
-
def rendered_content(rect)
|
|
60
|
-
content_rect = inner_rect(rect)
|
|
61
|
-
value = evaluate_content(content_rect)
|
|
62
|
-
return value unless clip || scroll
|
|
63
|
-
|
|
64
|
-
Components::Viewport.new(content: value, width: content_rect.width, height: content_rect.height, wrap: wrap).render
|
|
65
|
-
end
|
|
49
|
+
attr_reader :content, :block, :view, :geometry, :style, :behavior
|
|
66
50
|
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
def
|
|
51
|
+
# Returns the content string for *content_rect*, optionally clipped/scrolled by an
|
|
52
|
+
# embedded Viewport when *behavior.should_viewport?* is true.
|
|
53
|
+
def rendered_content(content_rect)
|
|
70
54
|
value = if block
|
|
71
55
|
block.arity.zero? ? view.instance_exec(&block) : view.instance_exec(content_rect, &block)
|
|
72
56
|
else
|
|
73
57
|
content
|
|
74
58
|
end
|
|
75
|
-
value.respond_to?(:render)
|
|
59
|
+
return value.to_s unless value.respond_to?(:render)
|
|
60
|
+
return value.render.to_s unless @behavior.should_viewport?
|
|
61
|
+
|
|
62
|
+
Components::Viewport.new(content: value.render, width: content_rect.width,
|
|
63
|
+
height: content_rect.height, wrap: @behavior.wrap).render.to_s
|
|
76
64
|
end
|
|
77
65
|
|
|
78
66
|
# Builds the outer style object with optional border and padding, sized to the
|
|
79
67
|
# inner rect of the pane.
|
|
80
|
-
def outer_style(
|
|
81
|
-
styled =
|
|
82
|
-
styled = styled.border(border_style) if border
|
|
83
|
-
styled = styled.padding(
|
|
84
|
-
styled.width(
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Returns the active style: the focused variant when the pane is focused, otherwise
|
|
88
|
-
# the configured style or a default UI::Style.
|
|
89
|
-
def current_style
|
|
90
|
-
return focused_pane_style if focused?
|
|
91
|
-
|
|
92
|
-
style || UI.style
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Returns the focused-pane style: the focused_style override, or the theme's title style.
|
|
96
|
-
def focused_pane_style
|
|
97
|
-
focused_style || view.__send__(:theme).title
|
|
68
|
+
def outer_style(inner)
|
|
69
|
+
styled = @style.resolve(view, focused: focused?)
|
|
70
|
+
styled = styled.border(@geometry.border_style) if @geometry.border
|
|
71
|
+
styled = styled.padding(*@geometry.padding_values) if @geometry.padding
|
|
72
|
+
styled.width(inner.width).height(inner.height)
|
|
98
73
|
end
|
|
99
74
|
|
|
100
|
-
# True when the pane is configured for focus and the view reports it as
|
|
75
|
+
# True when the pane is configured for focus and the view reports it as focused.
|
|
101
76
|
def focused?
|
|
102
|
-
focus && name && view.focused?(name)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Returns the inner Rect after border and padding insets are applied.
|
|
106
|
-
def inner_rect(rect)
|
|
107
|
-
rect.inset(
|
|
108
|
-
top: border_top + padding_top,
|
|
109
|
-
right: border_right + padding_right,
|
|
110
|
-
bottom: border_bottom + padding_bottom,
|
|
111
|
-
left: border_left + padding_left
|
|
112
|
-
)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Resolves the border style symbol: :normal when border is `true`, otherwise the configured value.
|
|
116
|
-
def border_style
|
|
117
|
-
(border == true) ? :normal : border
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Border thickness on each side (1 when a border is configured, 0 otherwise).
|
|
121
|
-
def border_top = border ? 1 : 0
|
|
122
|
-
def border_right = border ? 1 : 0
|
|
123
|
-
def border_bottom = border ? 1 : 0
|
|
124
|
-
def border_left = border ? 1 : 0
|
|
125
|
-
|
|
126
|
-
# The padding values normalized to [top, right, bottom, left] form.
|
|
127
|
-
def padding_values
|
|
128
|
-
@padding_values ||= expand_padding(Array(padding))
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Per-side padding values (0 when no padding is configured).
|
|
132
|
-
def padding_top = padding ? padding_values[0] : 0
|
|
133
|
-
def padding_right = padding ? padding_values[1] : 0
|
|
134
|
-
def padding_bottom = padding ? padding_values[2] : 0
|
|
135
|
-
def padding_left = padding ? padding_values[3] : 0
|
|
136
|
-
|
|
137
|
-
# Normalizes 1/2/4 padding arguments to [top, right, bottom, left].
|
|
138
|
-
def expand_padding(values)
|
|
139
|
-
case values.length
|
|
140
|
-
when 1 then [values[0], values[0], values[0], values[0]]
|
|
141
|
-
when 2 then [values[0], values[1], values[0], values[1]]
|
|
142
|
-
when 4 then values
|
|
143
|
-
else
|
|
144
|
-
raise ArgumentError, "padding expects 1, 2, or 4 values"
|
|
145
|
-
end
|
|
77
|
+
@behavior.focus && name && view.focused?(name)
|
|
146
78
|
end
|
|
147
79
|
end
|
|
148
80
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Charming
|
|
4
|
+
module Layout
|
|
5
|
+
# PaneBehavior holds the render-time options that control a Pane's
|
|
6
|
+
# interaction with the focus ring and the embedded Viewport.
|
|
7
|
+
class PaneBehavior
|
|
8
|
+
attr_reader :focus, :scroll, :clip, :wrap
|
|
9
|
+
|
|
10
|
+
def self.build(focus: false, scroll: false, clip: true, wrap: false)
|
|
11
|
+
new(focus: focus, scroll: scroll, clip: clip, wrap: wrap)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(focus:, scroll:, clip:, wrap:)
|
|
15
|
+
@focus, @scroll, @clip, @wrap = focus, scroll, clip, wrap
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ==(other)
|
|
20
|
+
other.is_a?(PaneBehavior) &&
|
|
21
|
+
focus == other.focus && scroll == other.scroll &&
|
|
22
|
+
clip == other.clip && wrap == other.wrap
|
|
23
|
+
end
|
|
24
|
+
alias_method :eql?, :==
|
|
25
|
+
|
|
26
|
+
def hash
|
|
27
|
+
[focus, scroll, clip, wrap].hash
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def focusable?
|
|
31
|
+
focus
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def should_viewport?
|
|
35
|
+
clip || scroll
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Charming
|
|
4
|
+
module Layout
|
|
5
|
+
# PaneGeometry holds a Pane's sizing (width, height, grow) and inset
|
|
6
|
+
# configuration (border + padding). It knows how to inset a Rect for the
|
|
7
|
+
# content area and how to expand CSS-style 1/2/4-value padding.
|
|
8
|
+
class PaneGeometry
|
|
9
|
+
attr_reader :width, :height, :grow, :border, :padding
|
|
10
|
+
|
|
11
|
+
def self.build(width: nil, height: nil, grow: nil, border: nil, padding: nil)
|
|
12
|
+
new(width: width, height: height, grow: grow,
|
|
13
|
+
border: (border == true) ? :normal : border, padding: padding)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(width:, height:, grow:, border:, padding:)
|
|
17
|
+
@width, @height, @grow, @border, @padding = width, height, grow, border, padding
|
|
18
|
+
@padding_values = padding ? expand_padding(Array(padding)) : [0, 0, 0, 0]
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
other.is_a?(PaneGeometry) &&
|
|
24
|
+
width == other.width && height == other.height && grow == other.grow &&
|
|
25
|
+
border == other.border && padding == other.padding
|
|
26
|
+
end
|
|
27
|
+
alias_method :eql?, :==
|
|
28
|
+
|
|
29
|
+
def hash
|
|
30
|
+
[width, height, grow, border, padding].hash
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def border_top = border ? 1 : 0
|
|
34
|
+
def border_right = border ? 1 : 0
|
|
35
|
+
def border_bottom = border ? 1 : 0
|
|
36
|
+
def border_left = border ? 1 : 0
|
|
37
|
+
|
|
38
|
+
attr_reader :padding_values
|
|
39
|
+
|
|
40
|
+
def padding_top = padding ? @padding_values[0] : 0
|
|
41
|
+
def padding_right = padding ? @padding_values[1] : 0
|
|
42
|
+
def padding_bottom = padding ? @padding_values[2] : 0
|
|
43
|
+
def padding_left = padding ? @padding_values[3] : 0
|
|
44
|
+
|
|
45
|
+
def inset(rect)
|
|
46
|
+
rect.inset(
|
|
47
|
+
top: border_top + padding_top,
|
|
48
|
+
right: border_right + padding_right,
|
|
49
|
+
bottom: border_bottom + padding_bottom,
|
|
50
|
+
left: border_left + padding_left
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def border_style
|
|
55
|
+
(border == true) ? :normal : border
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def expand_padding(values)
|
|
61
|
+
case values.length
|
|
62
|
+
when 1 then [values[0], values[0], values[0], values[0]]
|
|
63
|
+
when 2 then [values[0], values[1], values[0], values[1]]
|
|
64
|
+
when 4 then values
|
|
65
|
+
else
|
|
66
|
+
raise ArgumentError, "padding expects 1, 2, or 4 values"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Charming
|
|
4
|
+
module Layout
|
|
5
|
+
# PaneStyle holds a Pane's base style and the focused-state override.
|
|
6
|
+
# It resolves which style to use at render time given the pane's current
|
|
7
|
+
# focus state and the view's theme.
|
|
8
|
+
class PaneStyle
|
|
9
|
+
attr_reader :style, :focused_style
|
|
10
|
+
|
|
11
|
+
def self.build(style: nil, focused_style: nil)
|
|
12
|
+
new(style: style, focused_style: focused_style)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(style:, focused_style:)
|
|
16
|
+
@style, @focused_style = style, focused_style
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ==(other)
|
|
21
|
+
other.is_a?(PaneStyle) &&
|
|
22
|
+
style == other.style && focused_style == other.focused_style
|
|
23
|
+
end
|
|
24
|
+
alias_method :eql?, :==
|
|
25
|
+
|
|
26
|
+
def hash
|
|
27
|
+
[style, focused_style].hash
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the active style for *focused*: the focused override when the
|
|
31
|
+
# pane is focused, otherwise the configured *style* or a default UI::Style.
|
|
32
|
+
def resolve(view, focused:)
|
|
33
|
+
return focused_style || view.__send__(:theme).title if focused
|
|
34
|
+
|
|
35
|
+
style || UI.style
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -6,6 +6,11 @@ module Charming
|
|
|
6
6
|
# (width, height). Layout operations produce new Rect instances rather than mutating
|
|
7
7
|
# existing ones.
|
|
8
8
|
Rect = Data.define(:x, :y, :width, :height) do
|
|
9
|
+
# Returns true when the zero-based cell coordinate falls within this rectangle.
|
|
10
|
+
def cover?(point_x, point_y)
|
|
11
|
+
point_x >= x && point_x < x + width && point_y >= y && point_y < y + height
|
|
12
|
+
end
|
|
13
|
+
|
|
9
14
|
# Returns a new Rect inset by *top*/*right*/*bottom*/*left* cells. The result is
|
|
10
15
|
# clamped to a minimum width/height of 0.
|
|
11
16
|
def inset(top: 0, right: 0, bottom: 0, left: 0)
|
|
@@ -32,6 +32,13 @@ module Charming
|
|
|
32
32
|
child ? child.focusable_names : []
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# Returns all named pane mouse targets for the full screen layout.
|
|
36
|
+
def mouse_targets
|
|
37
|
+
return [] unless child
|
|
38
|
+
|
|
39
|
+
child.mouse_targets(Rect.new(x: 0, y: 0, width: screen.width, height: screen.height))
|
|
40
|
+
end
|
|
41
|
+
|
|
35
42
|
# Renders the child into the full-screen rect, then overlays each registered overlay
|
|
36
43
|
# on top in order.
|
|
37
44
|
def render
|
|
@@ -32,6 +32,13 @@ module Charming
|
|
|
32
32
|
children.flat_map(&:focusable_names)
|
|
33
33
|
end
|
|
34
34
|
|
|
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
|
|
41
|
+
|
|
35
42
|
# Renders each child into its own sub-rect, then overlays them on a blank canvas
|
|
36
43
|
# of the parent's dimensions.
|
|
37
44
|
def render(rect)
|
|
@@ -2,18 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
4
|
module Markdown
|
|
5
|
-
# RenderContext carries
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
+
)
|
|
11
16
|
end
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
32
|
+
|
|
33
|
+
def inherit(style_name)
|
|
34
|
+
current_style.inherit_visual(style[style_name])
|
|
17
35
|
end
|
|
18
36
|
end
|
|
19
37
|
end
|