ruby_jard 0.1.0 → 0.3.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/documentation.yml +65 -0
  6. data/.github/workflows/rspec.yml +96 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +90 -2
  9. data/CHANGELOG.md +112 -0
  10. data/Gemfile +14 -4
  11. data/README.md +95 -3
  12. data/benchmark/path_filter_bench.rb +58 -0
  13. data/bin/console +1 -2
  14. data/lib/ruby_jard.rb +68 -32
  15. data/lib/ruby_jard/box_drawer.rb +175 -0
  16. data/lib/ruby_jard/color_scheme.rb +28 -0
  17. data/lib/ruby_jard/color_schemes.rb +54 -0
  18. data/lib/ruby_jard/color_schemes/256_color_scheme.rb +50 -0
  19. data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +50 -0
  20. data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +49 -0
  21. data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +48 -0
  22. data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +47 -0
  23. data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +49 -0
  24. data/lib/ruby_jard/column.rb +26 -0
  25. data/lib/ruby_jard/commands/color_helpers.rb +32 -0
  26. data/lib/ruby_jard/commands/continue_command.rb +4 -9
  27. data/lib/ruby_jard/commands/down_command.rb +9 -8
  28. data/lib/ruby_jard/commands/exit_command.rb +27 -0
  29. data/lib/ruby_jard/commands/frame_command.rb +13 -11
  30. data/lib/ruby_jard/commands/jard/color_scheme_command.rb +74 -0
  31. data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
  32. data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
  33. data/lib/ruby_jard/commands/jard/output_command.rb +36 -0
  34. data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
  35. data/lib/ruby_jard/commands/jard_command.rb +52 -0
  36. data/lib/ruby_jard/commands/list_command.rb +31 -0
  37. data/lib/ruby_jard/commands/next_command.rb +11 -8
  38. data/lib/ruby_jard/commands/step_command.rb +11 -8
  39. data/lib/ruby_jard/commands/step_out_command.rb +34 -0
  40. data/lib/ruby_jard/commands/up_command.rb +10 -8
  41. data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
  42. data/lib/ruby_jard/config.rb +61 -0
  43. data/lib/ruby_jard/console.rb +158 -0
  44. data/lib/ruby_jard/control_flow.rb +73 -0
  45. data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
  46. data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
  47. data/lib/ruby_jard/decorators/color_decorator.rb +80 -0
  48. data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
  49. data/lib/ruby_jard/decorators/inspection_decorator.rb +109 -0
  50. data/lib/ruby_jard/decorators/loc_decorator.rb +108 -119
  51. data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
  52. data/lib/ruby_jard/decorators/path_decorator.rb +56 -60
  53. data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
  54. data/lib/ruby_jard/decorators/source_decorator.rb +3 -1
  55. data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
  56. data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
  57. data/lib/ruby_jard/frame.rb +68 -0
  58. data/lib/ruby_jard/key_binding.rb +14 -0
  59. data/lib/ruby_jard/key_bindings.rb +96 -0
  60. data/lib/ruby_jard/keys.rb +48 -0
  61. data/lib/ruby_jard/layout.rb +17 -88
  62. data/lib/ruby_jard/layout_calculator.rb +168 -0
  63. data/lib/ruby_jard/layout_picker.rb +34 -0
  64. data/lib/ruby_jard/layouts.rb +52 -0
  65. data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +32 -0
  66. data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
  67. data/lib/ruby_jard/layouts/tiny_layout.rb +29 -0
  68. data/lib/ruby_jard/layouts/wide_layout.rb +50 -0
  69. data/lib/ruby_jard/pager.rb +112 -0
  70. data/lib/ruby_jard/path_classifier.rb +133 -0
  71. data/lib/ruby_jard/path_filter.rb +125 -0
  72. data/lib/ruby_jard/reflection.rb +97 -0
  73. data/lib/ruby_jard/repl_processor.rb +151 -89
  74. data/lib/ruby_jard/repl_proxy.rb +337 -0
  75. data/lib/ruby_jard/row.rb +31 -0
  76. data/lib/ruby_jard/row_renderer.rb +119 -0
  77. data/lib/ruby_jard/screen.rb +14 -41
  78. data/lib/ruby_jard/screen_adjuster.rb +104 -0
  79. data/lib/ruby_jard/screen_drawer.rb +25 -0
  80. data/lib/ruby_jard/screen_manager.rb +167 -82
  81. data/lib/ruby_jard/screen_renderer.rb +152 -0
  82. data/lib/ruby_jard/screens.rb +31 -12
  83. data/lib/ruby_jard/screens/backtrace_screen.rb +118 -116
  84. data/lib/ruby_jard/screens/menu_screen.rb +73 -45
  85. data/lib/ruby_jard/screens/source_screen.rb +86 -106
  86. data/lib/ruby_jard/screens/threads_screen.rb +103 -78
  87. data/lib/ruby_jard/screens/variables_screen.rb +224 -142
  88. data/lib/ruby_jard/session.rb +151 -16
  89. data/lib/ruby_jard/span.rb +23 -0
  90. data/lib/ruby_jard/templates/layout_template.rb +35 -0
  91. data/lib/ruby_jard/templates/screen_template.rb +34 -0
  92. data/lib/ruby_jard/thread_info.rb +69 -0
  93. data/lib/ruby_jard/version.rb +1 -1
  94. data/ruby_jard.gemspec +7 -8
  95. metadata +84 -50
  96. data/.travis.yml +0 -6
  97. data/lib/ruby_jard/commands/finish_command.rb +0 -31
  98. data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
  99. data/lib/ruby_jard/layout_template.rb +0 -101
  100. data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
  101. data/lib/ruby_jard/screens/empty_screen.rb +0 -13
  102. data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # A helper to standardize control instruction passed around via
