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
@@ -2,133 +2,116 @@
2
2
 
3
3
  module RubyJard
4
4
  module Screens
5
+ ##
6
+ # Backtrace screen implements the content to display current thread's backtrace to the user.
5
7
  class BacktraceScreen < RubyJard::Screen
6
- def draw
7
- @output.print TTY::Box.frame(**frame_styles)
8
-
9
- @output.print TTY::Cursor.move_to(@col + 2, @row)
10
- @output.print decorate_text
11
- .with_highlight(true)
12
- .text(" Backtrace (#{frames_count}) ", :bright_yellow)
13
- .content
14
-
15
- decorate_frames.each_with_index do |frame_texts, index|
16
- left, right = frame_texts
17
- @output.print TTY::Cursor.move_to(@col + 1, @row + index + 1)
18
- @output.print left.content
19
-
20
- next unless @col + left.length < @col + @layout.width - right.length - 1
21
-
22
- # TODO: handle reducable components in case of smaller screen
23
- @output.print TTY::Cursor.move_to(@col + @layout.width - right.length, @row + index + 1)
24
- @output.print right.content
25
- end
8
+ def title
9
+ "Backtrace (#{frames_count})"
26
10
  end
27
11
 
28
- private
29
-
30
12
  def data_size
31
- @layout.height - 1
32
- end
33
-
34
- def frame_styles
35
- default_frame_styles.merge(
36
- top: @row, left: @col, width: @layout.width, height: @layout.height
37
- )
13
+ [@height, frames_count].min
38
14
  end
39
15
 
40
- def decorate_frames
16
+ def data_window
41
17
  return [] if data_size.zero?
42
18
 
43
- window_start = frame_pos / data_size * data_size
44
- window_end = [frames_count, window_start + data_size - 1].min
45
-
46
- backtrace[window_start..window_end]
47
- .map
48
- .with_index do |frame, frame_index|
49
- decorate_frame(frame, window_start + frame_index, window_start, window_end)
50
- end
19
+ @data_window ||= backtrace[data_window_start..data_window_end]
51
20
  end
52
21
 
53
- def decorate_frame(line, frame_id, window_start, window_end)
54
- location = line[0]
55
- object = line[1]
56
- klass = line[2]
57
-
58
- left =
59
- decorate_frame_id(frame_id, window_start, window_end) +
60
- ' ' +
61
- decorate_location_label(frame_id, location, object, klass)
62
- right = decorate_location_path(frame_id, location)
22
+ def data_window_start
23
+ return 0 if data_size.zero?
63
24
 
64
- [left, right]
25
+ current_frame / data_size * data_size
65
26
  end
66
27
 
67
- def reset
68
- @color = Pastel.new
28
+ def data_window_end
29
+ [frames_count, data_window_start + data_size - 1].min
69
30
  end
70
31
 
71
- def decorate_frame_id(frame_id, _window_start, window_end)
72
- decorate_text
73
- .with_highlight(frame_pos == frame_id)
74
- .text(frame_pos == frame_id ? '→ ' : ' ', :white)
75
- .text(frame_id.to_s.ljust(window_end.to_s.length), frame_pos == frame_id ? :bright_yellow : :white)
32
+ def span_mark(_frame, index)
33
+ [
34
+ current_frame?(index) ? '→' : ' ',
35
+ [:bright_yellow, current_frame?(index) ? :bold : nil]
36
+ ]
76
37
  end
77
38
 
78
- def decorate_location_label(frame_id, location, object, klass)
79
- decorate_text
80
- .with_highlight(frame_pos == frame_id)
81
- .text(backtrace[frame_id].last.nil? ? '[c] ' : '', :green)
82
- .text(decorate_object_label(object, klass), :green)
83
- .text(' in ', :white)
84
- .text(decorate_method_label(location), :green)
39
+ def span_frame_id(_frame, index)
40
+ frame_id = index + data_window_start
41
+ [
42
+ frame_id.to_s,
43
+ [
44
+ current_frame?(index) ? :bright_yellow : :white,
45
+ current_frame?(index) ? :bold : nil
46
+ ]
47
+ ]
85
48
  end
86
49
 
