ruby_jard 0.2.2 → 0.2.3

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.github/workflows/ruby.yml +85 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +70 -1
  7. data/CHANGELOG.md +31 -0
  8. data/Gemfile +6 -3
  9. data/README.md +122 -8
  10. data/bin/console +1 -2
  11. data/docs/color_schemes/256-light.png +0 -0
  12. data/docs/color_schemes/gruvbox.png +0 -0
  13. data/docs/color_schemes/one-half-dark.png +0 -0
  14. data/docs/color_schemes/one-half-light.png +0 -0
  15. data/lib/ruby_jard.rb +5 -5
  16. data/lib/ruby_jard/box_drawer.rb +4 -1
  17. data/lib/ruby_jard/color_schemes.rb +31 -15
  18. data/lib/ruby_jard/color_schemes/256_color_scheme.rb +37 -37
  19. data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +62 -0
  20. data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +36 -36
  21. data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +62 -0
  22. data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +61 -0
  23. data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +62 -0
  24. data/lib/ruby_jard/column.rb +3 -1
  25. data/lib/ruby_jard/commands/continue_command.rb +2 -3
  26. data/lib/ruby_jard/commands/down_command.rb +9 -5
  27. data/lib/ruby_jard/commands/exit_command.rb +27 -0
  28. data/lib/ruby_jard/commands/frame_command.rb +11 -10
  29. data/lib/ruby_jard/commands/jard/color_scheme_command.rb +52 -0
  30. data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
  31. data/lib/ruby_jard/commands/jard/output_command.rb +28 -0
  32. data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
  33. data/lib/ruby_jard/commands/jard_command.rb +50 -0
  34. data/lib/ruby_jard/commands/list_command.rb +5 -4
  35. data/lib/ruby_jard/commands/next_command.rb +10 -5
  36. data/lib/ruby_jard/commands/step_command.rb +10 -5
  37. data/lib/ruby_jard/commands/step_out_command.rb +10 -5
  38. data/lib/ruby_jard/commands/up_command.rb +10 -5
  39. data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
  40. data/lib/ruby_jard/config.rb +7 -3
  41. data/lib/ruby_jard/console.rb +10 -22
  42. data/lib/ruby_jard/control_flow.rb +3 -3
  43. data/lib/ruby_jard/decorators/color_decorator.rb +11 -5
  44. data/lib/ruby_jard/decorators/loc_decorator.rb +1 -1
  45. data/lib/ruby_jard/decorators/path_decorator.rb +20 -7
  46. data/lib/ruby_jard/decorators/source_decorator.rb +2 -0
  47. data/lib/ruby_jard/frame.rb +55 -0
  48. data/lib/ruby_jard/keys.rb +0 -3
  49. data/lib/ruby_jard/layout.rb +9 -2
  50. data/lib/ruby_jard/layout_calculator.rb +29 -12
  51. data/lib/ruby_jard/layout_picker.rb +34 -0
  52. data/lib/ruby_jard/layouts.rb +52 -0
  53. data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +28 -0
  54. data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
  55. data/lib/ruby_jard/layouts/tiny_layout.rb +25 -0
  56. data/lib/ruby_jard/layouts/wide_layout.rb +13 -15
  57. data/lib/ruby_jard/pager.rb +96 -0
  58. data/lib/ruby_jard/repl_processor.rb +61 -31
  59. data/lib/ruby_jard/repl_proxy.rb +193 -89
  60. data/lib/ruby_jard/row.rb +16 -1
  61. data/lib/ruby_jard/row_renderer.rb +51 -42
  62. data/lib/ruby_jard/screen.rb +2 -12
  63. data/lib/ruby_jard/screen_adjuster.rb +104 -0
  64. data/lib/ruby_jard/screen_drawer.rb +3 -0
  65. data/lib/ruby_jard/screen_manager.rb +32 -54
  66. data/lib/ruby_jard/screen_renderer.rb +30 -16
  67. data/lib/ruby_jard/screens.rb +31 -12
  68. data/lib/ruby_jard/screens/backtrace_screen.rb +23 -26
  69. data/lib/ruby_jard/screens/menu_screen.rb +53 -22
  70. data/lib/ruby_jard/screens/source_screen.rb +65 -37
  71. data/lib/ruby_jard/screens/threads_screen.rb +14 -14
  72. data/lib/ruby_jard/screens/variables_screen.rb +59 -34
  73. data/lib/ruby_jard/session.rb +19 -10
  74. data/lib/ruby_jard/span.rb +3 -0
  75. data/lib/ruby_jard/templates/layout_template.rb +1 -1
  76. data/lib/ruby_jard/templates/screen_template.rb +3 -4
  77. data/lib/ruby_jard/version.rb +1 -1
  78. data/ruby_jard.gemspec +1 -1
  79. metadata +38 -9
  80. data/lib/ruby_jard/commands/color_scheme_command.rb +0 -42
  81. data/lib/ruby_jard/layouts/narrow_layout.rb +0 -41
  82. data/lib/ruby_jard/screens/empty_screen.rb +0 -13
