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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +1 -1
- data/README.md +65 -2
- data/docs/guide-ui.png +0 -0
- data/lib/ruby_jard.rb +49 -12
- data/lib/ruby_jard/box_drawer.rb +126 -0
- data/lib/ruby_jard/column.rb +18 -0
- data/lib/ruby_jard/commands/continue_command.rb +1 -6
- data/lib/ruby_jard/commands/down_command.rb +1 -4
- data/lib/ruby_jard/commands/frame_command.rb +12 -11
- data/lib/ruby_jard/commands/next_command.rb +1 -4
- data/lib/ruby_jard/commands/step_command.rb +1 -4
- data/lib/ruby_jard/commands/step_out_command.rb +28 -0
- data/lib/ruby_jard/commands/up_command.rb +1 -4
- data/lib/ruby_jard/console.rb +86 -0
- data/lib/ruby_jard/control_flow.rb +71 -0
- data/lib/ruby_jard/decorators/color_decorator.rb +78 -0
- data/lib/ruby_jard/decorators/loc_decorator.rb +41 -28
- data/lib/ruby_jard/decorators/source_decorator.rb +1 -1
- data/lib/ruby_jard/key_binding.rb +14 -0
- data/lib/ruby_jard/key_bindings.rb +96 -0
- data/lib/ruby_jard/keys.rb +49 -0
- data/lib/ruby_jard/layout.rb +67 -55
- data/lib/ruby_jard/layouts/wide_layout.rb +138 -0
- data/lib/ruby_jard/repl_processor.rb +80 -90
- data/lib/ruby_jard/repl_proxy.rb +232 -0
- data/lib/ruby_jard/row.rb +16 -0
- data/lib/ruby_jard/screen.rb +114 -36
- data/lib/ruby_jard/screen_drawer.rb +89 -0
- data/lib/ruby_jard/screen_manager.rb +157 -56
- data/lib/ruby_jard/screens/backtrace_screen.rb +88 -97
- data/lib/ruby_jard/screens/menu_screen.rb +23 -31
- data/lib/ruby_jard/screens/source_screen.rb +42 -90
- data/lib/ruby_jard/screens/threads_screen.rb +50 -64
- data/lib/ruby_jard/screens/variables_screen.rb +96 -99
- data/lib/ruby_jard/session.rb +13 -7
- data/lib/ruby_jard/span.rb +18 -0
- data/lib/ruby_jard/templates/column_template.rb +17 -0
- data/lib/ruby_jard/templates/layout_template.rb +35 -0
- data/lib/ruby_jard/templates/row_template.rb +22 -0
- data/lib/ruby_jard/templates/screen_template.rb +35 -0
- data/lib/ruby_jard/templates/space_template.rb +15 -0
- data/lib/ruby_jard/templates/span_template.rb +25 -0
- data/lib/ruby_jard/version.rb +1 -1
- data/ruby_jard.gemspec +1 -4
- metadata +29 -41
- data/lib/ruby_jard/commands/finish_command.rb +0 -31
- data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
- data/lib/ruby_jard/layout_template.rb +0 -101
- data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
- data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
@@ -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
|
data/lib/ruby_jard/layout.rb
CHANGED
@@ -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.
|
10
|
-
|
11
|
-
layout.generate
|
12
|
-
layout
|
11
|
+
def self.calculate(**args)
|
12
|
+
new(**args).calculate
|
13
13
|
end
|
14
14
|
|
15
|
-
|
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
|
-
@
|
22
|
-
@
|
19
|
+
@x = x
|
20
|
+
@y = y
|
23
21
|
end
|
24
22
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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(
|
66
|
+
def calculate_child_height(child_layout, parent_layout, parent_height, index, total_height)
|
58
67
|
height =
|
59
|
-
if !
|
60
|
-
|
61
|
-
elsif
|
62
|
-
|
68
|
+
if !child_layout.height.nil?
|
69
|
+
child_layout.height
|
70
|
+
elsif child_layout.height_ratio.nil?
|
71
|
+
parent_height
|
63
72
|
else
|
64
|
-
|
73
|
+
parent_height * child_layout.height_ratio / 100
|
65
74
|
end
|
66
75
|
|
67
|
-
unless
|
68
|
-
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
|
72
|
-
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(
|
87
|
+
def calculate_child_width(child_layout, parent_layout, parent_width, index, total_width)
|
79
88
|
width =
|
80
|
-
if !
|
81
|
-
|
82
|
-
elsif
|
83
|
-
|
89
|
+
if !child_layout.width.nil?
|
90
|
+
child_layout.width
|
91
|
+
elsif child_layout.width_ratio.nil?
|
92
|
+
parent_width
|
84
93
|
else
|
85
|
-
|
94
|
+
parent_width * child_layout.width_ratio / 100
|
86
95
|
end
|
87
96
|
|
88
|
-
unless
|
89
|
-
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
|
93
|
-
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
|