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
@@ -6,56 +6,29 @@ module RubyJard
6
6
  # generated based on input layout specifiation, screen data, and top-left
7
7
  # corner cordination.
8
8
  class Screen
9
- attr_reader :output
9
+ attr_accessor :layout, :rows, :window, :cursor, :selected
10
10
 
11
- def initialize(layout:, output:, session:, row:, col:)
12
- @output = output
13
- @session = session
11
+ def initialize(layout, session: nil)
12
+ @session = session || RubyJard::Session
14
13
  @layout = layout
15
- @row = row
16
- @col = col
17
- @color_decorator = Pastel.new
14
+ @window = []
15
+ @cursor = nil
16
+ @selected = 0
17
+ @rows = []
18
18
  end
19
19
 
20
- def draw(_row, _col, _size)
21
- raise NotImplementedError, "#{self.class} must implement #draw method"
22
- end
20
+ def move_down; end
23
21
 
24
- def decorate_text
25
- # TODO: this interface is ugly as fuck
26
- RubyJard::Decorators::TextDecorator.new(@color_decorator)
27
- end
22
+ def move_up; end
28
23
 
29
- def decorate_path(path, lineno)
30
- # TODO: this interface is ugly as fuck
31
- RubyJard::Decorators::PathDecorator.new(path, lineno)
32
- end
24
+ def page_up; end
33
25
 
34
- def decorate_source(file, lineno, window)
35
- # TODO: this interface is ugly as fuck
36
- RubyJard::Decorators::SourceDecorator.new(file, lineno, window)
37
- end
26
+ def page_down; end
38
27
 
39
- def decorate_loc(loc, highlighted)
40
- # TODO: this interface is ugly as fuck
41
- RubyJard::Decorators::LocDecorator.new(@color_decorator, loc, highlighted)
42
- end
28
+ def click(relative_x, relative_y); end
43
29
 
44
- private
45
-
46
- def default_frame_styles
47
- {
48
- style: {
49
- fg: :white
50
- },
51
- border: {
52
- bottom_left: false,
53
- bottom_right: false,
54
- bottom: false,
55
- left: :line,
56
- right: false
57
- }
58
- }
30
+ def build
31
+ raise NotImplementedError, "#{self.class} should implement this method."
59
32
  end
60
33
  end