@@ -1,14 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby_jard/commands/continue_command'
4
- require 'ruby_jard/commands/up_command'
5
- require 'ruby_jard/commands/down_command'
6
- require 'ruby_jard/commands/next_command'
7
- require 'ruby_jard/commands/step_command'
8
- require 'ruby_jard/commands/step_out_command'
9
- require 'ruby_jard/commands/frame_command'
10
- require 'ruby_jard/commands/list_command'
11
- require 'ruby_jard/commands/color_scheme_command'
3
+ require 'pty'
4
+ require 'ruby_jard/pager'
12
5
 
13
6
  module RubyJard
14
7
  ##
@@ -44,7 +37,6 @@ module RubyJard
44
37
  'stat', # Included in jard UI
45
38
  'backtrace', # Re-implemented later
46
39
  'break', # Re-implemented later
47
- 'exit', # Conflicted with continue
48
40
  'exit-all', # Conflicted with continue
49
41
  'exit-program', # We already have `exit` native command
50
42
  '!pry', # No need to complicate things
@@ -54,119 +46,218 @@ module RubyJard
54
46
  'disable-pry' # No need to complicate things
55
47
  ].freeze
56
48
 
57
- COMMANDS = [
58
- CMD_FLOW = :flow,
59
- CMD_EVALUATE = :evaluate,
60
- CMD_IDLE = :idle,
61
- CMD_INTERRUPT = :interrupt
62
- ].freeze
63
-
64
- # rubocop:disable Layout/HashAlignment
65
49
  INTERNAL_KEY_BINDINGS = {
66
- RubyJard::Keys::END_LINE => (KEY_BINDING_ENDLINE = :end_line),
67
- RubyJard::Keys::CTRL_C => (KEY_BINDING_INTERRUPT = :interrupt)
50
+ RubyJard::Keys::CTRL_C => (KEY_BINDING_INTERRUPT = :interrupt)
68
51
  }.freeze
69
- # rubocop:enable Layout/HashAlignment
70
52
 
