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,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Adjust the layout between screens, render row's bitmap by calling RowRenderer,
6
+ # calculate screen window to ready for putting on the screen.
7
+ class ScreenRenderer
8
+ def initialize(screen:, color_scheme:)
9
+ @screen = screen
10
+ @color_scheme = color_scheme
11
+ end
12
+
13
+ def render
14
+ # Move this logic into a class called SreenRenderer
15
+ calculate_content_lengths
16
+ column_widths = calculate_column_widths
17
+ adjust_column_widths(column_widths)
18
+ calculate_window
19
+
20
+ @screen
21
+ end
22
+
23
+ private
24
+
25
+ def calculate_content_lengths
26
+ @screen.rows.each do |row|
27
+ row.columns.each do |column|
28
+ column.content_length = column.spans.map(&:content_length).inject(&:+) || 0
29
+ end
30
+ end
31
+ end
32
+
33
+ def calculate_column_widths
34
+ column_widths = {}
35
+ total_columns = count_columns
36
+
37
+ return column_widths if total_columns == 0
38
+
39
+ ideal_column_width = @screen.layout.width / total_columns
40
+ total_columns.times do |column_index|
41
+ column_widths[column_index] ||= 0
42
+ @screen.rows.each do |row|
43
+ column = row.columns[column_index]
44
+ if column.content_length > ideal_column_width - 1
45
+ column_widths[column_index] = nil
46
+ break
47
+ elsif column.content_length + 1 > column_widths[column_index]
48
+ column_widths[column_index] = column.content_length + 1
49
+ end
50
+ end
51
+ end
52
+ column_widths
53
+ end
54
+
55
+ def adjust_column_widths(column_widths)
56
+ dynamic_count = count_dynamic_columns(column_widths)
57
+ fixed_width = sum_fixed_width(column_widths)
58
+
59
+ @screen.rows.each do |row|
60
+ total_width = 0
61
+ row.columns.each_with_index do |column, column_index|
62
+ column.width =
63
+ if column_index == row.columns.length - 1
64
+ @screen.layout.width - total_width
65
+ elsif column_widths[column_index].nil?
66
+ (@screen.layout.width - fixed_width) / dynamic_count
67
+ else
68
+ column_widths[column_index]
69
+ end
70
+ column.content_width = column.width
71
+ column.content_width -= 1 if column_index < row.columns.length - 1
72
+
73
+ total_width += column.width
74
+ end
75
+ end
76
+ end
77
+
78
+ def count_dynamic_columns(column_widths)
79
+ column_widths.values.select(&:nil?).length
80
+ end
81
+
82
+ def sum_fixed_width(column_widths)
83
+ column_widths.values.inject(0) do |sum, col|
84
+ col.nil? ? sum : sum + col
85
+ end
86
+ end
87
+
88
+ def render_rows
89
+ @screen.rows.each do |row|
90
+ end
91
+ end
92
+
93
+ def calculate_window
94
+ @screen.window = []
95
+
96
+ if @screen.cursor.nil?
97
+ find_seleted_window
98
+ else
99
+ find_cursor_window
100
+ end
101
+ end
102
+
103
+ def find_seleted_window
104
+ @screen.rows.each_with_index do |row, row_index|
105
+ row_content(row).each_with_index do |line, line_index|
106
+ if @screen.window.length < @screen.layout.height
107
+ @screen.window << line
108
+ elsif row_index < @screen.selected
109
+ @screen.window = [line]
110
+ elsif row_index == @screen.selected
111
+ if line_index != 0
112
+ @screen.window.shift
113
+ @screen.window << line
114
+ else
115
+ @screen.window = [line]
116
+ end
117
+ else
118
+ return
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def find_cursor_window
125
+ cursor_line = -1
126
+ @screen.rows.each do |row|
127
+ row_content(row).each do |line|
128
+ cursor_line += 1
129
+ @screen.window << line if cursor_line >= @screen.cursor
130
+ return if @screen.window.length >= @screen.layout.height
131
+ end
132
+ end
133
+ end
134
+
135
+ def count_columns
136
+ @screen.rows.map { |row| row.columns.count }.max.to_i
137
+ end
138
+
139
+ def row_content(row)
140
+ unless row.rendered?
141
+ RubyJard::RowRenderer.new(
142
+ row: row,
143
+ width: @screen.layout.width,
144
+ height: @screen.layout.height,
145
+ color_scheme: @color_scheme
146
+ ).render
147
+ end
148
+
149
+ row.content
150
+ end
151
+ end
152
+ end
@@ -1,26 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ruby_jard/screen'
4
+
3
5
  module RubyJard