61
34
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Implement elastic screen height. If a screen is shrinkable (which means)
6
+ # its current window is less than its height, it will be forced to give
7
+ # away those spaces to other screens. This layout adjustment is small, and
8
+ # should not affect the original generated layout too much, nor work with
9
+ # nested layout.
10
+ class ScreenAdjuster
11
+ def initialize(screens)
12
+ @screens = screens
13
+ end
14
+
15
+ def adjust
16
+ groups = @screens.group_by { |screen| screen.layout.parent_template }
17
+ groups.each do |_, grouped_screens|
18
+ next if grouped_screens.length <= 1
19
+ next unless same_column?(grouped_screens)
20
+
21
+ grouped_screens.sort_by! { |screen| screen.layout.box_y }
22
+ shrinkable_screens = grouped_screens.select { |s| shrinkable?(s) }
23
+ expandable_screens = grouped_screens.select { |s| expandable?(s) }
24
+
25
+ next if shrinkable_screens.empty? || expandable_screens.empty?
26
+
27
+ budget = shrinkable_screens.map { |s| shrinkable_height(s) }.sum
28
+ expand_screens(expandable_screens, budget)
29
+ shrink_screens(shrinkable_screens)
30
+ compact_screens(grouped_screens)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def same_column?(screens)
37
+ column_x = screens.first.layout.box_x
38
+ column_width = screens.first.layout.box_width
39
+ screens.all? { |s| s.layout.box_x == column_x && s.layout.box_width == column_width }
40
+ end
41
+
42
+ def expand_screens(expandable_screens, budget)
43
+ budget_each = budget / expandable_screens.length
44
+
45
+ expandable_screens.each_with_index do |screen, index|
46
+ if index == expandable_screens.length - 1
47
+ screen.layout.height += budget
48
+ screen.layout.box_height += budget
49
+ else
50
+ screen.layout.height += budget_each
51
+ screen.layout.box_height += budget_each
52
+ budget_each -= budget_each
53
+ end
54
+ end
55
+ end
56
+
57
+ def shrink_screens(shrinkable_screens)
58
+ shrinkable_screens.each do |screen|
59
+ delta = shrinkable_height(screen)
60
+ screen.layout.height -= delta
61
+ screen.layout.box_height -= delta
62
+ end
63
+ end
64
+
65
+ def compact_screens(screens)
66
+ box_y = screens.first.layout.box_y
67
+ screens.each do |screen|
68
+ screen.layout.box_y = box_y
69
+ screen.layout.y = box_y + 1
70
+ box_y += screen.layout.box_height - 1
71
+ end
72
+ end
73
+
74
+ def shrinkable?(screen)
75
+ case screen.layout.template.adjust_mode
76
+ when :expand
77
+ false
78
+ else
79
+ screen.window.length < screen.layout.height
80
+ end
81
+ end
82
+
83
+ def expandable?(screen)
84
+ case screen.layout.template.adjust_mode
85
+ when :expand
86
+ true
87
+ else
88
+ screen.window.length >= screen.layout.height
89
+ end
90
+ end
91
+
92
+ def shrinkable_height(screen)
93
+ if screen.window.length < screen.layout.height
94
+ window_height = screen.window.length
95
+ if !screen.layout.template.min_height.nil? && screen.layout.template.min_height > window_height
96
+ window_height = screen.layout.template.min_height
97
+ end
98
+ screen.layout.height - window_height
99
+ else
100
+ 0
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Draw rendered screen bitmap in current screen window onto the screen.
6
+ # Fulfill missing window if needed
7
+ class ScreenDrawer
8
+ def initialize(output:, screen:, color_scheme:)
9
+ @output = output
10
+ @screen = screen
11
+ @color_decorator = RubyJard::Decorators::ColorDecorator.new(color_scheme)
12
+ end
13
+
14
+ def draw
15
+ @screen.window.each_with_index do |line, index|
16
+ RubyJard::Console.move_to(@output, @screen.layout.x, @screen.layout.y + index)
17
+ @output.print line
18
+ end
19
+ (@screen.window.length..@screen.layout.height - 1).each do |index|
20
+ RubyJard::Console.move_to(@output, @screen.layout.x, @screen.layout.y + index)
21
+ @output.print @color_decorator.decorate(:background, ' ' * @screen.layout.width)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,121 +1,206 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby_jard/decorators/text_decorator'
3
+ require 'ruby_jard/console'
4
+
5
+ require 'ruby_jard/decorators/color_decorator'
4
6
  require 'ruby_jard/decorators/path_decorator'
5
7
  require 'ruby_jard/decorators/loc_decorator'
6
8
  require 'ruby_jard/decorators/source_decorator'
7
- require 'ruby_jard/screen'
9
+ require 'ruby_jard/decorators/inspection_decorator'
10
+
8
11
  require 'ruby_jard/screens'
9
- require 'ruby_jard/screens/breakpoints_screen'
10
- require 'ruby_jard/screens/expressions_sreen'
11
- require 'ruby_jard/screens/source_screen'
12
- require 'ruby_jard/screens/backtrace_screen'
13
- require 'ruby_jard/screens/threads_screen'
14
- require 'ruby_jard/screens/variables_screen'
15
- require 'ruby_jard/screens/menu_screen'
16
- require 'ruby_jard/layout_template'
17
- require 'ruby_jard/layout'
12
+ require 'ruby_jard/color_schemes'
13
+ require 'ruby_jard/layouts'
14
+
15
+ require 'ruby_jard/row'
16
+ require 'ruby_jard/column'
17
+ require 'ruby_jard/span'
18
+ require 'ruby_jard/row_renderer'
19
+ require 'ruby_jard/screen_renderer'
20
+ require 'ruby_jard/screen_adjuster'
21
+ require 'ruby_jard/box_drawer'
22
+ require 'ruby_jard/screen_drawer'
18
23
 
19
24
  module RubyJard
20
25
  ##
21
26
  # This class acts as a coordinator, in which it combines the data and screen
