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,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # This class is a wrapper for Byebug::Frame. This class prevents direct
6
+ # access to Byebug's internal data structure, provides some more helpers
7
+ # and make Jard easier to test.
8
+ class Frame
9
+ attr_reader :real_pos
10
+ attr_writer :visible
11
+ attr_accessor :virtual_pos
12
+
13
+ def initialize(context, real_pos, virtual_pos: nil)
14
+ @context = context
15
+ @real_pos = real_pos
16
+ @virtual_pos = virtual_pos
17
+
18
+ @visible = true
19
+ end
20
+
21
+ def visible?
22
+ @visible == true
23
+ end
24
+
25
+ def hidden?
26
+ @visible == false
27
+ end
28
+
29
+ def frame_file
30
+ @context.frame_file(@real_pos)
31
+ end
32
+
33
+ def frame_line
34
+ @context.frame_line(@real_pos)
35
+ end
36
+
37
+ def frame_location
38
+ frame_backtrace = @context.backtrace[@real_pos]
39
+ return nil if frame_backtrace.nil?
40
+
41
+ frame_backtrace.first
42
+ end
43
+
44
+ def frame_self
45
+ @context.frame_self(@real_pos)
46
+ end
47
+
48
+ def frame_class
49
+ @context.frame_class(@real_pos)
50
+ end
51
+
52
+ def frame_binding
53
+ @context.frame_binding(@real_pos)
54
+ end
55
+
56
+ def frame_method
57
+ @context.frame_method(@real_pos)
58
+ end
59
+
60
+ def c_frame?
61
+ frame_binding.nil?
62
+ end
63
+
64
+ def thread
65
+ @context.thread
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # A key binding object
6
+ class KeyBinding
7
+ attr_reader :sequence, :action
8
+
9
+ def initialize(sequence, action)
10
+ @sequence = sequence
11
+ @action = action
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Register custom key bindings and corresponding action, try to
6
+ # match a key binding sequence from input.
7
+ # As this class is performant-sensitive, a lookup tree is built
8
+ # and updated whenever a new key is added.
9
+ class KeyBindings
10
+ attr_reader :indexes
11
+
12
+ def initialize
13
+ @key_bindings = []
14
+ @indexes = {}
15
+ end
16
+
17
+ def to_a
18
+ @key_bindings.dup
19
+ end
20
+
21
+ def push(sequence, action)
22
+ if sequence.is_a?(Array)
23
+ sequence.each { |s| push(s, action) }
24
+ return
25
+ end
26
+
27
+ raise RubyJard::Error if sequence.to_s.empty?
28
+
29
+ key_binding = RubyJard::KeyBinding.new(sequence, action)
30
+ reindex(key_binding)
31
+ @key_bindings << key_binding
32
+ end
33
+
34
+ def match(&read_key)
35
+ raise RubyJard::Error, 'This method requires a block' unless block_given?
36
+
37
+ buffer = ''
38
+ node = @indexes
39
+ loop do
40
+ keys = read_key.call
41
+ if keys.nil?
42
+ # No more key. Match the current node
43
+ if node[nil].nil?
44
+ return buffer
45
+ else
46
+ return node[nil]
47
+ end
48
+ end
49
+
50
+ buffer += keys
51
+ keys.bytes.each do |byte|
52
+ if node[byte].is_a?(Hash)
53
+ # Not sure, continue to match
54
+ node = node[byte]
55
+ elsif node[byte].nil?
56
+ # It's sure that no more bindings to match
57
+ return buffer
58
+ elsif buffer == node[byte].sequence
59
+ # Exact match current key binding
60
+ return node[byte]
61
+ else
62
+ return buffer
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def reindex(key_binding)
71
+ parent = nil
72
+ node = @indexes
73
+ bytes = key_binding.sequence.bytes
74
+ bytes.each do |byte|
75
+ if node[byte].nil?
76
+ node[byte] = {}
77
+ elsif node[byte].is_a?(KeyBinding)
78
+ return if node[byte].sequence == key_binding.sequence
79
+
80
+ # Propagate the tree node
81
+ node[byte] = {
82
+ nil => node[byte]
83
+ }
84
+ end
85
+ parent = node
86
+ node = node[byte]
87
+ end
88
+
89
+ if node.empty?
90
+ parent[bytes.last] = key_binding
91
+ else
92
+ node[nil] = key_binding
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # A helper class to store pre-defined keys and key bindings
6
+ class Keys
7
+ # X-Term: https://invisible-island.net/xterm/xterm-function-keys.html
8
+ END_LINE = ["\n", "\r\n", "\r"].freeze
9
+ CTRL_C = "\u0003"
10
+ CTRL_D = "\u0004"
11
+
12
+ F1 = "\eOP"
13
+ F2 = "\eOQ"
14
+ F3 = "\eOR"
15
+ F4 = "\eOS"
16
+ F5 = "\e[15~"
17
+ F6 = "\e[17~"
18
+ F7 = "\e[18~"
19
+ F8 = "\e[19~"
20
+ F9 = "\e[20~"
21
+ F10 = "\e[21~"
22
+ F11 = "\e[23~"
23
+ F12 = "\e[24~"
24
+
25
+ SHIFT_F1 = "\e1;2P"
26
+ SHIFT_F2 = "\e1;2Q"
27
+ SHIFT_F3 = "\e1;2R"
28
+ SHIFT_F4 = "\e1;2S"
29
+ SHIFT_F5 = "\e[15;2~"
30
+ SHIFT_F6 = "\e[17;2~"
31
+ SHIFT_F7 = "\e[18;2~"
32
+ SHIFT_F8 = "\e[19;2~"
33
+ SHIFT_F9 = "\e[20;2~"
34
+ SHIFT_F10 = "\e[21;2~"
35
+ SHIFT_F11 = "\e[23;2~"
36
+ SHIFT_F12 = "\e[24;2~"
37
+ DEFAULT_KEY_BINDINGS = {
38
+ F2 => (ACTION_FILTER = :switch_filter),
39
+ F5 => (ACTION_LIST = :list),
40
+ F6 => (ACTION_UP = :up),
41
+ SHIFT_F6 => (ACTION_DOWN = :down),
42
+ F7 => (ACTION_STEP = :step),
43
+ SHIFT_F7 => (ACTION_STEP_OUT = :step_out),
44
+ F8 => (ACTION_NEXT = :next),
45
+ F9 => (ACTION_CONTINUE = :continue)
46
+ }.freeze
47
+ end
48
+ end
@@ -2,98 +2,27 @@
2
2
 