87
- def decorate_object_label(object, klass)
88
- if klass.nil? || object.class == klass
89
- if object.is_a?(Class)
50
+ def span_klass_label(frame, index)
51
+ object = frame[1]
52
+ klass = frame[2]
53
+ klass_label =
54
+ if klass.nil? || object.class == klass
55
+ if object.is_a?(Class)
56
+ object.name
57
+ else
58
+ object.class.name
59
+ end
60
+ elsif klass.singleton_class?
61
+ # No easy way to get the original class of a singleton class
90
62
  object.name
91
63
  else
92
- object.class.name
64
+ klass.name
93
65
  end
94
- elsif klass.singleton_class?
95
- # No easy way to get the original class of a singleton class
96
- object.name
97
- else
98
- klass.name
99
- end
66
+ c_frame = frame_at(index).last.nil? ? '[c] ' : ''
67
+ [
68
+ "#{c_frame}#{klass_label}",
69
+ [:green, current_frame?(index) ? :bold : nil]
70
+ ]
100
71
  end
101
72
 
102
- def decorate_method_label(location)
103
- if location.label != location.base_label
104
- "#{location.base_label} (#{location.label.split(' ').first})"
105
- else
106
- location.base_label
107
- end
73
+ def span_label_preposition(_frame, index)
74
+ ['in', current_frame?(index) ? [:bright_white] : [:white]]
108
75
  end
109
76
 
110
- def decorate_location_path(frame_id, location)
77
+ def span_method_label(frame, index)
78
+ location = frame[0]
79
+ method_label =
80
+ if location.label != location.base_label
81
+ "#{location.base_label} (#{location.label.split(' ').first})"
82
+ else
83
+ location.base_label
84
+ end
85
+ [method_label, [:green, current_frame?(index) ? :bold : nil]]
86
+ end
87
+
88
+ def span_path_preposition(frame, index)
89
+ location = frame[0]
111
90
  decorated_path = decorate_path(location.absolute_path, location.lineno)
91
+ preposition = decorated_path.gem? ? 'in' : 'at'
92
+ [preposition, current_frame?(index) ? [:bright_white] : [:white]]
93
+ end
112
94
 
113
- if decorated_path.gem?
114
- decorate_text
115
- .with_highlight(frame_pos == frame_id)
116
- .text('in ', :bright_white)
117
- .text(decorated_path.gem, :bright_white)
118
- .text(' (', :bright_white)
119
- .text(decorated_path.gem_version, :bright_white)
120
- .text(')', :bright_white)
121
- else
122
- decorate_text
123
- .with_highlight(frame_pos == frame_id)
124
- .text('at ', :bright_white)
125
- .text(decorated_path.path, :bright_white)
126
- .text(':', :bright_white)
127
- .text(decorated_path.lineno, :bright_white)
128
- end
95
+ def span_path(frame, index)
96
+ location = frame[0]
97
+ decorated_path = decorate_path(location.absolute_path, location.lineno)
98
+
99
+ path_label =
100
+ if decorated_path.gem?
101
+ "#{decorated_path.gem} (#{decorated_path.gem_version})"
102
+ else
103
+ "#{decorated_path.path}:#{decorated_path.lineno}"
104
+ end
105
+ [path_label, current_frame?(index) ? [:bold, :bright_white] : [:white]]
129
106
  end
130
107
 
131
- def frame_pos
108
+ private
109
+
110
+ def current_frame?(index)
111
+ index + data_window_start == current_frame
112
+ end
113
+
114
+ def current_frame
132
115
  if @session.frame.nil?
133
116
  0
134
117
  else
@@ -136,6 +119,10 @@ module RubyJard
136
119
  end
137
120
  end
138
121
 
122
+ def frame_at(index)
123
+ backtrace[index + data_window_start]
124
+ end
125
+
139
126
  def frames_count
140
127
  @session.backtrace.length
141
128
  end
@@ -143,6 +130,10 @@ module RubyJard
143
130
  def backtrace
144
131
  @session.backtrace
145
132
  end
133
+
134
+ def decorate_path(path, lineno)
135
+ RubyJard::Decorators::PathDecorator.new(path, lineno)
136
+ end
146
137
  end
147
138
  end
148
139
  end
@@ -3,38 +3,23 @@
3
3
  module RubyJard
4
4
  module Screens
5
5
  class MenuScreen < RubyJard::Screen
