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
@@ -1,87 +1,83 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+ require 'rbconfig'
5
+
3
6
  module RubyJard
4
7
  module Decorators
5
8
  ##
6
9
  # Simplify and generate labels to indicate the location of a path.
7
- # If it's from gem, strip Gem paths, or Bundler paths to expose relative
8
- # location of the file.
9
- # If it's from the current working dir, strip the working dir.
10
+ # The return value is an array of two elements. The first one is overview,
11
+ # the second is detailed path location.
10
12
  class PathDecorator
11
- GEM_PATTERN = /(.*)\-(\d+\.\d+[\.\d]*[\.\d]*[\-\.\w]*)/i.freeze
12
- PATH_TYPES = [
13
- TYPE_UNKNOWN = :unknown,
14
- TYPE_PWD = :pwd,
15
- TYPE_GEM = :gem
16
- ].freeze
13
+ def initialize(path_classifier: nil)
14
+ @path_classifier = path_classifier || RubyJard::PathClassifier.new
15
+ end
17
16
 
18
- attr_reader :path, :lineno, :gem, :gem_version
17
+ def decorate(path, lineno = nil)
18
+ return ['at ???', 'at ???'] if path.nil?
19
19
 
20
- def initialize(path, lineno)
21
- @gem = nil
22
- @gem_version = nil
23
- @path = path
24
- @lineno = lineno
25
- @type = TYPE_UNKNOWN
20
+ type, *info = @path_classifier.classify(path)
26
21
 
27
- decorate
28
- end
22
+ lineno = ":#{lineno}" unless lineno.nil?
29
23
 
30
- def decorate
31
- if path.start_with?(Dir.pwd)
32
- @type = TYPE_PWD
33
- @path = @path[Dir.pwd.length..-1]
24
+ case type
25
+ when RubyJard::PathClassifier::TYPE_SOURCE_TREE
26
+ path = File.expand_path(path)
27
+ decorate_source_tree(path, lineno)
28
+ when RubyJard::PathClassifier::TYPE_GEM
29
+ decorate_gem(path, lineno, info)
30
+ when RubyJard::PathClassifier::TYPE_STDLIB
31
+ decorate_stdlib(path, lineno, info)
32
+ when RubyJard::PathClassifier::TYPE_INTERNAL
33
+ ["in #{path}", path]
34
+ when RubyJard::PathClassifier::TYPE_EVALUATION
35
+ ["at #{path}#{lineno}", "#{path}#{lineno}"]
36
+ when RubyJard::PathClassifier::TYPE_RUBY_SCRIPT
37
+ ["at (-e ruby script)#{lineno}", "(-e ruby script)#{lineno}"]
34
38
  else
35
- decorate_gem_path
39
+ path = compact_with_relative_path(path)
40
+ ["at #{path}#{lineno}", "#{path}#{lineno}"]
36
41
  end
37
-
38
- @path = @path[1..-1] if @path.start_with?('/')
39
- end
40
-
41
- def gem?
42
- @type == TYPE_GEM
43
42
  end
44
43
 
45
44
  private
46
45
 
47
- def decorate_gem_path
48
- gem_paths.each do |gem_path|
49
- next unless path.start_with?(gem_path)
46
+ def decorate_source_tree(path, lineno)
47
+ path = path[Dir.pwd.length..-1]
48
+ path = path[1..-1] if path.start_with?('/')
49
+ path = "#{path}#{lineno}"
50
+ ["at #{path}", path]
51
+ end
50
52
 
51
- @type = TYPE_GEM
52
- splitted_path =
53
- @path[gem_path.length..-1]
54
- .split('/')
55
- .reject(&:empty?)
56
- @path = splitted_path[1..-1].join('/')
57
- @gem = splitted_path.first
58
- match = GEM_PATTERN.match(@gem)
59
- if match
60
- @gem = match[1]
61
- @gem_version = match[2]
53
+ def decorate_gem(_path, lineno, info)
54
+ gem_name, gem_version, relative_path = info
55
+ overview =
56
+ if gem_version.nil?
57
+ "<#{gem_name}>"
58
+ else
59
+ "<#{gem_name} #{gem_version}>"
62
60
  end