71
- KEYPRESS_POLLING = 0.1 # 100ms
53
+ KEY_READ_TIMEOUT = 0.2 # 200ms
54
+ PTY_OUTPUT_TIMEOUT = 1.to_f / 60 # 60hz
55
+
56
+ ##
57
+ # A tool to communicate between functional threads and main threads
58
+ class FlowInterrupt < StandardError
59
+ attr_reader :flow
60
+
61
+ def initialize(msg = '', flow = nil)
62
+ super(msg)
63
+ @flow = flow
64
+ end
65
+ end
66
+
67
+ ##
68
+ # A class to store the state with multi-thread guarding
69
+ # Ready => Processing/Exiting
70
+ # Processing => Ready again
71
+ # Exiting => Exited
72
+ # Exited => Ready
73
+ class ReplState
74
+ STATES = [
75
+ STATE_READY = 0,
76
+ STATE_EXITING = 1,
77
+ STATE_PROCESSING = 2,
78
+ STATE_EXITED = 3
79
+ ].freeze
80
+ def initialize
81
+ @state = STATE_EXITED
82
+ @mutex = Mutex.new
83
+ end
84
+
85
+ def check(method_name)
86
+ @mutex.synchronize { yield if send(method_name) }
87
+ end
88
+
89
+ def ready?
90
+ @state == STATE_READY
91
+ end
92
+
93
+ def ready!
94
+ if ready? || processing? || exited?
95
+ @mutex.synchronize { @state = STATE_READY }
96
+ end
97
+ end
98
+
99
+ def processing?
100
+ @state == STATE_PROCESSING
101
+ end
102
+
103
+ def processing!
104
+ return unless ready?
105
+
106
+ @mutex.synchronize { @state = STATE_PROCESSING }
107
+ end
108
+
109
+ def exiting?
110
+ @state == STATE_EXITING
111
+ end
112
+
113
+ def exiting!
114
+ @mutex.synchronize { @state = STATE_EXITING }
115
+ end
116
+
117
+ def exited?
118
+ @state == STATE_EXITED
119
+ end
120
+
121
+ def exited!
122
+ @mutex.synchronize { @state = STATE_EXITED }
123
+ end
124
+ end
72
125
 
73
126
  def initialize(key_bindings: nil)
74
- @pry_read_stream, @pry_write_stream = IO.pipe
127
+ @state = ReplState.new
128
+
129
+ @pry_input_pipe_read, @pry_input_pipe_write = IO.pipe
130
+ @pry_output_pty_read, @pry_output_pty_write = PTY.open
75
131
  @pry = pry_instance
76
- @commands = Queue.new
132
+
77
133
  @key_bindings = key_bindings || RubyJard::KeyBindings.new
78
134
  INTERNAL_KEY_BINDINGS.each do |sequence, action|
79
135
  @key_bindings.push(sequence, action)
80
136
  end
81
- end
82
137
 
83
- def read_key
84
- RubyJard::Console.getch(STDIN, KEYPRESS_POLLING)
138
+ @pry_pty_output_thread = Thread.new { pry_pty_output }
139
+ @pry_pty_output_thread.name = '<<Jard: Pty Output Thread>>'
85
140
  end
86
141
 
87
142
  def repl(current_binding)
88
- Readline.input = @pry_read_stream
89
- @commands.clear
143
+ @state.ready!
144
+ @openning_pager = false
145
+
146
+ RubyJard::Console.disable_echo!
147
+ RubyJard::Console.raw!
148
+
149
+ Readline.input = @pry_input_pipe_read
150
+ Readline.output = @pry_output_pty_write
90
151
  @pry.binding_stack.clear
91
152
 
92
- pry_thread = Thread.new do
93
- pry_repl(current_binding)
94
- end
95
- pry_thread.report_on_exception = false if pry_thread.respond_to?(:report_on_exception)
96
- loop do
97
- break unless pry_thread.alive?
153
+ @main_thread = Thread.current
154
+
155
+ @pry_input_thread = Thread.new { pry_repl(current_binding) }
156
+ @pry_input_thread.abort_on_exception = true
157
+ @pry_input_thread.report_on_exception = false
158
+ @pry_input_thread.name = '<<Jard: Pry input thread >>'
98
159
 
99
- if @commands.empty?
100
- listen_key_press
160
+ @key_listen_thread = Thread.new { listen_key_press }
161
+ @key_listen_thread.abort_on_exception = true
162
+ @key_listen_thread.report_on_exception = false
163
+ @key_listen_thread.name = '<<Jard: Repl key listen >>'
164
+
165
+ [@pry_input_thread, @key_listen_thread].map(&:join)
166
+ rescue FlowInterrupt => e
167
+ @state.exiting!
168
+ sleep PTY_OUTPUT_TIMEOUT until @state.exited?
169
+ RubyJard::ControlFlow.dispatch(e.flow)
170
+ ensure
171
+ RubyJard::Console.enable_echo!
172
+ RubyJard::Console.cooked!
173
+ Readline.input = STDIN
174
+ Readline.output = STDOUT
175
+ @key_listen_thread&.exit if @key_listen_thread&.alive?
176
+ @pry_input_thread&.exit if @pry_input_thread&.alive?
177
+ @state.exited!
178
+ end
179
+
180
+ private
181
+
182
+ def read_key
183
+ RubyJard::Console.getch(STDIN, KEY_READ_TIMEOUT)
184
+ end
185
+
186
+ def pry_pty_output
187
+ loop do
188
+ if @state.exiting?
189
+ if @pry_output_pty_read.ready?
190
+ STDOUT.write @pry_output_pty_read.read_nonblock(2048), from_jard: true
191
+ else
192
+ @state.exited!
193
+ end
194
+ elsif @state.exited?
195
+ sleep PTY_OUTPUT_TIMEOUT
101
196
  else