4
6
  ##
5
7
  # Screen registry. The screens call add_screen right after they are declared.
6
- module Screens
8
+ class Screens
7
9
  class << self
8
- def screen_registry
9
- @screen_registry ||= {}
10
+ extend Forwardable
11
+ def_delegators :instance, :add_screen, :[], :get, :names
12
+
13
+ def instance
14
+ @instance ||= new
10
15
  end
16
+ end
11
17
 
12
- def add_screen(name, screen_class)
13
- unless screen_class < RubyJard::Screen
14
- raise RubyJard::Error, "#{screen_class} must implement, and inherit from #{RubyJard::Screen}"
15
- end
18
+ def initialize
19
+ @screen_registry = {}
20
+ end
16
21
 
17
- screen_registry[name] = screen_class
22
+ def add_screen(name, screen_class)
23
+ unless screen_class < RubyJard::Screen
24
+ raise RubyJard::Error, "#{screen_class} must implement, and inherit from #{RubyJard::Screen}"
18
25
  end
19
26
 
20
- def [](name)
21
- screen_registry[name]
22
- end
23
- alias get []
27
+ @screen_registry[name.to_s] = screen_class
28
+ end
29
+
30
+ def [](name)
31
+ @screen_registry[name.to_s]
32
+ end
33
+ alias_method :get, :[]
34
+
35
+ def names
36
+ @screen_registry.keys.sort.dup
24
37
  end
25
38
  end
26
39
  end
40
+
41
+ require 'ruby_jard/screens/source_screen'
42
+ require 'ruby_jard/screens/backtrace_screen'
43
+ require 'ruby_jard/screens/threads_screen'
44
+ require 'ruby_jard/screens/variables_screen'
45
+ require 'ruby_jard/screens/menu_screen'
@@ -1,150 +1,152 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyJard
4
- module Screens
4
+ class Screens
5
+ ##
6
+ # Backtrace screen implements the content to display current thread's backtrace to the user.
5
7
  class BacktraceScreen < RubyJard::Screen
6
- def draw
7
- @output.print TTY::Box.frame(**frame_styles)
8
-
9
- @output.print TTY::Cursor.move_to(@col + 2, @row)
10
- @output.print decorate_text
11
- .with_highlight(true)
12
- .text(" Backtrace (#{frames_count}) ", :bright_yellow)
13
- .content
14
-
15
- decorate_frames.each_with_index do |frame_texts, index|
16
- left, right = frame_texts
17
- @output.print TTY::Cursor.move_to(@col + 1, @row + index + 1)
18
- @output.print left.content
19
-
20
- next unless @col + left.length < @col + @layout.width - right.length - 1
8
+ def initialize(*args)
9
+ super(*args)
10
+ @current_frame = @session.current_frame
11
+ @frames =
12
+ @session
13
+ .current_backtrace
14
+ .select(&:visible?)
15
+ .sort { |a, b| a.virtual_pos.to_i <=> b.virtual_pos.to_i }
16
+ insert_current_frame
17
+ @selected =
18
+ if @current_frame.nil?
19
+ 0
20
+ else
21
+ @frames.find_index { |f| f.real_pos == @current_frame.real_pos }
22
+ end
23
+ @frames_count = @frames.length
24
+ @hidden_frames_count = @session.current_backtrace.length - @frames.length
21
25
 
