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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4eaeb2003b2133100d1723d4fc5b3de3ea0ce6c1fd9f5e754a69ac7a489198d4
4
- data.tar.gz: a1389c55f973eacda0e4fa6fdbf4b4f6f23feb6cf1ab38050f7fd3f8a1d689ed
3
+ metadata.gz: 94dbda134fd22365f583f71fa2aaddffae206045cc8da4fd6ee51e2eeaebfca7
4
+ data.tar.gz: af8c443ac2c40619cead142c3e77dd3b7f7ad59c19993966edcdd1eae8c31a3d
5
5
  SHA512:
6
- metadata.gz: 04f8ca93a51eef68e3139fa58a5d7acb5ece7f79023dcff53bde4796628518f7fdc86a40903df1d680f4e13c86542a9b48fdc228a899c15674bb9a18e33d8419
7
- data.tar.gz: 73608c00fe978926adaf05361e7d1467a6c9da325e6cdeba71ed25ddb00e8d027b788ca9154c2c74c13f4b36dbe4502b2e9f12fb9c82ba815b0967c7fa84765e
6
+ metadata.gz: 6d18a6894bcb24bae3bf30244dfcf1f39412e9804df5897e7adac6b74cdb8929b3353a8a0999ec55b687bfa60e87890a5db637bfe7d046b30a470b0d5158c46e
7
+ data.tar.gz: 7eaacc77cd4eaa4fdb7e183292872cef01217e5c92f75bea1882d454dad1ad1ae97ff3790ca129e76de4f893167d1b1b81b6deb7eb44b6931d67a23cca03f7f5
@@ -1,9 +1,19 @@
1
+ Naming/MethodParameterName:
2
+ Enabled: false
1
3
  Style/SymbolArray:
2
4
  Enabled: false
5
+ Style/GuardClause:
6
+ Enabled: false
7
+ Style/NumericPredicate:
8
+ Enabled: false
9
+ Style/IfUnlessModifier:
10
+ Enabled: false
3
11
  Metrics/MethodLength:
4
12
  Max: 20
5
13
  Metrics/PerceivedComplexity:
6
14
  Max: 15
15
+ Metrics/AbcSize:
16
+ Enabled: false
7
17
  Metrics/ParameterLists:
8
18
  Enabled: false
9
19
  Metrics/ClassLength:
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0 - Alpha 2] - UI completeness
4
+
5
+ ### UX/UI
6
+ - Improve box drawing.
7
+ - Isolate jard-related UI in an alternative termnial, just like Vim or Less.
8
+ - Restore printed information from STDOUT and STDERR after jard exits.
9
+ - Support keyboard shortcut.
10
+ - Support erb, haml highlighting.
11
+ - Increase contrast and enhance color scheme.
12
+ - Remove `finish` command.
13
+ - Add `frame` command.
14
+ - Add `step-out` command.
15
+ - Remove useless inline variables.
16
+ - Indicate types and inline variables in variable screen.
17
+
18
+ ### Bug fixes
19
+ - Fix line number and loc mismatching if the current source viewport is at the start of the file.
20
+ - Multiple layout broken, overlapping text glitches.
21
+
22
+ ### Internal & Refactoring
23
+ - Refactor screen's data flow.
24
+ - Standardize control flow.
25
+ - Replace `tty-cursor`, `tty-screen` by native Ruby alternative.
26
+ - Replace `tty-box` by home-growing solution.
27
+ - Remove text decorator.
28
+ - Implement color decorator
29
+ - Implement keybinding register and matching mechanism.
30
+ - Implement ReplProxy to wrap around Pry instance.
31
+ - Utility to debug and benchmark.
32
+
33
+ ## [0.1.0 - Alpha] - Alpha initial version
34
+ **Release date**: July 1st 2020
35
+
36
+ - Default Terminal UI, in which the layout and display are responsive to support different screen size.
37
+ - Highlighted source code screen.
38
+ - Stacktrace visulization and navigation.
39
+ - Auto explore and display variables in the current context.
40
+ - Multi-thread exploration and debugging.
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source 'http://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
data/README.md CHANGED
@@ -1,10 +1,73 @@
1
1
  # RubyJard
2
2
 