102
- cmd, value = @commands.deq
103
- handle_command(pry_thread, cmd, value)
197
+ output = @pry_output_pty_read.read_nonblock(2048)
198
+ unless output.nil?
199
+ STDOUT.write output, from_jard: true
200
+ end
104
201
  end
202
+ rescue IO::WaitReadable, IO::WaitWritable
203
+ # Retry
204
+ sleep PTY_OUTPUT_TIMEOUT
105
205
  end
106
- pry_thread&.join
107
- Readline.input = STDIN
108
206
  end
109
207
 
110
208
  def pry_repl(current_binding)
111
209
  flow = RubyJard::ControlFlow.listen do
112
210
  @pry.repl(current_binding)
113
211
  end
114
- @commands << [CMD_FLOW, flow]
115
- rescue StandardError => e
116
- RubyJard::ScreenManager.draw_error(e)
117
- raise
212
+ @state.check(:ready?) do
213
+ @main_thread.raise FlowInterrupt.new('Interrupt from repl thread', flow)
214
+ end
118
215
  end
119
216
 
120
217
  def listen_key_press
121
- key = @key_bindings.match { read_key }
122
- if key.is_a?(RubyJard::KeyBinding)
123
- handle_key_binding(key)
124
- elsif !key.empty?
125
- @pry_write_stream.write(key)
218
+ loop do
219
+ break if @state.exiting? || @state.exited?
220
+
221
+ if @state.processing? && @openning_pager
222
+ # Discard all keys unfortunately
223
+ sleep PTY_OUTPUT_TIMEOUT
224
+ else
225
+ key = @key_bindings.match { read_key }
226
+ if key.is_a?(RubyJard::KeyBinding)
227
+ continue = handle_key_binding(key)
228
+ break unless continue
229
+ elsif !key.empty?
230
+ @pry_input_pipe_write.write(key)
231
+ end
232
+ end
126
233
  end
127
234
  end
128
235
 
129
236
  def handle_key_binding(key_binding)
130
237
  case key_binding.action
131
- when KEY_BINDING_ENDLINE
132
- @pry_write_stream.write(key_binding.sequence)
133
- @commands << [CMD_EVALUATE]
134
238
  when KEY_BINDING_INTERRUPT
135
- @commands << [CMD_INTERRUPT]
239
+ handle_interrupt_command
240
+ true
136
241
  else
137
- @commands << [
138
- CMD_FLOW, RubyJard::ControlFlow.new(:key_binding, action: key_binding.action)
139
- ]
140
- end
141
- end
142
-
143
- def handle_command(pry_thread, cmd, value)
144
- case cmd
145
- when CMD_FLOW
146
- pry_thread.exit if pry_thread.alive?
147
- RubyJard::ControlFlow.dispatch(value)
148
- when CMD_EVALUATE
149
- loop do
150
- cmd, value = @commands.deq
151
- break if [CMD_IDLE, CMD_FLOW, CMD_INTERRUPT].include?(cmd)
242
+ flow = RubyJard::ControlFlow.new(:key_binding, action: key_binding.action)
243
+ @state.check(:ready?) do
244
+ @main_thread.raise FlowInterrupt.new('Interrupt from repl thread', flow)
152
245
  end
153
- handle_command(pry_thread, cmd, value)
154
- when CMD_INTERRUPT
155
- handle_interrupt_command(pry_thread)
156
- when CMD_IDLE
157
- # Ignore
246
+ false
158
247
  end
