ruby_jard 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -3,48 +3,65 @@
|
|
3
3
|
module RubyJard
|
4
4
|
module Screens
|
5
5
|
class ThreadsScreen < RubyJard::Screen
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
top: @row, left: @col, width: @layout.width, height: @layout.height
|
10
|
-
)
|
11
|
-
)
|
6
|
+
def title
|
7
|
+
"Threads (#{RubyJard.current_session.contexts.length})"
|
8
|
+
end
|
12
9
|
|
13
|
-
|
10
|
+
def data_size
|
11
|
+
[@height, RubyJard.current_session.contexts.length].min
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
@
|
17
|
-
.with_highlight(true)
|
18
|
-
.text(" Threads (#{RubyJard.current_session.contexts.length}) ", :bright_yellow)
|
19
|
-
.content
|
14
|
+
def data_window
|
15
|
+
return @data_window if defined?(@data_window)
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
@output.print thread.content
|
24
|
-
end
|
17
|
+
contexts = RubyJard.current_session.contexts.filter { |c| c.thread.alive? }
|
18
|
+
@data_window ||= sort_contexts(contexts).first(data_size)
|
25
19
|
end
|
26
20
|
|
27
|
-
|
21
|
+
def span_mark(context, _index)
|
22
|
+
[
|
23
|
+
current_thread?(context) ? '→' : ' ',
|
24
|
+
[:bright_yellow, current_thread?(context) ? :bold : nil]
|
25
|
+
]
|
26
|
+
end
|
28
27
|
|
29
|
-
def
|
30
|
-
|
28
|
+
def span_thread_id(context, _index)
|
29
|
+
[
|
30
|
+
context.thread.object_id.to_s,
|
31
|
+
[:green, current_thread?(context) ? :bold : nil]
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
def span_thread_status(context, _index)
|
36
|
+
status_color =
|
37
|
+
if context.suspended?
|
38
|
+
:red
|
39
|
+
elsif context.ignored?
|
40
|
+
:white
|
41
|
+
elsif context.thread.status == 'run'
|
42
|
+
:green
|
43
|
+
else
|
44
|
+
:white
|
45
|
+
end
|
46
|
+
[
|
47
|
+
"(#{context.thread.status})",
|
48
|
+
[status_color, current_thread?(context) ? :bold : nil]
|
49
|
+
]
|
31
50
|
end
|
32
51
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
.with_highlight(false)
|
42
|
-
.text(" (#{context.thread.status}) ", :white)
|
43
|
-
.with_highlight(current_thread?(context))
|
44
|
-
.text(thread_name(context), :bright_white)
|
52
|
+
def span_thread_name(context, _index)
|
53
|
+
if context.thread.name.nil?
|
54
|
+
last_backtrace = context.thread.backtrace_locations[1]
|
55
|
+
location = decorate_path(last_backtrace.path, last_backtrace.lineno)
|
56
|
+
["#{location.path}:#{location.lineno}", current_thread?(context) ? [:bright_white, :bold] : :white]
|
57
|
+
else
|
58
|
+
name = context.thread.name.to_s
|
59
|
+
[name, current_thread?(context) ? [:bright_white, :bold] : :white]
|
45
60
|
end
|
46
61
|
end
|
47
62
|
|
63
|
+
private
|
64
|
+
|
48
65
|
def sort_contexts(contexts)
|
49
66
|
# Sort: current context first
|
50
67
|
# Sort: not debug context first
|
@@ -75,39 +92,8 @@ module RubyJard
|
|
75
92
|
context.thread == Thread.current
|
76
93
|
end
|
77
94
|
|
78
|
-
def
|
79
|
-
|
80
|
-
decorate_text
|
81
|
-
.with_highlight(true)
|
82
|
-
.text(text, :bright_yellow)
|
83
|
-
elsif context.suspended?
|
84
|
-
decorate_text
|
85
|
-
.with_highlight(false)
|
86
|
-
.text(text, :red)
|
87
|
-
elsif context.ignored?
|
88
|
-
decorate_text
|
89
|
-
.with_highlight(false)
|
90
|
-
.text(text, :white)
|
91
|
-
else
|
92
|
-
decorate_text
|
93
|
-
.with_highlight(false)
|
94
|
-
.text(text, :bright_white)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def thread_name(context)
|
99
|
-
if context.thread.name.nil?
|
100
|
-
last_backtrace =
|
101
|
-
if context == RubyJard.current_session.current_context
|
102
|
-
context.backtrace[0][0]
|
103
|
-
else
|
104
|
-
context.thread.backtrace_locations[0]
|
105
|
-
end
|
106
|
-
location = decorate_path(last_backtrace.path, last_backtrace.lineno)
|
107
|
-
"#{location.path}:#{location.lineno}"
|
108
|
-
else
|
109
|
-
context.thread.name
|
110
|
-
end
|
95
|
+
def decorate_path(path, lineno)
|
96
|
+
RubyJard::Decorators::PathDecorator.new(path, lineno)
|
111
97
|
end
|
112
98
|
end
|
113
99
|
end
|
@@ -18,13 +18,11 @@ module RubyJard
|
|
18
18
|
[].class => :arr,
|
19
19
|
{}.class => :hash,
|
20
20
|
//.class => :reg,
|
21
|
+
(0..0).class => :rng,
|
21
22
|
Class => :cls # Sorry, I lied, Class will never change
|
22
23
|
}.freeze
|
23
|
-
TYPE_SYMBOL_PADDING = TYPE_SYMBOLS.map { |_, s| s.to_s.length }.max + 1
|
24
24
|
DEFAULT_TYPE_SYMBOL = :var
|
25
25
|
|
26
|
-
INSPECTION_ELLIPSIS = ' [...]'
|
27
|
-
|
28
26
|
KINDS = [
|
29
27
|
KIND_LOC = :loc,
|
30
28
|
KIND_INS = :ins,
|
@@ -38,36 +36,85 @@ module RubyJard
|
|
38
36
|
}.freeze
|
39
37
|
|
40
38
|
KIND_COLORS = {
|
41
|
-
KIND_LOC => :
|
42
|
-
KIND_INS => :
|
39
|
+
KIND_LOC => :bright_blue,
|
40
|
+
KIND_INS => :bright_blue,
|
43
41
|
KIND_CON => :green
|
44
42
|
}.freeze
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
44
|
+
INLINE_TOKEN_KIND_MAPS = {
|
45
|
+
KIND_LOC => :ident,
|
46
|
+
KIND_INS => :instance_variable,
|
47
|
+
KIND_CON => :constant
|
48
|
+
}.freeze
|
49
|
+
INLINE_TOKEN_KINDS = INLINE_TOKEN_KIND_MAPS.values
|
50
|
+
|
51
|
+
def title
|
52
|
+
'Variables'
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
@
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
def data_size
|
56
|
+
@height
|
57
|
+
end
|
58
|
+
|
59
|
+
def data_window
|
60
|
+
return [] if current_frame.nil?
|
61
|
+
return @data_window if defined?(@data_window)
|
58
62
|
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
variables = fetch_local_variables + fetch_instance_variables + fetch_constants
|
64
|
+
# Each row is an array of [kind, name, value]
|
65
|
+
@data_window = sort_variables(variables).first(data_size)
|
66
|
+
end
|
67
|
+
|
68
|
+
def span_inline(data_row, _index)
|
69
|
+
if inline?(data_row[0], data_row[1])
|
70
|
+
['•', [:bright_yellow, :bold]]
|
71
|
+
else
|
72
|
+
[' ']
|
62
73
|
end
|
63
74
|
end
|
64
75
|
|
65
|
-
|
76
|
+
def span_type(data_row, _index)
|
77
|
+
type_name = TYPE_SYMBOLS[data_row[2].class] || DEFAULT_TYPE_SYMBOL
|
78
|
+
[type_name.to_s, :white]
|
79
|
+
end
|
66
80
|
|
67
|
-
def
|
68
|
-
|
81
|
+
def span_name(data_row, _index)
|
82
|
+
[
|
83
|
+
data_row[1].to_s,
|
84
|
+
[
|
85
|
+
KIND_COLORS[data_row[0]] || :bright_white,
|
86
|
+
inline?(data_row[0], data_row[1]) ? :bold : nil
|
87
|
+
]
|
88
|
+
]
|
69
89
|
end
|
70
90
|
|
91
|
+
def span_indicator(_data_row, _index)
|
92
|
+
['=', [:bright_white]]
|
93
|
+
end
|
94
|
+
|
95
|
+
def span_size(data_row, _index)
|
96
|
+
value = data_row[2]
|
97
|
+
if value.is_a?(Array) && !value.empty?
|
98
|
+
["(size:#{value.length})", :white]
|
99
|
+
elsif value.is_a?(String) && value.length > 20
|
100
|
+
["(size:#{value.length})", :white]
|
101
|
+
elsif value.is_a?(Hash) && !value.empty?
|
102
|
+
["(size:#{value.length})", :white]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def span_inspection(data_row, _index)
|
107
|
+
# Hard limit: screen area
|
108
|
+
inspection = data_row[2].inspect[0..@height * @width]
|
109
|
+
# TODO: This is just a workable. Should write a decorator to inspect objects accurately
|
110
|
+
["\n", "\r", "\r\n"].each do |esc|
|
111
|
+
inspection.gsub!(esc, esc.inspect)
|
112
|
+
end
|
113
|
+
[inspection, :dim, :white]
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
71
118
|
def current_binding
|
72
119
|
RubyJard.current_session.frame._binding
|
73
120
|
end
|
@@ -84,16 +131,6 @@ module RubyJard
|
|
84
131
|
RubyJard.current_session.backtrace[RubyJard.current_session.frame.pos][2]
|
85
132
|
end
|
86
133
|
|
87
|
-
def decorated_variables
|
88
|
-
return [] if current_frame.nil?
|
89
|
-
|
90
|
-
variables = fetch_local_variables + fetch_instance_variables + fetch_constants
|
91
|
-
|
92
|
-
sort_variables(variables).map do |kind, name, value|
|
93
|
-
decorated_variable(kind, name, value)
|
94
|
-
end.flatten
|
95
|
-
end
|
96
|
-
|
97
134
|
def fetch_local_variables
|
98
135
|
variables = current_binding.local_variables
|
99
136
|
# Exclude Pry's sticky locals
|
@@ -138,73 +175,6 @@ module RubyJard
|
|
138
175
|
end.compact
|
139
176
|
end
|
140
177
|
|
141
|
-
def decorated_variable(kind, name, value)
|
142
|
-
text =
|
143
|
-
decorate_text
|
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
|
162
|
-
|
163
|
-
def addition_data(value)
|
164
|
-
if value.is_a?(Array) && !value.empty?
|
165
|
-
" (size: #{value.length})"
|
166
|
-
elsif value.is_a?(String) && value.length > 20
|
167
|
-
" (size: #{value.length})"
|
168
|
-
else
|
169
|
-
''
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def inspect_value(text, value)
|
174
|
-
# Split the lines, add padding to align with kind
|
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
|
189
|
-
|
190
|
-
if end_pos < value_inspect.length
|
191
|
-
texts.last[texts.last.length - INSPECTION_ELLIPSIS.length - 1..texts.last.length - 1] = INSPECTION_ELLIPSIS
|
192
|
-
end
|
193
|
-
|
194
|
-
texts
|
195
|
-
end
|
196
|
-
|
197
|
-
def decorated_type(value)
|
198
|
-
type_name = TYPE_SYMBOLS[value.class] || DEFAULT_TYPE_SYMBOL
|
199
|
-
decorate_text
|
200
|
-
.with_highlight(false)
|
201
|
-
.text(type_name.to_s.ljust(TYPE_SYMBOL_PADDING), :white)
|
202
|
-
end
|
203
|
-
|
204
|
-
def kind_color(kind)
|
205
|
-
KIND_COLORS[kind] || :white
|
206
|
-
end
|
207
|
-
|
208
178
|
def sort_variables(variables)
|
209
179
|
# Sort by kind
|
210
180
|
# Sort by "internal" character so that internal variable is pushed down
|
@@ -227,6 +197,33 @@ module RubyJard
|
|
227
197
|
end
|
228
198
|
end
|
229
199
|
end
|
200
|
+
|
201
|
+
def inline?(kind, name)
|
202
|
+
tokens = inline_tokens[INLINE_TOKEN_KIND_MAPS[kind]] || []
|
203
|
+
tokens.include?(name)
|
204
|
+
end
|
205
|
+
|
206
|
+
def inline_tokens
|
207
|
+
return @inline_tokens if defined?(@inline_tokens)
|
208
|
+
|
209
|
+
current_file = RubyJard.current_session.frame.file
|
210
|
+
current_line = RubyJard.current_session.frame.line
|
211
|
+
source_decorator = RubyJard::Decorators::SourceDecorator.new(current_file, current_line, 1)
|
212
|
+
loc_decorator = RubyJard::Decorators::LocDecorator.new(
|
213
|
+
current_file,
|
214
|
+
source_decorator.codes[current_line - source_decorator.window_start]
|
215
|
+
)
|
216
|
+
|
217
|
+
@inline_tokens = {}
|
218
|
+
loc_decorator.tokens.each_slice(2) do |token, kind|
|
219
|
+
next unless INLINE_TOKEN_KINDS.include?(kind)
|
220
|
+
|
221
|
+
@inline_tokens[kind] ||= []
|
222
|
+
@inline_tokens[kind] << token.to_s.to_sym
|
223
|
+
end
|
224
|
+
|
225
|
+
@inline_tokens
|
226
|
+
end
|
230
227
|
end
|
231
228
|
end
|
232
229
|
end
|
data/lib/ruby_jard/session.rb
CHANGED
@@ -11,7 +11,7 @@ module RubyJard
|
|
11
11
|
# other processes. Therefore, an internal, jard-specific data mapping should
|
12
12
|
# be built.
|
13
13
|
class Session
|
14
|
-
attr_reader :
|
14
|
+
attr_reader :backtrace, :frame, :contexts, :current_context
|
15
15
|
|
16
16
|
def initialize(options = {})
|
17
17
|
@options = options
|
@@ -21,14 +21,12 @@ module RubyJard
|
|
21
21
|
@contexts = []
|
22
22
|
|
23
23
|
@started = false
|
24
|
-
@
|
24
|
+
@session_lock = Mutex.new
|
25
25
|
end
|
26
26
|
|
27
27
|
def start
|
28
28
|
return if started?
|
29
29
|
|
30
|
-
@screen_manager.start
|
31
|
-
|
32
30
|
@started = true
|
33
31
|
end
|
34
32
|
|
@@ -43,12 +41,20 @@ module RubyJard
|
|
43
41
|
Byebug.current_context.step_out(2, false)
|
44
42
|
end
|
45
43
|
|
46
|
-
def
|
44
|
+
def update
|
47
45
|
@backtrace = Byebug.current_context.backtrace
|
48
46
|
@frame = Byebug.current_context.frame
|
49
47
|
@contexts = Byebug.contexts
|
50
|
-
|
51
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
def lock
|
51
|
+
raise RubyJard::Error, 'This method requires a block' unless block_given?
|
52
|
+
|
53
|
+
# TODO: This doesn't solve anything. However, debugging a multi-threaded process is hard.
|
54
|
+
# Let's deal with that later.
|
55
|
+
@session_lock.synchronize do
|
56
|
+
yield
|
57
|
+
end
|
52
58
|
end
|
53
59
|
end
|
54
60
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
class Span
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_accessor :span_template, :content, :content_length, :styles
|
8
|
+
|
9
|
+
def_delegators :@span_template, :margin_left, :margin_right, :word_wrap, :ellipsis
|
10
|
+
|
11
|
+
def initialize(span_template:, content: '', content_length: 0, styles: [])
|
12
|
+
@span_template = span_template
|
13
|
+
@content = content
|
14
|
+
@content_length = content_length
|
15
|
+
@styles = styles
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
module Templates
|
5
|
+
##
|
6
|
+
# Template for a column. All items in a column align with each other. A column includes one or more spans.
|
7
|
+
class ColumnTemplate
|
8
|
+
attr_reader :spans, :margin_right, :margin_left
|
9
|
+
|
10
|
+
def initialize(spans: [], margin_left: 0, margin_right: 0)
|
11
|
+
@spans = spans
|
12
|
+
@margin_left = margin_left
|
13
|
+
@margin_right = margin_right
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|