63
-
64
- break
65
- end
61
+ detail = "<#{gem_name}:#{relative_path}#{lineno}>"
62
+ ["in #{overview}", detail]
66
63
  end
67
64
 
68
- def gem_paths
69
- paths = []
65
+ def decorate_stdlib(_path, lineno, info)
66
+ lib_name, relative_path = info
70
67
 
71
- if defined?(Gem)
72
- Gem.path.each do |gem_path|
73
- paths << File.join(gem_path, 'gems')
74
- paths << gem_path
75
- end
76
- end
68
+ ["in <stdlib:#{lib_name}>", "<stdlib:#{relative_path}#{lineno}>"]
69
+ end
77
70
 
78
- if defined?(Bundler)
79
- bundle_path = Bundler.bundle_path.to_s
80
- paths << File.join(bundle_path, 'gems')
81
- paths << bundle_path
71
+ def compact_with_relative_path(path)
72
+ relative_path = Pathname.new(path).relative_path_from(Pathname.pwd).to_s
73
+ if relative_path.length < path.length
74
+ relative_path
75
+ else
76
+ path
82
77
  end
83
-
84
- paths
78
+ rescue ArgumentError
79
+ # Fail to get relative path, ignore
80
+ path
85
81
  end
86
82
  end
87
83
  end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # A collection of rails-specific decorators.