6
- def draw
7
- @output.print TTY::Cursor.move_to(@col, @row)
8
- frame = TTY::Box.frame(
9
- **default_frame_styles.merge(
10
- top: @row, left: @col, width: @layout.width, height: @layout.height,
11
- border: {
12
- left: false,
13
- top: :line,
14
- right: false,
15
- bottom: false
16
- },
17
- style: {
18
- fg: :white
19
- }
20
- )
21
- )
22
- @output.print frame
6
+ def draw(output)
7
+ RubyJard::Console.move_to(output, @x, @y)
23
8
 
24
9
  margin = 0
25
10
  left_menu = generate_left_menu
26
- left_menu.each do |item|
27
- @output.print TTY::Cursor.move_to(@col + 1 + margin, @row + 1)
28
- @output.print item.content
29
- margin += item.length + 3
11
+ left_menu.each do |text, length|
12
+ RubyJard::Console.move_to(output, @x + 1 + margin, @y)
13
+ output.print text
14
+ margin += length + 3
30
15
  end
31
16
 
32
17
  margin = 0
33
18
  right_menu = generate_right_menu
34
- right_menu.reverse.each do |item|
35
- @output.print TTY::Cursor.move_to(@col + @layout.width - margin - item.length - 1, @row + 1)
36
- @output.print item.content
37
- margin += item.length + 3
19
+ right_menu.reverse.each do |text, length|
20
+ RubyJard::Console.move_to(output, @x + @width - margin - length - 1, @y)
21
+ output.print text
22
+ margin += length + 3
38
23
  end
39
24
  end
40
25
 
@@ -42,19 +27,26 @@ module RubyJard
42
27
 
43
28
  def generate_left_menu
44
29
  [
45
- decorate_text.with_highlight(true).text('Debug console (F5)', :bright_yellow),
46
- decorate_text.text('Program output (F6)', :white)
30
+ # Fill in later
47
31
  ]
48
32
  end
49
33
 
50
34
  def generate_right_menu
51
35
  [
52
- decorate_text.text('Step (F7)', :white),
53
- decorate_text.text('Next (F8)', :white),
54
- decorate_text.text('Step out (Shift+F8)', :white),
55
- decorate_text.text('Continue (F9)', :white)
36
+ decorate_text('Step (F7)', :white),
37
+ decorate_text('Step Out (Shift+F7)', :white),
38
+ decorate_text('Next (F8)', :white),
39
+ decorate_text('Continue (F9)', :white)
56
40
  ]
57
41
  end
42
+
43
+ def decorate_text(str, *styles)
44
+ [color_decorator.decorate(str, *styles), str.length]
45
+ end
46
+
47
+ def color_decorator
48
+ @color_decorator ||= RubyJard::Decorators::ColorDecorator.new
49
+ end
58
50
  end
59
51
  end
60
52
  end
@@ -3,75 +3,50 @@
3
3
  module RubyJard
4
4
  module Screens
5
5
  class SourceScreen < RubyJard::Screen
6
- def draw
7
- @output.print TTY::Box.frame(
8
- **default_frame_styles.merge(
9
- top: @row, left: @col, width: @layout.width, height: @layout.height
10
- )
11
- )
12
-
13
- @output.print TTY::Cursor.move_to(@col + 2, @row)
14
- @output.print decorate_text
15
- .with_highlight(true)
16
- .text(' Source', :bright_yellow)
17
- .text(' (', :bright_yellow)
18
- .text(file_path, :bright_yellow)
19
- .text(') ', :bright_yellow)
20
- .content
21
-
22
- decorate_codes.each_with_index do |decorated_loc, index|
23
- @output.print TTY::Cursor.move_to(@col + 1, @row + 1 + index)
24
- @output.print decorated_loc.content
6
+ def title
7
+ return 'Source' if RubyJard.current_session.frame.nil?
8
+
9
+ decorated_path = path_decorator(current_file, current_line)
10
+ if decorated_path.gem?
11
+ "Source (#{decorated_path.gem} - #{decorated_path.path}:#{decorated_path.lineno})"
12
+ else
13
+ "Source (#{decorated_path.path}:#{decorated_path.lineno})"
25
14
  end
26
15
  end
27
16
 
28
- private
29
-
30
17
  def data_size
31
- @layout.height - 1
18
+ @height
32
19
  end
33
20
 
34
- def decorate_codes
21
+ def data_window
35
22
  return [] if RubyJard.current_session.frame.nil?
36
23
 