3
3
  module RubyJard
4
4
  ##
5
- # Layout calculator based on screen resolution to decide the height, width,
6
- # visibility, data size of each children screen.
7
- # TODO: Right now, the sizes are fixed regardless of current screen data size.
5
+ # Data object to store calculated layout
8
6
  class Layout
9
- def self.generate(**args)
10
- layout = new(**args)
11
- layout.generate
12
- layout
13
- end
14
-
15
- attr_accessor :width, :height, :screen, :children
16
-
17
- def initialize(template:, width: 0, height: 0)
7
+ attr_accessor :template, :parent_template,
8
+ :box_width, :box_height, :box_x, :box_y,
9
+ :width, :height, :x, :y
10
+
11
+ def initialize(
12
+ template: nil, parent_template: nil,
13
+ width: 0, height: 0, x: 0, y: 0,
14
+ box_width: 0, box_height: 0, box_x: 0, box_y: 0
15
+ )
18
16
  @template = template
17
+ @box_width = box_width
18
+ @box_height = box_height
19
+ @box_x = box_x
20
+ @box_y = box_y
19
21
  @width = width
20
22
  @height = height
21
- @screen = nil
22
- @children = []
23
- end
24
-
25
- def generate
26
- if @template.screen.nil? && !@template.children.empty?
27
- generate_childen
28
- else
29
- # Ignore children if a layout is a screen
30
- @screen = @template.screen
31
- end
32
-
33
- self
34
- end
35
-
36
- private
37
-
38
- def generate_childen
39
- total_height = 0
40
- total_width = 0
41
-
42
- @children = @template.children.map.with_index do |child_template, index|
43
- child = RubyJard::Layout.new(
44
- template: child_template,
45
- height: calculate_child_height(child_template, index, total_height),
46
- width: calculate_child_width(child_template, index, total_width)
47
- )
48
- child.generate
49
-
50
- total_width += child.width
51
- total_height += child.height
52
-
53
- child
54
- end
55
- end
56
-
57
- def calculate_child_height(child_template, index, total_height)
58
- height =
59
- if !child_template.height.nil?
60
- child_template.height
61
- elsif child_template.height_ratio.nil?
62
- @height
63
- else
64
- @height * child_template.height_ratio / 100
65
- end
66
-
67
- unless child_template.min_height.nil?
68
- height = child_template.min_height if height < child_template.min_height
69
- end
70
-
71
- if @template.fill_height && index == @template.children.length - 1
72
- height = @height - total_height if height < (@height - total_height)
73
- end
74
-
75
- height
76
- end
77
-
78
- def calculate_child_width(child_template, index, total_width)
79
- width =
80
- if !child_template.width.nil?
81
- child_template.width
82
- elsif child_template.width_ratio.nil?
83
- @width
84
- else
85
- @width * child_template.width_ratio / 100
86
- end
87
-
88
- unless child_template.min_width.nil?
89
- width = child_template.min_width if width < child_template.min_width
90
- end
91
-
92
- if @template.fill_width && index == @template.children.length - 1
93
- width = @width - total_width if width < (@width - total_width)
94
- end
95
-
96
- width
23
+ @x = x
24
+ @y = y
25
+ @parent_template = parent_template
97
26
  end
98
27
  end
99
28
  end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/MethodLength
