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