22
27
  # layout template, triggers each screen to draw on the terminal.
23
28
  class ScreenManager
24
- attr_reader :output
29
+ class << self
30
+ extend Forwardable
31
+
32
+ def_delegators :instance, :draw_screens, :puts, :draw_error, :start, :stop, :started?, :updating?
33
+
34
+ def instance
35
+ @instance ||= new
36
+ end
37
+ end
38
+
39
+ attr_reader :output, :output_storage
25
40
 
26
- def initialize(session:, output: STDOUT)
41
+ def initialize(output: STDOUT)
27
42
  @output = output
28
- @session = session
29
43
  @screens = {}
44
+ @started = false
45
+ @updating = false
30
46
  end
31
47
 
32
48
  def start
33
- refresh
34
- end
35
-
36
- def refresh
37
- clear_screen
38
- width = TTY::Screen.width
39
- height = TTY::Screen.height
40
- template = pick_template(width, height)
41
- layout = RubyJard::Layout.generate(template: template, width: width, height: height)
42
- begin
43
- draw(layout, 0, 0)
44
- rescue StandardError => e
45
- clear_screen
46
- @output.puts e
47
- @output.puts e.backtrace
48
- end
49
+ return if started?
50
+
51
+ # Load configurations
52
+ RubyJard.config
53
+ RubyJard::Console.clear_screen(@output)
54
+
55
+ @started = true
49
56
  end
50
57
 
51
- private
58
+ def started?
59
+ @started == true
60
+ end
52
61
 
53
- def clear_screen
54
- @output.print TTY::Cursor.clear_screen
55
- @output.print TTY::Cursor.move_to(0, 0)
62
+ def updating?
63
+ @updating == true
56
64
  end
57
65
 
58
- def draw(layout, row, col)
59
- @output.print TTY::Cursor.move_to(col, row)
66
+ def stop
67
+ return unless started?
60
68
 
61
- if layout.screen.nil?
62
- draw_children(layout, row, col)
63
- else
64
- screen = fetch_screen(layout.screen)
65
- screen&.new(
69
+ @started = false
70
+
71
+ RubyJard::Console.cooked!
72
+ RubyJard::Console.enable_echo!
73
+ RubyJard::Console.enable_cursor!
74
+ end
75
+
76
+ def draw_screens
77
+ start unless started?
78
+ @updating = true
79
+
80
+ RubyJard::Console.clear_screen(@output)
81
+ RubyJard::Console.disable_cursor!
82
+ width, height = RubyJard::Console.screen_size(@output)
83
+
84
+ @layouts = calculate_layouts(width, height)
85
+ @screens = build_screens(@layouts)
86
+
87
+ RubyJard::Console.move_to(@output, 0, 0)
88
+
89
+ draw_box(@screens)
90
+ @screens.each do |screen|
91
+ RubyJard::ScreenDrawer.new(
66
92
  output: @output,
67
- session: @session,
68
- layout: layout,
69
- row: row,
70
- col: col
71
- )&.draw
93
+ screen: screen,
94
+ color_scheme: pick_color_scheme
95
+ ).draw
72
96
  end
97
+
98
+ RubyJard::Console.move_to(@output, 0, total_screen_height(@layouts) + 1)
99
+ RubyJard::Console.clear_screen_to_end(@output)
100
+ rescue StandardError => e
101
+ RubyJard::Console.clear_screen(@output)
102
+ draw_error(e, height)
103
+ ensure
104
+ # You don't want to mess up previous user TTY no matter happens
105
+ RubyJard::Console.cooked!
106
+ RubyJard::Console.enable_echo!
107
+ RubyJard::Console.enable_cursor!
108
+ @updating = false
73
109
  end
74
110
 