3
- Just another ruby debugger. Jard provides an unified experience debugging Ruby source code in different platforms and editors. Jard is heavily under development. So, nothing to see here :smile:. When everything is ready, I'll update this file.
3
+ Jard stands for Just Another Ruby Debugger. It implements a layer of UI wrapping around byebug, aims to provide a unified experience when debugging Ruby source code. Ruby Jard supports the following major features at the moment:
4
+
5
+ - Default Terminal UI, in which the layout and display are responsive to support different screen sizes.
6
+ - Highlighted source code screen.
7
+ - Stacktrace visualization and navigation.
8
+ - Auto explore and display variables in the current context.
9
+ - Multi-thread exploration and debugging.
10
+
11
+ In the roadmap:
12
+ - Dynamic breakpoints.
13
+ - Watch expressions.
14
+ - Support different screen sizes.
15
+ - Minimal layout configuration.
16
+ - Fully layout configuration with Tmux.
17
+ - Integrate with Vim.
18
+ - Integrate with Visual Studio Code.
19
+ - Encrypted remote debugging.
20
+ - Some handful debug tools and data visualization.
21
+
22
+ Ruby Jard's core is Byebug, an awesome de factor debugger for Ruby. Therefore, Ruby Jard supports most of Byebug's functionalities.
23
+
24
+ If you still don't know what it is, let's watch this video. This is a record of debugging Code Ray - A syntax highlighter for Ruby.
25
+
26
+ [![asciicast](https://asciinema.org/a/ejWK3Px55QNQmhM4nbd1lSigW.svg)](https://asciinema.org/a/ejWK3Px55QNQmhM4nbd1lSigW)
27
+
28
+ ## Getting Started
29
+
30
+ **Warning**: Ruby Jard is still under heavy development. Bugs and weird behaviors are expected. If you see one, please don't hesitate to open an issue. I'll try my best to fix.
31
+
32
+ Add `ruby_jard` to your Gemfile, recommend to put it in test or development environment.
33
+
34
+ ``` ruby
35
+ gem 'ruby_jard'
36
+ ```
37
+
38
+ Add magic method `jard` before the line you want to debug, just like `byebug`
39
+
40
+ ```ruby
41
+ def test_method
42
+ a = 1
43
+ b = 2
44
+ jard # Debugger will stop here
45
+ c = a + b
46
+ end
47
+ ```
48
+
49
+ ## Guides
50
+
51
+ ![Guide UI](./docs/guide-ui.png)
52
+
53
+ The main page of the debugging UI includes 4 screens:
54
+ - Source: Show the current file, surrounding the source code of the executing line.
55
+ - Stacktrace: Show the current thread's stack trace. You can use `up`, `down`, `frame` to navigate through the stack trace.
56
+ - Variables: Show all the variables in the current context, including local variables, instance variables, and constants.
57
+ - Threads: Show all the threads of the process. This screen is valuable in case you are debugging a multi-threaded program.
58
+
59
+ At this alpha state, Ruby Jard support some basic commands:
60
+ - `next`: continue the execution to the next line.
61
+ - `step`: continue the execution, do deep into the implementation of methods in the current line.
62
+ - `continue`: continue the execution. The program gonna stops at the next breakpoint.
63
+ - `finish`: Finish the execution of the current frame, and go back.
64
+ - `up`: explore the stack trace, jump to the upper frame of the current program
65
+ - `down`: explore the stack trace, jump to the lower frame of the current program
66
+ - `frame`: explore the stack trace, jump to a precise frame in the stack trace.
4
67
 
5
68
  ## Contributing
6
69
 
7
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_jard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/ruby_jard/blob/master/CODE_OF_CONDUCT.md).
70
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nguyenquangminh0711/ruby_jard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nguyenquangminh0711/ruby_jard/blob/master/CODE_OF_CONDUCT.md).
8
71
 
9
72
 
10
73
  ## License
Binary file
@@ -1,21 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pry'
4
- require 'coderay'
5
4
  require 'byebug/core'
6
5
  require 'byebug/attacher'
7
- require 'tty-cursor'
8
- require 'tty-box'
9
- require 'tty-screen'
10
-
11
- require 'ruby_jard/commands/continue_command'
12
- require 'ruby_jard/commands/up_command'
13
- require 'ruby_jard/commands/down_command'
14
- require 'ruby_jard/commands/next_command'
15
- require 'ruby_jard/commands/step_command'
16
- require 'ruby_jard/commands/finish_command'
17
- require 'ruby_jard/commands/frame_command'
6
+ require 'forwardable'
7
+ require 'benchmark'
18
8
 
9
+ require 'ruby_jard/control_flow'
10
+ require 'ruby_jard/keys'
11
+ require 'ruby_jard/key_binding'
12
+ require 'ruby_jard/key_bindings'
13
+ require 'ruby_jard/repl_proxy'
19
14
  require 'ruby_jard/repl_processor'
20
15
  require 'ruby_jard/screen_manager'
21
16
 
@@ -46,9 +41,51 @@ require 'ruby_jard/version'
46
41
  module RubyJard
47
42
  class Error < StandardError; end
48
43
 
44
+ DEFAULT_LAYOUT_TEMPLATES = [
45
+ RubyJard::Layouts::WideLayout
46
+ ].freeze
47
+
49
48
  def self.current_session
50
49
  @current_session ||= RubyJard::Session.new