22
- # TODO: handle reducable components in case of smaller screen
23
- @output.print TTY::Cursor.move_to(@col + @layout.width - right.length, @row + index + 1)
24
- @output.print right.content
25
- end
26
+ @path_decorator = RubyJard::Decorators::PathDecorator.new
26
27
  end
27
28
 
28
- private
29
-
30
- def data_size
31
- @layout.height - 1
29
+ def title
30
+ if @hidden_frames_count <= 0
31
+ ['Backtrace', "#{@frames_count} frames"]
32
+ else
33
+ ['Backtrace', "#{@frames_count} frames - #{@hidden_frames_count} hidden"]
34
+ end
32
35
  end
33
36
 
34
- def frame_styles
35
- default_frame_styles.merge(
36
- top: @row, left: @col, width: @layout.width, height: @layout.height
37
- )
37
+ def build
38
+ @rows = @frames.map do |frame|
39
+ RubyJard::Row.new(
40
+ line_limit: 2,
41
+ columns: [
42
+ RubyJard::Column.new(
43
+ spans: [
44
+ span_frame_pos(frame)
45
+ ]
46
+ ),
47
+ RubyJard::Column.new(
48
+ spans: [
49
+ span_class_label(frame),
50
+ span_label_preposition,
51
+ span_method_label(frame),
52
+ span_path(frame)
53
+ ]
54
+ )
55
+ ]
56
+ )
57
+ end
38
58
  end
39
59
 
40
- def decorate_frames
41
- return [] if data_size.zero?
42
-
43
- window_start = frame_pos / data_size * data_size
44
- window_end = [frames_count, window_start + data_size - 1].min
60
+ private
45
61
 
46
- backtrace[window_start..window_end]
47
- .map
48
- .with_index do |frame, frame_index|
49
- decorate_frame(frame, window_start + frame_index, window_start, window_end)
62
+ def span_frame_pos(frame)
63
+ frame_pos_label =
64
+ if frame.hidden?
65
+ '*'.rjust(@frames_count.to_s.length)
66
+ else
67
+ frame.virtual_pos.to_s.rjust(@frames_count.to_s.length)
50
68
  end
69
+ if frame.real_pos == @current_frame.real_pos
70
+ RubyJard::Span.new(
71
+ content: "⮕ #{frame_pos_label}",
72
+ styles: :text_selected
73
+ )
74
+ else
75
+ RubyJard::Span.new(
76
+ content: " #{frame_pos_label}",
77
+ styles: :text_dim
78
+ )
79
+ end
51
80
  end
52
81
 
53
- def decorate_frame(line, frame_id, window_start, window_end)
54
- location = line[0]
55
- object = line[1]
56
- klass = line[2]
57
-
58
- left =
59
- decorate_frame_id(frame_id, window_start, window_end) +
60
- ' ' +
61
- decorate_location_label(frame_id, location, object, klass)
62
- right = decorate_location_path(frame_id, location)
63
-
64
- [left, right]
65
- end
66
-
67
- def reset
68
- @color = Pastel.new
69
- end
82
+ def span_class_label(frame)
83
+ self_class = RubyJard::Reflection.call_class(frame.frame_self)
84
+ class_label =
85
+ if frame.frame_class.nil? || self_class == frame.frame_class
86
+ if ::RubyJard::Reflection.call_is_a?(frame.frame_self, Class)
87
+ frame.frame_self.name
88
+ else
89
+ self_class.name
90
+ end
91
+ elsif frame.frame_class.singleton_class?
92
+ # No easy way to get the original class of a singleton class
93
+ frame.frame_self.respond_to?(:name) ? frame.frame_self.name : frame.frame_self.to_s
94
+ else
95
+ frame.frame_class.name
96
+ end
70
97
 
