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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -0
  3. data/CHANGELOG.md +40 -0
  4. data/Gemfile +1 -1
  5. data/README.md +65 -2
  6. data/docs/guide-ui.png +0 -0
  7. data/lib/ruby_jard.rb +49 -12
  8. data/lib/ruby_jard/box_drawer.rb +126 -0
  9. data/lib/ruby_jard/column.rb +18 -0
  10. data/lib/ruby_jard/commands/continue_command.rb +1 -6
  11. data/lib/ruby_jard/commands/down_command.rb +1 -4
  12. data/lib/ruby_jard/commands/frame_command.rb +12 -11
  13. data/lib/ruby_jard/commands/next_command.rb +1 -4
  14. data/lib/ruby_jard/commands/step_command.rb +1 -4
  15. data/lib/ruby_jard/commands/step_out_command.rb +28 -0
  16. data/lib/ruby_jard/commands/up_command.rb +1 -4
  17. data/lib/ruby_jard/console.rb +86 -0
  18. data/lib/ruby_jard/control_flow.rb +71 -0
  19. data/lib/ruby_jard/decorators/color_decorator.rb +78 -0
  20. data/lib/ruby_jard/decorators/loc_decorator.rb +41 -28
  21. data/lib/ruby_jard/decorators/source_decorator.rb +1 -1
  22. data/lib/ruby_jard/key_binding.rb +14 -0
  23. data/lib/ruby_jard/key_bindings.rb +96 -0
  24. data/lib/ruby_jard/keys.rb +49 -0
  25. data/lib/ruby_jard/layout.rb +67 -55
  26. data/lib/ruby_jard/layouts/wide_layout.rb +138 -0
  27. data/lib/ruby_jard/repl_processor.rb +80 -90
  28. data/lib/ruby_jard/repl_proxy.rb +232 -0
  29. data/lib/ruby_jard/row.rb +16 -0
  30. data/lib/ruby_jard/screen.rb +114 -36
  31. data/lib/ruby_jard/screen_drawer.rb +89 -0
  32. data/lib/ruby_jard/screen_manager.rb +157 -56
  33. data/lib/ruby_jard/screens/backtrace_screen.rb +88 -97
  34. data/lib/ruby_jard/screens/menu_screen.rb +23 -31
  35. data/lib/ruby_jard/screens/source_screen.rb +42 -90
  36. data/lib/ruby_jard/screens/threads_screen.rb +50 -64
  37. data/lib/ruby_jard/screens/variables_screen.rb +96 -99
  38. data/lib/ruby_jard/session.rb +13 -7
  39. data/lib/ruby_jard/span.rb +18 -0
  40. data/lib/ruby_jard/templates/column_template.rb +17 -0
  41. data/lib/ruby_jard/templates/layout_template.rb +35 -0
  42. data/lib/ruby_jard/templates/row_template.rb +22 -0
  43. data/lib/ruby_jard/templates/screen_template.rb +35 -0
  44. data/lib/ruby_jard/templates/space_template.rb +15 -0
  45. data/lib/ruby_jard/templates/span_template.rb +25 -0
  46. data/lib/ruby_jard/version.rb +1 -1
  47. data/ruby_jard.gemspec +1 -4
  48. metadata +29 -41
  49. data/lib/ruby_jard/commands/finish_command.rb +0 -31
  50. data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
  51. data/lib/ruby_jard/layout_template.rb +0 -101
  52. data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
  53. 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
- throw :control_flow, command: :next, pry: pry_instance
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
- throw :control_flow, command: :step, pry: pry_instance
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
- throw :control_flow, command: :up, pry: pry_instance
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
- # Pastel).
10
+ # Span).
9
11
  class LocDecorator
10
- attr_reader :loc, :tokens
12
+ attr_reader :spans, :tokens
11
13
 
12
- def initialize(color_decorator, loc, highlighted)
14
+ def initialize(file, loc)
15
+ @file = file
13
16
  @loc = loc
14
- @highlighted = highlighted
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, :ruby)
25
- @loc = @encoder.encode_tokens(tokens)
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: [:underline, :green],
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
- @color_decorator = options[:color_decorator]
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
- if color
151
- color = color[:self] if color.is_a? Hash
152
- @out << @color_decorator.decorate(text, *compose_color(color))
153
- else
154
- @out << @color_decorator.decorate(text, *compose_color([]))
155
- end
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