51
50
  end
51
+
52
+ def self.benchmark(name)
53
+ @benchmark_depth ||= 0
54
+ @benchmark_depth += 1
55
+ return_value = nil
56
+ time = Benchmark.realtime { return_value = yield }
57
+ debug("#{' ' * @benchmark_depth}Benchmark `#{name}`: #{time}")
58
+ @benchmark_depth -= 1
59
+ return_value
60
+ end
61
+
62
+ def self.debug(*info)
63
+ @debug_info ||= []
64
+ @debug_info += info
65
+ File.open('./jard_debugs.txt', 'a') do |f|
66
+ info.each do |line|
67
+ f.puts line
68
+ end
69
+ end
70
+ end
71
+
72
+ def self.debug_info
73
+ @debug_info ||= []
74
+ end
75
+
76
+ def self.clear_debug
77
+ @debug_info = []
78
+ end
79
+
80
+ def self.global_key_bindings
81
+ return @global_key_bindings if defined?(@global_key_bindings)
82
+
83
+ @global_key_bindings = RubyJard::KeyBindings.new
84
+ RubyJard::Keys::DEFAULT_KEY_BINDINGS.each do |sequence, action|
85
+ @global_key_bindings.push(sequence, action)
86
+ end
87
+ @global_key_bindings
88
+ end
52
89
  end
53
90
 
54
91
  ##
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Drawer to draw a nice single-line box that maximize screen area
6
+ #
7
+ # Each screen has 4 corners and clock-wise corresponding ID:
8
+ # - Top-left => 1
9
+ # - Top-right => 2
10
+ # - Bottom-right => 3
11
+ # - Bottom-left => 4
12
+ #
13
+ # For each screen, add each point to a coordinator hash.
14
+ # - If a point is occupied by 1 screen, draw normal box corner symbol.
15
+ # - If a point is occupied by 2 screens, look up in a map, and draw corresponding intersection corner symbol.
16
+ # - If a point is occupied by 3 or more screens, it's definitely a + symbol.
17
+ #
18
+ # The corner list at each point (x, y) is unique. If 2 screens overlap a point with same corner ID, it means
19
+ # 2 screens overlap, and have same corner symbol.
20
+ class BoxDrawer
21
+ CORNERS = [
22
+ TOP_LEFT = 1,
23
+ TOP_RIGHT = 2,
24
+ BOTTOM_RIGHT = 3,
25
+ BOTTOM_LEFT = 4
26
+ ].freeze
27
+
28
+ HORIZONTAL_LINE = '─'
29
+ VERTICAL_LINE = '│'
30
+ CROSS_CORNER = '┼'
31
+
32
+ NORMALS_CORNERS = {
33
+ TOP_LEFT => '┌',
34
+ TOP_RIGHT => '┐',
35
+ BOTTOM_RIGHT => '┘',
36
+ BOTTOM_LEFT => '└'
37
+ }.freeze
38
+
39
+ OVERLAPPED_CORNERS = {
40
+ [TOP_LEFT, TOP_RIGHT] => '┬',
41
+ [TOP_LEFT, BOTTOM_RIGHT] => '┼',
42
+ [TOP_LEFT, BOTTOM_LEFT] => '├',
43
+ [TOP_RIGHT, BOTTOM_RIGHT] => '┤',
44
+ [TOP_RIGHT, BOTTOM_LEFT] => '┼',
45
+ [BOTTOM_RIGHT, BOTTOM_LEFT] => '┴'
46
+ }.freeze
47
+
48
+ def initialize(output:, screens:)
49
+ @output = output
50
+ @screens = screens
51
+ @color_decorator = RubyJard::Decorators::ColorDecorator.new
52
+ end
53
+
54
+ def draw
55
+ draw_basic_lines
56
+ corners = calculate_corners
57
+ draw_corners(corners)
58
+ draw_titles
59
+ end
60
+
61
+ private
62
+
63
+ def draw_basic_lines
64
+ # Exclude the corners
65
+ @screens.each do |screen|
66
+ RubyJard::Console.move_to(@output, screen.x + 1, screen.y)
67
+ @output.print HORIZONTAL_LINE * (screen.width - 2)
68
+
69
+ RubyJard::Console.move_to(@output, screen.x + 1, screen.y + screen.height - 1)
70
+ @output.print HORIZONTAL_LINE * (screen.width - 2)
71
+
72
+ (screen.y + 1..screen.y + screen.height - 2).each do |moving_y|
73
+ RubyJard::Console.move_to(@output, screen.x, moving_y)
74
+ @output.print VERTICAL_LINE
75
+ RubyJard::Console.move_to(@output, screen.x + screen.width - 1, moving_y)
76
+ @output.print VERTICAL_LINE
77
+ end
78
+ end
79
+ end
80
+
81
+ def draw_corners(corners)
82
+ corners.each do |x, corners_x|
83
+ corners_x.each do |y, ids|
84
+ RubyJard::Console.move_to(@output, x, y)
85
+
86
+ case ids.length
87
+ when 1
88
+ @output.print NORMALS_CORNERS[ids.first]
89
+ when 2
90
+ ids = ids.sort
91
+ @output.print OVERLAPPED_CORNERS[ids]
92
+ else
93
+ @output.print CROSS_CORNER
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def draw_titles
100
+ @screens.each do |screen|
101
+ next unless screen.respond_to?(:title)
102
+ RubyJard::Console.move_to(@output, screen.x + 2, screen.y)
103
+ @output.print ' '
104
+ @output.print @color_decorator.decorate(screen.title, :bright_yellow, :bold)
105
+ @output.print ' '
106
+ end
107
+ end
108
+
109
+ def calculate_corners
110
+ corners = {}
111
+ @screens.each do |screen|
112
+ mark_corner(corners, screen.x, screen.y, TOP_LEFT)
113
+ mark_corner(corners, screen.x + screen.width - 1, screen.y, TOP_RIGHT)
114
+ mark_corner(corners, screen.x + screen.width - 1, screen.y + screen.height - 1, BOTTOM_RIGHT)
115
+ mark_corner(corners, screen.x, screen.y + screen.height - 1, BOTTOM_LEFT)
116
+ end
117
+ corners
118
+ end
119
+
120
+ def mark_corner(corners, x, y, id)
121
+ corners[x] ||= {}
122
+ corners[x][y] ||= []
123
+ corners[x][y] << id unless corners[x][y].include?(id)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ class Column
5
+ extend Forwardable
6
+
7
+ attr_accessor :column_template, :spans, :content_length, :width
8
+
9
+ def_delegators :@column_template, :margin_left, :margin_right
10
+
11
+ def initialize(column_template:)
12
+ @column_template = column_template
13
+ @spans = []
14
+ @width = 0
15
+ @content_length = 0
16
+ end
17
+ end
18
+ end
@@ -3,9 +3,6 @@
3
3
  module RubyJard
