charming 0.1.2 → 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/lib/charming/application.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/rendering.rb +2 -2
- data/lib/charming/controller/session_state.rb +1 -1
- data/lib/charming/generators/component_generator.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/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 +122 -118
- data/lib/charming/presentation/layout/rect.rb +14 -16
- data/lib/charming/presentation/layout/screen_layout.rb +40 -42
- data/lib/charming/presentation/layout/split.rb +101 -103
- data/lib/charming/presentation/layout.rb +28 -30
- data/lib/charming/presentation/markdown/block_renderers.rb +94 -96
- data/lib/charming/presentation/markdown/inline_renderers.rb +52 -54
- data/lib/charming/presentation/markdown/render_context.rb +12 -14
- data/lib/charming/presentation/markdown/renderer.rb +84 -86
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +57 -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 +103 -105
- data/lib/charming/runtime.rb +23 -10
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +3 -2
- metadata +2 -1
|
@@ -1,133 +1,131 @@
|
|
|
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
|
|
25
|
-
|
|
26
|
-
# Appends *node* (a child Split or Pane) to this Split.
|
|
27
|
-
def add_child(node)
|
|
28
|
-
children << node
|
|
29
|
-
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
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
# Appends *node* (a child Split or Pane) to this Split.
|
|
26
|
+
def add_child(node)
|
|
27
|
+
children << node
|
|
28
|
+
end
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
# Returns the flattened list of focusable names from all child nodes.
|
|
31
|
+
def focusable_names
|
|
32
|
+
children.flat_map(&:focusable_names)
|
|
33
|
+
end
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
# Renders each child into its own sub-rect, then overlays them on a blank canvas
|
|
36
|
+
# of the parent's dimensions.
|
|
37
|
+
def render(rect)
|
|
38
|
+
frame = UI.place("", width: rect.width, height: rect.height)
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
child_rects(rect).zip(children).each do |child_rect, child|
|
|
41
|
+
frame = UI.overlay(frame, child.render(child_rect), top: child_rect.y - rect.y, left: child_rect.x - rect.x)
|
|
46
42
|
end
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# The split direction (`:horizontal` or `:vertical`) and the inter-child gap.
|
|
51
|
-
attr_reader :direction, :gap, :children
|
|
44
|
+
frame
|
|
45
|
+
end
|
|
52
46
|
|
|
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
|
|
47
|
+
private
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
# The split direction (`:horizontal` or `:vertical`) and the inter-child gap.
|
|
50
|
+
attr_reader :direction, :gap, :children
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
# Returns an array of child rects sized according to each child's fixed dimensions
|
|
53
|
+
# and grow weights. Raises ArgumentError when *direction* is neither horizontal nor vertical.
|
|
54
|
+
def child_rects(rect)
|
|
55
|
+
return horizontal_rects(rect) if direction == :horizontal
|
|
56
|
+
return vertical_rects(rect) if direction == :vertical
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
left += width + gap
|
|
70
|
-
child_rect
|
|
71
|
-
end
|
|
72
|
-
end
|
|
58
|
+
raise ArgumentError, "unknown split direction: #{direction.inspect}"
|
|
59
|
+
end
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
# Computes per-child rects for a horizontal split.
|
|
62
|
+
def horizontal_rects(rect)
|
|
63
|
+
sizes = child_sizes(axis: :horizontal, available: rect.width)
|
|
64
|
+
left = rect.x
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
66
|
+
sizes.map do |width|
|
|
67
|
+
child_rect = Rect.new(x: left, y: rect.y, width: width, height: rect.height)
|
|
68
|
+
left += width + gap
|
|
69
|
+
child_rect
|
|
84
70
|
end
|
|
71
|
+
end
|
|
85
72
|
|
|
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
|
|
73
|
+
# Computes per-child rects for a vertical split.
|
|
74
|
+
def vertical_rects(rect)
|
|
75
|
+
sizes = child_sizes(axis: :vertical, available: rect.height)
|
|
76
|
+
top = rect.y
|
|
99
77
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
78
|
+
sizes.map do |height|
|
|
79
|
+
child_rect = Rect.new(x: rect.x, y: top, width: rect.width, height: height)
|
|
80
|
+
top += height + gap
|
|
81
|
+
child_rect
|
|
103
82
|
end
|
|
83
|
+
end
|
|
104
84
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
# Computes the size of each child along the *axis* given the *available* cells.
|
|
86
|
+
# Subtracts the total gap, allocates fixed sizes first, and distributes the remainder
|
|
87
|
+
# among flexible (non-fixed) children by their grow weights.
|
|
88
|
+
def child_sizes(axis:, available:)
|
|
89
|
+
gap_size = gap * [children.length - 1, 0].max
|
|
90
|
+
available_for_children = [available - gap_size, 0].max
|
|
91
|
+
fixed = children.map { |child| fixed_size(child, axis) }
|
|
92
|
+
flexible_indexes = fixed.each_index.select { |index| fixed[index].nil? }
|
|
93
|
+
sizes = fixed.map { |size| size&.to_i }
|
|
94
|
+
remaining = [available_for_children - sizes.compact.sum, 0].max
|
|
95
|
+
|
|
96
|
+
distribute_remaining(sizes, flexible_indexes, remaining)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Returns the fixed size of *child* along *axis* (`:horizontal` reads width, `:vertical` reads height).
|
|
100
|
+
def fixed_size(child, axis)
|
|
101
|
+
(axis == :horizontal) ? child.width : child.height
|
|
102
|
+
end
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
# Distributes the *remaining* cells across *flexible_indexes* by grow weight, with the
|
|
105
|
+
# last flexible child absorbing any rounding remainder.
|
|
106
|
+
def distribute_remaining(sizes, flexible_indexes, remaining)
|
|
107
|
+
return sizes.map { |size| size || 0 } if flexible_indexes.empty?
|
|
112
108
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
remaining - used
|
|
116
|
-
else
|
|
117
|
-
(remaining * grow_weight(children[index]) / total_grow).floor
|
|
118
|
-
end
|
|
109
|
+
total_grow = flexible_indexes.sum { |index| grow_weight(children[index]) }
|
|
110
|
+
used = 0
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
flexible_indexes.each_with_index do |index, flexible_index|
|
|
113
|
+
size = if flexible_index == flexible_indexes.length - 1
|
|
114
|
+
remaining - used
|
|
115
|
+
else
|
|
116
|
+
(remaining * grow_weight(children[index]) / total_grow).floor
|
|
122
117
|
end
|
|
123
118
|
|
|
124
|
-
sizes
|
|
119
|
+
sizes[index] = size
|
|
120
|
+
used += size
|
|
125
121
|
end
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
sizes
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns the grow weight of *child*, defaulting to 1 when unset.
|
|
127
|
+
def grow_weight(child)
|
|
128
|
+
[child.grow.to_i, 1].max
|
|
131
129
|
end
|
|
132
130
|
end
|
|
133
131
|
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,119 +1,117 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
end
|
|
4
|
+
module Markdown
|
|
5
|
+
# BlockRenderer dispatches Kramdown block-level elements (paragraph, header, list,
|
|
6
|
+
# code block, etc.) to their individual rendering handlers. Handlers are built once
|
|
7
|
+
# at construction time as a frozen hash of element-type symbols to callables.
|
|
8
|
+
class BlockRenderer
|
|
9
|
+
# *renderer* is the parent Renderer (used to wrap text, render inlines, and look up styles).
|
|
10
|
+
def initialize(renderer:)
|
|
11
|
+
@renderer = renderer
|
|
12
|
+
build_handlers
|
|
13
|
+
end
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
# Renders *element* using the handler registered for `element.type`. Unknown types
|
|
16
|
+
# fall through to `render_unknown`.
|
|
17
|
+
def render(element, context:)
|
|
18
|
+
handler = @handlers[element.type] || method(:render_unknown)
|
|
19
|
+
handler.call(element, context)
|
|
20
|
+
end
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# The frozen hash of element-type → handler mapping.
|
|
25
|
+
attr_reader :handlers
|
|
26
|
+
|
|
27
|
+
# Builds the handler hash. Each handler is a small lambda that calls back into the
|
|
28
|
+
# parent renderer (or one of the private render_* methods below).
|
|
29
|
+
def build_handlers
|
|
30
|
+
r = @renderer
|
|
31
|
+
@handlers = {
|
|
32
|
+
p: ->(element, context) { r.wrap(r.render_inlines(element.children), width: context.width) },
|
|
33
|
+
header: ->(element, context) { send(:render_header, element, context) },
|
|
34
|
+
blockquote: ->(element, context) { send(:render_blockquote, element, context) },
|
|
35
|
+
ul: ->(element, context) { send(:render_list, element, ordered: false, context: context) },
|
|
36
|
+
ol: ->(element, context) { send(:render_list, element, ordered: true, context: context) },
|
|
37
|
+
li: ->(element, context) { r.render_blocks(element.children, list_depth: context.list_depth, width: context.width) },
|
|
38
|
+
codeblock: ->(element, _context) { send(:render_codeblock, element) },
|
|
39
|
+
hr: ->(element, context) { send(:render_rule, width: context.width) },
|
|
40
|
+
blank: ->(_element, _context) {}
|
|
41
|
+
}.freeze
|
|
42
|
+
end
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
# Fallback for unknown block types: wraps the raw value when there are no children,
|
|
45
|
+
# otherwise recurses into the children.
|
|
46
|
+
def render_unknown(element, context)
|
|
47
|
+
return @renderer.wrap(element.value.to_s, width: context.width) if element.children.empty?
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
@renderer.render_blocks(element.children, list_depth: context.list_depth, width: context.width)
|
|
50
|
+
end
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
62
|
-
style.render(rendered)
|
|
52
|
+
# Renders a header element, using the `markdown_heading` style for h1 and the
|
|
53
|
+
# `markdown_subheading` style for h2+.
|
|
54
|
+
def render_header(element, context)
|
|
55
|
+
rendered = @renderer.wrap(@renderer.render_inlines(element.children), width: context.width)
|
|
56
|
+
style = if element.options[:level].to_i == 1
|
|
57
|
+
@renderer.style_for(:markdown_heading, fallback: @renderer.theme_style(:title))
|
|
58
|
+
else
|
|
59
|
+
@renderer.style_for(:markdown_subheading, fallback: @renderer.theme_style(:title))
|
|
63
60
|
end
|
|
61
|
+
style.render(rendered)
|
|
62
|
+
end
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
rendered.lines(chomp: true).map { |line| "#{border} #{quote_style.render(line)}" }.join("\n")
|
|
72
|
-
end
|
|
64
|
+
def render_blockquote(element, context)
|
|
65
|
+
quote_width = context.width ? [context.width - 2, 1].max : nil
|
|
66
|
+
rendered = @renderer.render_blocks(element.children, list_depth: context.list_depth, width: quote_width)
|
|
67
|
+
border = @renderer.style_for(:markdown_quote_border, fallback: @renderer.theme_style(:border)).render("|")
|
|
68
|
+
quote_style = @renderer.style_for(:markdown_quote, fallback: @renderer.theme_style(:muted))
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
marker = ordered ? "#{ordered_start(element) + index}." : "-"
|
|
77
|
-
render_list_item(item, marker: marker, context: context)
|
|
78
|
-
end.join("\n")
|
|
79
|
-
end
|
|
70
|
+
rendered.lines(chomp: true).map { |line| "#{border} #{quote_style.render(line)}" }.join("\n")
|
|
71
|
+
end
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
def render_list(element, ordered:, context:)
|
|
74
|
+
element.children.each_with_index.map do |item, index|
|
|
75
|
+
marker = ordered ? "#{ordered_start(element) + index}." : "-"
|
|
76
|
+
render_list_item(item, marker: marker, context: context)
|
|
77
|
+
end.join("\n")
|
|
78
|
+
end
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
def render_list_item(element, marker:, context:)
|
|
81
|
+
indent = " " * context.list_depth
|
|
82
|
+
first_prefix = "#{indent}#{marker} "
|
|
83
|
+
rest_prefix = "#{indent}#{" " * (marker.length + 1)}"
|
|
84
|
+
item_width = context.width ? [context.width - UI::Width.measure(first_prefix), 1].max : nil
|
|
85
|
+
body = @renderer.render_blocks(element.children, list_depth: context.list_depth + 1, width: item_width)
|
|
92
86
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
end
|
|
87
|
+
body.lines(chomp: true).each_with_index.map do |line, index|
|
|
88
|
+
"#{index.zero? ? first_prefix : rest_prefix}#{line}"
|
|
89
|
+
end.join("\n")
|
|
90
|
+
end
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
SyntaxHighlighter.new(theme: @renderer.theme).render(code, language: code_language(element))
|
|
101
|
-
else
|
|
102
|
-
@renderer.style_for(:markdown_code, fallback: @renderer.theme_style(:warn)).render(code)
|
|
103
|
-
end
|
|
92
|
+
def ordered_start(element)
|
|
93
|
+
element.options.fetch(:start, 1).to_i
|
|
94
|
+
end
|
|
104
95
|
|
|
105
|
-
|
|
96
|
+
def render_codeblock(element)
|
|
97
|
+
code = element.value.to_s
|
|
98
|
+
rendered = if @renderer.syntax_highlighting
|
|
99
|
+
SyntaxHighlighter.new(theme: @renderer.theme).render(code, language: code_language(element))
|
|
100
|
+
else
|
|
101
|
+
@renderer.style_for(:markdown_code, fallback: @renderer.theme_style(:warn)).render(code)
|
|
106
102
|
end
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
end
|
|
104
|
+
rendered.lines(chomp: true).map { |line| " #{line}" }.join("\n")
|
|
105
|
+
end
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
def render_rule(width:)
|
|
108
|
+
@renderer.style_for(:markdown_rule, fallback: @renderer.theme_style(:border)).render("-" * (width || Renderer::DEFAULT_RULE_WIDTH))
|
|
109
|
+
end
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
def code_language(element)
|
|
112
|
+
return element.options[:lang] if element.options[:lang]
|
|
113
|
+
|
|
114
|
+
element.attr["class"].to_s[/language-([^\s]+)/, 1]
|
|
117
115
|
end
|
|
118
116
|
end
|
|
119
117
|
end
|