37
- decorated_source = decorate_source(current_file, current_line, data_size)
38
-
39
- lineno_padding = decorated_source.window_end.to_s.length
40
-
41
- decorated_source.codes.map.with_index do |loc, index|
42
- lineno = decorated_source.window_start + index
43
- decorated_loc = decorate_loc(loc, current_line == lineno)
44
-
45
- if current_line == lineno
46
- decorate_text
47
- .with_highlight(true)
48
- .text('→ ')
49
- .text(lineno.to_s.ljust(lineno_padding), :bright_yellow)
50
- .text(' ')
51
- .text(decorated_loc.loc)
52
- .text(inline_variables(decorated_loc.tokens))
53
- else
54
- decorate_text
55
- .with_highlight(false)
56
- .text(' ')
57
- .text(lineno.to_s.ljust(lineno_padding), :white)
58
- .text(' ')
59
- .text(decorated_loc.loc)
60
- end
61
- end
24
+ @data_window ||= source_decorator.codes
62
25
  end
63
26
 
64
- def file_path
65
- return '' if RubyJard.current_session.frame.nil?
27
+ def span_mark(_loc, index)
28
+ lineno = source_lineno(index)
29
+ [
30
+ current_line == lineno ? '→' : ' ',
31
+ [:bright_yellow, current_line == lineno ? :bold : nil]
32
+ ]
33
+ end
66
34
 
67
- decorated_path = decorate_path(current_file, current_line)
68
- if decorated_path.gem?
69
- "#{decorated_path.gem}: #{decorated_path.path}:#{decorated_path.lineno}"
70
- else
71
- "#{decorated_path.path}:#{decorated_path.lineno}"
72
- end
35
+ def span_lineno(_loc, index)
36
+ lineno = source_lineno(index)
37
+ [
38
+ lineno.to_s,
39
+ current_line == lineno ? [:bold, :bright_yellow] : [:dim, :white]
40
+ ]
41
+ end
42
+
43
+ def span_code(loc, index)
44
+ lineno = source_lineno(index)
45
+ [loc_decorator(loc).spans, current_line == lineno ? [:brighter] : [:dim]]
73
46
  end
74
47
 
48
+ private
49
+
75
50
  def current_binding
76
51
  RubyJard.current_session.frame._binding
77
52
  end
@@ -88,43 +63,20 @@ module RubyJard
88
63
  RubyJard.current_session.frame.line
89
64
  end
90
65
 
91
- def inline_variables(tokens)
92
- variables = {}
93
- local_variables = current_binding.local_variables
94
- instance_variables = current_frame_scope.instance_variables
95
-
96
- tokens.each_slice(2).each do |token, kind|
97
- token = token.to_sym
98
-
99
- if kind == :ident && local_variables.include?(token)
100
- var = current_binding.local_variable_get(token)
101
- elsif kind == :instance_variable && instance_variables.include?(token)
102
- var = current_frame_scope.instance_variable_get(token)
103
- else
104
- next
105
- end
106
-
107
- next if variables.key?(token)
108
-
109
- var_inspect = var.inspect
110
- # TODO: dynamic fill the rest of the line instead
111
- variables[token] = var_inspect if var_inspect.length < 30
112
- end
113
-
114
- return '' if variables.empty?
66
+ def path_decorator(path, lineno)
67
+ @path_decorator ||= RubyJard::Decorators::PathDecorator.new(path, lineno)
68
+ end
115
69
 
116
- variables_text = decorate_text.with_highlight(false).text(' #→ ', :white)
117
- variables.to_a.each_with_index do |(var_name, var_inspect), index|
118
- variables_text
119
- .with_highlight(false)
120
- .text(var_name.to_s, :white)
121
- .text('=', :white)
122
- .text(var_inspect, :white)
70
+ def source_decorator
71
+ @source_decorator ||= RubyJard::Decorators::SourceDecorator.new(current_file, current_line, data_size)
72
+ end
123
73
 
124
- variables_text.with_highlight(false).text(', ', :white) if index != variables.length - 1
125
- end
74
+ def loc_decorator(loc)
75
+ RubyJard::Decorators::LocDecorator.new(current_file, loc)
76
+ end
126
77
 
127
- variables_text
78
+ def source_lineno(index)
79
+ source_decorator.window_start + index
128
80
  end
129
81
  end
130
82
  end