71
- def decorate_frame_id(frame_id, _window_start, window_end)
72
- decorate_text
73
- .with_highlight(frame_pos == frame_id)
74
- .text(frame_pos == frame_id ? '→ ' : ' ', :white)
75
- .text(frame_id.to_s.ljust(window_end.to_s.length), frame_pos == frame_id ? :bright_yellow : :white)
98
+ c_frame = frame.c_frame? ? '[c] ' : ''
99
+ RubyJard::Span.new(
100
+ content: "#{c_frame}#{class_label}",
101
+ margin_right: 1,
102
+ styles: :constant
103
+ )
76
104
  end
77
105
 
78
- def decorate_location_label(frame_id, location, object, klass)
79
- decorate_text
80
- .with_highlight(frame_pos == frame_id)
81
- .text(backtrace[frame_id].last.nil? ? '[c] ' : '', :green)
82
- .text(decorate_object_label(object, klass), :green)
83
- .text(' in ', :white)
84
- .text(decorate_method_label(location), :green)
106
+ def span_label_preposition
107
+ RubyJard::Span.new(
108
+ content: 'in',
109
+ margin_right: 1,
110
+ styles: :text_primary
111
+ )
85
112
  end
86
113
 
87
- def decorate_object_label(object, klass)
88
- if klass.nil? || object.class == klass
89
- if object.is_a?(Class)
90
- object.name
114
+ def span_method_label(frame)
115
+ method_label =
116
+ if frame.frame_location.label != frame.frame_location.base_label
117
+ "#{frame.frame_location.base_label} (#{frame.frame_location.label.split(' ').first})"
91
118
  else
92
- object.class.name
119
+ frame.frame_location.base_label
93
120
  end
94
- elsif klass.singleton_class?
95
- # No easy way to get the original class of a singleton class
96
- object.name
97
- else
98
- klass.name
99
- end
121
+ RubyJard::Span.new(
122
+ content: method_label,
123
+ margin_right: 1,
124
+ styles: :method
125
+ )
100
126
  end
101
127
 
102
- def decorate_method_label(location)
103
- if location.label != location.base_label
104
- "#{location.base_label} (#{location.label.split(' ').first})"
105
- else
106
- location.base_label
107
- end
128
+ def span_path(frame)
129
+ path_label, = @path_decorator.decorate(
130
+ frame.frame_location.path, frame.frame_location.lineno
131
+ )
132
+ RubyJard::Span.new(
133
+ content: path_label,
134
+ styles: :text_primary
135
+ )
108
136
  end
109
137
 
110
- def decorate_location_path(frame_id, location)
111
- decorated_path = decorate_path(location.absolute_path, location.lineno)
112
-
113
- if decorated_path.gem?
114
- decorate_text
115
- .with_highlight(frame_pos == frame_id)
116
- .text('in ', :bright_white)
117
- .text(decorated_path.gem, :bright_white)
118
- .text(' (', :bright_white)
119
- .text(decorated_path.gem_version, :bright_white)
120
- .text(')', :bright_white)
121
- else
122
- decorate_text
123
- .with_highlight(frame_pos == frame_id)
124
- .text('at ', :bright_white)
125
- .text(decorated_path.path, :bright_white)
126
- .text(':', :bright_white)
127
- .text(decorated_path.lineno, :bright_white)
128
- end
129
- end
138
+ def insert_current_frame
139
+ return if @current_frame.visible?
130
140
 
131
- def frame_pos
132
- if @session.frame.nil?
133
- 0
141
+ index = @frames.find_index { |f| @current_frame.real_pos < f.real_pos }
142
+ if index.nil?
143
+ @frames << @current_frame
134
144
  else
135
- @session.frame.pos.to_i
145
+ @frames.insert(index, @current_frame)
136
146
  end
137
147
  end
138
-
139
- def frames_count
140
- @session.backtrace.length
141
- end
142
-
143
- def backtrace
144
- @session.backtrace
145
- end
146
148
  end
147
149
  end
148
150
  end
149
151
 
150
- RubyJard::Screens.add_screen(:backtrace, RubyJard::Screens::BacktraceScreen)
152
+ RubyJard::Screens.add_screen('backtrace', RubyJard::Screens::BacktraceScreen)