ruby_jard 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/documentation.yml +65 -0
- data/.github/workflows/rspec.yml +96 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +90 -2
- data/CHANGELOG.md +112 -0
- data/Gemfile +14 -4
- data/README.md +95 -3
- data/benchmark/path_filter_bench.rb +58 -0
- data/bin/console +1 -2
- data/lib/ruby_jard.rb +68 -32
- data/lib/ruby_jard/box_drawer.rb +175 -0
- data/lib/ruby_jard/color_scheme.rb +28 -0
- data/lib/ruby_jard/color_schemes.rb +54 -0
- data/lib/ruby_jard/color_schemes/256_color_scheme.rb +50 -0
- data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +50 -0
- data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +49 -0
- data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +48 -0
- data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +47 -0
- data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +49 -0
- data/lib/ruby_jard/column.rb +26 -0
- data/lib/ruby_jard/commands/color_helpers.rb +32 -0
- data/lib/ruby_jard/commands/continue_command.rb +4 -9
- data/lib/ruby_jard/commands/down_command.rb +9 -8
- data/lib/ruby_jard/commands/exit_command.rb +27 -0
- data/lib/ruby_jard/commands/frame_command.rb +13 -11
- data/lib/ruby_jard/commands/jard/color_scheme_command.rb +74 -0
- data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
- data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
- data/lib/ruby_jard/commands/jard/output_command.rb +36 -0
- data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
- data/lib/ruby_jard/commands/jard_command.rb +52 -0
- data/lib/ruby_jard/commands/list_command.rb +31 -0
- data/lib/ruby_jard/commands/next_command.rb +11 -8
- data/lib/ruby_jard/commands/step_command.rb +11 -8
- data/lib/ruby_jard/commands/step_out_command.rb +34 -0
- data/lib/ruby_jard/commands/up_command.rb +10 -8
- data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
- data/lib/ruby_jard/config.rb +61 -0
- data/lib/ruby_jard/console.rb +158 -0
- data/lib/ruby_jard/control_flow.rb +73 -0
- data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
- data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
- data/lib/ruby_jard/decorators/color_decorator.rb +80 -0
- data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
- data/lib/ruby_jard/decorators/inspection_decorator.rb +109 -0
- data/lib/ruby_jard/decorators/loc_decorator.rb +108 -119
- data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
- data/lib/ruby_jard/decorators/path_decorator.rb +56 -60
- data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
- data/lib/ruby_jard/decorators/source_decorator.rb +3 -1
- data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
- data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
- data/lib/ruby_jard/frame.rb +68 -0
- data/lib/ruby_jard/key_binding.rb +14 -0
- data/lib/ruby_jard/key_bindings.rb +96 -0
- data/lib/ruby_jard/keys.rb +48 -0
- data/lib/ruby_jard/layout.rb +17 -88
- data/lib/ruby_jard/layout_calculator.rb +168 -0
- data/lib/ruby_jard/layout_picker.rb +34 -0
- data/lib/ruby_jard/layouts.rb +52 -0
- data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +32 -0
- data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
- data/lib/ruby_jard/layouts/tiny_layout.rb +29 -0
- data/lib/ruby_jard/layouts/wide_layout.rb +50 -0
- data/lib/ruby_jard/pager.rb +112 -0
- data/lib/ruby_jard/path_classifier.rb +133 -0
- data/lib/ruby_jard/path_filter.rb +125 -0
- data/lib/ruby_jard/reflection.rb +97 -0
- data/lib/ruby_jard/repl_processor.rb +151 -89
- data/lib/ruby_jard/repl_proxy.rb +337 -0
- data/lib/ruby_jard/row.rb +31 -0
- data/lib/ruby_jard/row_renderer.rb +119 -0
- data/lib/ruby_jard/screen.rb +14 -41
- data/lib/ruby_jard/screen_adjuster.rb +104 -0
- data/lib/ruby_jard/screen_drawer.rb +25 -0
- data/lib/ruby_jard/screen_manager.rb +167 -82
- data/lib/ruby_jard/screen_renderer.rb +152 -0
- data/lib/ruby_jard/screens.rb +31 -12
- data/lib/ruby_jard/screens/backtrace_screen.rb +118 -116
- data/lib/ruby_jard/screens/menu_screen.rb +73 -45
- data/lib/ruby_jard/screens/source_screen.rb +86 -106
- data/lib/ruby_jard/screens/threads_screen.rb +103 -78
- data/lib/ruby_jard/screens/variables_screen.rb +224 -142
- data/lib/ruby_jard/session.rb +151 -16
- data/lib/ruby_jard/span.rb +23 -0
- data/lib/ruby_jard/templates/layout_template.rb +35 -0
- data/lib/ruby_jard/templates/screen_template.rb +34 -0
- data/lib/ruby_jard/thread_info.rb +69 -0
- data/lib/ruby_jard/version.rb +1 -1
- data/ruby_jard.gemspec +7 -8
- metadata +84 -50
- data/.travis.yml +0 -6
- data/lib/ruby_jard/commands/finish_command.rb +0 -31
- data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
- data/lib/ruby_jard/layout_template.rb +0 -101
- data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
- data/lib/ruby_jard/screens/empty_screen.rb +0 -13
- data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
@@ -0,0 +1,337 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pty'
|
4
|
+
require 'ruby_jard/pager'
|
5
|
+
|
6
|
+
module RubyJard
|
7
|
+
##
|
8
|
+
# A wrapper to wrap around Pry instance.
|
9
|
+
#
|
10
|
+
# Pry depends heavily on GNU Readline, or any Readline-like input libraries. Those libraries
|
11
|
+
# serve limited use cases, and specific interface to support those. Unfortunately, to serve
|
12
|
+
# Jard's keyboard functionalities, those libraries must support individual keyboard events,
|
13
|
+
# programmatically input control, etc. Ruby's GNU Readline binding obviously doesn't support
|
14
|
+
# those fancy features. Other pure-ruby implementation such as coolline, tty-reader is not
|
15
|
+
# a perfit fit, while satisfying performance and boringly stablility of GNU Readline. Indeed,
|
16
|
+
# while testing those libraries, I meet some weird quirks, lagging, cursor jumping around.
|
17
|
+
# Putting efforts in a series of monkey patches help a little bit, but it harms in long-term.
|
18
|
+
# Re-implementing is just like jumping into another rabbit hole.
|
19
|
+
#
|
20
|
+
# That's why I come up with another approach:
|
21
|
+
# - Create a proxy wrapping around pry instance, so that it reads characters one by one, in
|
22
|
+
# *raw* mode
|
23
|
+
# - Keyboard combinations are captured and handled before piping the rest to the pry instance
|
24
|
+
# - The proxy interacts with Pry's REPL loop via Pry hooks (Thank God) to seamlessly switch
|
25
|
+
# between *raw* mode and *cooked* mode while Pry interacts with TTY.
|
26
|
+
# - Control flow instructions are threw out, and captured by ReplProcessor.
|
27
|
+
#
|
28
|
+
# As a result, Jard may support key-binding customization without breaking pry functionalities.
|
29
|
+
class ReplProxy
|
30
|
+
# Some commands overlaps with Jard, Ruby, and even cause confusion for
|
31
|
+
# users. It's better ignore or re-implement those commands.
|
32
|
+
PRY_EXCLUDED_COMMANDS = [
|
33
|
+
'pry-backtrace', # Redundant method for normal user
|
34
|
+
'watch', # Conflict with byebug and jard watch
|
35
|
+
'edit', # Sorry, but a file should not be editted while debugging, as it made breakpoints shifted
|
36
|
+
'play', # What if the played files or methods include jard again?
|
37
|
+
'stat', # Included in jard UI
|
38
|
+
'backtrace', # Re-implemented later
|
39
|
+
'break', # Re-implemented later
|
40
|
+
'exit-all', # Conflicted with continue
|
41
|
+
'exit-program', # We already have `exit` native command
|
42
|
+
'!pry', # No need to complicate things
|
43
|
+
'jump-to', # No need to complicate things
|
44
|
+
'nesting', # No need to complicate things
|
45
|
+
'switch-to', # No need to complicate things
|
46
|
+
'disable-pry' # No need to complicate things
|
47
|
+
].freeze
|
48
|
+
|
49
|
+
INTERNAL_KEY_BINDINGS = {
|
50
|
+
RubyJard::Keys::CTRL_C => (KEY_BINDING_INTERRUPT = :interrupt)
|
51
|
+
}.freeze
|
52
|
+
|
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
|
125
|
+
|
126
|
+
def initialize(key_bindings: nil)
|
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
|
131
|
+
@pry = pry_instance
|
132
|
+
|
133
|
+
@key_bindings = key_bindings || RubyJard::KeyBindings.new
|
134
|
+
INTERNAL_KEY_BINDINGS.each do |sequence, action|
|
135
|
+
@key_bindings.push(sequence, action)
|
136
|
+
end
|
137
|
+
|
138
|
+
@pry_pty_output_thread = Thread.new { pry_pty_output }
|
139
|
+
@pry_pty_output_thread.name = '<<Jard: Pty Output Thread>>'
|
140
|
+
end
|
141
|
+
|
142
|
+
def repl(current_binding)
|
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
|
151
|
+
@pry.binding_stack.clear
|
152
|
+
|
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 >>'
|
159
|
+
|
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
|
196
|
+
else
|
197
|
+
output = @pry_output_pty_read.read_nonblock(2048)
|
198
|
+
unless output.nil?
|
199
|
+
STDOUT.write output, from_jard: true
|
200
|
+
end
|
201
|
+
end
|
202
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
203
|
+
# Retry
|
204
|
+
sleep PTY_OUTPUT_TIMEOUT
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def pry_repl(current_binding)
|
209
|
+
flow = RubyJard::ControlFlow.listen do
|
210
|
+
@pry.repl(current_binding)
|
211
|
+
end
|
212
|
+
@state.check(:ready?) do
|
213
|
+
@main_thread.raise FlowInterrupt.new('Interrupt from repl thread', flow)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def listen_key_press
|
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
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def handle_key_binding(key_binding)
|
237
|
+
case key_binding.action
|
238
|
+
when KEY_BINDING_INTERRUPT
|
239
|
+
handle_interrupt_command
|
240
|
+
true
|
241
|
+
else
|
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)
|
245
|
+
end
|
246
|
+
false
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def handle_interrupt_command
|
251
|
+
@state.check(:ready?) do
|
252
|
+
@pry_input_thread&.raise Interrupt if @pry_input_thread&.alive?
|
253
|
+
end
|
254
|
+
loop do
|
255
|
+
begin
|
256
|
+
sleep PTY_OUTPUT_TIMEOUT
|
257
|
+
rescue Interrupt
|
258
|
+
# Interrupt spam. Ignore.
|
259
|
+
end
|
260
|
+
break unless @pry_input_thread&.pending_interrupt?
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def pry_instance
|
265
|
+
pry_instance = Pry.new(
|
266
|
+
prompt: pry_jard_prompt,
|
267
|
+
quiet: true,
|
268
|
+
commands: pry_command_set,
|
269
|
+
hooks: pry_hooks,
|
270
|
+
output: @pry_output_pty_write
|
271
|
+
)
|
272
|
+
# I'll be burned in hell for this
|
273
|
+
# TODO: Contact pry author to add :after_handle_line hook
|
274
|
+
class << pry_instance
|
275
|
+
def _jard_handle_line(*args)
|
276
|
+
_original_handle_line(*args)
|
277
|
+
exec_hook :after_handle_line, *args, self
|
278
|
+
end
|
279
|
+
alias_method :_original_handle_line, :handle_line
|
280
|
+
alias_method :handle_line, :_jard_handle_line
|
281
|
+
|
282
|
+
def pager
|
283
|
+
RubyJard::Pager.new(self)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
pry_instance
|
287
|
+
end
|
288
|
+
|
289
|
+
def pry_command_set
|
290
|
+
set = Pry::CommandSet.new
|
291
|
+
set.import_from(
|
292
|
+
Pry.config.commands,
|
293
|
+
*(Pry.config.commands.list_commands - PRY_EXCLUDED_COMMANDS)
|
294
|
+
)
|
295
|
+
set
|
296
|
+
end
|
297
|
+
|
298
|
+
def pry_jard_prompt
|
299
|
+
Pry::Prompt.new(
|
300
|
+
:jard,
|
301
|
+
'Custom pry promt for Jard', [
|
302
|
+
proc do |_context, _nesting, _pry_instance|
|
303
|
+
'jard >> '
|
304
|
+
end,
|
305
|
+
proc do |_context, _nesting, _pry_instance|
|
306
|
+
'jard *> '
|
307
|
+
end
|
308
|
+
]
|
309
|
+
)
|
310
|
+
end
|
311
|
+
|
312
|
+
def pry_hooks
|
313
|
+
hooks = Pry::Hooks.default
|
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
|
319
|
+
end
|
320
|
+
hooks.add_hook(:after_handle_line, :jard_proxy_release_lock) do
|
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!
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
##
|
5
|
+
# This class is an object to store a row of data display on a screen
|
6
|
+
class Row
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_accessor :columns, :line_limit, :content, :rendered
|
10
|
+
|
11
|
+
def initialize(line_limit: 1, columns: [], ellipsis: true)
|
12
|
+
@content = []
|
13
|
+
@columns = columns
|
14
|
+
@ellipsis = ellipsis
|
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
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
##
|
5
|
+
# Generate bitmap lines from a row's data
|
6
|
+
class RowRenderer
|
7
|
+
ELLIPSIS = ' »'
|
8
|
+
|
9
|
+
def initialize(row:, width:, height:, color_scheme:)
|
10
|
+
@row = row
|
11
|
+
@width = width
|
12
|
+
@height = height
|
13
|
+
@color_decorator = RubyJard::Decorators::ColorDecorator.new(color_scheme)
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
@row.reset_rendered
|
18
|
+
|
19
|
+
@x = 0
|
20
|
+
@y = 0
|
21
|
+
@original_x = 0
|
22
|
+
@original_y = 0
|
23
|
+
@content_map = []
|
24
|
+
|
25
|
+
@row.columns.each do |column|
|
26
|
+
@x = @original_x
|
27
|
+
@y = @original_y
|
28
|
+
|
29
|
+
@drawing_width = 0
|
30
|
+
@drawing_lines = 1
|
31
|
+
|
32
|
+
column.spans.each do |span|
|
33
|
+
to_continue = render_span(column, span)
|
34
|
+
break unless to_continue
|
35
|
+
end
|
36
|
+
|
37
|
+
@original_x += column.width
|
38
|
+
end
|
39
|
+
|
40
|
+
generate_bitmap
|
41
|
+
|
42
|
+
@row.mark_rendered
|
43
|
+
end
|
44
|
+
|
45
|
+
# rubocop:disable Metrics/MethodLength
|
46
|
+
def render_span(column, span)
|
47
|
+
line_content = span.content
|
48
|
+
|
49
|
+
until line_content.nil? || line_content.empty?
|
50
|
+
if column.word_wrap == RubyJard::Column::WORD_WRAP_NORMAL
|
51
|
+
if column.content_width - @drawing_width < line_content.length && @drawing_width != 0
|
52
|
+
@drawing_width = 0
|
53
|
+
@drawing_lines += 1
|
54
|
+
@y += 1
|
55
|
+
@x = @original_x
|
56
|
+
end
|
57
|
+
elsif column.word_wrap == RubyJard::Column::WORD_WRAP_BREAK_WORD
|
58
|
+
if column.content_width - @drawing_width <= 0
|
59
|
+
@drawing_width = 0
|
60
|
+
@drawing_lines += 1
|
61
|
+
@y += 1
|
62
|
+
@x = @original_x
|
63
|
+
end
|
64
|
+
elsif column.content_width - @drawing_width <= 0
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
drawing_content = line_content[0..column.content_width - @drawing_width - 1]
|
68
|
+
line_content = line_content[column.content_width - @drawing_width..-1]
|
69
|
+
@drawing_width += drawing_content.length
|
70
|
+
|
71
|
+
if !@row.line_limit.nil? &&
|
72
|
+
@drawing_lines >= @row.line_limit &&
|
73
|
+
!line_content.nil? &&
|
74
|
+
!line_content.empty?
|
75
|
+
drawing_content[drawing_content.length - ELLIPSIS.length..-1] = ELLIPSIS
|
76
|
+
draw_content(drawing_content, span.styles)
|
77
|
+
return false
|
78
|
+
else
|
79
|
+
draw_content(drawing_content, span.styles)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
true
|
83
|
+
end
|
84
|
+
# rubocop:enable Metrics/MethodLength
|
85
|
+
|
86
|
+
def draw_content(drawing_content, styles)
|
87
|
+
return if @y < 0 || @y >= @height
|
88
|
+
|
89
|
+
@content_map[@y] ||= []
|
90
|
+
@content_map[@y][@x] = [styles, drawing_content]
|
91
|
+
@x += drawing_content.length
|
92
|
+
end
|
93
|
+
|
94
|
+
def generate_bitmap
|
95
|
+
@row.content = []
|
96
|
+
@content_map.each do |line|
|
97
|
+
line_content = ''
|
98
|
+
pending_content = ''
|
99
|
+
|
100
|
+
cell_index = 0
|
101
|
+
while cell_index < @width
|
102
|
+
cell = line[cell_index]
|
103
|
+
if cell.nil? || cell[1].empty?
|
104
|
+
pending_content += ' '
|
105
|
+
cell_index += 1
|
106
|
+
else
|
107
|
+
line_content += @color_decorator.decorate(:background, pending_content)
|
108
|
+
line_content += @color_decorator.decorate(cell[0], cell[1])
|
109
|
+
pending_content = ''
|
110
|
+
cell_index += cell[1].length
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
line_content += @color_decorator.decorate(:background, pending_content) unless pending_content.empty?
|
115
|
+
@row.content << line_content
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|