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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/charming/application.rb +3 -3
  3. data/lib/charming/controller/class_methods.rb +2 -2
  4. data/lib/charming/controller/command_palette.rb +2 -2
  5. data/lib/charming/controller/rendering.rb +2 -2
  6. data/lib/charming/controller/session_state.rb +1 -1
  7. data/lib/charming/generators/component_generator.rb +1 -1
  8. data/lib/charming/generators/templates/app/application.template +1 -1
  9. data/lib/charming/generators/templates/app/layout.template +3 -6
  10. data/lib/charming/generators/templates/app/view.template +1 -1
  11. data/lib/charming/generators/templates/component/component.rb.template +1 -1
  12. data/lib/charming/generators/templates/screen/view.rb.template +1 -1
  13. data/lib/charming/generators/templates/view/view.rb.template +1 -1
  14. data/lib/charming/internal/renderer/differential.rb +13 -5
  15. data/lib/charming/internal/terminal/tty_backend.rb +22 -2
  16. data/lib/charming/presentation/component.rb +3 -5
  17. data/lib/charming/presentation/components/activity_indicator.rb +173 -134
  18. data/lib/charming/presentation/components/command_palette.rb +94 -96
  19. data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
  20. data/lib/charming/presentation/components/empty_state.rb +47 -49
  21. data/lib/charming/presentation/components/form/builder.rb +52 -54
  22. data/lib/charming/presentation/components/form/confirm.rb +49 -51
  23. data/lib/charming/presentation/components/form/field.rb +94 -96
  24. data/lib/charming/presentation/components/form/input.rb +53 -55
  25. data/lib/charming/presentation/components/form/note.rb +27 -29
  26. data/lib/charming/presentation/components/form/select.rb +84 -86
  27. data/lib/charming/presentation/components/form/textarea.rb +67 -69
  28. data/lib/charming/presentation/components/form.rb +120 -122
  29. data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
  30. data/lib/charming/presentation/components/list.rb +123 -125
  31. data/lib/charming/presentation/components/markdown.rb +21 -23
  32. data/lib/charming/presentation/components/modal.rb +46 -48
  33. data/lib/charming/presentation/components/progressbar.rb +51 -53
  34. data/lib/charming/presentation/components/spinner.rb +40 -42
  35. data/lib/charming/presentation/components/table.rb +109 -111
  36. data/lib/charming/presentation/components/text_area.rb +219 -221
  37. data/lib/charming/presentation/components/text_input.rb +120 -122
  38. data/lib/charming/presentation/components/viewport.rb +218 -220
  39. data/lib/charming/presentation/layout/builder.rb +64 -66
  40. data/lib/charming/presentation/layout/overlay.rb +48 -50
  41. data/lib/charming/presentation/layout/pane.rb +122 -118
  42. data/lib/charming/presentation/layout/rect.rb +14 -16
  43. data/lib/charming/presentation/layout/screen_layout.rb +40 -42
  44. data/lib/charming/presentation/layout/split.rb +101 -103
  45. data/lib/charming/presentation/layout.rb +28 -30
  46. data/lib/charming/presentation/markdown/block_renderers.rb +94 -96
  47. data/lib/charming/presentation/markdown/inline_renderers.rb +52 -54
  48. data/lib/charming/presentation/markdown/render_context.rb +12 -14
  49. data/lib/charming/presentation/markdown/renderer.rb +84 -86
  50. data/lib/charming/presentation/markdown/syntax_highlighter.rb +57 -59
  51. data/lib/charming/presentation/markdown.rb +4 -6
  52. data/lib/charming/presentation/template_view.rb +22 -24
  53. data/lib/charming/presentation/templates/erb_handler.rb +4 -6
  54. data/lib/charming/presentation/templates.rb +47 -49
  55. data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
  56. data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
  57. data/lib/charming/presentation/ui/border.rb +24 -26
  58. data/lib/charming/presentation/ui/border_painter.rb +37 -39
  59. data/lib/charming/presentation/ui/canvas.rb +59 -61
  60. data/lib/charming/presentation/ui/style.rb +173 -175
  61. data/lib/charming/presentation/ui/theme.rb +133 -135
  62. data/lib/charming/presentation/ui/width.rb +12 -14
  63. data/lib/charming/presentation/ui.rb +69 -71
  64. data/lib/charming/presentation/view.rb +103 -105
  65. data/lib/charming/runtime.rb +23 -10
  66. data/lib/charming/version.rb +1 -1
  67. data/lib/charming.rb +3 -2
  68. metadata +2 -1