75
- # rubocop:disable Metrics/AbcSize
76
- def draw_children(layout, row, col)
77
- children_row = row
78
- children_col = col
79
- drawing_width = 0
80
- max_height = 0
81
- layout.children.each do |child|
82
- draw(child, children_row, children_col)
83
-
84
- drawing_width += child.width
85
- max_height = child.height if max_height < child.height
86
- # Overflow. Break to next line
87
- if drawing_width >= layout.width
88
- children_row += max_height
89
- children_col = col
90
- drawing_width = 0
91
- max_height = 0
92
- else
93
- children_col += child.width
94
- end
111
+ def scroll; end
112
+
113
+ def click; end
114
+
115
+ def draw_error(exception, height = 0)
116
+ @output.print RubyJard::Decorators::ColorDecorator::CSI_RESET
117
+ @output.puts '--- Error ---'
118
+ @output.puts "Internal error from Jard. I'm sorry to mess up your debugging experience."
119
+ @output.puts 'It would be great if you can submit an issue in https://github.com/nguyenquangminh0711/ruby_jard/issues'
120
+ @output.puts ''
121
+ @output.puts exception
122
+ if height == 0
123
+ @output.puts exception.backtrace
124
+ else
125
+ @output.puts exception.backtrace.first(10)
95
126
  end
127
+ @output.puts '-------------'
128
+ RubyJard.error(exception)
129
+ end
130
+
131
+ def puts(content)
132
+ @output.write "#{content}\n", from_jard: true
133
+ end
96
134
 
97
- @output.print TTY::Cursor.move_to(0, children_row + 1)
135
+ private
136
+
137
+ def calculate_layouts(width, height)
138
+ layout = RubyJard::LayoutPicker.new(width, height).pick
139
+ RubyJard::LayoutCalculator.calculate(
140
+ layout_template: layout,
141
+ width: width, height: height,
142
+ x: 0, y: 0
143
+ )
144
+ end
145
+
146
+ def build_screens(layouts)
147
+ screens = layouts.map do |layout|
148
+ screen_class = fetch_screen(layout.template.screen)
149
+ screen = screen_class.new(layout)
150
+ screen.build
151
+ render_screen(screen)
152
+ screen
153
+ end
154
+ RubyJard::ScreenAdjuster.new(screens).adjust
155
+ layouts.map do |layout|
156
+ screen_class = fetch_screen(layout.template.screen)
157
+ screen = screen_class.new(layout)
158
+ screen.build
159
+ render_screen(screen)
160
+ screen
161
+ end
162
+ end
163
+
164
+ def draw_box(screens)
165
+ RubyJard::BoxDrawer.new(
166
+ output: @output,
167
+ screens: screens,
168
+ color_scheme: pick_color_scheme
169
+ ).draw
170
+ end
171
+
172
+ def render_screen(screen)
173
+ RubyJard::ScreenRenderer.new(
174
+ screen: screen,
175
+ color_scheme: pick_color_scheme
176
+ ).render
177
+ end
178
+
179
+ def draw_debug(_width, height)
180
+ @output.print RubyJard::Decorators::ColorDecorator::CSI_RESET
181
+ unless RubyJard.debug_info.empty?
182
+ @output.puts '--- Debug ---'
183
+ RubyJard.debug_info.first(height - 2).each do |line|
184
+ @output.puts line
185
+ end
186
+ @output.puts '-------------'
187
+ end
188
+ RubyJard.clear_debug
98
189
  end
99
- # rubocop:enable Metrics/AbcSize
100
190
 
101
191
  def fetch_screen(name)
102
192
  RubyJard::Screens[name]
103
193
  end
104
194
 
105
- def pick_template(width, height)
106
- RubyJard::DEFAULT_LAYOUT_TEMPLATES.each do |template|
107
- matched = true
108
- matched &&= (
109
- template.min_width.nil? ||
110
- width > template.min_width
111
- )
112
- matched &&= (
113
- template.min_height.nil? ||
114
- height > template.min_height
115
- )
116
- return template if matched
117
- end
118
- RubyJard::DEFAULT_LAYOUT_TEMPLATES.first
195
+ def total_screen_height(layouts)
196
+ layouts.map { |layout| layout.y + layout.height }.max || 0
197
+ end
198
+
199
+ def pick_color_scheme
200
+ color_scheme_class =
201
+ RubyJard::ColorSchemes[RubyJard.config.color_scheme] ||
202
+ RubyJard::ColorSchemes[RubyJard::Config::DEFAULT_COLOR_SCHEME]
203
+ color_scheme_class.new
119
204
  end
120
205
  end
121
206
  end