7
+ # Why?
8
+ # Because Rails is magic, and it is like stepping on a minefield. Rails objects
9
+ # can trigger side-effects (like calling database queries, or even API queries).
10
+ # And from the end-user perspective, Rails' internal variables are useless. They
11
+ # care more about database attributes, which requires some extra steps to display
12
+ # if I don't want to use `#inspect`.
13
+ class RailsDecorator
14
+ ##
15
+ # Individual Active Record object is trivial. The object is a mapping from a DB
16
+ # entity to Ruby object. It is always in the memory.
17
+ class ActiveRecordBaseDecorator
18
+ def initialize(generic_decorator)
19
+ @generic_decorator = generic_decorator
20
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
21
+ end
22
+
23
+ def match?(variable)
24
+ return false unless defined?(ActiveRecord::Base)
25
+
26
+ RubyJard::Reflection.call_is_a?(variable, ActiveRecord::Base)
27
+ end
28
+
29
+ def decorate_singleline(variable, line_limit:, depth: 0)
30
+ label = RubyJard::Span.new(
31
+ content: RubyJard::Reflection.call_to_s(variable).chomp!('>'),
32
+ margin_right: 1, styles: :text_primary
33
+ )
34
+ spans = [label]
35
+ spans += @attributes_decorator.inline_pairs(
36
+ variable.attributes.each_with_index,
37
+ total: variable.attributes.length, line_limit: line_limit - label.content_length - 2,
38
+ process_key: false, depth: depth + 1
39
+ )
40
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
41
+ end
42
+
43
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
44
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
45
+
46
+ if singleline.map(&:content_length).sum < line_limit
47
+ [singleline]
48
+ else
49
+ spans = [RubyJard::Span.new(content: RubyJard::Reflection.call_to_s(variable), styles: :text_primary)]
50
+
51
+ item_count = 0
52
+ variable.attributes.each_with_index do |(key, value), index|
53
+ spans << @attributes_decorator.pair(
54
+ key, value, line_limit: line_limit, process_key: false, depth: depth + 1
55
+ )
56
+ item_count += 1
57
+ break if index >= lines - 2
58
+ end
59
+ if variable.attributes.length > item_count
60
+ spans << [RubyJard::Span.new(
61
+ content: "▸ #{variable.attributes.length - item_count} more...",
62
+ margin_left: 2, styles: :text_dim
63
+ )]
64
+ end
65
+ spans
66
+ end
67
+ end
68
+ end
69
+
70
+ ##
71
+ # When creating an active record relation, Rails won't trigger any SQL query, until
72
+ # to_ary events. It is required to check for records loaded before recursively display
73
+ # its children. Hint if the relation is not loaded yet.
74
+ class ActiveRecordRelationDecorator
75
+ def initialize(generic_decorator)
76
+ @generic_decorator = generic_decorator
77
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
78
+ end
79
+
80
+ def match?(variable)
81
+ return false unless defined?(ActiveRecord::Relation)
82
+
83
+ RubyJard::Reflection.call_class(variable) < ActiveRecord::Relation
84
+ rescue StandardError
85
+ false
86
+ end
87
+
88
+ def decorate_singleline(variable, line_limit:, depth: 0)
89
+ if variable.respond_to?(:loaded?) && variable.loaded?
90
+ spans = []
91
+ label = RubyJard::Span.new(
92
+ content: RubyJard::Reflection.call_to_s(variable).chomp('>'), styles: :text_primary
93
+ )
94
+ spans << label
95
+ spans += @attributes_decorator.inline_values(
96
+ variable.each_with_index,
97
+ total: variable.length, line_limit: line_limit - label.content_length - 2,
98
+ depth: depth + 1
99
+ )
100
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
101
+
102
+ spans
103
+ else
104
+ relation_summary(variable, line_limit)
105
+ end
106
+ end
107
+
108
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
109
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
110
+ if singleline.map(&:content_length).sum < line_limit
111
+ [singleline]
112
+ elsif !variable.respond_to?(:loaded?) || !variable.loaded?
113
+ [relation_summary(variable, first_line_limit)]
114
+ else
115
+ spans = [[RubyJard::Span.new(content: RubyJard::Reflection.call_to_s(variable), styles: :text_primary)]]
116
+
117
+ item_count = 0
118
+ variable.each_with_index do |value, index|
119
+ spans << @attributes_decorator.value(value, line_limit: line_limit, depth: depth + 1)
120
+
121
+ item_count += 1
122
+ break if index >= lines - 2
123
+ end
124
+ if variable.length > item_count
125
+ spans << [RubyJard::Span.new(
126
+ content: "▸ #{variable.length - item_count} more...",
127
+ margin_left: 2, styles: :text_dim
128
+ )]
129
+ end
130
+ spans
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def relation_summary(variable, line_limit)
137
+ overview = RubyJard::Reflection.call_to_s(variable).chomp('>')
138
+ width = overview.length + 1 + 12
139
+ spans = [RubyJard::Span.new(content: overview, styles: :text_primary)]
140
+ if RubyJard::Reflection.call_respond_to?(variable, :to_sql) && width < line_limit
141
+ detail = variable.to_sql
142
+ detail = detail[0..line_limit - width - 2] + '…' if width + detail.length < line_limit
143
+ spans << RubyJard::Span.new(content: detail, styles: :text_dim, margin_left: 1)
144
+ end
145
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
146
+ spans << RubyJard::Span.new(content: '(not loaded)', margin_left: 1, styles: :text_dim)
147
+ spans
148
+ end
149
+ end
150
+
151
+ def initialize(generic_decorator)
152
+ @generic_decorator = generic_decorator
153
+ @sub_decorators = [
154
+ @active_record_base_decorator = ActiveRecordBaseDecorator.new(generic_decorator),
155
+ @active_record_relation_decorator = ActiveRecordRelationDecorator.new(generic_decorator)
156
+ ]
157
+ end
158
+
159
+ def match?(variable)
160
+ @sub_decorators.any? { |sub_decorator| sub_decorator.match?(variable) }
161
+ rescue StandardError
162
+ false
163
+ end
164
+
165
+ def decorate_singleline(variable, line_limit:, depth: 0)
166
+ @sub_decorators.each do |sub_decorator|
167
+ next unless sub_decorator.match?(variable)
168
+
169
+ return sub_decorator.decorate_singleline(
170
+ variable, line_limit: line_limit, depth: depth
171
+ )
172
+ end
173
+
174
+ nil
175
+ end
176
+
177
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
178
+ @sub_decorators.each do |sub_decorator|
179
+ next unless sub_decorator.match?(variable)
180
+
181
+ return sub_decorator.decorate_multiline(
182
+ variable,
183
+ first_line_limit: first_line_limit,
184
+ lines: lines,
185
+ line_limit: line_limit,
186
+ depth: depth
187
+ )
188
+ end
189
+
190
+ nil
191
+ end
192
+ end
193
+ end
194
+ end
@@ -12,6 +12,8 @@ module RubyJard
12
12
  @file = file
13
13
  @lineno = lineno
14
14
  @window = window
15
+ @window_start = 0
16
+ @window_end = 0
15
17
  @codes = []
16
18
 
17
19
  decorate