@@ -1,133 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Layout
6
- # Split divides a parent Rect among its child nodes horizontally or vertically.
7
- # Children with a configured `width`/`height` are placed at that fixed size; children
8
- # without a fixed size share the remaining space according to their `grow` weight.
9
- class Split
10
- # The fixed width/height of the split (when set) and the grow weight for the split itself.
11
- attr_reader :width, :height, :grow
12
-
13
- # *direction* is `:horizontal` or `:vertical`. *gap* (in cells) separates children.
14
- # *width*/*height* are optional fixed dimensions for the split as a whole.
15
- # *grow* is the weight for distributing remaining space (used when this Split is a
16
- # child of another Split).
17
- def initialize(direction:, gap: 0, width: nil, height: nil, grow: nil)
18
- @direction = direction.to_sym
19
- @gap = gap.to_i
20
- @width = width
21
- @height = height
22
- @grow = grow
23
- @children = []
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
- # Returns the flattened list of focusable names from all child nodes.
32
- def focusable_names
33
- children.flat_map(&:focusable_names)
34
- end
25
+ # Appends *node* (a child Split or Pane) to this Split.
26
+ def add_child(node)
27
+ children << node
28
+ end
35
29
 
36
- # Renders each child into its own sub-rect, then overlays them on a blank canvas
37
- # of the parent's dimensions.
38
- def render(rect)
39
- frame = UI.place("", width: rect.width, height: rect.height)
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
- child_rects(rect).zip(children).each do |child_rect, child|
42
- frame = UI.overlay(frame, child.render(child_rect), top: child_rect.y - rect.y, left: child_rect.x - rect.x)
43
- end
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
- frame
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
- private
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
- # Returns an array of child rects sized according to each child's fixed dimensions
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
- raise ArgumentError, "unknown split direction: #{direction.inspect}"
60
- end
49
+ # The split direction (`:horizontal` or `:vertical`) and the inter-child gap.
50
+ attr_reader :direction, :gap, :children
61
51
 
62
- # Computes per-child rects for a horizontal split.
63
- def horizontal_rects(rect)
64
- sizes = child_sizes(axis: :horizontal, available: rect.width)
65
- left = rect.x
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
- sizes.map do |width|
68
- child_rect = Rect.new(x: left, y: rect.y, width: width, height: rect.height)
69
- left += width + gap
70
- child_rect
71
- end
72
- end
58
+ raise ArgumentError, "unknown split direction: #{direction.inspect}"
59
+ end
73
60
 
74
- # Computes per-child rects for a vertical split.
75
- def vertical_rects(rect)
76
- sizes = child_sizes(axis: :vertical, available: rect.height)
77
- top = rect.y
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
- sizes.map do |height|
80
- child_rect = Rect.new(x: rect.x, y: top, width: rect.width, height: height)
81
- top += height + gap
82
- child_rect
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
- # Computes the size of each child along the *axis* given the *available* cells.
87
- # Subtracts the total gap, allocates fixed sizes first, and distributes the remainder
88
- # among flexible (non-fixed) children by their grow weights.
89
- def child_sizes(axis:, available:)
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
- # Returns the fixed size of *child* along *axis* (`:horizontal` reads width, `:vertical` reads height).
101
- def fixed_size(child, axis)
102
- (axis == :horizontal) ? child.width : child.height
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
- # Distributes the *remaining* cells across *flexible_indexes* by grow weight, with the
106
- # last flexible child absorbing any rounding remainder.
107
- def distribute_remaining(sizes, flexible_indexes, remaining)
108
- return sizes.map { |size| size || 0 } if flexible_indexes.empty?
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
- total_grow = flexible_indexes.sum { |index| grow_weight(children[index]) }
111
- used = 0
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
- flexible_indexes.each_with_index do |index, flexible_index|
114
- size = if flexible_index == flexible_indexes.length - 1
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
- sizes[index] = size
121
- used += size
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
- # Returns the grow weight of *child*, defaulting to 1 when unset.
128
- def grow_weight(child)
129
- [child.grow.to_i, 1].max
130
- end
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
- module Presentation
5
- # Layout contains generic screen-size math and composition helpers. It is
6
- # intentionally unaware of application shells such as sidebars or nav panes.
7
- module Layout
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
- def clamp_size(value, min: nil, max: nil)
11
- size = value.to_i
12
- size = [size, min].max if min
13
- size = [size, max].min if max
14
- size
15
- end
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
- def available_width(screen, reserved: 0, min: nil, max: nil)
18
- clamp_size(screen.width - reserved, min: min, max: max)
19
- end
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
- def available_height(screen, reserved: 0, min: nil, max: nil)
22
- clamp_size(screen.height - reserved, min: min, max: max)
23
- end
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
- def stack_or_row(*blocks, narrow:, gap: 0)
26
- if narrow
27
- UI.join_vertical(*blocks, gap: gap)
28
- else
29
- UI.join_horizontal(*blocks, gap: gap)
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
- def selected_window_start(selected_index:, item_count:, window_size:)
34
- count = item_count.to_i
35
- size = [window_size.to_i, 1].max
36
- selected = selected_index.to_i.clamp(0, [count - 1, 0].max)
37
- max_start = [count - size, 0].max
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
- (selected - size + 1).clamp(0, max_start)
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 Presentation
5
- module Markdown
6
- # BlockRenderer dispatches Kramdown block-level elements (paragraph, header, list,
7
- # code block, etc.) to their individual rendering handlers. Handlers are built once
8
- # at construction time as a frozen hash of element-type symbols to callables.
9
- class BlockRenderer
10
- # *renderer* is the parent Renderer (used to wrap text, render inlines, and look up styles).
11
- def initialize(renderer:)
12
- @renderer = renderer
13
- build_handlers
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
- # Renders *element* using the handler registered for `element.type`. Unknown types
17
- # fall through to `render_unknown`.
18
- def render(element, context:)
19
- handler = @handlers[element.type] || method(:render_unknown)
20
- handler.call(element, context)
21
- end
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
- private
24
-
25
- # The frozen hash of element-type → handler mapping.
26
- attr_reader :handlers
27
-
28
- # Builds the handler hash. Each handler is a small lambda that calls back into the
29
- # parent renderer (or one of the private render_* methods below).
30
- def build_handlers
31
- r = @renderer
32
- @handlers = {
33
- p: ->(element, context) { r.wrap(r.render_inlines(element.children), width: context.width) },
34
- header: ->(element, context) { send(:render_header, element, context) },
35
- blockquote: ->(element, context) { send(:render_blockquote, element, context) },
36
- ul: ->(element, context) { send(:render_list, element, ordered: false, context: context) },
37
- ol: ->(element, context) { send(:render_list, element, ordered: true, context: context) },
38
- li: ->(element, context) { r.render_blocks(element.children, list_depth: context.list_depth, width: context.width) },
39
- codeblock: ->(element, _context) { send(:render_codeblock, element) },
40
- hr: ->(element, context) { send(:render_rule, width: context.width) },
41
- blank: ->(_element, _context) {}
42
- }.freeze
43
- end
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
- # Fallback for unknown block types: wraps the raw value when there are no children,
46
- # otherwise recurses into the children.
47
- def render_unknown(element, context)
48
- return @renderer.wrap(element.value.to_s, width: context.width) if element.children.empty?
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
- @renderer.render_blocks(element.children, list_depth: context.list_depth, width: context.width)
51
- end
49
+ @renderer.render_blocks(element.children, list_depth: context.list_depth, width: context.width)
50
+ end
52
51
 