159
248
  end
160
249
 
161
- def handle_interrupt_command(pry_thread)
162
- pry_thread.raise Interrupt if pry_thread.alive?
250
+ def handle_interrupt_command
251
+ @state.check(:ready?) do
252
+ @pry_input_thread&.raise Interrupt if @pry_input_thread&.alive?
253
+ end
163
254
  loop do
164
255
  begin
165
- sleep 0.1
256
+ sleep PTY_OUTPUT_TIMEOUT
166
257
  rescue Interrupt
167
258
  # Interrupt spam. Ignore.
168
259
  end
169
- break unless pry_thread.pending_interrupt?
260
+ break unless @pry_input_thread&.pending_interrupt?
170
261
  end
171
262
  end
172
263
 
@@ -175,7 +266,8 @@ module RubyJard
175
266
  prompt: pry_jard_prompt,
176
267
  quiet: true,
177
268
  commands: pry_command_set,
178
- hooks: pry_hooks
269
+ hooks: pry_hooks,
270
+ output: @pry_output_pty_write
179
271
  )
180
272
  # I'll be burned in hell for this
181
273
  # TODO: Contact pry author to add :after_handle_line hook
@@ -186,6 +278,10 @@ module RubyJard
186
278
  end
187
279
  alias_method :_original_handle_line, :handle_line
188
280
  alias_method :handle_line, :_jard_handle_line
281
+
282
+ def pager
283
+ RubyJard::Pager.new(self)
284
+ end
189
285
  end
190
286
  pry_instance
191
287
  end
@@ -215,18 +311,26 @@ module RubyJard
215
311
 
216
312
  def pry_hooks
217
313
  hooks = Pry::Hooks.default
218
- hooks.add_hook(:after_read, :jard_proxy_acquire_lock) do |read_string, _pry|
219
- @commands <<
220
- if Pry::Code.complete_expression?(read_string)
221
- [CMD_EVALUATE]
222
- else
223
- [CMD_IDLE]
224
- end
225
- rescue SyntaxError
226
- @commands << [CMD_IDLE]
314
+ hooks.add_hook(:after_read, :jard_proxy_acquire_lock) do |_read_string, _pry|
315
+ RubyJard::Console.cooked!
316
+ @state.processing!
317
+ # Sleep 2 ticks, wait for pry to print out all existing output in the queue
318
+ sleep PTY_OUTPUT_TIMEOUT * 2
227
319
  end
228
320
  hooks.add_hook(:after_handle_line, :jard_proxy_release_lock) do
229
- @commands << [CMD_IDLE]
321
+ RubyJard::Console.raw!
322
+ @state.ready!
323
+ end
324
+ hooks.add_hook(:before_pager, :jard_proxy_before_pager) do
325
+ @openning_pager = true
326
+
327
+ @state.processing!
328
+ RubyJard::Console.cooked!
329
+ end
330
+ hooks.add_hook(:after_pager, :jard_proxy_after_pager) do
331
+ @openning_pager = false
332
+ @state.ready!
333
+ RubyJard::Console.raw!
230
334
  end
231
335
  end
232
336
  end
@@ -1,16 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyJard
4
+ ##
5
+ # This class is an object to store a row of data display on a screen
4
6
  class Row
5
7
  extend Forwardable
6
8
 
7
- attr_accessor :columns, :line_limit, :content
9
+ attr_accessor :columns, :line_limit, :content, :rendered
8
10
 
9
11
  def initialize(line_limit: 1, columns: [], ellipsis: true)
10
12
  @content = []
11
13
  @columns = columns
12
14
  @ellipsis = ellipsis
13
15
  @line_limit = line_limit
16
+ @rendered = false
17
+ end
18
+
19
+ def rendered?
20
+ @rendered == true
21
+ end
22
+
23
+ def mark_rendered
24
+ @rendered = true
25
+ end
26
+
27
+ def reset_rendered
28
+ @rendered = false
14
29
  end
15
30
  end
16
31
  end
@@ -14,63 +14,72 @@ module RubyJard
14
14
  end
