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
@@ -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
|