ruby_jard 0.1.0 → 0.2.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -0
  3. data/CHANGELOG.md +40 -0
  4. data/Gemfile +1 -1
  5. data/README.md +65 -2
  6. data/docs/guide-ui.png +0 -0
  7. data/lib/ruby_jard.rb +49 -12
  8. data/lib/ruby_jard/box_drawer.rb +126 -0
  9. data/lib/ruby_jard/column.rb +18 -0
  10. data/lib/ruby_jard/commands/continue_command.rb +1 -6
  11. data/lib/ruby_jard/commands/down_command.rb +1 -4
  12. data/lib/ruby_jard/commands/frame_command.rb +12 -11
  13. data/lib/ruby_jard/commands/next_command.rb +1 -4
  14. data/lib/ruby_jard/commands/step_command.rb +1 -4
  15. data/lib/ruby_jard/commands/step_out_command.rb +28 -0
  16. data/lib/ruby_jard/commands/up_command.rb +1 -4
  17. data/lib/ruby_jard/console.rb +86 -0
  18. data/lib/ruby_jard/control_flow.rb +71 -0
  19. data/lib/ruby_jard/decorators/color_decorator.rb +78 -0
  20. data/lib/ruby_jard/decorators/loc_decorator.rb +41 -28
  21. data/lib/ruby_jard/decorators/source_decorator.rb +1 -1
  22. data/lib/ruby_jard/key_binding.rb +14 -0
  23. data/lib/ruby_jard/key_bindings.rb +96 -0
  24. data/lib/ruby_jard/keys.rb +49 -0
  25. data/lib/ruby_jard/layout.rb +67 -55
  26. data/lib/ruby_jard/layouts/wide_layout.rb +138 -0
  27. data/lib/ruby_jard/repl_processor.rb +80 -90
  28. data/lib/ruby_jard/repl_proxy.rb +232 -0
  29. data/lib/ruby_jard/row.rb +16 -0
  30. data/lib/ruby_jard/screen.rb +114 -36
  31. data/lib/ruby_jard/screen_drawer.rb +89 -0
  32. data/lib/ruby_jard/screen_manager.rb +157 -56
  33. data/lib/ruby_jard/screens/backtrace_screen.rb +88 -97
  34. data/lib/ruby_jard/screens/menu_screen.rb +23 -31
  35. data/lib/ruby_jard/screens/source_screen.rb +42 -90
  36. data/lib/ruby_jard/screens/threads_screen.rb +50 -64
  37. data/lib/ruby_jard/screens/variables_screen.rb +96 -99
  38. data/lib/ruby_jard/session.rb +13 -7
  39. data/lib/ruby_jard/span.rb +18 -0
  40. data/lib/ruby_jard/templates/column_template.rb +17 -0
  41. data/lib/ruby_jard/templates/layout_template.rb +35 -0
  42. data/lib/ruby_jard/templates/row_template.rb +22 -0
  43. data/lib/ruby_jard/templates/screen_template.rb +35 -0
  44. data/lib/ruby_jard/templates/space_template.rb +15 -0
  45. data/lib/ruby_jard/templates/span_template.rb +25 -0
  46. data/lib/ruby_jard/version.rb +1 -1
  47. data/ruby_jard.gemspec +1 -4
  48. metadata +29 -41
  49. data/lib/ruby_jard/commands/finish_command.rb +0 -31
  50. data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
  51. data/lib/ruby_jard/layout_template.rb +0 -101
  52. data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
  53. data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
@@ -25,7 +25,7 @@ module RubyJard
25
25
  end
26
26
 
27
27
  @window_start = @lineno - @window / 2
28
- @window_start = 0 if @window_start.negative?
28
+ @window_start = 1 if @window_start.negative?
29
29
  @window_end = @window_start + @window
30
30
 
31
31
  until file.eof?
