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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +1 -1
- data/README.md +65 -2
- data/docs/guide-ui.png +0 -0
- data/lib/ruby_jard.rb +49 -12
- data/lib/ruby_jard/box_drawer.rb +126 -0
- data/lib/ruby_jard/column.rb +18 -0
- data/lib/ruby_jard/commands/continue_command.rb +1 -6
- data/lib/ruby_jard/commands/down_command.rb +1 -4
- data/lib/ruby_jard/commands/frame_command.rb +12 -11
- data/lib/ruby_jard/commands/next_command.rb +1 -4
- data/lib/ruby_jard/commands/step_command.rb +1 -4
- data/lib/ruby_jard/commands/step_out_command.rb +28 -0
- data/lib/ruby_jard/commands/up_command.rb +1 -4
- data/lib/ruby_jard/console.rb +86 -0
- data/lib/ruby_jard/control_flow.rb +71 -0
- data/lib/ruby_jard/decorators/color_decorator.rb +78 -0
- data/lib/ruby_jard/decorators/loc_decorator.rb +41 -28
- data/lib/ruby_jard/decorators/source_decorator.rb +1 -1
- data/lib/ruby_jard/key_binding.rb +14 -0
- data/lib/ruby_jard/key_bindings.rb +96 -0
- data/lib/ruby_jard/keys.rb +49 -0
- data/lib/ruby_jard/layout.rb +67 -55
- data/lib/ruby_jard/layouts/wide_layout.rb +138 -0
- data/lib/ruby_jard/repl_processor.rb +80 -90
- data/lib/ruby_jard/repl_proxy.rb +232 -0
- data/lib/ruby_jard/row.rb +16 -0
- data/lib/ruby_jard/screen.rb +114 -36
- data/lib/ruby_jard/screen_drawer.rb +89 -0
- data/lib/ruby_jard/screen_manager.rb +157 -56
- data/lib/ruby_jard/screens/backtrace_screen.rb +88 -97
- data/lib/ruby_jard/screens/menu_screen.rb +23 -31
- data/lib/ruby_jard/screens/source_screen.rb +42 -90
- data/lib/ruby_jard/screens/threads_screen.rb +50 -64
- data/lib/ruby_jard/screens/variables_screen.rb +96 -99
- data/lib/ruby_jard/session.rb +13 -7
- data/lib/ruby_jard/span.rb +18 -0
- data/lib/ruby_jard/templates/column_template.rb +17 -0
- data/lib/ruby_jard/templates/layout_template.rb +35 -0
- data/lib/ruby_jard/templates/row_template.rb +22 -0
- data/lib/ruby_jard/templates/screen_template.rb +35 -0
- data/lib/ruby_jard/templates/space_template.rb +15 -0
- data/lib/ruby_jard/templates/span_template.rb +25 -0
- data/lib/ruby_jard/version.rb +1 -1
- data/ruby_jard.gemspec +1 -4
- metadata +29 -41
- 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/expressions_sreen.rb +0 -22
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
class Row
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_accessor :row_template, :columns
|
8
|
+
|
9
|
+
def_delegators :@row_template, :line_limit
|
10
|
+
|
11
|
+
def initialize(row_template:, columns: [])
|
12
|
+
@row_template = row_template
|
13
|
+
@columns = columns
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/ruby_jard/screen.rb
CHANGED
@@ -6,56 +6,134 @@ module RubyJard
|
|
6
6
|
# generated based on input layout specifiation, screen data, and top-left
|
7
7
|
# corner cordination.
|
8
8
|
class Screen
|
9
|
-
|
9
|
+
attr_accessor :output, :rows, :width, :height, :x, :y
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
11
|
+
def initialize(screen_template:, session: nil, width:, height:, x:, y:)
|
12
|
+
@session = session || RubyJard.current_session
|
13
|
+
@screen_template = screen_template
|
14
|
+
@width = width
|
15
|
+
@height = height
|
16
|
+
@x = x
|
17
|
+
@y = y
|
18
18
|
end
|
19
19
|
|
20
|
-
def draw(
|
21
|
-
|
20
|
+
def draw(output)
|
21
|
+
calculate
|
22
|
+
drawer = RubyJard::ScreenDrawer.new(
|
23
|
+
output: output,
|
24
|
+
screen: self,
|
25
|
+
x: @x,
|
26
|
+
y: @y
|
27
|
+
)
|
28
|
+
drawer.draw
|
22
29
|
end
|
23
30
|
|
24
|
-
def
|
25
|
-
|
26
|
-
RubyJard::Decorators::TextDecorator.new(@color_decorator)
|
31
|
+
def data_size
|
32
|
+
raise NotImplementedError, "#{self.class} must implement #data_size method"
|
27
33
|
end
|
28
34
|
|
29
|
-
def
|
30
|
-
|
31
|
-
RubyJard::Decorators::PathDecorator.new(path, lineno)
|
35
|
+
def data_window
|
36
|
+
raise NotImplementedError, "#{self.class} must implement #data_window method"
|
32
37
|
end
|
33
38
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
39
|
+
def calculate
|
40
|
+
@rows = []
|
41
|
+
row_template = @screen_template.row_template
|
42
|
+
@rows = data_window.map.with_index do |data_row, index|
|
43
|
+
create_row(row_template, data_row, index)
|
44
|
+
end
|
45
|
+
column_widths = calculate_column_widths(row_template, @rows)
|
46
|
+
fill_column_widths(@rows, column_widths)
|
37
47
|
end
|
38
48
|
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
private
|
50
|
+
|
51
|
+
def calculate_column_widths(row_template, rows)
|
52
|
+
column_widths = {}
|
53
|
+
ideal_column_width = @width / row_template.columns.length
|
54
|
+
row_template.columns.each_with_index do |_column_template, column_index|
|
55
|
+
column_widths[column_index] ||= 0
|
56
|
+
rows.each do |row|
|
57
|
+
column = row.columns[column_index]
|
58
|
+
if column.content_length > ideal_column_width
|
59
|
+
column_widths[column_index] = nil
|
60
|
+
break
|
61
|
+
elsif column.content_length > column_widths[column_index]
|
62
|
+
column_widths[column_index] = column.content_length
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
column_widths
|
42
67
|
end
|
43
68
|
|
44
|
-
|
69
|
+
def fill_column_widths(rows, column_widths)
|
70
|
+
fixed_count = column_widths.length
|
71
|
+
fixed_width = column_widths.values.inject(0) do |sum, col|
|
72
|
+
col.nil? ? sum : sum + col
|
73
|
+
end
|
74
|
+
|
75
|
+
rows.each do |row|
|
76
|
+
total_width = 0
|
77
|
+
row.columns.each_with_index do |column, column_index|
|
78
|
+
column.width =
|
79
|
+
if column_index == row.columns.length - 1
|
80
|
+
@width - total_width
|
81
|
+
elsif column_widths[column_index].nil?
|
82
|
+
(@width - fixed_width) / fixed_count
|
83
|
+
else
|
84
|
+
column_widths[column_index]
|
85
|
+
end
|
86
|
+
total_width += column.width
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_row(row_template, data_row, index)
|
92
|
+
row = Row.new(row_template: row_template)
|
93
|
+
row.columns = row_template.columns.map do |column_template|
|
94
|
+
create_column(column_template, data_row, index)
|
95
|
+
end
|
96
|
+
row
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_column(column_template, data_row, index)
|
100
|
+
column = Column.new(column_template: column_template)
|
101
|
+
column.spans = column_template.spans.map do |span_template|
|
102
|
+
create_span(span_template, data_row, index)
|
103
|
+
end.flatten
|
104
|
+
column.content_length =
|
105
|
+
column.spans.map(&:content_length).inject(&:+) +
|
106
|
+
column.margin_left +
|
107
|
+
column.margin_right
|
108
|
+
column
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_span(span_template, data_row, index)
|
112
|
+
span = Span.new(span_template: span_template)
|
113
|
+
span_content_method = "span_#{span_template.name}".to_sym
|
114
|
+
|
115
|
+
if respond_to?(span_content_method)
|
116
|
+
content, styles = send(span_content_method, data_row, index)
|
117
|
+
if content.nil?
|
118
|
+
span.content = ''
|
119
|
+
span.content_length = 0
|
120
|
+
elsif content.is_a?(Array)
|
121
|
+
content.each do |sub_span|
|
122
|
+
sub_span.styles += Array(styles).flatten.compact
|
123
|
+
end
|
124
|
+
return content
|
125
|
+
else
|
126
|
+
content = ' ' * span_template.margin_left + content if span_template.margin_left
|
127
|
+
content += ' ' * span_template.margin_right if span_template.margin_right
|
128
|
+
span.content = content
|
129
|
+
span.styles = Array(styles).flatten.compact
|
130
|
+
span.content_length = span.content.length
|
131
|
+
end
|
132
|
+
else
|
133
|
+
raise NotImplementedError, "#{self.class} must implement #{span_content_method} method"
|
134
|
+
end
|
45
135
|
|
46
|
-
|
47
|
-
{
|
48
|
-
style: {
|
49
|
-
fg: :white
|
50
|
-
},
|
51
|
-
border: {
|
52
|
-
bottom_left: false,
|
53
|
-
bottom_right: false,
|
54
|
-
bottom: false,
|
55
|
-
left: :line,
|
56
|
-
right: false
|
57
|
-
}
|
58
|
-
}
|
136
|
+
span
|
59
137
|
end
|
60
138
|
end
|
61
139
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyJard
|
4
|
+
##
|
5
|
+
# Draw a screen and its rows into the output interface.
|
6
|
+
class ScreenDrawer
|
7
|
+
attr_reader :output
|
8
|
+
|
9
|
+
ELLIPSIS = ' »'
|
10
|
+
|
11
|
+
def initialize(output:, screen:, x:, y:)
|
12
|
+
@output = output
|
13
|
+
@color_decorator = RubyJard::Decorators::ColorDecorator.new
|
14
|
+
@x = x
|
15
|
+
@y = y
|
16
|
+
@original_x = x
|
17
|
+
@original_y = y
|
18
|
+
@screen = screen
|
19
|
+
end
|
20
|
+
|
21
|
+
def draw
|
22
|
+
@original_x = @x
|
23
|
+
@screen.rows.each do |row|
|
24
|
+
draw_columns(row, row.columns)
|
25
|
+
@y += 1
|
26
|
+
@x = @original_x
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def draw_columns(row, columns)
|
33
|
+
columns.each do |column|
|
34
|
+
width = 0
|
35
|
+
lines = 1
|
36
|
+
column_content_width = column.width - column.margin_left - column.margin_right
|
37
|
+
@x += column.margin_left
|
38
|
+
RubyJard::Console.move_to(@output, @x, @y)
|
39
|
+
|
40
|
+
column.spans.each do |span|
|
41
|
+
line_content = span.content
|
42
|
+
|
43
|
+
until line_content.nil? || line_content.empty?
|
44
|
+
if column_content_width - width <= 0
|
45
|
+
width = 0
|
46
|
+
lines += 1
|
47
|
+
@y += 1
|
48
|
+
RubyJard::Console.move_to(@output, @x, @y)
|
49
|
+
end
|
50
|
+
drawing_content = line_content[0..column_content_width - width - 1]
|
51
|
+
line_content = line_content[column_content_width - width..-1]
|
52
|
+
width += drawing_content.length
|
53
|
+
|
54
|
+
if !row.line_limit.nil? && lines >= row.line_limit && !line_content.nil? && !line_content.empty?
|
55
|
+
drawing_content[drawing_content.length - ELLIPSIS.length..-1] = ELLIPSIS
|
56
|
+
protected_print @color_decorator.decorate(drawing_content, *span.styles)
|
57
|
+
break
|
58
|
+
else
|
59
|
+
protected_print @color_decorator.decorate(drawing_content, *span.styles)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@x += column_content_width + column.margin_right
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def protected_print(content)
|
68
|
+
# TODO: currently, only row overflow is detected. Definitely should handle column overflow
|
69
|
+
return if @y < @original_y || @y > @original_y + @screen.height - 1
|
70
|
+
|
71
|
+
@output.print content
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_frame_styles
|
75
|
+
{
|
76
|
+
style: {
|
77
|
+
fg: :white
|
78
|
+
},
|
79
|
+
border: {
|
80
|
+
bottom_left: false,
|
81
|
+
bottom_right: false,
|
82
|
+
bottom: false,
|
83
|
+
left: :line,
|
84
|
+
right: false
|
85
|
+
}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -1,108 +1,209 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'ruby_jard/
|
3
|
+
require 'ruby_jard/console'
|
4
|
+
|
5
|
+
require 'ruby_jard/decorators/color_decorator'
|
4
6
|
require 'ruby_jard/decorators/path_decorator'
|
5
7
|
require 'ruby_jard/decorators/loc_decorator'
|
6
8
|
require 'ruby_jard/decorators/source_decorator'
|
9
|
+
|
7
10
|
require 'ruby_jard/screen'
|
11
|
+
require 'ruby_jard/box_drawer'
|
12
|
+
require 'ruby_jard/screen_drawer'
|
8
13
|
require 'ruby_jard/screens'
|
9
|
-
require 'ruby_jard/screens/breakpoints_screen'
|
10
|
-
require 'ruby_jard/screens/expressions_sreen'
|
11
14
|
require 'ruby_jard/screens/source_screen'
|
12
15
|
require 'ruby_jard/screens/backtrace_screen'
|
13
16
|
require 'ruby_jard/screens/threads_screen'
|
14
17
|
require 'ruby_jard/screens/variables_screen'
|
15
18
|
require 'ruby_jard/screens/menu_screen'
|
16
|
-
|
19
|
+
|
20
|
+
require 'ruby_jard/templates/layout_template'
|
21
|
+
require 'ruby_jard/templates/screen_template'
|
22
|
+
require 'ruby_jard/templates/row_template'
|
23
|
+
require 'ruby_jard/templates/column_template'
|
24
|
+
require 'ruby_jard/templates/span_template'
|
25
|
+
require 'ruby_jard/templates/space_template'
|
26
|
+
|
27
|
+
require 'ruby_jard/layouts/wide_layout'
|
17
28
|
require 'ruby_jard/layout'
|
29
|
+
require 'ruby_jard/row'
|
30
|
+
require 'ruby_jard/column'
|
31
|
+
require 'ruby_jard/span'
|
18
32
|
|
19
33
|
module RubyJard
|
20
34
|
##
|
21
35
|
# This class acts as a coordinator, in which it combines the data and screen
|
22
36
|
# layout template, triggers each screen to draw on the terminal.
|
23
37
|
class ScreenManager
|
24
|
-
|
38
|
+
class << self
|
39
|
+
extend Forwardable
|
40
|
+
|
41
|
+
def_delegators :instance, :update, :draw_error, :started?, :updating?
|
25
42
|
|
26
|
-
|
43
|
+
def instance
|
44
|
+
@instance ||= new
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :output, :output_storage
|
49
|
+
|
50
|
+
def initialize(output: STDOUT)
|
27
51
|
@output = output
|
28
|
-
@session = session
|
29
52
|
@screens = {}
|
53
|
+
@started = false
|
54
|
+
@updating = false
|
55
|
+
@output_storage = StringIO.new
|
30
56
|
end
|
31
57
|
|
32
58
|
def start
|
33
|
-
|
59
|
+
return if started?
|
60
|
+
|
61
|
+
RubyJard::Console.start_alternative_terminal(@output)
|
62
|
+
RubyJard::Console.hard_clear_screen(@output)
|
63
|
+
|
64
|
+
def $stdout.write(string)
|
65
|
+
if !RubyJard::ScreenManager.updating? && RubyJard::ScreenManager.started?
|
66
|
+
RubyJard::ScreenManager.instance.output_storage.write(string)
|
67
|
+
end
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
at_exit { stop }
|
72
|
+
@started = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def started?
|
76
|
+
@started == true
|
77
|
+
end
|
78
|
+
|
79
|
+
def updating?
|
80
|
+
@updating == true
|
81
|
+
end
|
82
|
+
|
83
|
+
def stop
|
84
|
+
return unless started?
|
85
|
+
|
86
|
+
@started = false
|
87
|
+
|
88
|
+
RubyJard::Console.stop_alternative_terminal(@output)
|
89
|
+
RubyJard::Console.cooked!(@output)
|
90
|
+
RubyJard::Console.echo!(@output)
|
91
|
+
RubyJard::Console.show_cursor(@output)
|
92
|
+
|
93
|
+
unless @output_storage.string.empty?
|
94
|
+
@output.puts ''
|
95
|
+
@output.write @output_storage.string
|
96
|
+
@output.puts ''
|
97
|
+
end
|
98
|
+
@output_storage.close
|
34
99
|
end
|
35
100
|
|
36
|
-
def
|
101
|
+
def update
|
102
|
+
start unless started?
|
103
|
+
@updating = true
|
104
|
+
|
105
|
+
RubyJard::Console.hide_cursor(@output)
|
106
|
+
clear_screen
|
107
|
+
width, height = RubyJard::Console.screen_size(@output)
|
108
|
+
screen_layouts = calculate_layouts(width, height)
|
109
|
+
draw_screens(screen_layouts)
|
110
|
+
jump_to_prompt(screen_layouts)
|
111
|
+
draw_debug(width, height)
|
112
|
+
rescue StandardError => e
|
37
113
|
clear_screen
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
114
|
+
draw_error(e, height)
|
115
|
+
ensure
|
116
|
+
# You don't want to mess up previous user TTY no matter happens
|
117
|
+
RubyJard::Console.cooked!(@output)
|
118
|
+
RubyJard::Console.echo!(@output)
|
119
|
+
RubyJard::Console.show_cursor(@output)
|
120
|
+
@updating = false
|
121
|
+
end
|
122
|
+
|
123
|
+
def draw_error(exception, height = 0)
|
124
|
+
@output.puts '--- Error ---'
|
125
|
+
@output.puts "Internal error from Jard. I'm sorry to mess up your debugging experience."
|
126
|
+
@output.puts 'It would be great if you can submit an issue in https://github.com/nguyenquangminh0711/ruby_jard/issues'
|
127
|
+
@output.puts ''
|
128
|
+
@output.puts exception
|
129
|
+
if height == 0
|
130
|
+
@output.puts exception.backtrace
|
131
|
+
else
|
132
|
+
@output.puts exception.backtrace.first(height - 5)
|
48
133
|
end
|
134
|
+
@output.puts '-------------'
|
49
135
|
end
|
50
136
|
|
137
|
+
|
51
138
|
private
|
52
139
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
140
|
+
def calculate_layouts(width, height)
|
141
|
+
layout = pick_layout(width, height)
|
142
|
+
RubyJard::Layout.calculate(
|
143
|
+
layout: layout,
|
144
|
+
width: width, height: height,
|
145
|
+
x: 0, y: 0
|
146
|
+
)
|
56
147
|
end
|
57
148
|
|
58
|
-
def
|
59
|
-
|
149
|
+
def draw_box(screens)
|
150
|
+
RubyJard::BoxDrawer.new(
|
151
|
+
output: @output,
|
152
|
+
screens: screens
|
153
|
+
).draw
|
154
|
+
end
|
60
155
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
screen = fetch_screen(layout.screen)
|
156
|
+
def draw_screens(screen_layouts)
|
157
|
+
screens = screen_layouts.map do |screen_template, width, height, x, y|
|
158
|
+
screen = fetch_screen(screen_template.screen)
|
65
159
|
screen&.new(
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
160
|
+
screen_template: screen_template,
|
161
|
+
width: width, height: height,
|
162
|
+
x: x, y: y
|
163
|
+
)
|
164
|
+
end
|
165
|
+
draw_box(screens)
|
166
|
+
adjust_screen_contents(screens)
|
167
|
+
screens.each do |screen|
|
168
|
+
screen.draw(@output)
|
72
169
|
end
|
73
170
|
end
|
74
171
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
max_height = child.height if max_height < child.height
|
86
|
-
# Overflow. Break to next line
|
87
|
-
if drawing_width >= layout.width
|
88
|
-
children_row += max_height
|
89
|
-
children_col = col
|
90
|
-
drawing_width = 0
|
91
|
-
max_height = 0
|
92
|
-
else
|
93
|
-
children_col += child.width
|
172
|
+
def jump_to_prompt(screen_layouts)
|
173
|
+
prompt_y = screen_layouts.map { |_template, _width, screen_height, _x, y| y + screen_height }.max
|
174
|
+
RubyJard::Console.move_to(@output, 0, prompt_y)
|
175
|
+
end
|
176
|
+
|
177
|
+
def draw_debug(_width, height)
|
178
|
+
unless RubyJard.debug_info.empty?
|
179
|
+
@output.puts '--- Debug ---'
|
180
|
+
RubyJard.debug_info.first(height - 2).each do |line|
|
181
|
+
@output.puts line
|
94
182
|
end
|
183
|
+
@output.puts '-------------'
|
95
184
|
end
|
185
|
+
RubyJard.clear_debug
|
186
|
+
end
|
96
187
|
|
97
|
-
|
188
|
+
def adjust_screen_contents(screens)
|
189
|
+
# After drawing the box, screen sizes should be updated to reflect content-only area
|
190
|
+
screens.each do |screen|
|
191
|
+
screen.width -= 2
|
192
|
+
screen.height -= 2
|
193
|
+
screen.x += 1
|
194
|
+
screen.y += 1
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def clear_screen
|
199
|
+
RubyJard::Console.clear_screen(@output)
|
98
200
|
end
|
99
|
-
# rubocop:enable Metrics/AbcSize
|
100
201
|
|
101
202
|
def fetch_screen(name)
|
102
203
|
RubyJard::Screens[name]
|
103
204
|
end
|
104
205
|
|
105
|
-
def
|
206
|
+
def pick_layout(width, height)
|
106
207
|
RubyJard::DEFAULT_LAYOUT_TEMPLATES.each do |template|
|
107
208
|
matched = true
|
108
209
|
matched &&= (
|