terminal-layout 0.1.1 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/lib/ansi_string.rb +34 -11
- data/lib/terminal_layout.rb +80 -24
- data/spec/ansi_string_spec.rb +97 -0
- data/terminal-layout.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c65b1156a204469a85174ffa2bb761a1f4f15593
|
4
|
+
data.tar.gz: 70c589abe13e269025404af7efe2555c2567e906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85ba780f984691a236879b5fc8754e0cf490f7df4d5b9288aa03411c735f985d335a5a454bbb72de72a16fe93b69458acf6c8dc8b2d1c2323d3eb9bc91857abc
|
7
|
+
data.tar.gz: 47c7317c78a4d5814682665fa77e27e9782b07c5af658eb8a60e73c78bb65ba90ccf490f3284240f08448a54bb38599ddb70d2e4d170574aac489e9e7620da69
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
terminal-layout (0.
|
4
|
+
terminal-layout (0.2.0)
|
5
5
|
highline
|
6
6
|
ruby-terminfo (~> 0.1.1)
|
7
7
|
ruby-termios (~> 0.9.6)
|
@@ -14,7 +14,7 @@ GEM
|
|
14
14
|
coderay (1.1.0)
|
15
15
|
columnize (0.9.0)
|
16
16
|
diff-lcs (1.2.5)
|
17
|
-
highline (1.7.
|
17
|
+
highline (1.7.8)
|
18
18
|
method_source (0.8.2)
|
19
19
|
pry (0.10.1)
|
20
20
|
coderay (~> 1.1.0)
|
@@ -57,4 +57,4 @@ DEPENDENCIES
|
|
57
57
|
terminal-layout!
|
58
58
|
|
59
59
|
BUNDLED WITH
|
60
|
-
1.
|
60
|
+
1.11.2
|
data/lib/ansi_string.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
1
5
|
class ANSIString
|
6
|
+
extend Forwardable
|
2
7
|
attr_reader :raw, :without_ansi
|
3
8
|
|
9
|
+
def_delegators :@without_ansi, :each_char, :each_byte, :index,
|
10
|
+
:match, :=~
|
11
|
+
|
4
12
|
def initialize(str)
|
5
13
|
process_string raw_string_for(str)
|
6
14
|
end
|
@@ -16,6 +24,10 @@ class ANSIString
|
|
16
24
|
self
|
17
25
|
end
|
18
26
|
|
27
|
+
def empty?
|
28
|
+
length == 0
|
29
|
+
end
|
30
|
+
|
19
31
|
def [](range)
|
20
32
|
# convert numeric position to a range
|
21
33
|
range = (range..range) if range.is_a?(Integer)
|
@@ -47,15 +59,6 @@ class ANSIString
|
|
47
59
|
self
|
48
60
|
end
|
49
61
|
|
50
|
-
# See String#index for arguments
|
51
|
-
def index(*args)
|
52
|
-
@without_ansi.index(*args)
|
53
|
-
end
|
54
|
-
|
55
|
-
def match(*args, &blk)
|
56
|
-
@without_ansi.match(*args, &blk)
|
57
|
-
end
|
58
|
-
|
59
62
|
# See String#rindex for arguments
|
60
63
|
def rindex(*args)
|
61
64
|
@without_ansi.rindex(*args)
|
@@ -73,6 +76,22 @@ class ANSIString
|
|
73
76
|
ANSIString.new str
|
74
77
|
end
|
75
78
|
|
79
|
+
def scan(pattern)
|
80
|
+
results = []
|
81
|
+
without_ansi.enum_for(:scan, pattern).each do
|
82
|
+
md = Regexp.last_match
|
83
|
+
if md.captures.any?
|
84
|
+
results << md.captures.map.with_index do |_, i|
|
85
|
+
# captures use 1-based indexing
|
86
|
+
self[md.begin(i+1)...md.end(i+1)]
|
87
|
+
end
|
88
|
+
else
|
89
|
+
results << self[md.begin(0)...md.end(0)]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
results
|
93
|
+
end
|
94
|
+
|
76
95
|
def slice(index, length=nil)
|
77
96
|
range = nil
|
78
97
|
index = index.without_ansi if index.is_a?(ANSIString)
|
@@ -178,6 +197,10 @@ class ANSIString
|
|
178
197
|
(other.class == self.class && other.raw == @raw) || (other.kind_of?(String) && other == @raw)
|
179
198
|
end
|
180
199
|
|
200
|
+
def <=>(other)
|
201
|
+
(other.class == self.class && @raw <=> other.raw)
|
202
|
+
end
|
203
|
+
|
181
204
|
private
|
182
205
|
|
183
206
|
def raw_string_for(str)
|
@@ -312,12 +335,12 @@ class ANSIString
|
|
312
335
|
str << [location[:start_ansi_sequence], location[:text], location[:end_ansi_sequence]].join
|
313
336
|
|
314
337
|
elsif location[:begins_at] >= range.begin && location[:begins_at] <= range.end
|
315
|
-
str << [location[:start_ansi_sequence], location[:text][
|
338
|
+
str << [location[:start_ansi_sequence], location[:text][0..(range.end - location[:begins_at])], location[:end_ansi_sequence]].join
|
316
339
|
|
317
340
|
# If the location falls within the given range then make sure we pull
|
318
341
|
# out the bits that we want, and keep ANSI escape sequenece intact while
|
319
342
|
# doing so.
|
320
|
-
|
343
|
+
elsif (location[:begins_at] <= range.begin && location[:ends_at] >= range.end) || range.cover?(location[:ends_at])
|
321
344
|
start_index = range.begin - location[:begins_at]
|
322
345
|
end_index = range.end - location[:begins_at]
|
323
346
|
str << [location[:start_ansi_sequence], location[:text][start_index..end_index], location[:end_ansi_sequence]].join
|
data/lib/terminal_layout.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'pry'
|
2
1
|
require 'ansi_string'
|
3
2
|
require 'ostruct'
|
4
3
|
|
@@ -348,9 +347,18 @@ module TerminalLayout
|
|
348
347
|
end
|
349
348
|
|
350
349
|
def content=(str)
|
351
|
-
|
352
|
-
@content
|
353
|
-
|
350
|
+
new_content = ANSIString.new(str)
|
351
|
+
if @content != new_content
|
352
|
+
old_content = @content
|
353
|
+
@content = new_content
|
354
|
+
emit :content_changed, old_content, @content
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def children=(new_children)
|
359
|
+
old_children = @children
|
360
|
+
@children = new_children
|
361
|
+
emit :child_changed, old_children, new_children
|
354
362
|
end
|
355
363
|
|
356
364
|
def position
|
@@ -398,14 +406,27 @@ module TerminalLayout
|
|
398
406
|
|
399
407
|
def initialize(*args)
|
400
408
|
super
|
409
|
+
@computed = { x: 0, y: 0 }
|
401
410
|
@cursor_offset_x = 0
|
402
411
|
@cursor_position = OpenStruct.new(x: 0, y: 0)
|
412
|
+
@position = 0
|
413
|
+
end
|
414
|
+
|
415
|
+
def cursor_off
|
416
|
+
@style.update(cursor: 'none')
|
417
|
+
end
|
418
|
+
|
419
|
+
def cursor_on
|
420
|
+
@style.update(cursor: 'auto')
|
403
421
|
end
|
404
422
|
|
405
423
|
def content=(str)
|
406
|
-
|
407
|
-
@content
|
408
|
-
|
424
|
+
new_content = ANSIString.new(str)
|
425
|
+
if @content != new_content
|
426
|
+
old_content = @content
|
427
|
+
@content = new_content
|
428
|
+
emit :content_changed, old_content, @content
|
429
|
+
end
|
409
430
|
end
|
410
431
|
|
411
432
|
def position=(position)
|
@@ -416,7 +437,11 @@ module TerminalLayout
|
|
416
437
|
|
417
438
|
def update_computed(style)
|
418
439
|
@computed.merge!(style)
|
419
|
-
|
440
|
+
if style[:y] > 0
|
441
|
+
@cursor_position.x = @computed[:width] #@computed[:width] - (style[:x] + @cursor_offset_x)
|
442
|
+
else
|
443
|
+
@cursor_position.x = style[:x] + @cursor_offset_x
|
444
|
+
end
|
420
445
|
@cursor_position.y = style[:y]
|
421
446
|
end
|
422
447
|
end
|
@@ -433,6 +458,7 @@ module TerminalLayout
|
|
433
458
|
def initialize(output: $stdout)
|
434
459
|
@output = output
|
435
460
|
@term_info = TermInfo.new ENV["TERM"], @output
|
461
|
+
@previously_printed_lines = []
|
436
462
|
@x, @y = 0, 0
|
437
463
|
end
|
438
464
|
|
@@ -445,27 +471,45 @@ module TerminalLayout
|
|
445
471
|
cursor_y = cursor_position.y
|
446
472
|
|
447
473
|
# TODO: make this work when lines wrap
|
448
|
-
if cursor_x < 0
|
474
|
+
if cursor_x < 0 && cursor_y == 0
|
449
475
|
cursor_x = terminal_width
|
450
476
|
cursor_y -= 1
|
451
477
|
elsif cursor_x >= terminal_width
|
478
|
+
cursor_y = cursor_x / terminal_width
|
479
|
+
cursor_x -= terminal_width
|
452
480
|
end
|
453
481
|
|
454
|
-
|
455
|
-
|
482
|
+
if @y < cursor_y
|
483
|
+
# moving backwards
|
484
|
+
move_up_n_rows(@y - cursor_y)
|
485
|
+
elsif @y > cursor_y
|
486
|
+
# moving forwards
|
487
|
+
move_down_n_rows(cursor_y - @y)
|
488
|
+
end
|
456
489
|
|
457
|
-
|
458
|
-
|
490
|
+
move_down_n_rows cursor_y
|
491
|
+
move_to_beginning_of_row
|
492
|
+
move_right_n_characters cursor_x
|
459
493
|
|
460
|
-
|
461
|
-
|
494
|
+
@x = cursor_x
|
495
|
+
@y = cursor_y
|
496
|
+
|
497
|
+
if input_box.style[:cursor] == 'none'
|
498
|
+
@output.print @term_info.control_string "civis"
|
499
|
+
else
|
500
|
+
@output.print @term_info.control_string "cnorm"
|
501
|
+
end
|
462
502
|
end
|
463
503
|
|
464
|
-
def reset
|
465
|
-
|
504
|
+
def render(object, reset: false)
|
505
|
+
dumb_render(object, reset: reset)
|
466
506
|
end
|
467
507
|
|
468
|
-
def dumb_render(object)
|
508
|
+
def dumb_render(object, reset: false)
|
509
|
+
if reset
|
510
|
+
@y = 0
|
511
|
+
@previously_printed_lines.clear
|
512
|
+
end
|
469
513
|
@output.print @term_info.control_string "civis"
|
470
514
|
move_up_n_rows @y
|
471
515
|
move_to_beginning_of_row
|
@@ -476,26 +520,38 @@ module TerminalLayout
|
|
476
520
|
end
|
477
521
|
|
478
522
|
object_width = object.width
|
479
|
-
clear_screen_down
|
480
523
|
|
481
524
|
rendered_content = object.render
|
482
525
|
printable_content = rendered_content.sub(/\s*\Z/m, '')
|
483
526
|
|
484
|
-
printable_content.
|
485
|
-
|
486
|
-
|
527
|
+
printable_lines = printable_content.split(/\n/).each_with_object([]) do |line, results|
|
528
|
+
if line.empty?
|
529
|
+
results << line
|
530
|
+
else
|
531
|
+
results.concat line.scan(/.{1,#{terminal_width}}/)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
printable_lines.zip(@previously_printed_lines) do |new_line, previous_line|
|
536
|
+
if new_line != previous_line
|
537
|
+
term_info.control "el"
|
538
|
+
move_to_beginning_of_row
|
539
|
+
@output.puts new_line
|
540
|
+
else
|
541
|
+
move_down_n_rows 1
|
542
|
+
end
|
487
543
|
end
|
488
544
|
move_to_beginning_of_row
|
545
|
+
clear_screen_down
|
489
546
|
|
490
547
|
# calculate lines drawn so we know where we are
|
491
548
|
lines_drawn = (printable_content.length / object_width.to_f).ceil
|
492
549
|
@y = lines_drawn
|
493
550
|
|
494
551
|
input_box = find_input_box(object.box)
|
495
|
-
|
496
552
|
render_cursor(input_box)
|
497
553
|
|
498
|
-
@
|
554
|
+
@previously_printed_lines = printable_lines
|
499
555
|
end
|
500
556
|
|
501
557
|
def find_input_box(dom_node)
|
data/spec/ansi_string_spec.rb
CHANGED
@@ -14,6 +14,13 @@ describe 'ANSIString' do
|
|
14
14
|
ansi_string = ANSIString.new "this #{blue('is')} a string"
|
15
15
|
expect(ansi_string).to be
|
16
16
|
end
|
17
|
+
|
18
|
+
it "can be constructed with UTF-8 characters" do;
|
19
|
+
expect do
|
20
|
+
ansi_string = ANSIString.new "this #{blue('ƒ')} a string"
|
21
|
+
expect(ansi_string).to be
|
22
|
+
end.to_not raise_error
|
23
|
+
end
|
17
24
|
end
|
18
25
|
|
19
26
|
describe "redundant ANSI sequences" do
|
@@ -48,6 +55,30 @@ describe 'ANSIString' do
|
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
58
|
+
describe "#each_byte" do
|
59
|
+
let(:blue_ansi_string){ ANSIString.new blue_string }
|
60
|
+
let(:blue_string){ blue("this is blue") }
|
61
|
+
|
62
|
+
it "iterates over each character ignoring ANSI sequences" do
|
63
|
+
expected = "this is blue"
|
64
|
+
actual = ""
|
65
|
+
blue_ansi_string.each_byte { |ch| actual << ch }
|
66
|
+
expect(actual).to eq(expected)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#each_char" do
|
71
|
+
let(:blue_ansi_string){ ANSIString.new blue_string }
|
72
|
+
let(:blue_string){ blue("this is blue") }
|
73
|
+
|
74
|
+
it "iterates over each character ignoring ANSI sequences" do
|
75
|
+
expected = "this is blue"
|
76
|
+
actual = ""
|
77
|
+
blue_ansi_string.each_char { |ch| actual << ch }
|
78
|
+
expect(actual).to eq(expected)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
51
82
|
describe "#<<" do
|
52
83
|
it "appends a String onto the end of the current ANSIString" do
|
53
84
|
ansi_string = ANSIString.new ""
|
@@ -83,6 +114,22 @@ describe 'ANSIString' do
|
|
83
114
|
end
|
84
115
|
end
|
85
116
|
|
117
|
+
describe "#empty?" do
|
118
|
+
it "returns true when empty" do
|
119
|
+
expect(ANSIString.new("").empty?).to be(true)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "returns true when it only contains ANSI sequences" do
|
123
|
+
expect(ANSIString.new(blue("")).empty?).to be(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "returns false when there are non-ANSI characters" do
|
127
|
+
expect(ANSIString.new("a").empty?).to be(false)
|
128
|
+
expect(ANSIString.new(blue("a")).empty?).to be(false)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
86
133
|
describe "#index" do
|
87
134
|
it "returns the index of the first occurrence of the given substring" do
|
88
135
|
ansi_string = ANSIString.new("this is not blue")
|
@@ -179,6 +226,11 @@ describe 'ANSIString' do
|
|
179
226
|
expect(ansi_string[17..-5]).to eq yellow("is is ye")
|
180
227
|
end
|
181
228
|
|
229
|
+
it "returns the correct substring when location of an ANSI sequence comes before the end of the request" do
|
230
|
+
s = ANSIString.new("ABC \e[7mGemfile.lock\e[0m LICENSE.txt README.md")
|
231
|
+
expect(s[4...28]).to eq ANSIString.new("\e[7mGemfile.lock\e[0m LICENSE.txt")
|
232
|
+
end
|
233
|
+
|
182
234
|
it "returns text that is not ANSI escaped" do
|
183
235
|
expect(ansi_string[12..14]).to eq "ABC"
|
184
236
|
end
|
@@ -398,6 +450,17 @@ describe 'ANSIString' do
|
|
398
450
|
end
|
399
451
|
end
|
400
452
|
|
453
|
+
describe "<=>" do
|
454
|
+
let(:string_1){ ANSIString.new blue("abc") }
|
455
|
+
let(:string_2){ ANSIString.new blue("def") }
|
456
|
+
|
457
|
+
it "behaves the same as a normal string" do
|
458
|
+
expect(string_1 <=> string_2).to eq(-1)
|
459
|
+
expect(string_1 <=> string_1).to eq(0)
|
460
|
+
expect(string_2 <=> string_1).to eq(1)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
401
464
|
describe "#match" do
|
402
465
|
it "matches on a string pattren" do
|
403
466
|
string = "apples are bananas are they not?"
|
@@ -412,6 +475,40 @@ describe 'ANSIString' do
|
|
412
475
|
end
|
413
476
|
end
|
414
477
|
|
478
|
+
describe "#=~" do
|
479
|
+
it "matches on a regex pattren" do
|
480
|
+
string = "apples are bananas are they not?"
|
481
|
+
ansi_string = ANSIString.new("app#{red('les are bananas')} are they not?")
|
482
|
+
expect(ansi_string =~ /are/).to eq(string =~ /are/)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
describe "#scan" do
|
487
|
+
it "scans without capture groups" do
|
488
|
+
string = "567"
|
489
|
+
ansi_string = ANSIString.new("1234#{red('5678')}90")
|
490
|
+
expect(ansi_string.scan(/.{2}/)).to eq([
|
491
|
+
ANSIString.new("12"),
|
492
|
+
ANSIString.new("34"),
|
493
|
+
ANSIString.new("#{red('56')}"),
|
494
|
+
ANSIString.new("#{red('78')}"),
|
495
|
+
ANSIString.new("90")
|
496
|
+
])
|
497
|
+
end
|
498
|
+
|
499
|
+
it "scans with capture groups" do
|
500
|
+
string = "567"
|
501
|
+
ansi_string = ANSIString.new("1234#{red('5678')}90")
|
502
|
+
expect(ansi_string.scan(/(.)./)).to eq([
|
503
|
+
[ANSIString.new("1")],
|
504
|
+
[ANSIString.new("3")],
|
505
|
+
[ANSIString.new("#{red('5')}")],
|
506
|
+
[ANSIString.new("#{red('7')}")],
|
507
|
+
[ANSIString.new("9")]
|
508
|
+
])
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
415
512
|
describe "#replace" do
|
416
513
|
it "replaces the contents of the current string with the new string" do
|
417
514
|
ansi_string = ANSIString.new("abc")
|
data/terminal-layout.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "terminal-layout"
|
7
|
-
spec.version = "0.
|
7
|
+
spec.version = "0.2.0"
|
8
8
|
spec.authors = ["Zach Dennis"]
|
9
9
|
spec.email = ["zach.dennis@gmail.com"]
|
10
10
|
spec.summary = %q{A terminal layout manager}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terminal-layout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-terminfo
|
@@ -148,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
148
|
version: '0'
|
149
149
|
requirements: []
|
150
150
|
rubyforge_project:
|
151
|
-
rubygems_version: 2.4.
|
151
|
+
rubygems_version: 2.4.5.1
|
152
152
|
signing_key:
|
153
153
|
specification_version: 4
|
154
154
|
summary: A terminal layout manager
|
@@ -156,3 +156,4 @@ test_files:
|
|
156
156
|
- spec/ansi_string_spec.rb
|
157
157
|
- spec/spec_helper.rb
|
158
158
|
- spec/terminal_layout_spec.rb
|
159
|
+
has_rdoc:
|