terminal-layout 0.1.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 +7 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +60 -0
- data/README.md +7 -0
- data/block-flow.rb +361 -0
- data/lib/ansi_string.rb +315 -0
- data/lib/terminal_layout.rb +527 -0
- data/spec/ansi_string_spec.rb +499 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/terminal_layout_spec.rb +745 -0
- data/terminal-layout.gemspec +28 -0
- data/test-1.rb +90 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 283b397c0181527c2ded64f2865ebc71ede5aac9
|
4
|
+
data.tar.gz: adb2c56149641dcd9322173e512be240e1b6eca2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5b2b2c1eb88264c1084e879cdef933a5115edf521ca0288a3b3d594ce360a1155429847af7fe52a791d222c94885f89d75d3890e96477164f26f84880dbbd4f5
|
7
|
+
data.tar.gz: f28f73e2b8c7aa43bfab59f807103194096e621dee8e05a593ad1b99a1db9c4d2cfe24331f6f4897c960b522ea3b08e7524ccac4b77d574900a640493ff75314
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
terminal-layout (0.1.0)
|
5
|
+
highline
|
6
|
+
ruby-terminfo (~> 0.1.1)
|
7
|
+
ruby-termios (~> 0.9.6)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
byebug (5.0.0)
|
13
|
+
columnize (= 0.9.0)
|
14
|
+
coderay (1.1.0)
|
15
|
+
columnize (0.9.0)
|
16
|
+
diff-lcs (1.2.5)
|
17
|
+
highline (1.7.2)
|
18
|
+
method_source (0.8.2)
|
19
|
+
pry (0.10.1)
|
20
|
+
coderay (~> 1.1.0)
|
21
|
+
method_source (~> 0.8.1)
|
22
|
+
slop (~> 3.4)
|
23
|
+
pry-byebug (3.2.0)
|
24
|
+
byebug (~> 5.0)
|
25
|
+
pry (~> 0.10)
|
26
|
+
rake (10.4.2)
|
27
|
+
rspec (3.3.0)
|
28
|
+
rspec-core (~> 3.3.0)
|
29
|
+
rspec-expectations (~> 3.3.0)
|
30
|
+
rspec-mocks (~> 3.3.0)
|
31
|
+
rspec-core (3.3.1)
|
32
|
+
rspec-support (~> 3.3.0)
|
33
|
+
rspec-expectations (3.3.0)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.3.0)
|
36
|
+
rspec-mocks (3.3.1)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.3.0)
|
39
|
+
rspec-support (3.3.0)
|
40
|
+
ruby-terminfo (0.1.1)
|
41
|
+
ruby-termios (0.9.6)
|
42
|
+
slop (3.6.0)
|
43
|
+
term-ansicolor (1.3.0)
|
44
|
+
tins (~> 1.0)
|
45
|
+
tins (1.3.5)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
bundler (~> 1.7)
|
52
|
+
pry
|
53
|
+
pry-byebug
|
54
|
+
rake (~> 10.0)
|
55
|
+
rspec
|
56
|
+
term-ansicolor
|
57
|
+
terminal-layout!
|
58
|
+
|
59
|
+
BUNDLED WITH
|
60
|
+
1.10.4
|
data/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
# terminal-layout
|
6
|
+
|
7
|
+
TerminalLayout is a layout and rendering engine for terminals. It is essentially a bare bones browser rendering engine as it generates a basic DOM and supports style nodes similar to CSS.
|
data/block-flow.rb
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
$z = File.open("/tmp/z.log", "w+")
|
4
|
+
$z.sync = true
|
5
|
+
|
6
|
+
$stdout.sync = true
|
7
|
+
|
8
|
+
class Layout
|
9
|
+
def initialize(box, offset_x:0, offset_y:0)
|
10
|
+
@box = box
|
11
|
+
@x = offset_x
|
12
|
+
@y = offset_y
|
13
|
+
end
|
14
|
+
|
15
|
+
def layout_float(fbox)
|
16
|
+
# only allow the float to be as wide as its parent
|
17
|
+
if fbox.width > @box.width
|
18
|
+
fbox.width = @box.width
|
19
|
+
end
|
20
|
+
|
21
|
+
if fbox.float == :left
|
22
|
+
# if we cannot fit on this line, go to the next
|
23
|
+
if @x + fbox.width > @box.width
|
24
|
+
@x = 0
|
25
|
+
@y += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
fbox.x = @x
|
29
|
+
fbox.y = @y
|
30
|
+
|
31
|
+
Layout.new(fbox, offset_x:@x, offset_y:@y).layout
|
32
|
+
|
33
|
+
@x += fbox.width
|
34
|
+
elsif fbox.float == :right
|
35
|
+
# loop in case there are left floats on the left as we move down rows
|
36
|
+
loop do
|
37
|
+
starting_x = starting_x_for_current_y
|
38
|
+
# if we cannot fit on this line, go to the next
|
39
|
+
if starting_x + fbox.width > @box.width
|
40
|
+
@x = 0
|
41
|
+
@y += 1
|
42
|
+
else
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@x = ending_x_for_current_y - fbox.width
|
48
|
+
fbox.x = @x
|
49
|
+
fbox.y = @y
|
50
|
+
|
51
|
+
Layout.new(fbox, offset_x:@x, offset_y:@y).layout
|
52
|
+
|
53
|
+
# reset X back to what it should be
|
54
|
+
@x = starting_x_for_current_y
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def starting_x_for_current_y
|
59
|
+
x = 0
|
60
|
+
@box.children.select { |cbox| cbox.display == :float && cbox.float == :left }.each do |fbox|
|
61
|
+
next unless fbox.y && fbox.y <= @y && (fbox.y + fbox.height) >= @y
|
62
|
+
x = [fbox.x + fbox.width, x].max
|
63
|
+
end
|
64
|
+
x
|
65
|
+
end
|
66
|
+
|
67
|
+
def ending_x_for_current_y
|
68
|
+
x = @box.width
|
69
|
+
@box.children.select { |cbox| cbox.display == :float && cbox.float == :right }.each do |fbox|
|
70
|
+
next unless fbox.y && fbox.y <= @y && (fbox.y + fbox.height) >= @y
|
71
|
+
x = [fbox.x, x].min
|
72
|
+
end
|
73
|
+
x
|
74
|
+
end
|
75
|
+
|
76
|
+
def layout
|
77
|
+
@tree = []
|
78
|
+
|
79
|
+
@box.children.each_with_index do |cbox, i|
|
80
|
+
previous_box = i > 0 ? @box.children[i - 1] : nil
|
81
|
+
if cbox.display == :float
|
82
|
+
layout_float cbox
|
83
|
+
|
84
|
+
@tree.push cbox
|
85
|
+
elsif cbox.display == :block
|
86
|
+
if previous_box && previous_box.display == :inline
|
87
|
+
@y += 1
|
88
|
+
end
|
89
|
+
@x = starting_x_for_current_y
|
90
|
+
|
91
|
+
cbox.width = ending_x_for_current_y - @x
|
92
|
+
new_box = Box.new("", style: cbox.style.merge(width: cbox.width))
|
93
|
+
new_box.children = [Box.new(cbox.content, children:[], style: {display: :inline})].concat cbox.children
|
94
|
+
new_box.children = Layout.new(new_box, offset_x:@x, offset_y:@y).layout
|
95
|
+
new_box.x = @x
|
96
|
+
new_box.y = @y
|
97
|
+
|
98
|
+
new_box.height = (new_box.children.map(&:y).max - new_box.y) + new_box.children.map(&:height).max
|
99
|
+
|
100
|
+
@y += new_box.height
|
101
|
+
@x = 0
|
102
|
+
|
103
|
+
@tree.push new_box
|
104
|
+
elsif cbox.display == :inline
|
105
|
+
if @x == 0
|
106
|
+
@x = starting_x_for_current_y
|
107
|
+
end
|
108
|
+
available_width = ending_x_for_current_y - @x
|
109
|
+
|
110
|
+
content_i = 0
|
111
|
+
content = ""
|
112
|
+
|
113
|
+
loop do
|
114
|
+
chars_needed = available_width
|
115
|
+
partial_content = cbox.content[content_i..(content_i + chars_needed)]
|
116
|
+
chars_needed = partial_content.length
|
117
|
+
@tree << Box.new(partial_content, children:[], style: {display: :inline, x:@x, y: @y, width:chars_needed, height:1})
|
118
|
+
|
119
|
+
content_i += chars_needed
|
120
|
+
|
121
|
+
if @x + chars_needed > available_width
|
122
|
+
@y += 1
|
123
|
+
@x = starting_x_for_current_y
|
124
|
+
else
|
125
|
+
@x += chars_needed
|
126
|
+
end
|
127
|
+
|
128
|
+
break if content_i >= cbox.content.length
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@box.height = @box.height || (@box.children.map(&:y).max) - (@box.y || 0)
|
134
|
+
|
135
|
+
@tree
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Box
|
140
|
+
attr_accessor :children, :style, :content
|
141
|
+
|
142
|
+
def initialize(content, children:[], style:nil)
|
143
|
+
@style = style || { display: :block }
|
144
|
+
@content = content
|
145
|
+
@children = children
|
146
|
+
end
|
147
|
+
|
148
|
+
%w(width height display x y float).each do |method|
|
149
|
+
define_method(method){ style[method.to_sym] }
|
150
|
+
define_method("#{method}="){ |value| style[method.to_sym] = value }
|
151
|
+
end
|
152
|
+
|
153
|
+
def width
|
154
|
+
style[:width] || content.length
|
155
|
+
end
|
156
|
+
|
157
|
+
def height
|
158
|
+
if style[:height]
|
159
|
+
style[:height]
|
160
|
+
elsif style[:width]
|
161
|
+
(@content.to_s.length / style[:width].to_f).ceil
|
162
|
+
else # auto
|
163
|
+
0
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def inspect
|
168
|
+
to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_str
|
172
|
+
to_s
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_s
|
176
|
+
"<Box position=(#{x},#{y}) dimensions=#{width}x#{height} display=#{display.inspect} content=#{content}/>"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
require 'terminfo'
|
182
|
+
class TerminalRenderer
|
183
|
+
attr_reader :term_info
|
184
|
+
|
185
|
+
def initialize
|
186
|
+
@term_info = TermInfo.new ENV["TERM"], $stdout
|
187
|
+
# clear_screen
|
188
|
+
@x, @y = 0, 0
|
189
|
+
end
|
190
|
+
|
191
|
+
def log(str)
|
192
|
+
$z.puts str
|
193
|
+
end
|
194
|
+
|
195
|
+
def render(tree)
|
196
|
+
tree.each_with_index do |cbox, i|
|
197
|
+
previous_box = i > 0 ? tree[i-1] : nil
|
198
|
+
if cbox.display == :block
|
199
|
+
render_block cbox
|
200
|
+
elsif cbox.display == :inline
|
201
|
+
@x = cbox.x
|
202
|
+
log "move to column #{@x}"
|
203
|
+
move_to_column @x
|
204
|
+
|
205
|
+
render_inline cbox
|
206
|
+
elsif cbox.display == :float
|
207
|
+
needed_lines = cbox.y - @y
|
208
|
+
log "float puts #{needed_lines} times"
|
209
|
+
needed_lines.times{ $stdout.puts }
|
210
|
+
@y = cbox.y
|
211
|
+
|
212
|
+
render_float cbox
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def render_float(fbox)
|
218
|
+
@x = fbox.x
|
219
|
+
log "move to column #{fbox.x}"
|
220
|
+
move_to_column fbox.x
|
221
|
+
|
222
|
+
render_inline Box.new(fbox.content, style:{
|
223
|
+
display: :inline,
|
224
|
+
width: fbox.width,
|
225
|
+
height: fbox.height,
|
226
|
+
x:fbox.x,
|
227
|
+
y:fbox.y
|
228
|
+
})
|
229
|
+
|
230
|
+
render(fbox.children)
|
231
|
+
end
|
232
|
+
|
233
|
+
def render_block(cbox)
|
234
|
+
@x = cbox.x
|
235
|
+
log "move to column #{@x}"
|
236
|
+
move_to_column @x
|
237
|
+
|
238
|
+
needed_lines = cbox.y - @y
|
239
|
+
log "puts #{needed_lines} times (#{cbox.y} - #{@y})"
|
240
|
+
if needed_lines >= 0
|
241
|
+
needed_lines.times { $stdout.puts }
|
242
|
+
else
|
243
|
+
move_up_n_rows needed_lines.abs
|
244
|
+
end
|
245
|
+
@y = cbox.y
|
246
|
+
|
247
|
+
if cbox.children.any?
|
248
|
+
render(cbox.children)
|
249
|
+
end
|
250
|
+
|
251
|
+
@x = cbox.x
|
252
|
+
|
253
|
+
render_inline(Box.new(cbox.content, style:{display: :inline, width: cbox.width, x:cbox.x, y:cbox.y}))
|
254
|
+
end
|
255
|
+
|
256
|
+
def render_inline(cbox)
|
257
|
+
count = 0
|
258
|
+
rel_count = 0
|
259
|
+
|
260
|
+
# make sure we start in the right place
|
261
|
+
log "inline @y: #{@y}"
|
262
|
+
needed_lines = cbox.y - @y
|
263
|
+
log "inline puts #{needed_lines} times"
|
264
|
+
if needed_lines >= 0
|
265
|
+
needed_lines.times{ $stdout.puts }
|
266
|
+
else
|
267
|
+
move_up_n_rows needed_lines.abs
|
268
|
+
end
|
269
|
+
@y = cbox.y
|
270
|
+
|
271
|
+
loop do
|
272
|
+
if count >= cbox.content.length
|
273
|
+
log "breaking because count >= cbox.content.length (#{count} >= #{cbox.content.length}) x:#{@x} y:#{@y} count:#{count}"
|
274
|
+
break
|
275
|
+
end
|
276
|
+
|
277
|
+
if rel_count >= cbox.width
|
278
|
+
$stdout.puts
|
279
|
+
rel_count = 0
|
280
|
+
@y += 1
|
281
|
+
@x = cbox.x
|
282
|
+
log "puts because inline content is at width (count % cbox.width) == 0 (#{count} % #{cbox.width}) == 0 x:#{@x} y:#{@y} count:#{count}"
|
283
|
+
end
|
284
|
+
|
285
|
+
log "print #{cbox.content[count].inspect} x:#{@x} y:#{@y} count:#{count}"
|
286
|
+
move_to_column @x
|
287
|
+
|
288
|
+
$stdout.print cbox.content[count]
|
289
|
+
count += 1
|
290
|
+
rel_count += 1
|
291
|
+
@x += 1
|
292
|
+
end
|
293
|
+
|
294
|
+
log "Done inline: x:#{@x} y:#{@y}"
|
295
|
+
end
|
296
|
+
|
297
|
+
def clear_to_beginning_of_line ; term_info.control "el1" ; end
|
298
|
+
def clear_screen ; term_info.control "clear" ; end
|
299
|
+
def clear_screen_down ; term_info.control "ed" ; end
|
300
|
+
def move_to_beginning_of_row ; move_to_column 0 ; end
|
301
|
+
def move_left ; move_left_n_characters 1 ; end
|
302
|
+
def move_left_n_characters(n) ; term_info.control "cub1" ; end
|
303
|
+
def move_right_n_characters(n) ; term_info.control "cuf1" ; end
|
304
|
+
def move_to_column_and_row(column, row) ; term_info.control "cup", column, row ; end
|
305
|
+
def move_to_column(n) ; term_info.control "hpa", n ; end
|
306
|
+
def move_up_n_rows(n) ; n.times { term_info.control "cuu1" } ; end
|
307
|
+
def move_down_n_rows(n) ; n.times { term_info.control "cud1" } ; end
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
|
312
|
+
parent = Box.new(nil,
|
313
|
+
children: [
|
314
|
+
Box.new("<"*5, style:{display: :float, float: :left, width:5}),
|
315
|
+
Box.new("r"*40, style:{display: :float, float: :right, width:20}),
|
316
|
+
Box.new("A"*40, style: {display: :block}),
|
317
|
+
Box.new(">"*15, style:{display: :float, float: :left, width:5}),
|
318
|
+
Box.new("_"*6, style: {display: :inline}),
|
319
|
+
Box.new("-"*6, style: {display: :inline}),
|
320
|
+
Box.new("~"*9, style: {display: :inline}),
|
321
|
+
Box.new("B", style: {display: :block},
|
322
|
+
children: [
|
323
|
+
Box.new("b"*10, style: {display: :block}, children:[
|
324
|
+
Box.new("*"*3, style: {display: :inline}),
|
325
|
+
Box.new("!"*3, style: {display: :inline}),
|
326
|
+
Box.new("$"*3, style: {display: :inline}),
|
327
|
+
Box.new("["*3, style: {display: :block})
|
328
|
+
]),
|
329
|
+
]),
|
330
|
+
Box.new("C"*60, style: {display: :block}),
|
331
|
+
Box.new("D"*20, style: {display: :block}),
|
332
|
+
Box.new("E"*10, style: {display: :block})
|
333
|
+
],
|
334
|
+
style: {
|
335
|
+
display: :block,
|
336
|
+
width: 60,
|
337
|
+
height: nil
|
338
|
+
}
|
339
|
+
)
|
340
|
+
|
341
|
+
|
342
|
+
layout_tree = Layout.new(parent).layout
|
343
|
+
|
344
|
+
|
345
|
+
def print_tree(tree, indent=0)
|
346
|
+
tree.each do |box|
|
347
|
+
print " " * indent
|
348
|
+
puts box
|
349
|
+
print_tree box.children, indent + 2
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
|
354
|
+
print_tree layout_tree
|
355
|
+
TerminalRenderer.new.render(layout_tree)
|
356
|
+
|
357
|
+
|
358
|
+
# print_tree layout_tree
|
359
|
+
# require 'pry'
|
360
|
+
# binding.pry
|
361
|
+
puts
|