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,9 +3,6 @@
|
|
3
3
|
module RubyJard
|
4
4
|
module Commands
|
5
5
|
# Command used to continue program execution to the next line.
|
6
|
-
# Data attached in the throw:
|
7
|
-
# * command: constant symbol (:next)
|
8
|
-
# * pry: current context pry instance
|
9
6
|
class NextCommand < Pry::ClassCommand
|
10
7
|
group 'RubyJard'
|
11
8
|
description 'Next into the execution of the current line'
|
@@ -22,7 +19,7 @@ module RubyJard
|
|
22
19
|
BANNER
|
23
20
|
|
24
21
|
def process
|
25
|
-
|
22
|
+
RubyJard::ControlFlow.dispatch(:next)
|
26
23
|
end
|
27
24
|
end
|
28
25
|
end
|
@@ -3,9 +3,6 @@
|
|
3
3
|
module RubyJard
|
4
4
|
module Commands
|
5
5
|
# Command used to Step into the execution of the current line.
|
6
|
-
# Data attached in the throw:
|
7
|
-
# * command: constant symbol (:step)
|
8
|
-
# * pry: current context pry instance
|
9
6
|
class StepCommand < Pry::ClassCommand
|
10
7
|
group 'RubyJard'
|
11
8
|
description 'Step into the execution of the current line'
|
@@ -22,7 +19,7 @@ module RubyJard
|
|
22
19
|
BANNER
|
23
20
|
|
24
21
|
def process
|
25
|
-
|
22
|
+
RubyJard::ControlFlow.dispatch(:step)
|
26
23
|
end
|
27
24
|
end
|
28
25
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
module Commands
|
5
|
+
# Command used to Step into the execution of the current line.
|
6
|
+
class StepOutCommand < Pry::ClassCommand
|
7
|
+
group 'RubyJard'
|
8
|
+
description 'Step out of current frame and move to the execution of the upper frame'
|
9
|
+
|
10
|
+
match 'step-out'
|
11
|
+
|
12
|
+
banner <<-BANNER
|
13
|
+
Usage: step-out
|
14
|
+
|
15
|
+
Step out of current frame and move to the execution of the upper frame
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
step-out
|
19
|
+
BANNER
|
20
|
+
|
21
|
+
def process
|
22
|
+
RubyJard::ControlFlow.dispatch(:step_out)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Pry::Commands.add_command(RubyJard::Commands::StepOutCommand)
|
@@ -3,9 +3,6 @@
|
|
3
3
|
module RubyJard
|
4
4
|
module Commands
|
5
5
|
# Command used to explore stacktrace.
|
6
|
-
# Data attached in the throw:
|
7
|
-
# * command: constant symbol (:up)
|
8
|
-
# * pry: current context pry instance
|
9
6
|
class UpCommand < Pry::ClassCommand
|
10
7
|
group 'RubyJard'
|
11
8
|
description 'Explore the frames above the current stopped line in the backtrace'
|
@@ -22,7 +19,7 @@ module RubyJard
|
|
22
19
|
BANNER
|
23
20
|
|
24
21
|
def process
|
25
|
-
|
22
|
+
RubyJard::ControlFlow.dispatch(:up)
|
26
23
|
end
|
27
24
|
end
|
28
25
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/console'
|
4
|
+
require 'English'
|
5
|
+
|
6
|
+
module RubyJard
|
7
|
+
# Wrapper for utilities to control screen
|
8
|
+
class Console
|
9
|
+
class << self
|
10
|
+
def start_alternative_terminal(output)
|
11
|
+
return unless output.tty?
|
12
|
+
|
13
|
+
output.print tput('smcup')
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop_alternative_terminal(output)
|
17
|
+
return unless output.tty?
|
18
|
+
|
19
|
+
output.print tput('rmcup')
|
20
|
+
end
|
21
|
+
|
22
|
+
def move_to(output, x, y)
|
23
|
+
return unless output.tty?
|
24
|
+
|
25
|
+
output.goto(y, x)
|
26
|
+
end
|
27
|
+
|
28
|
+
def screen_size(output)
|
29
|
+
return [0, 0] unless output.tty?
|
30
|
+
|
31
|
+
height, width = output.winsize
|
32
|
+
[width, height]
|
33
|
+
end
|
34
|
+
|
35
|
+
def hard_clear_screen(output)
|
36
|
+
return unless output.tty?
|
37
|
+
|
38
|
+
output.print tput('clear')
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_screen(output)
|
42
|
+
return unless output.tty?
|
43
|
+
|
44
|
+
output.clear_screen
|
45
|
+
end
|
46
|
+
|
47
|
+
def hide_cursor(output)
|
48
|
+
return unless output.tty?
|
49
|
+
|
50
|
+
output.print tput('civis')
|
51
|
+
end
|
52
|
+
|
53
|
+
def show_cursor(output)
|
54
|
+
return unless output.tty?
|
55
|
+
|
56
|
+
output.print tput('cvvis')
|
57
|
+
end
|
58
|
+
|
59
|
+
def cooked!(output)
|
60
|
+
return unless output.tty?
|
61
|
+
|
62
|
+
output.cooked!
|
63
|
+
end
|
64
|
+
|
65
|
+
def echo!(output)
|
66
|
+
return unless output.tty?
|
67
|
+
|
68
|
+
output.echo = true
|
69
|
+
end
|
70
|
+
|
71
|
+
def tput(*args)
|
72
|
+
# TODO: Should implement multiple fallbacks here to support different platforms
|
73
|
+
|
74
|
+
command = "tput #{args.join(' ')}"
|
75
|
+
output = `#{command}`
|
76
|
+
if $CHILD_STATUS.success?
|
77
|
+
output
|
78
|
+
else
|
79
|
+
raise Ruby::Error, "Fail to call `#{command}`: #{$CHILD_STATUS}"
|
80
|
+
end
|
81
|
+
rescue StandardError => e
|
82
|
+
raise Ruby::Error, "Fail to call `#{command}`. Error: #{e}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
##
|
5
|
+
# A helper to standardize control instruction passed around via
|
6
|
+
# throw and catch mechanism.
|
7
|
+
class ControlFlow
|
8
|
+
THROW_KEYWORD = :jard_control_flow
|
9
|
+
ALLOW_LIST = {
|
10
|
+
continue: [:times], # lib/ruby_jard/commands/continue_command.rb
|
11
|
+
frame: [:frame], # lib/ruby_jard/commands/frame_command.rb
|
12
|
+
up: [:times], # lib/ruby_jard/commands/up_command.rb
|
13
|
+
down: [:times], # lib/ruby_jard/commands/down_command.rb
|
14
|
+
next: [:times], # lib/ruby_jard/commands/next_command.rb
|
15
|
+
step: [:times], # lib/ruby_jard/commands/step_command.rb
|
16
|
+
step_out: [:times], # lib/ruby_jard/commands/step_out_command.rb
|
17
|
+
key_binding: [:action] # lib/ruby_jard/commands/step_command.rb
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
attr_reader :command, :arguments
|
21
|
+
|
22
|
+
def initialize(command, arguments)
|
23
|
+
@command = command
|
24
|
+
@arguments = arguments
|
25
|
+
|
26
|
+
validate!
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate!
|
30
|
+
if command.to_s.empty?
|
31
|
+
raise RubyJard::Error, 'Control command is empty'
|
32
|
+
end
|
33
|
+
|
34
|
+
unless ALLOW_LIST.key?(command)
|
35
|
+
raise RubyJard::Error,
|
36
|
+
"Control command `#{command}` is not registered in the allow list."
|
37
|
+
end
|
38
|
+
|
39
|
+
invalid_keys = arguments.keys - ALLOW_LIST[command]
|
40
|
+
unless invalid_keys.empty?
|
41
|
+
raise RubyJard::Error,
|
42
|
+
"Control command `#{command}` is attached with unregister arguments: #{invalid_keys}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
def dispatch(command, arguments = {})
|
48
|
+
if command.is_a?(RubyJard::ControlFlow)
|
49
|
+
throw THROW_KEYWORD, command
|
50
|
+
else
|
51
|
+
throw THROW_KEYWORD, new(command, arguments)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def listen
|
56
|
+
raise RubyJard::Error, 'This method requires a block' unless block_given?
|
57
|
+
|
58
|
+
flow = catch(THROW_KEYWORD) do
|
59
|
+
yield
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
if flow.nil? || flow.is_a?(RubyJard::ControlFlow)
|
64
|
+
flow
|
65
|
+
else
|
66
|
+
raise RubyJard::Error, 'Control flow misused!'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pastel'
|
4
|
+
|
5
|
+
module RubyJard
|
6
|
+
module Decorators
|
7
|
+
##
|
8
|
+
# Manipulate and decorate color for texts. The core is Pastel, which is a library to
|
9
|
+
# inject escape sequences to let the terminal emulator aware of target color. This
|
10
|
+
# class wraps around the core, validates, and standardizes the styles before feeding
|
11
|
+
# styling information to Pastel.
|
12
|
+
class ColorDecorator
|
13
|
+
COLORS = [
|
14
|
+
:black,
|
15
|
+
:red,
|
16
|
+
:green,
|
17
|
+
:yellow,
|
18
|
+
:blue,
|
19
|
+
:magenta,
|
20
|
+
:cyan,
|
21
|
+
:white
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@pastel = Pastel.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def decorate(text, *styles)
|
29
|
+
styles = standardize_styles(styles)
|
30
|
+
@pastel.decorate(text, *styles)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def standardize_styles(styles)
|
36
|
+
return [] if styles.include?(:clear)
|
37
|
+
|
38
|
+
if styles.include?(:darker)
|
39
|
+
# Convert all bright_color -> color
|
40
|
+
styles = darker(styles)
|
41
|
+
elsif styles.include?(:brighter)
|
42
|
+
# Convert all color -> bright_color
|
43
|
+
styles = brighter(styles)
|
44
|
+
end
|
45
|
+
|
46
|
+
styles.uniq.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def darker(styles)
|
50
|
+
styles.map do |color|
|
51
|
+
next if [:darker, :brighter].include?(color)
|
52
|
+
|
53
|
+
color_str = color.to_s
|
54
|
+
if color_str.start_with?('bright_')
|
55
|
+
color_str.gsub(/^bright_/i, '').to_sym
|
56
|
+
else
|
57
|
+
color
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def brighter(styles)
|
63
|
+
styles.map do |color|
|
64
|
+
next if [:darker, :brighter].include?(color)
|
65
|
+
|
66
|
+
color_str = color.to_s
|
67
|
+
if color_str.start_with?('bright_')
|
68
|
+
color
|
69
|
+
elsif COLORS.include?(color)
|
70
|
+
"bright_#{color}".to_sym
|
71
|
+
else
|
72
|
+
color
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,32 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'coderay'
|
4
|
+
|
3
5
|
module RubyJard
|
4
6
|
module Decorators
|
5
7
|
##
|
6
8
|
# Decorate a line of code fetched from the source file.
|
7
9
|
# The line is tokenized, and feed into JardEncoder to append color (with
|
8
|
-
#
|
10
|
+
# Span).
|
9
11
|
class LocDecorator
|
10
|
-
attr_reader :
|
12
|
+
attr_reader :spans, :tokens
|
11
13
|
|
12
|
-
def initialize(
|
14
|
+
def initialize(file, loc)
|
15
|
+
@file = file
|
13
16
|
@loc = loc
|
14
|
-
@
|
15
|
-
@encoder = JardLocEncoder.new(
|
16
|
-
color_decorator: color_decorator,
|
17
|
-
highlighted: highlighted
|
18
|
-
)
|
17
|
+
@encoder = JardLocEncoder.new
|
19
18
|
|
20
19
|
decorate
|
21
20
|
end
|
22
21
|
|
23
22
|
def decorate
|
24
|
-
@tokens = CodeRay.scan(@loc,
|
25
|
-
@
|
23
|
+
@tokens = CodeRay.scan(@loc, extension)
|
24
|
+
@spans = @encoder.encode_tokens(tokens)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def extension
|
30
|
+
# TODO: A map constant is better
|
31
|
+
if @file =~ /.*\.erb$/
|
32
|
+
:erb
|
33
|
+
elsif @file =~ /.*\.haml$/
|
34
|
+
:haml
|
35
|
+
else
|
36
|
+
:ruby
|
37
|
+
end
|
26
38
|
end
|
27
39
|
|
28
40
|
# A shameless copy from https://github.com/rubychan/coderay/blob/master/lib/coderay/encoders/terminal.rb
|
29
41
|
class JardLocEncoder < CodeRay::Encoders::Encoder
|
42
|
+
DEFAULT_COLOR = [:white].freeze
|
30
43
|
TOKEN_COLORS = {
|
31
44
|
debug: [:white, :on_blue],
|
32
45
|
annotation: [:blue],
|
@@ -49,7 +62,7 @@ module RubyJard
|
|
49
62
|
char: [:white],
|
50
63
|
delimiter: [:white]
|
51
64
|
},
|
52
|
-
constant: [:
|
65
|
+
constant: [:green],
|
53
66
|
decorator: [:blue],
|
54
67
|
definition: [:blue],
|
55
68
|
directive: [:blue],
|
@@ -129,7 +142,8 @@ module RubyJard
|
|
129
142
|
head: {
|
130
143
|
self: [:on_red],
|
131
144
|
filename: [:white, :on_red]
|
132
|
-
}
|
145
|
+
},
|
146
|
+
instance_variable: [:blue]
|
133
147
|
}.freeze
|
134
148
|
|
135
149
|
protected
|
@@ -138,8 +152,7 @@ module RubyJard
|
|
138
152
|
super
|
139
153
|
@opened = []
|
140
154
|
@color_scopes = [TOKEN_COLORS]
|
141
|
-
@
|
142
|
-
@highlighted = options[:highlighted]
|
155
|
+
@out = []
|
143
156
|
end
|
144
157
|
|
145
158
|
public
|
@@ -147,12 +160,20 @@ module RubyJard
|
|
147
160
|
def text_token(text, kind)
|
148
161
|
color = @color_scopes.last[kind]
|
149
162
|
text.gsub!("\n", '')
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
163
|
+
styles =
|
164
|
+
if !color
|
165
|
+
DEFAULT_COLOR
|
166
|
+
elsif color.is_a? Hash
|
167
|
+
color[:self]
|
168
|
+
else
|
169
|
+
color
|
170
|
+
end
|
171
|
+
@out << Span.new(
|
172
|
+
span_template: nil,
|
173
|
+
content: text,
|
174
|
+
content_length: text.length,
|
175
|
+
styles: styles
|
176
|
+
)
|
156
177
|
end
|
157
178
|
|
158
179
|
def begin_group(kind)
|
@@ -186,14 +207,6 @@ module RubyJard
|
|
186
207
|
@color_scopes.last
|
187
208
|
end
|
188
209
|
end
|
189
|
-
|
190
|
-
def compose_color(color)
|
191
|
-
if @highlighted
|
192
|
-
[:clear] + color
|
193
|
-
else
|
194
|
-
[:dim] + color
|
195
|
-
end
|
196
|
-
end
|
197
210
|
end
|
198
211
|
end
|
199
212
|
end
|