4
+ module RubyJard
5
+ ##
6
+ # Layout calculator based on screen resolution to decide the height, width,
7
+ # visibility, data size of each children screen.
8
+ # TODO: Right now, the sizes are fixed regardless of current screen data size.
9
+ class LayoutCalculator
10
+ def self.calculate(**args)
11
+ new(**args).calculate
12
+ end
13
+
14
+ def initialize(
15
+ layout_template:, width: 0, height: 0, x: 0, y: 0,
16
+ config: RubyJard.config
17
+ )
18
+ @layout_template = layout_template
19
+ @width = width
20
+ @height = height
21
+ @x = x
22
+ @y = y
23
+ @layouts = []
24
+ @config = config
25
+ end
26
+
27
+ def calculate
28
+ @layouts = []
29
+ calculate_layout(@layout_template, @width, @height, @x, @y, nil)
30
+ @layouts
31
+ end
32
+
33
+ private
34
+
35
+ def calculate_layout(template, width, height, x, y, parent_template)
36
+ if template.is_a?(RubyJard::Templates::ScreenTemplate)
37
+ layout = RubyJard::Layout.new(
38
+ template: template, parent_template: parent_template,
39
+ width: width - 2, height: height - 2, x: x + 1, y: y + 1,
40
+ box_width: width, box_height: height, box_x: x, box_y: y
41
+ )
42
+ adjust_layout_overlap(layout)
43
+ @layouts << layout
44
+ else
45
+ overflow_width = 0
46
+ child_x = x
47
+ child_y = y
48
+ max_height = 0
49
+
50
+ lines = [[]]
51
+ visible_children(template).each do |child_template|
52
+ child_height = calculate_child_height(child_template, height)
53
+ child_width = calculate_child_width(child_template, width)
54
+
55
+ overflow_width += child_width
56
+ # Overflow. Break to next line
57
+ if overflow_width > width
58
+ child_y += max_height
59
+ child_x = x
60
+ overflow_width = 0
61
+ max_height = child_height
62
+ lines << []
63
+ elsif max_height < child_height
64
+ max_height = child_height
65
+ end
66
+
67
+ lines.last << [child_template, child_width, child_height, child_x, child_y]
68
+ child_x += child_width
69
+ end
70
+
71
+ stretch_children_layouts(template, width, height, lines)
72
+ lines.each do |line|
73
+ line.each do |child_template, child_width, child_height, xx, yy|
74
+ calculate_layout(child_template, child_width, child_height, xx, yy, template)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def visible_children(template)
81
+ template.children.select do |child|
82
+ visible?(child)
83
+ end
84
+ end
85
+
86
+ def visible?(template)
87
+ if template.is_a?(RubyJard::Templates::ScreenTemplate)
88
+ @config.enabled_screens.include?(template.screen.to_s.strip)
89
+ else
90
+ template.children.any? { |child| visible?(child) }
91
+ end
92
+ end
93
+
94
+ def adjust_layout_overlap(layout)
95
+ if layout.box_x != 0
96
+ layout.width += 1
97
+ layout.x -= 1
98
+ layout.box_width += 1
99
+ layout.box_x -= 1
100
+ end
101
+
102
+ if layout.box_y != 0
103
+ layout.height += 1
104
+ layout.y -= 1
105
+ layout.box_height += 1
106
+ layout.box_y -= 1
107
+ end
108
+ end
109
+
110
+ # Stretch the children layouts to fill the gaps, remove redundant spaces inside the parent layout
111
+ def stretch_children_layouts(parent_template, parent_width, parent_height, lines)
112
+ total_height = 0
113
+ lines.each_with_index do |line, line_index|
114
+ desired_height =
115
+ if line_index == lines.length - 1
116
+ parent_height - total_height
117
+ else
118
+ line.map { |_child_template, _child_width, child_height, _x, _y| child_height }.max
119
+ end
120
+
121
+ total_width = 0
122
+ line.map!.with_index do |(child_template, child_width, child_height, x, y), index|
123
+ child_height = desired_height if parent_template.fill_height
124
+ child_width = parent_width - total_width if parent_template.fill_width && index == line.length - 1
125
+ total_width += child_width
126
+ [child_template, child_width, child_height, x, y]
127
+ end
128
+ total_height += desired_height
129
+ end
130
+ end
131
+
132
+ def calculate_child_height(child_template, parent_height)
133
+ height =
134
+ if !child_template.height.nil?
135
+ child_template.height
136
+ elsif child_template.height_ratio.nil?
137
+ parent_height
138
+ else
139
+ parent_height * child_template.height_ratio / 100
140
+ end
141
+
142
+ unless child_template.min_height.nil?
143
+ height = child_template.min_height if height < child_template.min_height
144
+ end
145
+
146
+ height
147
+ end
148
+
149
+ def calculate_child_width(child_template, parent_width)
150
+ width =
151
+ if !child_template.width.nil?
152
+ child_template.width
153
+ elsif child_template.width_ratio.nil?
154
+ parent_width
155
+ else
156
+ parent_width * child_template.width_ratio / 100
157
+ end
158
+
159
+ unless child_template.min_width.nil?
160
+ width = child_template.min_width if width < child_template.min_width
161
+ end
162
+
163
+ width
164
+ end
165
+ end
166
+ end
167
+
168
+ # rubocop:enable Metrics/MethodLength