@@ -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,49 @@
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
+
38
+ # rubocop:disable Layout/HashAlignment
39
+ DEFAULT_KEY_BINDINGS = {
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
+ # rubocop:enable Layout/HashAlignment
48
+ end
49
+ end
@@ -1,99 +1,111 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/MethodLength
4
+ # rubocop:disable Metrics/CyclomaticComplexity
3
5
  module RubyJard
4
6
  ##
5
7
  # Layout calculator based on screen resolution to decide the height, width,
6
8
  # visibility, data size of each children screen.
7
9
  # TODO: Right now, the sizes are fixed regardless of current screen data size.
8
10
  class Layout
9
- def self.generate(**args)
10
- layout = new(**args)
11
- layout.generate
12
- layout
11
+ def self.calculate(**args)
12
+ new(**args).calculate
13
13
  end
14
14
 
15
- attr_accessor :width, :height, :screen, :children
16
-
17
- def initialize(template:, width: 0, height: 0)
18
- @template = template
15
+ def initialize(layout:, width: 0, height: 0, x: 0, y: 0)
16
+ @layout = layout
19
17
  @width = width
20
18
  @height = height
21
- @screen = nil
22
- @children = []
19
+ @x = x
20
+ @y = y
23
21
  end
24
22
 
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
23
+ def calculate
24
+ screens = []
25
+ calculate_layout(screens, @layout, @width, @height, @x, @y)
26
+ screens
34
27
  end
35
28
 
36
29
  private
37
30
 
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
31
+ def calculate_layout(screens, layout, width, height, x, y)
32
+ if layout.is_a?(RubyJard::Templates::ScreenTemplate)
33
+ screens << [layout, width, height, x, y]
34
+ else
35
+ total_height = 0
36
+ total_width = 0
37
+ overflow_width = 0
38
+ child_x = x
39
+ child_y = y
40
+ max_height = 0
41
+
42
+ layout.children.each_with_index do |child_layout, index|
43
+ child_height = calculate_child_height(child_layout, layout, height, index, total_height)
44
+ child_width = calculate_child_width(child_layout, layout, width, index, total_width)
45
+
46
+ calculate_layout(screens, child_layout, child_width, child_height, child_x, child_y)
47
+
48
+ overflow_width += child_width
49
+ max_height = child_height if max_height < child_height
50
+ # Overflow. Break to next line
51
+ if overflow_width >= width
52
+ child_y += max_height - 1
53
+ child_x = x
54
+ overflow_width = 0
55
+ max_height = 0
56
+ else
57
+ child_x += child_width - 1
58
+ end
59
+
60
+ total_width += child_width - 1
61
+ total_height += child_height - 1
62
+ end
54
63
  end
55
64
  end
56
65
 
57
- def calculate_child_height(child_template, index, total_height)
66
+ def calculate_child_height(child_layout, parent_layout, parent_height, index, total_height)
58
67
  height =
59
- if !child_template.height.nil?
60
- child_template.height
61
- elsif child_template.height_ratio.nil?
62
- @height
68
+ if !child_layout.height.nil?
69
+ child_layout.height
70
+ elsif child_layout.height_ratio.nil?
71
+ parent_height
63
72
  else
64
- @height * child_template.height_ratio / 100
73
+ parent_height * child_layout.height_ratio / 100
65
74
  end
66
75
 
67
- unless child_template.min_height.nil?
68
- height = child_template.min_height if height < child_template.min_height
76
+ unless child_layout.min_height.nil?
77
+ height = child_layout.min_height if height < child_layout.min_height
69
78
  end
70
79
 
71
- if @template.fill_height && index == @template.children.length - 1
72
- height = @height - total_height if height < (@height - total_height)
80
+ if parent_layout.fill_height && index == parent_layout.children.length - 1
81
+ height = parent_height - total_height if height < (parent_height - total_height)
73
82
  end
74
83
 
75
84
  height
76
85
  end
77
86
 
78
- def calculate_child_width(child_template, index, total_width)
87
+ def calculate_child_width(child_layout, parent_layout, parent_width, index, total_width)
79
88
  width =
80
- if !child_template.width.nil?
81
- child_template.width
82
- elsif child_template.width_ratio.nil?
83
- @width
89
+ if !child_layout.width.nil?
90
+ child_layout.width
91
+ elsif child_layout.width_ratio.nil?
92
+ parent_width
84
93
  else
85
- @width * child_template.width_ratio / 100
94
+ parent_width * child_layout.width_ratio / 100
86
95
  end
87
96
 
88
- unless child_template.min_width.nil?
89
- width = child_template.min_width if width < child_template.min_width
97
+ unless child_layout.min_width.nil?
98
+ width = child_layout.min_width if width < child_layout.min_width
90
99
  end
91
100
 
92
- if @template.fill_width && index == @template.children.length - 1
93
- width = @width - total_width if width < (@width - total_width)
101
+ if parent_layout.fill_width && index == parent_layout.children.length - 1
102
+ width = parent_width - total_width if width < (parent_width - total_width)
94
103
  end
95
104
 
96
105
  width
97
106
  end
98
107
  end
99
108
  end
109
+
110
+ # rubocop:enable Metrics/MethodLength
111
+ # rubocop:enable Metrics/CyclomaticComplexity
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Layouts
5
+ WideLayout = RubyJard::Templates::LayoutTemplate.new(
6
+ min_width: 120,
7
+ min_height: 10,
8
+ fill_width: true,
9
+ fill_height: false,
10
+ children: [
11
+ RubyJard::Templates::LayoutTemplate.new(
12
+ height_ratio: 50,
13
+ min_height: 7,
14
+ fill_width: true,
15
+ children: [
16
+ RubyJard::Templates::ScreenTemplate.new(
17
+ screen: :source,
18
+ width_ratio: 50,
19
+ row_template: RubyJard::Templates::RowTemplate.new(
20
+ columns: [
21
+ RubyJard::Templates::ColumnTemplate.new(
22
+ margin_right: 1,
23
+ spans: [
24
+ RubyJard::Templates::SpanTemplate.new(:mark, margin_right: 1),
25
+ RubyJard::Templates::SpanTemplate.new(:lineno)
26
+ ]
27
+ ),
28
+ RubyJard::Templates::ColumnTemplate.new(
29
+ spans: [
30
+ RubyJard::Templates::SpanTemplate.new(:code)
31
+ ]
32
+ )
33
+ ]
34
+ )
35
+ ),
36
+ RubyJard::Templates::LayoutTemplate.new(
37
+ width_ratio: 50,
38
+ fill_height: true,
39
+ children: [
40
+ RubyJard::Templates::ScreenTemplate.new(
41
+ screen: :variables,
42
+ width_ratio: 100,
43
+ height_ratio: 100,
44
+ min_height: 3,
45
+ row_template: RubyJard::Templates::RowTemplate.new(
46
+ line_limit: 3,
47
+ columns: [
48
+ RubyJard::Templates::ColumnTemplate.new(
49
+ spans: [
50
+ RubyJard::Templates::SpanTemplate.new(:inline, margin_right: 1)
51
+ ]
52
+ ),
53
+ RubyJard::Templates::ColumnTemplate.new(
54
+ margin_right: 1,
55
+ spans: [
56
+ RubyJard::Templates::SpanTemplate.new(:type)
57
+ ]
58
+ ),
59
+ RubyJard::Templates::ColumnTemplate.new(
60
+ spans: [
61
+ RubyJard::Templates::SpanTemplate.new(:name, margin_right: 1),
62
+ RubyJard::Templates::SpanTemplate.new(:size, margin_right: 1),
63
+ RubyJard::Templates::SpanTemplate.new(:indicator, margin_right: 1),
64
+ RubyJard::Templates::SpanTemplate.new(:inspection)
65
+ ]
66
+ )
67
+ ]
68
+ )
69
+ )
70
+ ]
71
+ )
72
+ ]
73
+ ),
74
+ RubyJard::Templates::LayoutTemplate.new(
75
+ height_ratio: 30,
76
+ min_height: 3,
77
+ fill_width: true,
78
+ children: [
79
+ RubyJard::Templates::ScreenTemplate.new(
80
+ screen: :backtrace,
81
+ width_ratio: 50,
82
+ row_template: RubyJard::Templates::RowTemplate.new(
83
+ columns: [
84
+ RubyJard::Templates::ColumnTemplate.new(
85
+ margin_right: 1,
86
+ spans: [
87
+ RubyJard::Templates::SpanTemplate.new(:mark, margin_right: 1),
88
+ RubyJard::Templates::SpanTemplate.new(:frame_id)
89
+ ]
90
+ ),
91
+ RubyJard::Templates::ColumnTemplate.new(
92
+ spans: [
93
+ RubyJard::Templates::SpanTemplate.new(:klass_label, margin_right: 1),
94
+ RubyJard::Templates::SpanTemplate.new(:label_preposition, margin_right: 1),
95
+ RubyJard::Templates::SpanTemplate.new(:method_label, margin_right: 1),
96
+ RubyJard::Templates::SpanTemplate.new(:path_preposition, margin_right: 1),
97
+ RubyJard::Templates::SpanTemplate.new(:path)
98
+ ]
99
+ )
100
+ ]
101
+ )
102
+ ),
103
+ RubyJard::Templates::ScreenTemplate.new(
104
+ screen: :threads,
105
+ width_ratio: 50,
106
+ row_template: RubyJard::Templates::RowTemplate.new(
107
+ columns: [
108
+ RubyJard::Templates::ColumnTemplate.new(
109
+ margin_right: 1,
110
+ spans: [
111
+ RubyJard::Templates::SpanTemplate.new(:mark, margin_right: 1),
112
+ RubyJard::Templates::SpanTemplate.new(:thread_id)
113
+ ]
114
+ ),
115
+ RubyJard::Templates::ColumnTemplate.new(
116
+ margin_right: 1,
117
+ spans: [
118
+ RubyJard::Templates::SpanTemplate.new(:thread_status)
119
+ ]
120
+ ),
121
+ RubyJard::Templates::ColumnTemplate.new(
122
+ spans: [
123
+ RubyJard::Templates::SpanTemplate.new(:thread_name)
124
+ ]
125
+ )
126
+ ]
127
+ )
128
+ )
129
+ ]
130
+ ),
131
+ RubyJard::Templates::ScreenTemplate.new(
132
+ height: 3,
133
+ screen: :menu
134
+ )
135
+ ]
136
+ )
137
+ end
138
+ end