4
4
  module Commands
5
5
  # Command used to continue program execution.
6
- # Data attached in the throw:
7
- # * command: constant symbol (:continue)
8
- # * pry: current context pry instance
9
6
  class ContinueCommand < Pry::ClassCommand
10
7
  group 'RubyJard'
11
8
  description 'Continue program execution.'
@@ -22,9 +19,7 @@ module RubyJard
22
19
  BANNER
23
20
 
24
21
  def process
25
- throw :control_flow, command: :continue, pry: pry_instance
26
- ensure
27
- Byebug.stop if Byebug.stoppable?
22
+ RubyJard::ControlFlow.dispatch(:continue)
28
23
  end
29
24
  end
30
25
  end
@@ -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 (:down)
8
- # * pry: current context pry instance
9
6
  class DownCommand < Pry::ClassCommand
10
7
  group 'RubyJard'
11
8
  description 'Explore the frames bellow 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: :down, pry: pry_instance
22
+ RubyJard::ControlFlow.dispatch(:down)
26
23
  end
27
24
  end
28
25
  end
@@ -3,10 +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 (:frame)
8
- # * pry: current context pry instance
9
- # * frame (optional): frame id of the destination frame
10
6
  class FrameCommand < Pry::ClassCommand
11
7
  group 'RubyJard'
12
8
  description 'Explore to any frame of current stacktrace.'
@@ -14,20 +10,25 @@ module RubyJard
14
10
  match 'frame'
15
11
 
16
12
  banner <<-BANNER
17
- Usage: frame
13
+ Usage: frame [FRAME_ID]
18
14
 
19
15
  Explore to any frame of current stacktrace.
20
16
 
21
17
  Examples:
22
- frame [FRAME_ID]
18
+ frame 4 # Jump to frame 4 in the backtrace
23
19
  BANNER
24
20
 
25
21
  def process
26
- throw :control_flow,
27
- command: :frame,
28
- pry: pry_instance,
29
- # TODO: Remove redundant options
30
- options: { frame: args.first }
22
+ frame = args.first
23
+ raise Pry::CommandError, 'Frame ID is required' if frame.nil?
24
+ raise Pry::CommandError, 'Frame ID must be numeric' unless frame =~ /^\d+$/i
25
+
26
+ frame = frame.to_i
27
+ if frame >= RubyJard.current_session.backtrace.length || frame < 0
28
+ raise Pry::CommandError, "Frame #{frame} does not exist!"
29
+ else
30
+ RubyJard::ControlFlow.dispatch(:frame, frame: args.first)
31
+ end
31
32
  end
32
33
  end
33
34
  end