tty_string 0.1.0 → 1.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 +5 -5
- data/CHANGELOG.md +21 -0
- data/Gemfile +0 -2
- data/README.md +62 -29
- data/lib/tty_string.rb +14 -5
- data/lib/tty_string/code.rb +64 -0
- data/lib/tty_string/code_definitions.rb +55 -0
- data/lib/tty_string/csi_code.rb +50 -0
- data/lib/tty_string/csi_code_definitions.rb +139 -0
- data/lib/tty_string/cursor.rb +2 -15
- data/lib/tty_string/parser.rb +18 -60
- data/lib/tty_string/screen.rb +10 -0
- data/lib/tty_string/version.rb +1 -1
- data/tty_string.gemspec +25 -11
- metadata +98 -31
- data/.gitignore +0 -11
- data/.gitmodules +0 -3
- data/.rspec +0 -3
- data/.rubocop.yml +0 -1
- data/.travis.yml +0 -5
- data/Gemfile.lock +0 -63
- data/Rakefile +0 -8
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/lib/tty_string/renderer.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b638ef2c66276f621f7ae5620e25af1ccfb1d79f634202aa59935e181baf53e2
|
4
|
+
data.tar.gz: ecce132aa8201f22a69c2c2a201143bb1f2775abf4ea0785553592a2f2615640
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5799b2b9a14ecdb5d86cfd8d6af0d10f896c20de36dfed2801756172fe73afebb427d5cd5816a7bd18502a08640d5c9a691d6f65b2cde9cbf2e9932ca4ca55e8
|
7
|
+
data.tar.gz: 76d8856718792bcf2f4da207e6cb5aa69b8bf0cb81e9cc6e60aaf9ec1f8734ce0ca6abb4dca54ec04d97d95604badba2b9ba316ba7079fd3d6b198e34193c474
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# v1.1.0
|
2
|
+
- suppress bracketed paste mode codes
|
3
|
+
|
4
|
+
# v1.0.1
|
5
|
+
- test push to rubygems, no functional changes
|
6
|
+
|
7
|
+
# v1.0.0
|
8
|
+
- added TTYString.parse as a shortcut for .new#to_s
|
9
|
+
- added TTYString.to_proc for lols
|
10
|
+
- added jruby to travis matrix, fortunately it just works™
|
11
|
+
|
12
|
+
# v0.2.1
|
13
|
+
- fixed a bug with ignoring \a
|
14
|
+
|
15
|
+
# v0.2.0
|
16
|
+
- Stricter rendering because it turns out terminals are randomly permissive
|
17
|
+
- removed support for ruby 2.3
|
18
|
+
- added \e[S and \e[T handling
|
19
|
+
|
20
|
+
# v0.1.0
|
21
|
+
- Initial Release
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,33 +1,49 @@
|
|
1
1
|
# TTYString
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
-
|
23
|
-
|
3
|
+
[](https://travis-ci.com/robotdana/tty_string)
|
4
|
+
[](https://rubygems.org/gems/tty_string)
|
5
|
+
|
6
|
+
Render to a string like your terminal does by (narrowly) parsing ANSI TTY codes.
|
7
|
+
Intended for use in tests of command line interfaces.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- supports ruby 2.4 - 3.0.0.preview1, and jruby
|
12
|
+
- has no dependencies outside ruby stdlib
|
13
|
+
|
14
|
+
## Supported codes
|
15
|
+
|
16
|
+
| Code | Description | Default |
|
17
|
+
|------|-------------|---------|
|
18
|
+
| `\a` | bell: suppressed | |
|
19
|
+
| `\b` | backspace: clear the character to the left of the cursor and move the cursor back one column | |
|
20
|
+
| `\n` | newline: move the cursor to the start of the next line | |
|
21
|
+
| `\r` | return: move the cursor to the start of the current line | |
|
22
|
+
| `\t` | tab: move the cursor to the next multiple-of-8 column | |
|
23
|
+
| `\e[nA` | move the cursor up _n_ lines | _n_=`1` |
|
24
|
+
| `\e[nB` | move the cursor down _n_ lines | _n_=`1` |
|
25
|
+
| `\e[nC` | move the cursor right _n_ columns | _n_=`1` |
|
26
|
+
| `\e[nD` | move the cursor left _n_ columns | _n_=`1` |
|
27
|
+
| `\e[nE` | move the cursor down _n_ lines, and to the start of the line | _n_=`1` |
|
28
|
+
| `\e[nF` | move the cursor up _n_ lines, and to the start of the line | _n_=`1` |
|
29
|
+
| `\e[nG` | move the cursor to column _n_. `1` is left-most column | _n_=`1` |
|
30
|
+
| `\e[n;mH` <br> `\e[n;mf` | move the cursor to row _n_, column _m_. `1;1` is top left corner | _n_=`1` _m_=`1` |
|
31
|
+
| `\e[nJ` | _n_=`0`: clear the screen from the cursor forward <br>_n_=`1`: clear the screen from the cursor backward <br>_n_=`2` or _n_=`3`: clear the screen | _n_=`0` |
|
32
|
+
| `\e[nK` | _n_=`0`: clear the line from the cursor forward <br>_n_=`1`: clear the line from the cursor backward <br>_n_=`2`: clear the line | _n_=`0` |
|
33
|
+
| `\e[nS` | scroll up _n_ rows | _n_=`1` |
|
34
|
+
| `\e[nT` | scroll down _n_ rows | _n_=`1` |
|
35
|
+
| `\e[m` | styling codes: optionally suppressed with `clear_style: false` | |
|
36
|
+
| `\e[?2004h` | enabled bracketed paste: suppressed | |
|
37
|
+
| `\e[?2004l` | disable bracketed paste: suppressed | |
|
38
|
+
| `\e[200~` | bracketed paste start: suppressed | |
|
39
|
+
| `\e[201~` | bracketed paste end: suppressed | |
|
24
40
|
|
25
41
|
## Installation
|
26
42
|
|
27
43
|
Add this line to your application's Gemfile:
|
28
44
|
|
29
45
|
```ruby
|
30
|
-
gem 'tty_string'
|
46
|
+
gem 'tty_string', '~> 1.0'
|
31
47
|
```
|
32
48
|
|
33
49
|
And then execute:
|
@@ -40,24 +56,41 @@ Or install it yourself as:
|
|
40
56
|
|
41
57
|
## Usage
|
42
58
|
|
43
|
-
```
|
44
|
-
TTYString.
|
59
|
+
```ruby
|
60
|
+
TTYString.parse("th\ta string\e[3Gis is")
|
61
|
+
=> "this is a string"
|
45
62
|
```
|
46
63
|
|
47
64
|
Styling information is suppressed by default:
|
48
|
-
```
|
49
|
-
TTYString.
|
65
|
+
```ruby
|
66
|
+
TTYString.parse("th\ta \e[31mstring\e[0m\e[3Gis is")
|
67
|
+
=> "this is a string"
|
50
68
|
```
|
51
69
|
But can be passed through:
|
70
|
+
```ruby
|
71
|
+
TTYString.parse("th\ta \e[31mstring\e[0m\e[3Gis is", clear_style: false)
|
72
|
+
=> "this is a \e[31mstring\e[0m"
|
52
73
|
```
|
53
|
-
|
74
|
+
|
75
|
+
Just for fun TTYString.to_proc provides the `parse` method as a lambda, so:
|
76
|
+
```ruby
|
77
|
+
["th\ta string\e[3Gis is"].each(&TTYString)
|
78
|
+
=> ["this is a string"]
|
54
79
|
```
|
55
80
|
|
81
|
+
## Limitations
|
82
|
+
|
83
|
+
- Various terminals are wildly variously permissive with what they accept,
|
84
|
+
so this doesn't even try to cover all possible cases,
|
85
|
+
instead it covers the narrowest possible case, and leaves the codes in place when unrecognized
|
86
|
+
|
87
|
+
- `clear_style: false` treats the style codes as regular text which may work differently when rendering codes that move the cursor.
|
88
|
+
|
56
89
|
## Development
|
57
90
|
|
58
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
91
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and linters. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
59
92
|
|
60
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
93
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
61
94
|
|
62
95
|
## Contributing
|
63
96
|
|
data/lib/tty_string.rb
CHANGED
@@ -3,19 +3,28 @@
|
|
3
3
|
require_relative 'tty_string/parser'
|
4
4
|
|
5
5
|
# Renders a string taking into ANSI escape codes and \t\r\n etc
|
6
|
-
# Usage: TTYString.
|
6
|
+
# Usage: TTYString.parse("This\r\e[KThat") => "That"
|
7
7
|
class TTYString
|
8
|
+
class << self
|
9
|
+
def parse(input_string, clear_style: true)
|
10
|
+
new(input_string, clear_style: clear_style).to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_proc
|
14
|
+
method(:parse).to_proc
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
def initialize(input_string, clear_style: true)
|
9
19
|
@parser = Parser.new(input_string)
|
10
|
-
@clear_style = clear_style
|
20
|
+
@parser.clear_style = clear_style
|
11
21
|
end
|
12
22
|
|
13
23
|
def to_s
|
14
|
-
parser.render
|
24
|
+
parser.render
|
15
25
|
end
|
16
26
|
|
17
27
|
private
|
18
28
|
|
19
|
-
attr_reader :clear_style
|
20
|
-
attr_reader :parser
|
29
|
+
attr_reader :clear_style, :parser
|
21
30
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TTYString
|
4
|
+
class Code
|
5
|
+
class << self
|
6
|
+
def descendants
|
7
|
+
@@descendants
|
8
|
+
end
|
9
|
+
|
10
|
+
def inherited(klass)
|
11
|
+
@@descendants ||= [] # rubocop:disable Style/ClassVars I want it to be shared between subclasses.
|
12
|
+
@@descendants << klass
|
13
|
+
@@descendants.uniq!
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def render(parser)
|
19
|
+
return unless match?(parser)
|
20
|
+
|
21
|
+
new(parser).action(*args(parser))
|
22
|
+
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def char(value = nil)
|
27
|
+
@char = value if value
|
28
|
+
@char ||= name.split('::').last
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def re
|
34
|
+
@re ||= /#{char}/.freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
def args(_scanner)
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
def match?(parser)
|
42
|
+
parser.skip(re)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(parser)
|
47
|
+
@parser = parser
|
48
|
+
end
|
49
|
+
|
50
|
+
def action; end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :parser
|
55
|
+
|
56
|
+
def screen
|
57
|
+
parser.screen
|
58
|
+
end
|
59
|
+
|
60
|
+
def cursor
|
61
|
+
parser.cursor
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'code'
|
4
|
+
require_relative 'csi_code'
|
5
|
+
|
6
|
+
class TTYString
|
7
|
+
class Code
|
8
|
+
class SlashA < TTYString::Code # leftovers:allow
|
9
|
+
char "\a"
|
10
|
+
end
|
11
|
+
|
12
|
+
class SlashB < TTYString::Code # leftovers:allow
|
13
|
+
char "\b"
|
14
|
+
|
15
|
+
def self.match?(scanner)
|
16
|
+
# can't use `scan(/\b/)` because it matches everything
|
17
|
+
return false unless scanner.peek(1) == "\b"
|
18
|
+
|
19
|
+
scanner.pos += 1
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def action
|
24
|
+
cursor.left
|
25
|
+
screen.clear_at_cursor
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class SlashN < TTYString::Code # leftovers:allow
|
30
|
+
char "\n"
|
31
|
+
|
32
|
+
def action
|
33
|
+
cursor.down
|
34
|
+
cursor.col = 0
|
35
|
+
screen.write('')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class SlashR < TTYString::Code # leftovers:allow
|
40
|
+
char "\r"
|
41
|
+
|
42
|
+
def action
|
43
|
+
cursor.col = 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class SlashT < TTYString::Code # leftovers:allow
|
48
|
+
char "\t"
|
49
|
+
|
50
|
+
def action
|
51
|
+
cursor.right(8 - (cursor.col % 8))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'code'
|
4
|
+
|
5
|
+
class TTYString
|
6
|
+
class CSICode < TTYString::Code
|
7
|
+
class << self
|
8
|
+
def default_arg(value = nil)
|
9
|
+
@default_arg ||= value
|
10
|
+
@default_arg || 1
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def match?(parser)
|
16
|
+
parser.scan(re)
|
17
|
+
end
|
18
|
+
|
19
|
+
def args(parser)
|
20
|
+
a = parser.matched.slice(2..-2).split(';')
|
21
|
+
a = a.slice(0, max_args) unless max_args == -1
|
22
|
+
a.map! { |n| n.empty? ? default_arg : n.to_i }
|
23
|
+
a
|
24
|
+
end
|
25
|
+
|
26
|
+
def re
|
27
|
+
@re ||= /\e\[#{args_re}#{char}/
|
28
|
+
end
|
29
|
+
|
30
|
+
def args_re # rubocop:disable Metrics/MethodLength
|
31
|
+
case max_args
|
32
|
+
when 0 then nil
|
33
|
+
when 1 then /#{arg_re}?/
|
34
|
+
when -1 then /(#{arg_re}?(;#{arg_re})*)?/
|
35
|
+
else /(#{arg_re}?(;#{arg_re}){0,#{max_args - 1}})?/
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def arg_re
|
40
|
+
/\d*/
|
41
|
+
end
|
42
|
+
|
43
|
+
def max_args
|
44
|
+
@max_args ||= instance_method(:action).arity
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
TTYString::Code.descendants.pop
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'csi_code'
|
4
|
+
|
5
|
+
class TTYString
|
6
|
+
class CSICode
|
7
|
+
class A < TTYString::CSICode # leftovers:allow
|
8
|
+
def action(rows = 1)
|
9
|
+
cursor.up(rows)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class B < TTYString::CSICode # leftovers:allow
|
14
|
+
def action(rows = 1)
|
15
|
+
cursor.down(rows)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class C < TTYString::CSICode # leftovers:allow
|
20
|
+
def action(cols = 1)
|
21
|
+
cursor.right(cols)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class D < TTYString::CSICode # leftovers:allow
|
26
|
+
def action(cols = 1)
|
27
|
+
cursor.left(cols)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class E < TTYString::CSICode # leftovers:allow
|
32
|
+
def action(rows = 1)
|
33
|
+
cursor.down(rows)
|
34
|
+
cursor.col = 0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class F < TTYString::CSICode # leftovers:allow
|
39
|
+
def action(rows = 1)
|
40
|
+
cursor.up(rows)
|
41
|
+
cursor.col = 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class G < TTYString::CSICode # leftovers:allow
|
46
|
+
def action(col = 1)
|
47
|
+
# cursor is zero indexed, arg is 1 indexed
|
48
|
+
cursor.col = col.to_i - 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class H < TTYString::CSICode # leftovers:allow
|
53
|
+
def action(row = 1, col = 1)
|
54
|
+
# cursor is zero indexed, arg is 1 indexed
|
55
|
+
cursor.row = row.to_i - 1
|
56
|
+
cursor.col = col.to_i - 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class LowH < TTYString::CSICode # leftovers:allow
|
61
|
+
char(/\?2004h/)
|
62
|
+
end
|
63
|
+
|
64
|
+
class LowF < TTYString::CSICode::H # leftovers:allow
|
65
|
+
char 'f'
|
66
|
+
end
|
67
|
+
|
68
|
+
class J < TTYString::CSICode # leftovers:allow
|
69
|
+
default_arg 0
|
70
|
+
|
71
|
+
def self.arg_re
|
72
|
+
/[0-3]?/
|
73
|
+
end
|
74
|
+
|
75
|
+
def action(mode = 0)
|
76
|
+
# :nocov: else won't ever be called. don't worry about it
|
77
|
+
case mode
|
78
|
+
# :nocov:
|
79
|
+
when 0 then screen.clear_forward
|
80
|
+
when 1 then screen.clear_backward
|
81
|
+
when 2, 3 then screen.clear
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class K < TTYString::CSICode # leftovers:allow
|
87
|
+
default_arg 0
|
88
|
+
|
89
|
+
def self.arg_re
|
90
|
+
/[0-2]?/
|
91
|
+
end
|
92
|
+
|
93
|
+
def action(mode = 0)
|
94
|
+
# :nocov: else won't ever be called. don't worry about it
|
95
|
+
case mode
|
96
|
+
# :nocov:
|
97
|
+
when 0 then screen.clear_line_forward
|
98
|
+
when 1 then screen.clear_line_backward
|
99
|
+
when 2 then screen.clear_line
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class LowL < TTYString::CSICode # leftovers:allow
|
105
|
+
char(/\?2004l/)
|
106
|
+
end
|
107
|
+
|
108
|
+
class LowM < TTYString::CSICode # leftovers:allow
|
109
|
+
char 'm'
|
110
|
+
|
111
|
+
def self.arg_re
|
112
|
+
# 0-255
|
113
|
+
/(?:\d|\d\d|1\d\d|2[0-4]\d|25[0-5])?/
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.render(renderer)
|
117
|
+
super if renderer.clear_style
|
118
|
+
end
|
119
|
+
|
120
|
+
def action(*args); end
|
121
|
+
end
|
122
|
+
|
123
|
+
class S < TTYString::CSICode # leftovers:allow
|
124
|
+
def action(rows = 1)
|
125
|
+
rows.times { screen.scroll_up }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class T < TTYString::CSICode # leftovers:allow
|
130
|
+
def action(rows = 1)
|
131
|
+
rows.times { screen.scroll_down }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Tilde < TTYString::CSICode # leftovers:allow
|
136
|
+
char(/(?:200|201)~/)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/tty_string/cursor.rb
CHANGED
@@ -11,46 +11,33 @@ class TTYString
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def row=(value)
|
14
|
-
@row = value
|
14
|
+
@row = value
|
15
15
|
@row = 0 if @row.negative?
|
16
16
|
end
|
17
17
|
|
18
18
|
def col=(value)
|
19
|
-
@col = value
|
19
|
+
@col = value
|
20
20
|
@col = 0 if @col.negative?
|
21
21
|
end
|
22
22
|
|
23
23
|
def left(count = 1)
|
24
|
-
count = count.to_i
|
25
|
-
raise ArgumentError if count.negative?
|
26
|
-
|
27
24
|
self.col -= count
|
28
25
|
end
|
29
26
|
|
30
27
|
def up(count = 1)
|
31
|
-
count = count.to_i
|
32
|
-
raise ArgumentError if count.negative?
|
33
|
-
|
34
28
|
self.row -= count
|
35
29
|
end
|
36
30
|
|
37
31
|
def down(count = 1)
|
38
|
-
count = count.to_i
|
39
|
-
raise ArgumentError unless count >= 0
|
40
|
-
|
41
32
|
self.row += count
|
42
33
|
end
|
43
34
|
|
44
35
|
def right(count = 1)
|
45
|
-
count = count.to_i
|
46
|
-
raise ArgumentError unless count >= 0
|
47
|
-
|
48
36
|
self.col += count
|
49
37
|
end
|
50
38
|
|
51
39
|
def to_ary
|
52
40
|
[row, col]
|
53
41
|
end
|
54
|
-
alias to_a to_ary
|
55
42
|
end
|
56
43
|
end
|
data/lib/tty_string/parser.rb
CHANGED
@@ -1,82 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'strscan'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'code_definitions'
|
5
|
+
require_relative 'csi_code_definitions'
|
6
|
+
|
7
|
+
require_relative 'screen'
|
5
8
|
|
6
9
|
class TTYString
|
7
10
|
# Reads the text string a
|
8
11
|
class Parser < StringScanner
|
9
|
-
|
12
|
+
attr_accessor :clear_style
|
13
|
+
attr_reader :screen
|
14
|
+
|
15
|
+
def render
|
10
16
|
reset
|
11
|
-
@
|
12
|
-
@renderer = Renderer.new
|
17
|
+
@screen = Screen.new
|
13
18
|
read until eos?
|
14
|
-
|
19
|
+
screen.to_s
|
15
20
|
end
|
16
21
|
|
17
|
-
|
22
|
+
def cursor
|
23
|
+
screen.cursor
|
24
|
+
end
|
18
25
|
|
19
|
-
|
20
|
-
attr_reader :clear_style
|
26
|
+
private
|
21
27
|
|
22
28
|
def write(string)
|
23
|
-
|
29
|
+
screen.write(string)
|
24
30
|
end
|
25
31
|
|
26
32
|
def read
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def text
|
31
|
-
write(matched) if scan(text_regexp)
|
32
|
-
end
|
33
|
-
|
34
|
-
def slash_n
|
35
|
-
renderer.slash_n if skip(/\n/)
|
36
|
-
end
|
37
|
-
|
38
|
-
def slash_r
|
39
|
-
renderer.slash_r if skip(/\r/)
|
40
|
-
end
|
41
|
-
|
42
|
-
def slash_t
|
43
|
-
renderer.slash_t if skip(/\t/)
|
44
|
-
end
|
45
|
-
|
46
|
-
def slash_b
|
47
|
-
# can't use `scan(/\b/)` because it matches everything
|
48
|
-
return unless peek(1) == "\b"
|
49
|
-
|
50
|
-
self.pos += 1
|
51
|
-
renderer.slash_b
|
52
|
-
end
|
53
|
-
|
54
|
-
def slash_e
|
55
|
-
return unless scan(csi_regexp)
|
56
|
-
|
57
|
-
args = matched.slice(2..-2).split(';')
|
58
|
-
command = matched.slice(-1)
|
59
|
-
render_csi(:"csi_#{command}", *args)
|
60
|
-
end
|
61
|
-
|
62
|
-
def csi_regexp
|
63
|
-
@csi_regexp ||= Regexp.new("\e#{csi_pattern}")
|
64
|
-
end
|
65
|
-
|
66
|
-
def csi_pattern
|
67
|
-
"\\[(\\d+;?|;)*[ABCDEFGHJKf#{'m' if clear_style}]"
|
68
|
-
end
|
69
|
-
|
70
|
-
def text_regexp
|
71
|
-
@text_regexp ||= Regexp.new("[^\b\t\r\n\e]|\e(?!#{csi_pattern})")
|
33
|
+
TTYString::Code.descendants.any? { |c| c.render(self) } || default
|
72
34
|
end
|
73
35
|
|
74
|
-
def
|
75
|
-
|
76
|
-
params = method.parameters
|
77
|
-
args = args.take(params.length) unless params.assoc(:rest)
|
78
|
-
args = [] if args.all?(&:empty?)
|
79
|
-
method.call(*args)
|
36
|
+
def default
|
37
|
+
write(getch)
|
80
38
|
end
|
81
39
|
end
|
82
40
|
end
|
data/lib/tty_string/screen.rb
CHANGED
data/lib/tty_string/version.rb
CHANGED
data/tty_string.gemspec
CHANGED
@@ -11,21 +11,35 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.email = ['robot@dana.sh']
|
12
12
|
|
13
13
|
spec.summary = 'Render a string using ANSI TTY codes'
|
14
|
-
spec.homepage =
|
14
|
+
spec.homepage = 'https://github.com/robotdana/tty_string'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
17
|
-
spec.
|
18
|
-
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
20
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
19
21
|
end
|
20
|
-
|
21
|
-
spec.
|
22
|
+
|
23
|
+
spec.files = Dir.glob('{lib}/**/*') + %w{
|
24
|
+
CHANGELOG.md
|
25
|
+
Gemfile
|
26
|
+
LICENSE.txt
|
27
|
+
README.md
|
28
|
+
tty_string.gemspec
|
29
|
+
}
|
30
|
+
spec.required_ruby_version = '>= 2.4'
|
22
31
|
spec.require_paths = ['lib']
|
23
32
|
|
24
|
-
spec.add_development_dependency 'bundler', '~>
|
25
|
-
spec.add_development_dependency '
|
26
|
-
spec.add_development_dependency '
|
33
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
34
|
+
spec.add_development_dependency 'fast_ignore', '>= 0.15.1'
|
35
|
+
spec.add_development_dependency 'leftovers', '>= 0.2.0'
|
36
|
+
spec.add_development_dependency 'pry', '~> 0.12'
|
37
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
27
38
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
29
|
-
spec.add_development_dependency 'rubocop-performance', '~> 1.
|
30
|
-
spec.add_development_dependency 'rubocop-rspec', '~> 1.
|
39
|
+
spec.add_development_dependency 'rubocop', '~> 0.93.1'
|
40
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.8.1'
|
41
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.44.1'
|
42
|
+
spec.add_development_dependency 'simplecov', '~> 0.18.5'
|
43
|
+
spec.add_development_dependency 'simplecov-console'
|
44
|
+
spec.add_development_dependency 'spellr', '>= 0.8.1'
|
31
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tty_string
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dana Sherson
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,42 +16,70 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fast_ignore
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.15.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.15.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: leftovers
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.2.0
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: pry
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
59
|
- - "~>"
|
32
60
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.12
|
61
|
+
version: '0.12'
|
34
62
|
type: :development
|
35
63
|
prerelease: false
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
37
65
|
requirements:
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.12
|
68
|
+
version: '0.12'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rake
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
|
-
- - "
|
73
|
+
- - ">="
|
46
74
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
75
|
+
version: 12.3.3
|
48
76
|
type: :development
|
49
77
|
prerelease: false
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
|
-
- - "
|
80
|
+
- - ">="
|
53
81
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
82
|
+
version: 12.3.3
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: rspec
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,42 +100,84 @@ dependencies:
|
|
72
100
|
requirements:
|
73
101
|
- - "~>"
|
74
102
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
103
|
+
version: 0.93.1
|
76
104
|
type: :development
|
77
105
|
prerelease: false
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
79
107
|
requirements:
|
80
108
|
- - "~>"
|
81
109
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
110
|
+
version: 0.93.1
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: rubocop-performance
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
86
114
|
requirements:
|
87
115
|
- - "~>"
|
88
116
|
- !ruby/object:Gem::Version
|
89
|
-
version: 1.
|
117
|
+
version: 1.8.1
|
90
118
|
type: :development
|
91
119
|
prerelease: false
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
93
121
|
requirements:
|
94
122
|
- - "~>"
|
95
123
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.
|
124
|
+
version: 1.8.1
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: rubocop-rspec
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
100
128
|
requirements:
|
101
129
|
- - "~>"
|
102
130
|
- !ruby/object:Gem::Version
|
103
|
-
version: 1.
|
131
|
+
version: 1.44.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.44.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.18.5
|
104
146
|
type: :development
|
105
147
|
prerelease: false
|
106
148
|
version_requirements: !ruby/object:Gem::Requirement
|
107
149
|
requirements:
|
108
150
|
- - "~>"
|
109
151
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
152
|
+
version: 0.18.5
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simplecov-console
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: spellr
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.8.1
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.8.1
|
111
181
|
description:
|
112
182
|
email:
|
113
183
|
- robot@dana.sh
|
@@ -115,29 +185,27 @@ executables: []
|
|
115
185
|
extensions: []
|
116
186
|
extra_rdoc_files: []
|
117
187
|
files:
|
118
|
-
-
|
119
|
-
- ".gitmodules"
|
120
|
-
- ".rspec"
|
121
|
-
- ".rubocop.yml"
|
122
|
-
- ".travis.yml"
|
188
|
+
- CHANGELOG.md
|
123
189
|
- Gemfile
|
124
|
-
- Gemfile.lock
|
125
190
|
- LICENSE.txt
|
126
191
|
- README.md
|
127
|
-
- Rakefile
|
128
|
-
- bin/console
|
129
|
-
- bin/setup
|
130
192
|
- lib/tty_string.rb
|
193
|
+
- lib/tty_string/code.rb
|
194
|
+
- lib/tty_string/code_definitions.rb
|
195
|
+
- lib/tty_string/csi_code.rb
|
196
|
+
- lib/tty_string/csi_code_definitions.rb
|
131
197
|
- lib/tty_string/cursor.rb
|
132
198
|
- lib/tty_string/parser.rb
|
133
|
-
- lib/tty_string/renderer.rb
|
134
199
|
- lib/tty_string/screen.rb
|
135
200
|
- lib/tty_string/version.rb
|
136
201
|
- tty_string.gemspec
|
137
202
|
homepage: https://github.com/robotdana/tty_string
|
138
203
|
licenses:
|
139
204
|
- MIT
|
140
|
-
metadata:
|
205
|
+
metadata:
|
206
|
+
homepage_uri: https://github.com/robotdana/tty_string
|
207
|
+
source_code_uri: https://github.com/robotdana/tty_string
|
208
|
+
changelog_uri: https://github.com/robotdana/tty_string/blob/main/CHANGELOG.md
|
141
209
|
post_install_message:
|
142
210
|
rdoc_options: []
|
143
211
|
require_paths:
|
@@ -146,15 +214,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
214
|
requirements:
|
147
215
|
- - ">="
|
148
216
|
- !ruby/object:Gem::Version
|
149
|
-
version: '
|
217
|
+
version: '2.4'
|
150
218
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
219
|
requirements:
|
152
220
|
- - ">="
|
153
221
|
- !ruby/object:Gem::Version
|
154
222
|
version: '0'
|
155
223
|
requirements: []
|
156
|
-
|
157
|
-
rubygems_version: 2.5.2.1
|
224
|
+
rubygems_version: 3.0.3
|
158
225
|
signing_key:
|
159
226
|
specification_version: 4
|
160
227
|
summary: Render a string using ANSI TTY codes
|
data/.gitignore
DELETED
data/.gitmodules
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rubocop_yml/rubocop.yml
|
data/.travis.yml
DELETED
data/Gemfile.lock
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
tty_string (0.1.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
ast (2.4.0)
|
10
|
-
coderay (1.1.2)
|
11
|
-
diff-lcs (1.3)
|
12
|
-
jaro_winkler (1.5.3)
|
13
|
-
method_source (0.9.2)
|
14
|
-
parallel (1.17.0)
|
15
|
-
parser (2.6.4.1)
|
16
|
-
ast (~> 2.4.0)
|
17
|
-
pry (0.12.2)
|
18
|
-
coderay (~> 1.1.0)
|
19
|
-
method_source (~> 0.9.0)
|
20
|
-
rainbow (3.0.0)
|
21
|
-
rake (10.5.0)
|
22
|
-
rspec (3.8.0)
|
23
|
-
rspec-core (~> 3.8.0)
|
24
|
-
rspec-expectations (~> 3.8.0)
|
25
|
-
rspec-mocks (~> 3.8.0)
|
26
|
-
rspec-core (3.8.2)
|
27
|
-
rspec-support (~> 3.8.0)
|
28
|
-
rspec-expectations (3.8.4)
|
29
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
-
rspec-support (~> 3.8.0)
|
31
|
-
rspec-mocks (3.8.1)
|
32
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
-
rspec-support (~> 3.8.0)
|
34
|
-
rspec-support (3.8.2)
|
35
|
-
rubocop (0.74.0)
|
36
|
-
jaro_winkler (~> 1.5.1)
|
37
|
-
parallel (~> 1.10)
|
38
|
-
parser (>= 2.6)
|
39
|
-
rainbow (>= 2.2.2, < 4.0)
|
40
|
-
ruby-progressbar (~> 1.7)
|
41
|
-
unicode-display_width (>= 1.4.0, < 1.7)
|
42
|
-
rubocop-performance (1.4.1)
|
43
|
-
rubocop (>= 0.71.0)
|
44
|
-
rubocop-rspec (1.35.0)
|
45
|
-
rubocop (>= 0.60.0)
|
46
|
-
ruby-progressbar (1.10.1)
|
47
|
-
unicode-display_width (1.6.0)
|
48
|
-
|
49
|
-
PLATFORMS
|
50
|
-
ruby
|
51
|
-
|
52
|
-
DEPENDENCIES
|
53
|
-
bundler (~> 1.16)
|
54
|
-
pry (~> 0.12.2)
|
55
|
-
rake (~> 10.0)
|
56
|
-
rspec (~> 3.0)
|
57
|
-
rubocop (~> 0.74.0)
|
58
|
-
rubocop-performance (~> 1.4.1)
|
59
|
-
rubocop-rspec (~> 1.35.0)
|
60
|
-
tty_string!
|
61
|
-
|
62
|
-
BUNDLED WITH
|
63
|
-
1.16.0
|
data/Rakefile
DELETED
data/bin/console
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'bundler/setup'
|
5
|
-
require 'tty_string'
|
6
|
-
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
9
|
-
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
-
# require "pry"
|
12
|
-
# Pry.start
|
13
|
-
|
14
|
-
require 'irb'
|
15
|
-
IRB.start(__FILE__)
|
data/bin/setup
DELETED
data/lib/tty_string/renderer.rb
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'screen'
|
4
|
-
class TTYString
|
5
|
-
# turns the text string into screen instructions
|
6
|
-
class Renderer
|
7
|
-
attr_reader :screen
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@screen = Screen.new
|
11
|
-
end
|
12
|
-
|
13
|
-
def to_s
|
14
|
-
screen.to_s
|
15
|
-
end
|
16
|
-
|
17
|
-
def write(string)
|
18
|
-
screen.write(string)
|
19
|
-
end
|
20
|
-
|
21
|
-
# rubocop:disable Naming/MethodName
|
22
|
-
def csi_A(rows = 1)
|
23
|
-
cursor.up(rows)
|
24
|
-
end
|
25
|
-
|
26
|
-
def csi_B(rows = 1)
|
27
|
-
cursor.down(rows)
|
28
|
-
end
|
29
|
-
|
30
|
-
def csi_C(cols = 1)
|
31
|
-
cursor.right(cols)
|
32
|
-
end
|
33
|
-
|
34
|
-
def csi_D(cols = 1)
|
35
|
-
cursor.left(cols)
|
36
|
-
end
|
37
|
-
|
38
|
-
def csi_E(rows = 1)
|
39
|
-
cursor.down(rows)
|
40
|
-
cursor.col = 0
|
41
|
-
end
|
42
|
-
|
43
|
-
def csi_F(rows = 1)
|
44
|
-
cursor.up(rows)
|
45
|
-
cursor.col = 0
|
46
|
-
end
|
47
|
-
|
48
|
-
def csi_G(col = 1)
|
49
|
-
cursor.col = col.to_i - 1 # cursor is zero indexed, arg is 1 indexed
|
50
|
-
end
|
51
|
-
|
52
|
-
def csi_H(row = 1, col = 1)
|
53
|
-
# cursor is zero indexed, arg is 1 indexed
|
54
|
-
cursor.row = row.to_i - 1
|
55
|
-
cursor.col = col.to_i - 1
|
56
|
-
end
|
57
|
-
alias csi_f csi_H
|
58
|
-
|
59
|
-
def csi_J(mode = 0)
|
60
|
-
case mode.to_i
|
61
|
-
when 0 then screen.clear_forward
|
62
|
-
when 1 then screen.clear_backward
|
63
|
-
when 2, 3 then screen.clear
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def csi_K(mode = 0)
|
68
|
-
case mode.to_i
|
69
|
-
when 0 then screen.clear_line_forward
|
70
|
-
when 1 then screen.clear_line_backward
|
71
|
-
when 2 then screen.clear_line
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def csi_m(*args)
|
76
|
-
end
|
77
|
-
# rubocop:enable Naming/MethodName
|
78
|
-
|
79
|
-
def slash_b
|
80
|
-
cursor.left
|
81
|
-
screen.clear_at_cursor
|
82
|
-
end
|
83
|
-
|
84
|
-
def slash_n
|
85
|
-
cursor.down
|
86
|
-
cursor.col = 0
|
87
|
-
screen.write('')
|
88
|
-
end
|
89
|
-
|
90
|
-
def slash_r
|
91
|
-
cursor.col = 0
|
92
|
-
end
|
93
|
-
|
94
|
-
def slash_t
|
95
|
-
cursor.right(8 - (cursor.col % 8))
|
96
|
-
end
|
97
|
-
|
98
|
-
private
|
99
|
-
|
100
|
-
def cursor
|
101
|
-
screen.cursor
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|