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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/documentation.yml +65 -0
- data/.github/workflows/rspec.yml +96 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +90 -2
- data/CHANGELOG.md +112 -0
- data/Gemfile +14 -4
- data/README.md +95 -3
- data/benchmark/path_filter_bench.rb +58 -0
- data/bin/console +1 -2
- data/lib/ruby_jard.rb +68 -32
- data/lib/ruby_jard/box_drawer.rb +175 -0
- data/lib/ruby_jard/color_scheme.rb +28 -0
- data/lib/ruby_jard/color_schemes.rb +54 -0
- data/lib/ruby_jard/color_schemes/256_color_scheme.rb +50 -0
- data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +50 -0
- data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +49 -0
- data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +48 -0
- data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +47 -0
- data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +49 -0
- data/lib/ruby_jard/column.rb +26 -0
- data/lib/ruby_jard/commands/color_helpers.rb +32 -0
- data/lib/ruby_jard/commands/continue_command.rb +4 -9
- data/lib/ruby_jard/commands/down_command.rb +9 -8
- data/lib/ruby_jard/commands/exit_command.rb +27 -0
- data/lib/ruby_jard/commands/frame_command.rb +13 -11
- data/lib/ruby_jard/commands/jard/color_scheme_command.rb +74 -0
- data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
- data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
- data/lib/ruby_jard/commands/jard/output_command.rb +36 -0
- data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
- data/lib/ruby_jard/commands/jard_command.rb +52 -0
- data/lib/ruby_jard/commands/list_command.rb +31 -0
- data/lib/ruby_jard/commands/next_command.rb +11 -8
- data/lib/ruby_jard/commands/step_command.rb +11 -8
- data/lib/ruby_jard/commands/step_out_command.rb +34 -0
- data/lib/ruby_jard/commands/up_command.rb +10 -8
- data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
- data/lib/ruby_jard/config.rb +61 -0
- data/lib/ruby_jard/console.rb +158 -0
- data/lib/ruby_jard/control_flow.rb +73 -0
- data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
- data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
- data/lib/ruby_jard/decorators/color_decorator.rb +80 -0
- data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
- data/lib/ruby_jard/decorators/inspection_decorator.rb +109 -0
- data/lib/ruby_jard/decorators/loc_decorator.rb +108 -119
- data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
- data/lib/ruby_jard/decorators/path_decorator.rb +56 -60
- data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
- data/lib/ruby_jard/decorators/source_decorator.rb +3 -1
- data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
- data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
- data/lib/ruby_jard/frame.rb +68 -0
- data/lib/ruby_jard/key_binding.rb +14 -0
- data/lib/ruby_jard/key_bindings.rb +96 -0
- data/lib/ruby_jard/keys.rb +48 -0
- data/lib/ruby_jard/layout.rb +17 -88
- data/lib/ruby_jard/layout_calculator.rb +168 -0
- data/lib/ruby_jard/layout_picker.rb +34 -0
- data/lib/ruby_jard/layouts.rb +52 -0
- data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +32 -0
- data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
- data/lib/ruby_jard/layouts/tiny_layout.rb +29 -0
- data/lib/ruby_jard/layouts/wide_layout.rb +50 -0
- data/lib/ruby_jard/pager.rb +112 -0
- data/lib/ruby_jard/path_classifier.rb +133 -0
- data/lib/ruby_jard/path_filter.rb +125 -0
- data/lib/ruby_jard/reflection.rb +97 -0
- data/lib/ruby_jard/repl_processor.rb +151 -89
- data/lib/ruby_jard/repl_proxy.rb +337 -0
- data/lib/ruby_jard/row.rb +31 -0
- data/lib/ruby_jard/row_renderer.rb +119 -0
- data/lib/ruby_jard/screen.rb +14 -41
- data/lib/ruby_jard/screen_adjuster.rb +104 -0
- data/lib/ruby_jard/screen_drawer.rb +25 -0
- data/lib/ruby_jard/screen_manager.rb +167 -82
- data/lib/ruby_jard/screen_renderer.rb +152 -0
- data/lib/ruby_jard/screens.rb +31 -12
- data/lib/ruby_jard/screens/backtrace_screen.rb +118 -116
- data/lib/ruby_jard/screens/menu_screen.rb +73 -45
- data/lib/ruby_jard/screens/source_screen.rb +86 -106
- data/lib/ruby_jard/screens/threads_screen.rb +103 -78
- data/lib/ruby_jard/screens/variables_screen.rb +224 -142
- data/lib/ruby_jard/session.rb +151 -16
- data/lib/ruby_jard/span.rb +23 -0
- data/lib/ruby_jard/templates/layout_template.rb +35 -0
- data/lib/ruby_jard/templates/screen_template.rb +34 -0
- data/lib/ruby_jard/thread_info.rb +69 -0
- data/lib/ruby_jard/version.rb +1 -1
- data/ruby_jard.gemspec +7 -8
- metadata +84 -50
- data/.travis.yml +0 -6
- 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/empty_screen.rb +0 -13
- data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
@@ -1,208 +1,245 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RubyJard
|
4
|
-
|
4
|
+
class Screens
|
5
|
+
##
|
6
|
+
# Display the relevant variables and constants of current context, scopes
|
5
7
|
class VariablesScreen < RubyJard::Screen
|
6
|
-
TYPE_SYMBOLS = {
|
7
|
-
# Intertal classes for those values may differ between Ruby versions
|
8
|
-
# For example: Bignum is renamed to Integer
|
9
|
-
# So, it's safer to use discrete value's class as the key for this mapping.
|
10
|
-
true.class => :bool,
|
11
|
-
false.class => :bool,
|
12
|
-
1.class => :int,
|
13
|
-
1.1.class => :flt,
|
14
|
-
1.to_r.class => :rat, # Rational: (1/1)
|
15
|
-
1.to_c.class => :com, # Complex: (1+0i)
|
16
|
-
''.class => :str,
|
17
|
-
:sym.class => :sym,
|
18
|
-
[].class => :arr,
|
19
|
-
{}.class => :hash,
|
20
|
-
//.class => :reg,
|
21
|
-
Class => :cls # Sorry, I lied, Class will never change
|
22
|
-
}.freeze
|
23
|
-
TYPE_SYMBOL_PADDING = TYPE_SYMBOLS.map { |_, s| s.to_s.length }.max + 1
|
24
|
-
DEFAULT_TYPE_SYMBOL = :var
|
25
|
-
|
26
|
-
INSPECTION_ELLIPSIS = ' [...]'
|
27
|
-
|
28
8
|
KINDS = [
|
29
|
-
|
30
|
-
|
31
|
-
|
9
|
+
KIND_SELF = :self,
|
10
|
+
KIND_LOC = :local_variable,
|
11
|
+
KIND_INS = :instance_variable,
|
12
|
+
KIND_CON = :constant,
|
13
|
+
KIND_GLOB = :global_variable
|
32
14
|
].freeze
|
33
15
|
|
16
|
+
KIND_STYLES = {
|
17
|
+
KIND_SELF => :constant,
|
18
|
+
KIND_LOC => :local_variable,
|
19
|
+
KIND_INS => :instance_variable,
|
20
|
+
KIND_CON => :constant,
|
21
|
+
KIND_GLOB => :instance_variable
|
22
|
+
}.freeze
|
23
|
+
|
34
24
|
KIND_PRIORITIES = {
|
35
|
-
|
36
|
-
|
37
|
-
|
25
|
+
KIND_SELF => 0,
|
26
|
+
KIND_LOC => 1,
|
27
|
+
KIND_INS => 2,
|
28
|
+
KIND_CON => 3,
|
29
|
+
KIND_GLOB => 4
|
38
30
|
}.freeze
|
39
31
|
|
40
|
-
|
41
|
-
KIND_LOC
|
42
|
-
KIND_INS
|
43
|
-
KIND_CON
|
32
|
+
TOKEN_KIND_MAPS = {
|
33
|
+
ident: KIND_LOC,
|
34
|
+
instance_variable: KIND_INS,
|
35
|
+
constant: KIND_CON,
|
36
|
+
predefined_constant: KIND_CON,
|
37
|
+
global_variable: KIND_GLOB
|
44
38
|
}.freeze
|
39
|
+
TOKEN_KINDS = TOKEN_KIND_MAPS.keys.flatten
|
45
40
|
|
46
|
-
def
|
47
|
-
|
48
|
-
**default_frame_styles.merge(
|
49
|
-
top: @row, left: @col, width: @layout.width, height: @layout.height
|
50
|
-
)
|
51
|
-
)
|
41
|
+
def initialize(*args)
|
42
|
+
super
|
52
43
|
|
53
|
-
@
|
54
|
-
@
|
55
|
-
|
56
|
-
|
57
|
-
|
44
|
+
@frame_file = @session.current_frame&.frame_file
|
45
|
+
@frame_line = @session.current_frame&.frame_line
|
46
|
+
@frame_self = @session.current_frame&.frame_self
|
47
|
+
@frame_class = @session.current_frame&.frame_class
|
48
|
+
@frame_binding = @session.current_frame&.frame_binding
|
58
49
|
|
59
|
-
|
60
|
-
|
61
|
-
@output.print variable.content
|
62
|
-
end
|
63
|
-
end
|
50
|
+
@inline_tokens = generate_inline_tokens(@frame_file, @frame_line)
|
51
|
+
@file_tokens = generate_file_tokens(@frame_file)
|
64
52
|
|
65
|
-
|
53
|
+
@inspection_decorator = RubyJard::Decorators::InspectionDecorator.new
|
66
54
|
|
67
|
-
|
68
|
-
@layout.height - 1
|
55
|
+
@selected = 0
|
69
56
|
end
|
70
57
|
|
71
|
-
def
|
72
|
-
|
58
|
+
def title
|
59
|
+
'Variables'
|
73
60
|
end
|
74
61
|
|
75
|
-
def
|
76
|
-
|
62
|
+
def build
|
63
|
+
variables = fetch_relevant_variables
|
64
|
+
@rows = variables.map do |variable|
|
65
|
+
name = span_name(variable)
|
66
|
+
size = span_size(variable)
|
67
|
+
assignment = RubyJard::Span.new(margin_right: 1, margin_left: 1, content: '=', styles: :text_primary)
|
68
|
+
inline_limit =
|
69
|
+
(@layout.width - 3) * 3 - name.content_length - size.content_length - assignment.content_length
|
70
|
+
inspections = @inspection_decorator.decorate_multiline(
|
71
|
+
variable[2], first_line_limit: inline_limit, line_limit: @layout.width - 3, lines: 7
|
72
|
+
)
|
73
|
+
base_inspection = inspections.shift
|
74
|
+
mark = span_mark(variable, inspections)
|
75
|
+
[
|
76
|
+
base_row(name, size, assignment, mark, base_inspection),
|
77
|
+
nested_rows(variable, inspections)
|
78
|
+
]
|
79
|
+
end.flatten.compact
|
77
80
|
end
|
78
81
|
|
79
|
-
def
|
80
|
-
|
82
|
+
def fetch_relevant_variables
|
83
|
+
sort_variables(
|
84
|
+
self_variable +
|
85
|
+
fetch_local_variables +
|
86
|
+
fetch_instance_variables +
|
87
|
+
fetch_constants +
|
88
|
+
fetch_global_variables
|
89
|
+
)
|
81
90
|
end
|
82
91
|
|
83
|
-
def
|
84
|
-
RubyJard.
|
92
|
+
def base_row(name, size, assignment, mark, base_inspection)
|
93
|
+
RubyJard::Row.new(
|
94
|
+
line_limit: 3,
|
95
|
+
columns: [
|
96
|
+
RubyJard::Column.new(spans: [mark]),
|
97
|
+
RubyJard::Column.new(
|
98
|
+
word_wrap: RubyJard::Column::WORD_WRAP_BREAK_WORD,
|
99
|
+
spans: [name, size, assignment, base_inspection].flatten.compact
|
100
|
+
)
|
101
|
+
]
|
102
|
+
)
|
85
103
|
end
|
86
104
|
|
87
|
-
def
|
88
|
-
return
|
105
|
+
def nested_rows(variable, nested_inspections)
|
106
|
+
return nil if nested_inspections.empty? || variable[0] == KIND_SELF
|
107
|
+
|
108
|
+
nested_inspections.map do |spans|
|
109
|
+
RubyJard::Row.new(
|
110
|
+
line_limit: 1,
|
111
|
+
columns: [
|
112
|
+
RubyJard::Column.new,
|
113
|
+
RubyJard::Column.new(
|
114
|
+
word_wrap: RubyJard::Column::WORD_WRAP_BREAK_WORD,
|
115
|
+
spans: spans
|
116
|
+
)
|
117
|
+
]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
89
121
|
|
90
|
-
|
122
|
+
def span_mark(variable, nested_inspections)
|
123
|
+
if variable[0] == KIND_SELF || nested_inspections.empty?
|
124
|
+
RubyJard::Span.new(
|
125
|
+
content: ' ',
|
126
|
+
styles: :text_dim
|
127
|
+
)
|
128
|
+
else
|
129
|
+
RubyJard::Span.new(
|
130
|
+
content: '▾',
|
131
|
+
styles: :text_dim
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
91
135
|
|
92
|
-
|
93
|
-
|
94
|
-
|
136
|
+
def span_name(variable)
|
137
|
+
RubyJard::Span.new(
|
138
|
+
content: variable[1].to_s,
|
139
|
+
styles: [KIND_STYLES[variable[0].to_sym], inline?(variable[1]) ? :underline : nil]
|
140
|
+
)
|
95
141
|
end
|
96
142
|
|
143
|
+
def span_size(variable)
|
144
|
+
value = variable[2]
|
145
|
+
size_label =
|
146
|
+
if RubyJard::Reflection.call_is_a?(value, Array) && !value.empty?
|
147
|
+
"(len:#{value.length})"
|
148
|
+
elsif RubyJard::Reflection.call_is_a?(value, String) && value.length > 20
|
149
|
+
"(len:#{value.length})"
|
150
|
+
elsif RubyJard::Reflection.call_is_a?(value, Hash) && !value.empty?
|
151
|
+
"(size:#{value.length})"
|
152
|
+
end
|
153
|
+
RubyJard::Span.new(
|
154
|
+
margin_left: 1,
|
155
|
+
content: size_label,
|
156
|
+
styles: :text_primary
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
97
162
|
def fetch_local_variables
|
98
|
-
|
163
|
+
return [] if @frame_binding == nil
|
164
|
+
return [] unless RubyJard::Reflection.call_is_a?(@frame_binding, ::Binding)
|
165
|
+
|
166
|
+
variables = @frame_binding.local_variables
|
99
167
|
# Exclude Pry's sticky locals
|
100
168
|
pry_sticky_locals =
|
101
169
|
if variables.include?(:pry_instance)
|
102
|
-
|
170
|
+
@frame_binding.local_variable_get(:pry_instance)&.sticky_locals&.keys || []
|
103
171
|
else
|
104
172
|
[]
|
105
173
|
end
|
106
174
|
variables -= pry_sticky_locals
|
107
175
|
variables.map do |variable|
|
108
|
-
[KIND_LOC, variable,
|
176
|
+
[KIND_LOC, variable, @frame_binding.local_variable_get(variable)]
|
109
177
|
rescue NameError
|
110
178
|
nil
|
111
179
|
end.compact
|
112
180
|
end
|
113
181
|
|
114
182
|
def fetch_instance_variables
|
115
|
-
|
116
|
-
|
183
|
+
return [] if @frame_self == nil
|
184
|
+
|
185
|
+
instance_variables =
|
186
|
+
RubyJard::Reflection
|
187
|
+
.call_instance_variables(@frame_self)
|
188
|
+
.select { |v| relevant?(KIND_INS, v) }
|
189
|
+
|
190
|
+
instance_variables.map do |variable|
|
191
|
+
[KIND_INS, variable, RubyJard::Reflection.call_instance_variable_get(@frame_self, variable)]
|
117
192
|
rescue NameError
|
118
193
|
nil
|
119
194
|
end.compact
|
120
195
|
end
|
121
196
|
|
122
197
|
def fetch_constants
|
198
|
+
return [] if @frame_class == nil
|
199
|
+
|
123
200
|
# Filter out truly constants (CONSTANT convention) only
|
124
201
|
constant_source =
|
125
|
-
if
|
126
|
-
|
202
|
+
if @frame_class&.singleton_class?
|
203
|
+
@frame_self
|
127
204
|
else
|
128
|
-
|
205
|
+
@frame_class
|
129
206
|
end
|
130
207
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
nil
|
138
|
-
end.compact
|
208
|
+
(@file_tokens[KIND_CON] || {})
|
209
|
+
.keys
|
210
|
+
.select { |c| c.upcase == c }
|
211
|
+
.uniq
|
212
|
+
.map { |const| fetch_constant(constant_source, const) }
|
213
|
+
.compact
|
139
214
|
end
|
140
215
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
144
|
-
.text(decorated_type(value))
|
145
|
-
.with_highlight(true)
|
146
|
-
.text(name.to_s, kind_color(kind))
|
147
|
-
.text(addition_data(value), :white)
|
148
|
-
.text(' = ')
|
149
|
-
.with_highlight(false)
|
150
|
-
inspect_texts = inspect_value(text, value)
|
151
|
-
text.text(inspect_texts.first, :bright_white)
|
152
|
-
|
153
|
-
# TODO: Fix this ugly code
|
154
|
-
[text] +
|
155
|
-
inspect_texts[1..-1].map do |line|
|
156
|
-
decorate_text
|
157
|
-
.with_highlight(false)
|
158
|
-
.text(' ' * TYPE_SYMBOL_PADDING)
|
159
|
-
.text(line, :bright_white)
|
160
|
-
end
|
161
|
-
end
|
216
|
+
def fetch_constant(constant_source, const)
|
217
|
+
return nil if %w[NIL TRUE FALSE].include?(const.to_s)
|
218
|
+
return nil unless RubyJard::Reflection.call_const_defined?(constant_source, const)
|
162
219
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
elsif value.is_a?(String) && value.length > 20
|
167
|
-
" (size: #{value.length})"
|
168
|
-
else
|
169
|
-
''
|
170
|
-
end
|
220
|
+
[KIND_CON, const, RubyJard::Reflection.call_const_get(constant_source, const)]
|
221
|
+
rescue NameError
|
222
|
+
nil
|
171
223
|
end
|
172
224
|
|
173
|
-
def
|
174
|
-
|
175
|
-
length = @layout.width - TYPE_SYMBOL_PADDING - 1
|
176
|
-
value_inspect = value.inspect.to_s
|
177
|
-
|
178
|
-
start_pos = 0
|
179
|
-
end_pos = @layout.width - 2 - text.length
|
180
|
-
|
181
|
-
texts = []
|
182
|
-
3.times do |_index|
|
183
|
-
texts << value_inspect[start_pos..end_pos]
|
184
|
-
break if end_pos >= value_inspect.length
|
185
|
-
|
186
|
-
start_pos = end_pos + 1
|
187
|
-
end_pos += length
|
188
|
-
end
|
225
|
+
def fetch_global_variables
|
226
|
+
return [] if @frame_self == nil
|
189
227
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
decorate_text
|
200
|
-
.with_highlight(false)
|
201
|
-
.text(type_name.to_s.ljust(TYPE_SYMBOL_PADDING), :white)
|
228
|
+
variables =
|
229
|
+
::Kernel
|
230
|
+
.global_variables
|
231
|
+
.select { |v| relevant?(KIND_GLOB, v) }
|
232
|
+
variables.map do |variable|
|
233
|
+
[KIND_GLOB, variable, ::Kernel.instance_eval(variable.to_s)]
|
234
|
+
rescue NameError
|
235
|
+
nil
|
236
|
+
end.compact
|
202
237
|
end
|
203
238
|
|
204
|
-
def
|
205
|
-
|
239
|
+
def self_variable
|
240
|
+
[[KIND_SELF, :self, @frame_self]]
|
241
|
+
rescue StandardError
|
242
|
+
[]
|
206
243
|
end
|
207
244
|
|
208
245
|
def sort_variables(variables)
|
@@ -227,8 +264,53 @@ module RubyJard
|
|
227
264
|
end
|
228
265
|
end
|
229
266
|
end
|
267
|
+
|
268
|
+
def inline?(name)
|
269
|
+
@inline_tokens[name]
|
270
|
+
end
|
271
|
+
|
272
|
+
def relevant?(kind, name)
|
273
|
+
@file_tokens[kind] != nil && @file_tokens[kind][name]
|
274
|
+
end
|
275
|
+
|
276
|
+
def generate_inline_tokens(file, line)
|
277
|
+
return [] if file == nil || line == nil
|
278
|
+
|
279
|
+
loc_decorator = RubyJard::Decorators::LocDecorator.new
|
280
|
+
source_decorator = RubyJard::Decorators::SourceDecorator.new(file, line, 1)
|
281
|
+
_spans, tokens = loc_decorator.decorate(
|
282
|
+
source_decorator.codes[line - source_decorator.window_start],
|
283
|
+
file
|
284
|
+
)
|
285
|
+
|
286
|
+
inline_tokens = {}
|
287
|
+
tokens.each_slice(2) do |token, kind|
|
288
|
+
next if TOKEN_KIND_MAPS[kind] == nil
|
289
|
+
|
290
|
+
inline_tokens[token.to_s.to_sym] = true
|
291
|
+
end
|
292
|
+
inline_tokens
|
293
|
+
end
|
294
|
+
|
295
|
+
def generate_file_tokens(file)
|
296
|
+
return [] if file == nil
|
297
|
+
|
298
|
+
loc_decorator = RubyJard::Decorators::LocDecorator.new
|
299
|
+
# TODO: This is a mess
|
300
|
+
source_decorator = RubyJard::Decorators::SourceDecorator.new(file, 1, 10_000)
|
301
|
+
_spans, tokens = loc_decorator.decorate(source_decorator.codes.join("\n"), file)
|
302
|
+
|
303
|
+
file_tokens = {}
|
304
|
+
tokens.each_slice(2) do |token, kind|
|
305
|
+
next if TOKEN_KIND_MAPS[kind] == nil
|
306
|
+
|
307
|
+
file_tokens[TOKEN_KIND_MAPS[kind]] ||= {}
|
308
|
+
file_tokens[TOKEN_KIND_MAPS[kind]][token.to_s.to_sym] = true
|
309
|
+
end
|
310
|
+
file_tokens
|
311
|
+
end
|
230
312
|
end
|
231
313
|
end
|
232
314
|
end
|
233
315
|
|
234
|
-
RubyJard::Screens.add_screen(
|
316
|
+
RubyJard::Screens.add_screen('variables', RubyJard::Screens::VariablesScreen)
|
data/lib/ruby_jard/session.rb
CHANGED
@@ -4,34 +4,96 @@ module RubyJard
|
|
4
4
|
##
|
5
5
|
# Centralized flow control and data storage to feed into screens. Each
|
6
6
|
# process supposes to have only one instance of this class.
|
7
|
-
# TODO: If attachment event happens after any threads are created, race
|
8
|
-
# condition may happen. Should use a mutex to wrap around.
|
9
7
|
# TODO: This class is created to store data, but byebug data structures are
|
10
8
|
# leaked, and accessible from outside and this doesn't work if screens stay in
|
11
9
|
# other processes. Therefore, an internal, jard-specific data mapping should
|
12
10
|
# be built.
|
13
11
|
class Session
|
14
|
-
|
12
|
+
class << self
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def_delegators :instance,
|
16
|
+
:attach, :lock,
|
17
|
+
:sync, :should_stop?,
|
18
|
+
:step_over, :step_into, :frame=,
|
19
|
+
:threads, :current_frame, :current_thread, :current_backtrace,
|
20
|
+
:output_buffer, :append_output_buffer
|
21
|
+
|
22
|
+
def instance
|
23
|
+
@instance ||= new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
OUTPUT_BUFFER_LENGTH = 10_000 # 10k lines
|
28
|
+
|
29
|
+
attr_accessor :output_buffer
|
15
30
|
|
16
31
|
def initialize(options = {})
|
17
32
|
@options = options
|
33
|
+
@started = false
|
34
|
+
@session_lock = Mutex.new
|
35
|
+
@output_buffer = []
|
18
36
|
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
37
|
+
@current_frame = nil
|
38
|
+
@current_backtrace = []
|
39
|
+
@threads = []
|
40
|
+
@current_thread = nil
|
22
41
|
|
23
|
-
@
|
24
|
-
@screen_manager = RubyJard::ScreenManager.new(session: self)
|
42
|
+
@path_filter = RubyJard::PathFilter.new
|
25
43
|
end
|
26
44
|
|
27
45
|
def start
|
28
46
|
return if started?
|
29
47
|
|
30
|
-
|
48
|
+
##
|
49
|
+
# Globally configure Byebug. Byebug doesn't allow configuration by instance.
|
50
|
+
# So, I have no choice.
|
51
|
+
# TODO: Byebug autoloaded configuration may override those values.
|
52
|
+
Byebug::Setting[:autolist] = false
|
53
|
+
Byebug::Setting[:autoirb] = false
|
54
|
+
Byebug::Setting[:autopry] = false
|
55
|
+
Byebug::Context.processor = RubyJard::ReplProcessor
|
56
|
+
# Exclude all files in Ruby Jard source code from the stacktrace.
|
57
|
+
Byebug::Context.ignored_files = Byebug::Context.all_files + Dir.glob(
|
58
|
+
File.join(
|
59
|
+
File.expand_path(__dir__, '../lib'),
|
60
|
+
'**',
|
61
|
+
'*.rb'
|
62
|
+
)
|
63
|
+
)
|
64
|
+
# rubocop:disable Lint/NestedMethodDefinition
|
65
|
+
def $stdout.write(*string, from_jard: false)
|
66
|
+
# NOTE: `RubyJard::ScreenManager.instance` is a must. Jard doesn't work well with delegator
|
67
|
+
# TODO: Debug and fix the issues permanently
|
68
|
+
if from_jard
|
69
|
+
super(*string)
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
unless RubyJard::ScreenManager.instance.updating?
|
74
|
+
RubyJard::Session.instance.append_output_buffer(string)
|
75
|
+
end
|
76
|
+
|
77
|
+
super(*string)
|
78
|
+
end
|
79
|
+
# rubocop:enable Lint/NestedMethodDefinition
|
80
|
+
|
81
|
+
at_exit { stop }
|
31
82
|
|
32
83
|
@started = true
|
33
84
|
end
|
34
85
|
|
86
|
+
def append_output_buffer(string)
|
87
|
+
@output_buffer.shift if @output_buffer.length > OUTPUT_BUFFER_LENGTH
|
88
|
+
@output_buffer << string
|
89
|
+
end
|
90
|
+
|
91
|
+
def stop
|
92
|
+
return unless started?
|
93
|
+
|
94
|
+
RubyJard::ScreenManager.stop
|
95
|
+
end
|
96
|
+
|
35
97
|
def started?
|
36
98
|
@started == true
|
37
99
|
end
|
@@ -40,15 +102,88 @@ module RubyJard
|
|
40
102
|
start unless started?
|
41
103
|
|
42
104
|
Byebug.attach
|
43
|
-
Byebug.current_context.step_out(
|
105
|
+
Byebug.current_context.step_out(3, true)
|
106
|
+
end
|
107
|
+
|
108
|
+
def should_stop?
|
109
|
+
@path_filter.match?(@current_context.frame_file)
|
110
|
+
end
|
111
|
+
|
112
|
+
def sync(context)
|
113
|
+
@current_context = context
|
114
|
+
# Remove cache
|
115
|
+
@current_frame = nil
|
116
|
+
@current_thread = nil
|
117
|
+
@current_backtrace = nil
|
118
|
+
@threads = nil
|
44
119
|
end
|
45
120
|
|
46
|
-
def
|
47
|
-
@
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
121
|
+
def current_frame
|
122
|
+
@current_frame ||=
|
123
|
+
begin
|
124
|
+
frame = RubyJard::Frame.new(@current_context, @current_context.frame.pos)
|
125
|
+
frame.visible = @path_filter.match?(frame.frame_file)
|
126
|
+
frame
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def current_thread
|
131
|
+
@current_thread ||= RubyJard::ThreadInfo.new(@current_context.thread)
|
132
|
+
end
|
133
|
+
|
134
|
+
def current_backtrace
|
135
|
+
@current_backtrace ||= generate_backtrace
|
136
|
+
end
|
137
|
+
|
138
|
+
def threads
|
139
|
+
@threads ||=
|
140
|
+
Thread
|
141
|
+
.list
|
142
|
+
.select(&:alive?)
|
143
|
+
.reject { |t| t.name.to_s =~ /<<Jard:.*>>/ }
|
144
|
+
.map { |t| RubyJard::ThreadInfo.new(t) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def frame=(real_pos)
|
148
|
+
@current_context.frame = @current_backtrace[real_pos].real_pos
|
149
|
+
@current_frame = @current_backtrace[real_pos]
|
150
|
+
end
|
151
|
+
|
152
|
+
def step_into(times)
|
153
|
+
@current_context.step_into(times, current_frame.real_pos)
|
154
|
+
end
|
155
|
+
|
156
|
+
def step_over(times)
|
157
|
+
@current_context.step_over(times, current_frame.real_pos)
|
158
|
+
end
|
159
|
+
|
160
|
+
def lock
|
161
|
+
raise RubyJard::Error, 'This method requires a block' unless block_given?
|
162
|
+
|
163
|
+
# TODO: This doesn't solve anything. However, debugging a multi-threaded process is hard.
|
164
|
+
# Let's deal with that later.
|
165
|
+
@session_lock.synchronize do
|
166
|
+
yield
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def generate_backtrace
|
173
|
+
virtual_pos = 0
|
174
|
+
backtrace = @current_context.backtrace.map.with_index do |_frame, index|
|
175
|
+
frame = RubyJard::Frame.new(@current_context, index)
|
176
|
+
if @path_filter.match?(frame.frame_file)
|
177
|
+
frame.visible = true
|
178
|
+
frame.virtual_pos = virtual_pos
|
179
|
+
virtual_pos += 1
|
180
|
+
else
|
181
|
+
frame.visible = false
|
182
|
+
end
|
183
|
+
frame
|
184
|
+
end
|
185
|
+
current_frame.virtual_pos = backtrace[current_frame.real_pos].virtual_pos
|
186
|
+
backtrace
|
52
187
|
end
|
53
188
|
end
|
54
189
|
end
|