53
- # Renders a header element, using the `markdown_heading` style for h1 and the
54
- # `markdown_subheading` style for h2+.
55
- def render_header(element, context)
56
- rendered = @renderer.wrap(@renderer.render_inlines(element.children), width: context.width)
57
- style = if element.options[:level].to_i == 1
58
- @renderer.style_for(:markdown_heading, fallback: @renderer.theme_style(:title))
59
- else
60
- @renderer.style_for(:markdown_subheading, fallback: @renderer.theme_style(:title))
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
- def render_blockquote(element, context)
66
- quote_width = context.width ? [context.width - 2, 1].max : nil
67
- rendered = @renderer.render_blocks(element.children, list_depth: context.list_depth, width: quote_width)
68
- border = @renderer.style_for(:markdown_quote_border, fallback: @renderer.theme_style(:border)).render("|")
69
- quote_style = @renderer.style_for(:markdown_quote, fallback: @renderer.theme_style(:muted))
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
- def render_list(element, ordered:, context:)
75
- element.children.each_with_index.map do |item, index|
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
- def render_list_item(element, marker:, context:)
82
- indent = " " * context.list_depth
83
- first_prefix = "#{indent}#{marker} "
84
- rest_prefix = "#{indent}#{" " * (marker.length + 1)}"
85
- item_width = context.width ? [context.width - UI::Width.measure(first_prefix), 1].max : nil
86
- body = @renderer.render_blocks(element.children, list_depth: context.list_depth + 1, width: item_width)
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
- body.lines(chomp: true).each_with_index.map do |line, index|
89
- "#{index.zero? ? first_prefix : rest_prefix}#{line}"
90
- end.join("\n")
91
- end
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
- def ordered_start(element)
94
- element.options.fetch(:start, 1).to_i
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
- def render_codeblock(element)
98
- code = element.value.to_s
99
- rendered = if @renderer.syntax_highlighting
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
- rendered.lines(chomp: true).map { |line| " #{line}" }.join("\n")
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
- def render_rule(width:)
109
- @renderer.style_for(:markdown_rule, fallback: @renderer.theme_style(:border)).render("-" * (width || Renderer::DEFAULT_RULE_WIDTH))
110
- end
104
+ rendered.lines(chomp: true).map { |line| " #{line}" }.join("\n")
105
+ end
111
106
 
112
- def code_language(element)
113
- return element.options[:lang] if element.options[:lang]
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
- element.attr["class"].to_s[/language-([^\s]+)/, 1]
116
- end
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