@@ -25,7 +27,7 @@ module RubyJard
25
27
  end
26
28
 
27
29
  @window_start = @lineno - @window / 2
28
- @window_start = 0 if @window_start.negative?
30
+ @window_start = 1 if @window_start <= 0
29
31
  @window_end = @window_start + @window
30
32
 
31
33
  until file.eof?
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # A light decorator for a string. String should be escaped, and cut off.
7
+ class StringDecorator
8
+ def initialize(generic_decorator)
9
+ @generic_decorator = generic_decorator
10
+ end
11
+
12
+ def match?(variable)
13
+ RubyJard::Reflection.call_is_a?(variable, String)
14
+ end
15
+
16
+ # rubocop:disable Lint/UnusedMethodArgument
17
+ def decorate_multiline(variable, first_line_limit:, line_limit:, lines:, depth: 0)
18
+ [
19
+ decorate_singleline(variable, line_limit: first_line_limit)
20
+ ]
21
+ end
22
+
23
+ def decorate_singleline(variable, line_limit:, depth: 0)
24
+ inspection = variable.inspect[1..-1].chomp!('"')
25
+ str =
26
+ if inspection.length < line_limit - 2
27
+ inspection
28
+ else
29
+ inspection[0..line_limit - 4] + '…'
30
+ end
31
+ [
32
+
33
+ RubyJard::Span.new(content: '"', styles: :string),
34
+ RubyJard::Span.new(content: str, styles: :string),
35
+ RubyJard::Span.new(content: '"', styles: :string)
36
+ ]
37
+ end
38
+ # rubocop:enable Lint/UnusedMethodArgument
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Decorator for Struct.
7
+ # TODO: This one should handle Open Struct too
8
+ class StructDecorator
9
+ def initialize(generic_decorator)
10
+ @generic_decorator = generic_decorator
11
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
12
+ end
13
+
14
+ def match?(variable)
15
+ RubyJard::Reflection.call_is_a?(variable, Struct)
16
+ end
17
+
18
+ def decorate_singleline(variable, line_limit:, depth: 0)
19
+ spans = [RubyJard::Span.new(content: '#<struct', margin_right: 1, styles: :text_dim)]
20
+ unless variable.class.name.nil?
21
+ spans << RubyJard::Span.new(content: variable.class.name.to_s, margin_right: 1, styles: :text_primary)
22
+ end
23
+ spans += @attributes_decorator.inline_pairs(
24
+ variable.members.each_with_index,
25
+ total: variable.length, line_limit: line_limit - spans.map(&:content_length).sum - 1,
26
+ process_key: false, depth: depth + 1,
27
+ value_proc: ->(key) { variable[key] }
28
+ )
29
+ spans << RubyJard::Span.new(content: '>', styles: :text_dim)
30
+ end
31
+
32
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
33
+ if variable.size > lines * 1.5
34
+ return do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
35
+ end
36
+
37
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
38
+
39
+ if singleline.map(&:content_length).sum < line_limit || variable.length <= 1
40
+ [singleline]
41
+ else
42
+ do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def do_decorate_multiline(variable, lines:, line_limit:, depth: 0)
49
+ spans = []
50
+ start = [RubyJard::Span.new(content: '#<struct', styles: :text_dim)]
51
+ unless variable.class.name.nil?
52
+ start << RubyJard::Span.new(content: variable.class.name.to_s, margin_left: 1, styles: :text_primary)
53
+ end
54
+ start << RubyJard::Span.new(content: '>', styles: :text_dim)
55
+ spans << start
56
+
57
+ item_count = 0
58
+ variable.members.each_with_index do |member, index|
59
+ spans << @attributes_decorator.pair(
60
+ member, variable[member], line_limit: line_limit, process_key: false, depth: depth + 1
61
+ )
62
+ item_count += 1
63
+ break if index >= lines - 2
64
+ end
65
+ spans << last_line(variable.length, item_count) if variable.length > item_count
66
+ spans
67
+ end
68
+
69
+ def last_line(total, item_count)
70
+ [
71
+ RubyJard::Span.new(
72
+ content: "▸ #{total - item_count} more...",
73
+ margin_left: 2, styles: :text_dim
74
+ )
75
+ ]
76
+ end
77
+ end
78
+ end
79
+ end