15
15
 
16
16
  def render
17
+ @row.reset_rendered
18
+
17
19
  @x = 0
18
20
  @y = 0
21
+ @original_x = 0
22
+ @original_y = 0
19
23
  @content_map = []
20
24
 
21
- original_x = 0
22
- @row.columns.each_with_index do |column, index|
23
- @y = 0
24
- @x = original_x
25
- content_width = column.width
26
- content_width -= 1 if index < @row.columns.length - 1
27
- render_column(column, original_x, content_width)
25
+ @row.columns.each do |column|
26
+ @x = @original_x
27
+ @y = @original_y
28
+
29
+ @drawing_width = 0
30
+ @drawing_lines = 1
28
31
 
29
- original_x += column.width
32
+ column.spans.each do |span|
33
+ render_span(column, span)
34
+ end
35
+
36
+ @original_x += column.width
30
37
  end
31
38
 
32
39
  generate_bitmap
40
+
41
+ @row.mark_rendered
33
42
  end
34
43
 
35
- def render_column(column, original_x, content_width)
36
- width = 0
37
- lines = 1
38
-
39
- column.spans.each do |span|
40
- line_content = span.content
41
-
42
- until line_content.nil? || line_content.empty?
43
- if column.word_wrap == RubyJard::Column::WORD_WRAP_NORMAL
44
- if content_width - width < line_content.length && width != 0
45
- width = 0
46
- lines += 1
47
- @y += 1
48
- @x = original_x
49
- end
50
- elsif column.word_wrap == RubyJard::Column::WORD_WRAP_BREAK_WORD
51
- if content_width - width <= 0
52
- width = 0
53
- lines += 1
54
- @y += 1
55
- @x = original_x
56
- end
57
- elsif content_width - width <= 0
58
- return
44
+ # rubocop:disable Metrics/MethodLength
45
+ def render_span(column, span)
46
+ line_content = span.content
47
+
48
+ until line_content.nil? || line_content.empty?
49
+ if column.word_wrap == RubyJard::Column::WORD_WRAP_NORMAL
50
+ if column.content_width - @drawing_width < line_content.length && @drawing_width != 0
51
+ @drawing_width = 0
52
+ @drawing_lines += 1
53
+ @y += 1
54
+ @x = @original_x
59
55
  end
60
- drawing_content = line_content[0..content_width - width - 1]
61
- line_content = line_content[content_width - width..-1]
62
- width += drawing_content.length
63
-
64
- if !@row.line_limit.nil? && lines >= @row.line_limit && !line_content.nil? && !line_content.empty?
65
- drawing_content[drawing_content.length - ELLIPSIS.length..-1] = ELLIPSIS
66
- draw_content(drawing_content, span.styles)
67
- return
68
- else
69
- draw_content(drawing_content, span.styles)
56
+ elsif column.word_wrap == RubyJard::Column::WORD_WRAP_BREAK_WORD
57
+ if column.content_width - @drawing_width <= 0
58
+ @drawing_width = 0
59
+ @drawing_lines += 1
60
+ @y += 1
61
+ @x = @original_x
70
62
  end
63
+ elsif column.content_width - @drawing_width <= 0
64
+ return
65
+ end
66
+ drawing_content = line_content[0..column.content_width - @drawing_width - 1]
67
+ line_content = line_content[column.content_width - @drawing_width..-1]
68
+ @drawing_width += drawing_content.length
69
+
70
+ if !@row.line_limit.nil? &&
71
+ @drawing_lines >= @row.line_limit &&
72
+ !line_content.nil? &&
73
+ !line_content.empty?
74
+ drawing_content[drawing_content.length - ELLIPSIS.length..-1] = ELLIPSIS
75
+ draw_content(drawing_content, span.styles)
76
+ return
77
+ else
78
+ draw_content(drawing_content, span.styles)
71
79
  end
72
80
  end
73
81
  end
82
+ # rubocop:enable Metrics/MethodLength
74
83
 
75
84
  def draw_content(drawing_content, styles)
76
85
  return if @y < 0 || @y >= @height