6
+ # throw and catch mechanism.
7
+ class ControlFlow
8
+ THROW_KEYWORD = :jard_control_flow
9
+ ALLOW_LIST = {
10
+ continue: [], # lib/ruby_jard/commands/continue_command.rb
11
+ exit: [], # lib/ruby_jard/commands/exit_command.rb
12
+ frame: [:frame], # lib/ruby_jard/commands/frame_command.rb
13
+ up: [:times], # lib/ruby_jard/commands/up_command.rb
14
+ down: [:times], # lib/ruby_jard/commands/down_command.rb
15
+ next: [:times], # lib/ruby_jard/commands/next_command.rb
16
+ step: [:times], # lib/ruby_jard/commands/step_command.rb
17
+ step_out: [:times], # lib/ruby_jard/commands/step_out_command.rb
18
+ key_binding: [:action], # lib/ruby_jard/commands/step_command.rb
19
+ list: [] # lib/ruby_jard/commands/list_command.rb
20
+ }.freeze
21
+
22
+ attr_reader :command, :arguments
23
+
24
+ def initialize(command, arguments)
25
+ @command = command
26
+ @arguments = arguments
27
+
28
+ validate!
29
+ end
30
+
31
+ def validate!
32
+ if command.to_s.empty?
33
+ raise RubyJard::Error, 'Control command is empty'
34
+ end
35
+
36
+ unless ALLOW_LIST.key?(command)
37
+ raise RubyJard::Error,
38
+ "Control command `#{command}` is not registered in the allow list."
39
+ end
40
+
41
+ invalid_keys = arguments.keys - ALLOW_LIST[command]
42
+ unless invalid_keys.empty?
43
+ raise RubyJard::Error,
44
+ "Control command `#{command}` is attached with unregister arguments: #{invalid_keys}"
45
+ end
46
+ end
47
+
48
+ class << self
49
+ def dispatch(command, arguments = {})
50
+ if command.is_a?(RubyJard::ControlFlow)
51
+ throw THROW_KEYWORD, command
52
+ else
53
+ throw THROW_KEYWORD, new(command, arguments)
54
+ end
55
+ end
56
+
57
+ def listen
58
+ raise RubyJard::Error, 'This method requires a block' unless block_given?
59
+
60
+ flow = catch(THROW_KEYWORD) do
61
+ yield
62
+ nil
63
+ end
64
+
65
+ if flow.nil? || flow.is_a?(RubyJard::ControlFlow)
66
+ flow
67
+ else
68
+ raise RubyJard::Error, 'Control flow misused!'
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Decorate Array data structure, supports singleline and multiline form.
7
+ class ArrayDecorator
8
+ def initialize(generic_decorator)
9
+ @generic_decorator = generic_decorator
10
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
11
+ end
12
+
13
+ def match?(variable)
14
+ RubyJard::Reflection.call_is_a?(variable, Array)
15
+ end
16
+
17
+ def decorate_singleline(variable, line_limit:, depth: 0)
18
+ spans = []
19
+ spans << RubyJard::Span.new(content: '[', styles: :text_primary)
20
+ spans += @attributes_decorator.inline_values(
21
+ variable.each_with_index, total: variable.length, line_limit: line_limit - 2, depth: depth + 1
22
+ )
23
+ spans << RubyJard::Span.new(content: ']', styles: :text_primary)
24
+
25
+ spans
26
+ end
27
+
28
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
29
+ if variable.length > lines * 2
30
+ return do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
31
+ end
32
+
33
+ singleline = decorate_singleline(variable, line_limit: first_line_limit, depth: depth)
34
+ if (singleline.map(&:content_length).sum < line_limit && same_type?(variable, lines)) || variable.length <= 1
35
+ [singleline]
36
+ else
37
+ do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def same_type?(variable, sample)
44
+ variable.first(sample).map { |item| RubyJard::Reflection.call_class(item) }.uniq.length <= 1
45
+ end
46
+
47
+ def do_decorate_multiline(variable, lines:, line_limit:, depth: 0)
48
+ spans = [[RubyJard::Span.new(content: '[', styles: :text_primary)]]
49
+
50
+ item_count = 0
51
+ variable.each_with_index do |value, index|
52
+ spans << @attributes_decorator.value(value, line_limit: line_limit, depth: depth + 1)
53
+
54
+ item_count += 1
55
+ break if index >= lines - 2
56
+ end
57
+
58
+ spans << last_line(variable.length, item_count)
59
+ end
60
+
61
+ def last_line(total, item_count)
62
+ if total > item_count
63
+ [
64
+ RubyJard::Span.new(
65
+ content: "▸ #{total - item_count} more...",
66
+ margin_left: 2, styles: :text_dim
67
+ ),
68
+ RubyJard::Span.new(
69
+ content: ']',
70
+ styles: :text_primary
71
+ )
72
+ ]
73
+ else
74
+ [RubyJard::Span.new(content: ']', styles: :text_primary)]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ TYPICAL_DEPTH = 3
6
+ MAX_DEPTH = 5
7
+ DO_NOT_WASTE_LENGTH = 40
8
+ ##
9
+ # Decorate collection data structure. Support:
10
+ # - Collection of values
11
+ # - Collection of key-value pairs
12
+ # - Individual value
13
+ # - Individual pair
14
+ # This decorator should not be used directly.
15
+ class AttributesDecorator
16
+ def initialize(generic_decorator)
17
+ @generic_decorator = generic_decorator
18
+ end
19
+
20
+ def inline_pairs(enum, total:, line_limit:, process_key:, value_proc: nil, depth: 0)
21
+ return [ellipsis_span] if too_deep?(depth, line_limit)
22
+
23
+ spans = []
24
+ width = 1
25
+ item_limit = total == 0 ? 0 : [line_limit / total, pair_limit(depth)].max
26
+
27
+ enum.each do |(key, value), index|
28
+ key_inspection = inspect_key(key, item_limit, process_key: process_key, depth: depth)
29
+ key_inspection_length = key_inspection.map(&:content_length).sum
30
+
31
+ value_inspection = @generic_decorator.decorate_singleline(
32
+ value_proc.nil? ? value : value_proc.call(key),
33
+ line_limit: [item_limit - key_inspection_length, pair_limit(depth)].max, depth: depth
34
+ )
35
+ value_inspection_length = value_inspection.map(&:content_length).sum
36
+
37
+ if index > 0
38
+ spans << separator_span
39
+ width += 2
40
+ end
41
+
42
+ if width + key_inspection_length + value_inspection_length + 5 > line_limit
43
+ spans << ellipsis_span
44
+ break
45
+ end
46
+
47
+ spans += key_inspection
48
+ width += key_inspection_length
49
+
50
+ spans << arrow_span
51
+ width += 3
52
+
53
+ spans += value_inspection
54
+ width += value_inspection_length
55
+ end
56
+
57
+ spans
58
+ end
59
+
60
+ def pair(key, value, line_limit:, process_key:, depth: 0)
61
+ return [ellipsis_span] if too_deep?(depth, line_limit)
62
+
63
+ spans = []
64
+ spans << indent_span
65
+ width = indent_span.content_length
66
+
67
+ key_inspection = inspect_key(key, line_limit - width, process_key: process_key, depth: depth)
68
+ key_inspection_length = key_inspection.map(&:content_length).sum
69
+
70
+ spans += key_inspection
71
+ width += key_inspection_length
72
+
73
+ spans << arrow_span
74
+ width += 3
75
+
76
+ value_inspection = @generic_decorator.decorate_singleline(
77
+ value, line_limit: [line_limit - width, pair_limit(depth)].max, depth: depth
78
+ )
79
+
80
+ spans + value_inspection
81
+ end
82
+
83
+ def inline_values(enum, total:, line_limit:, depth: 0)
84
+ return [ellipsis_span] if too_deep?(depth, line_limit)
85
+
86
+ spans = []
87
+ width = 1
88
+ item_limit = total == 0 ? 0 : [line_limit / total, value_limit(depth)].max
89
+
90
+ enum.each do |value, index|
91
+ value_inspection = @generic_decorator.decorate_singleline(
92
+ value, line_limit: [item_limit, value_limit(depth)].max, depth: depth
93
+ )
94
+ value_inspection_length = value_inspection.map(&:content_length).sum
95
+
96
+ if index > 0
97
+ spans << separator_span
98
+ width += 2
99
+ end
100
+
101
+ if width + value_inspection_length + 2 > line_limit
102
+ spans << ellipsis_span
103
+ break
104
+ end
105
+
106
+ spans += value_inspection
107
+ width += value_inspection_length
108
+ end
109
+
110
+ spans
111
+ end
112
+
113
+ def value(value, line_limit:, depth: 0)
114
+ return [ellipsis_span] if too_deep?(depth, line_limit)
115
+
116
+ spans = []
117
+ spans << indent_span
118
+ width = indent_span.content_length
119
+
120
+ value_inspection = @generic_decorator.decorate_singleline(
121
+ value, line_limit: [line_limit - width, value_limit(depth)].max, depth: depth
122
+ )
123
+
124
+ spans + value_inspection
125
+ end
126
+
127
+ private
128
+
129
+ def inspect_key(key, item_limit, process_key:, depth: 0)
130
+ if process_key
131
+ @generic_decorator.decorate_singleline(
132
+ key, line_limit: item_limit, depth: depth
133
+ )
134
+ else
135
+ [RubyJard::Span.new(content: key.to_s, styles: :text_primary)]
136
+ end
137
+ end
138
+
139
+ def arrow_span
140
+ RubyJard::Span.new(content: '→', margin_left: 1, margin_right: 1, styles: :text_highlighted)
141
+ end
142
+
143
+ def separator_span
144
+ RubyJard::Span.new(content: ',', margin_right: 1, styles: :text_primary)
145
+ end
146
+
147
+ def ellipsis_span
148
+ RubyJard::Span.new(content: '…', styles: :text_dim)
149
+ end
150
+
151
+ def indent_span
152
+ RubyJard::Span.new(content: '▸', margin_right: 1, margin_left: 2, styles: :text_dim)
153
+ end
154
+
155
+ def too_deep?(depth, line_limit)
156
+ return true if depth > MAX_DEPTH
157
+ return false if line_limit > DO_NOT_WASTE_LENGTH
158
+
159
+ depth > TYPICAL_DEPTH
160
+ end
161
+
162
+ def pair_limit(depth)
163
+ # The deeper structure, the less meaningful the actual data is
164
+ 30 - depth * 5
165
+ end
166
+
167
+ def value_limit(_depth)
168
+ 30
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Manipulate and decorate color for texts.
7
+ # This class translate colors to corresponding escape sequences.
8
+ # Support 24-bit color (#51617d format) or 256 colors (https://jonasjacek.github.io/colors/)
9
+ # Example:
10
+ # - #fafafa => \e[38;2;250;250;250m
11
+ # - #aaa => \e[38;2;170;170;170m
12
+ # - 77 => \e[38;2;170;170;170m
13
+ class ColorDecorator
14
+ HEX_PATTERN_6 = /^#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$/i.freeze
15
+ HEX_PATTERN_3 = /^#([A-Fa-f0-9]{1})([A-Fa-f0-9]{1})([A-Fa-f0-9]{1})$/i.freeze
16
+ XTERM_NUMBER_PATTERN = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/i.freeze
17
+
18
+ CSI_RESET = "\e[0m"
19
+ CSI_FOREGROUND_24BIT = "\e[38;2;%d;%d;%dm"
20
+ CSI_BACKGROUND_24BIT = "\e[48;2;%d;%d;%dm"
21
+ CSI_FOREGROUND_256 = "\e[38;5;%dm"
22
+ CSI_BACKGROUND_256 = "\e[48;5;%dm"
23
+
24
+ CSI_ITALIC = "\e[3m"
25
+ CSI_UNDERLINE = "\e[4m"
26
+ CSI_BOLD = "\e[1m"
27
+
28
+ STYLES_CSI_MAP = {
29
+ underline: CSI_UNDERLINE,
30
+ italic: CSI_ITALIC,
31
+ bold: CSI_BOLD
32
+ }.freeze
33
+
34
+ def initialize(color_scheme)
35
+ @color_scheme = color_scheme
36
+ end
37
+
38
+ def decorate(style_names, content)
39
+ attributes = nil
40
+ if style_names.is_a?(Symbol)
41
+ styles = @color_scheme.styles_for(style_names)
42
+ else
43
+ attributes = translate_styles(style_names)
44
+ styles = @color_scheme.styles_for(style_names.shift)
45
+ end
46
+ foreground = translate_color(styles.shift, true)
47
+ background = translate_color(styles.shift, false)
48
+ "#{foreground}#{background}#{attributes}#{content}#{CSI_RESET}"
49
+ end
50
+
51
+ def translate_color(color, foreground)
52
+ if (matches = HEX_PATTERN_6.match(color.to_s))
53
+ red = matches[1].to_i(16)
54
+ green = matches[2].to_i(16)
55
+ blue = matches[3].to_i(16)
56
+ sequence = foreground ? CSI_FOREGROUND_24BIT : CSI_BACKGROUND_24BIT
57
+ format sequence, red, green, blue
58
+ elsif (matches = HEX_PATTERN_3.match(color.to_s))
59
+ red = (matches[1] * 2).to_i(16)
60
+ green = (matches[2] * 2).to_i(16)
61
+ blue = (matches[3] * 2).to_i(16)
62
+ sequence = foreground ? CSI_FOREGROUND_24BIT : CSI_BACKGROUND_24BIT
63
+ format sequence, red, green, blue
64
+ elsif (matches = XTERM_NUMBER_PATTERN.match(color.to_s))
65
+ color = matches[1]
66
+ sequence = foreground ? CSI_FOREGROUND_256 : CSI_BACKGROUND_256
67
+ format sequence, color
68
+ else
69
+ ''
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def translate_styles(styles = [])
76
+ styles.map { |key| STYLES_CSI_MAP[key] }.compact.join
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Decorate Hash data structure, supports singleline and multiline form.
7
+ class HashDecorator
8
+ def initialize(generic_decorator)
9
+ @generic_decorator = generic_decorator
10
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
11
+ end
12
+
13
+ def decorate_singleline(variable, line_limit:, depth: 0)
14
+ spans = []
15
+ spans << RubyJard::Span.new(content: '{', styles: :text_primary)
16
+ spans += @attributes_decorator.inline_pairs(
17
+ variable.each_with_index,
18
+ total: variable.length, line_limit: line_limit - 2, process_key: true, depth: depth + 1
19
+ )
20
+ spans << RubyJard::Span.new(content: '}', styles: :text_primary)
21
+ end
22
+
23
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
24
+ if variable.size > lines * 1.5
25
+ return do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
26
+ end
27
+
28
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
29
+ if singleline.map(&:content_length).sum < line_limit || variable.length <= 1
30
+ [singleline]
31
+ else
32
+ do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
33
+ end
34
+ end
35
+
36
+ def match?(variable)
37
+ RubyJard::Reflection.call_is_a?(variable, Hash)
38
+ end
39
+
40
+ private
41
+
42
+ def do_decorate_multiline(variable, lines:, line_limit:, depth: 0)
43
+ spans = [[RubyJard::Span.new(content: '{', styles: :text_primary)]]
44
+
45
+ item_count = 0
46
+ variable.each_with_index do |(key, value), index|
47
+ spans << @attributes_decorator.pair(
48
+ key, value, line_limit: line_limit, process_key: true, depth: depth + 1
49
+ )
50
+ item_count += 1
51
+ break if index >= lines - 2
52
+ end
53
+ spans << last_line(variable.length, item_count)
54
+ end
55
+
56
+ def last_line(total, item_count)
57
+ if total > item_count
58
+ [
59
+ RubyJard::Span.new(
60
+ content: "▸ #{total - item_count} more...",
61
+ margin_left: 2, styles: :text_dim
62
+ ),
63
+ RubyJard::Span.new(
64
+ content: '}',
65
+ styles: :text_primary
66
+ )
67
+ ]
68
+ else
69
+ [RubyJard::Span.new(content: '}', styles: :text_primary)]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end