terminal_rb 0.17.2 → 0.19.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/README.md +3 -3
- data/bin/bbcode +1 -1
- data/lib/terminal/input/ansi.rb +47 -0
- data/lib/terminal/input/dumb.rb +42 -0
- data/lib/terminal/input.rb +88 -179
- data/lib/terminal/output/ansi.rb +114 -0
- data/lib/terminal/output/dumb.rb +76 -0
- data/lib/terminal/output.rb +238 -0
- data/lib/terminal/rspec/helper.rb +7 -3
- data/lib/terminal/text/char_width.rb +166 -31
- data/lib/terminal/text.rb +90 -66
- data/lib/terminal/version.rb +1 -1
- data/lib/terminal.rb +6 -288
- data/terminal_rb.gemspec +3 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76294a5517d1c24b4c85f855cf802ce01dd1fa574dfac92753edd78b7a8fd465
|
|
4
|
+
data.tar.gz: 3e95e7b5ae8f40a9e284cdd3abddbd5a77a70e0a4df40e0deed2702acaf2ed07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99e9e190caea636278031df7116ce0fdaf15fa6fdf2c6c1912906862341eae25e3b6aacc77296aca15b833e4548cbad36edf7f41160fc313059c6ecbe472fb2a
|
|
7
|
+
data.tar.gz: 0b5cbc5ef6ed87179d6b74663607bec29c07ad53ca4fc7c1593577214e31d5b08281fa68a119be361eb4ce720420767d09690b5679ee2f6e43ef3061c40b7ec7
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Terminal.rb supports you with input and output on your terminal. Simple [BBCode]
|
|
|
4
4
|
|
|
5
5
|
- Gem: [rubygems.org](https://rubygems.org/gems/terminal_rb)
|
|
6
6
|
- Source: [codeberg.org](https://codeberg.org/mblumtritt/Terminal.rb)
|
|
7
|
-
- Help: [rubydoc.info](https://rubydoc.info/gems/terminal_rb/Terminal)
|
|
7
|
+
- Help: [rubydoc.info](https://rubydoc.info/gems/terminal_rb/0.19.0/Terminal)
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
@@ -60,7 +60,7 @@ TEXT
|
|
|
60
60
|
# => "attribute syntax."]
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Have a look at the [examples](
|
|
63
|
+
Have a look at the [examples](https://codeberg.org/mblumtritt/Terminal.rb/src/branch/main/examples) directory to learn from code.
|
|
64
64
|
|
|
65
65
|
## Installation
|
|
66
66
|
|
|
@@ -70,7 +70,7 @@ You can install the gem in your system with
|
|
|
70
70
|
gem install terminal_rb
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
or you can use [Bundler](
|
|
73
|
+
or you can use [Bundler](https://bundler.io) to add Terminal.rb to your own project:
|
|
74
74
|
|
|
75
75
|
```shell
|
|
76
76
|
bundle add terminal_rb
|
data/bin/bbcode
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terminal
|
|
4
|
+
module AnsiInput
|
|
5
|
+
attr_reader :input_mode
|
|
6
|
+
|
|
7
|
+
def on_key_event(mouse: false, mouse_move: false, focus: false)
|
|
8
|
+
raise('already reading key events') if (@in_recursion += 1) != 1
|
|
9
|
+
return unless block_given?
|
|
10
|
+
opts = __in_option(mouse, mouse_move, focus)
|
|
11
|
+
opts &&= @in.syswrite("#{opts}h") ? "#{opts}l" : nil
|
|
12
|
+
while (raw = @in.getch)
|
|
13
|
+
(yield(KeyEvent[raw]) ? next : break) if raw != "\e"
|
|
14
|
+
lesci = 0
|
|
15
|
+
while String === (nc = @in.read_nonblock(1, exception: false))
|
|
16
|
+
lesci = raw.size if nc == "\e"
|
|
17
|
+
raw << nc
|
|
18
|
+
end
|
|
19
|
+
(yield(KeyEvent[raw]) ? next : break) if lesci < 2
|
|
20
|
+
break unless raw[1..].split("\e").all? { yield(KeyEvent["\e#{_1}"]) }
|
|
21
|
+
end
|
|
22
|
+
rescue IOError, SystemCallError
|
|
23
|
+
__input_error
|
|
24
|
+
false
|
|
25
|
+
ensure
|
|
26
|
+
@in_recursion -= 1
|
|
27
|
+
@in&.syswrite(opts) if opts
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def __in_option(mouse, mouse_move, focus)
|
|
33
|
+
opts = +(mouse ? '1000' : '')
|
|
34
|
+
# highlight: '1001'
|
|
35
|
+
# drag: '1002'
|
|
36
|
+
opts << ';1003' if mouse_move
|
|
37
|
+
opts << ';1004' if focus
|
|
38
|
+
# ext: '1005'
|
|
39
|
+
# sgr: '1006'
|
|
40
|
+
# urxvt: '1015'
|
|
41
|
+
# pixel: '1016'
|
|
42
|
+
"\e[?#{opts};1006" unless opts.empty?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private_constant :AnsiInput
|
|
47
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terminal
|
|
4
|
+
module DumpInput
|
|
5
|
+
def input_mode = :dumb
|
|
6
|
+
|
|
7
|
+
def on_key_event(*_)
|
|
8
|
+
raise('already reading key events') if (@in_recursion += 1) != 1
|
|
9
|
+
return unless block_given?
|
|
10
|
+
while (raw = __getc)
|
|
11
|
+
opts = dumb_keys[raw.ord] if raw.size == 1
|
|
12
|
+
return unless yield(KeyEvent.new(raw, *opts))
|
|
13
|
+
end
|
|
14
|
+
rescue IOError, SystemCallError
|
|
15
|
+
__input_error
|
|
16
|
+
false
|
|
17
|
+
ensure
|
|
18
|
+
@in_recursion -= 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def dumb_keys
|
|
24
|
+
@dumb_keys ||= {
|
|
25
|
+
0x05 => ['c', 4],
|
|
26
|
+
0x08 => :Back,
|
|
27
|
+
0x09 => :Tab,
|
|
28
|
+
0x0a => :Enter,
|
|
29
|
+
0x0d => :Return,
|
|
30
|
+
0x1b => :Esc
|
|
31
|
+
}.compare_by_identity.freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def __getc
|
|
35
|
+
STDIN.getc
|
|
36
|
+
rescue Interrupt
|
|
37
|
+
"\x05"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private_constant :DumpInput
|
|
42
|
+
end
|
data/lib/terminal/input.rb
CHANGED
|
@@ -3,208 +3,117 @@
|
|
|
3
3
|
require 'io/console'
|
|
4
4
|
|
|
5
5
|
module Terminal
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
# @!group Attributes
|
|
7
|
+
|
|
8
|
+
# @attribute [r] self.input_mode
|
|
9
|
+
#
|
|
10
|
+
# Supported input mode.
|
|
11
|
+
#
|
|
12
|
+
# @return [:csi_u]
|
|
13
|
+
# when [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
|
|
14
|
+
# supported
|
|
15
|
+
# @return [:legacy]
|
|
16
|
+
# for standard terminal
|
|
17
|
+
# @return [:dumb]
|
|
18
|
+
# for non-interactive input (pipes etc.)
|
|
19
|
+
# @return [:error]
|
|
20
|
+
# when input device is not avail (closed)
|
|
21
|
+
|
|
22
|
+
# @!endgroup
|
|
23
|
+
|
|
24
|
+
# @!group Input methods
|
|
25
|
+
|
|
26
|
+
# @!method self.read_key_event
|
|
27
|
+
# Read next {KeyEvent} from standard input.
|
|
28
|
+
#
|
|
29
|
+
# @return [KeyEvent] next event
|
|
30
|
+
# @return [nil] in error case
|
|
31
|
+
|
|
32
|
+
# @!method self.on_key_event(mouse: false, mouse_move: false, focus: false, &block)
|
|
33
|
+
# Event loop for key and mouse events.
|
|
34
|
+
#
|
|
35
|
+
# @param mouse [true, false]
|
|
36
|
+
# whether mouse buttons should be reported
|
|
37
|
+
# @param mouse_move [true, false]
|
|
38
|
+
# whether mouse movement should be reported
|
|
39
|
+
# @param focus [true, false]
|
|
40
|
+
# whether focus/unfocus of terminal window should be reported
|
|
41
|
+
# @yieldparam event [KeyEvent]
|
|
42
|
+
# next event
|
|
43
|
+
# @yieldreturn [true, false]
|
|
44
|
+
# whether the loop should be continued
|
|
45
|
+
# @return [true]
|
|
46
|
+
# when the loop was started
|
|
47
|
+
# @return [false]
|
|
48
|
+
# when the current input device is not available
|
|
49
|
+
# @return [nil]
|
|
50
|
+
# when no block was given
|
|
51
|
+
|
|
52
|
+
# @!endgroup
|
|
53
|
+
|
|
54
|
+
module Input
|
|
20
55
|
def input_mode
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# @return [String] key code ("as is") in `:raw` mode
|
|
33
|
-
# @return [String] key name in `:named` mode
|
|
34
|
-
# @return [[String, String]] key code and key name in `:both` mode
|
|
35
|
-
# @return [nil] in error case
|
|
36
|
-
def read_key(mode: :named)
|
|
37
|
-
warn(
|
|
38
|
-
'Terminal.read_key is deprecaded; use Terminal.read_key_event instead.',
|
|
39
|
-
uplevel: 1
|
|
40
|
-
)
|
|
41
|
-
event = read_key_event or return
|
|
42
|
-
return event.raw if mode == :raw
|
|
43
|
-
key, name = event
|
|
44
|
-
mode == :both ? [key, name] : name || key
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Read next {KeyEvent} from standard input.
|
|
48
|
-
#
|
|
49
|
-
# @return [KeyEvent] next event
|
|
50
|
-
# @return [nil] in error case
|
|
51
|
-
def read_key_event
|
|
52
|
-
prevent_input_recursion do
|
|
53
|
-
case input_mode
|
|
54
|
-
when :dumb
|
|
55
|
-
raw = read_dumb or return
|
|
56
|
-
opts = @dumb_keys[raw.ord] if raw.size == 1
|
|
57
|
-
KeyEvent.new(raw, *opts)
|
|
58
|
-
when :csi_u, :legacy
|
|
59
|
-
raw = read_tty or return
|
|
60
|
-
KeyEvent[raw]
|
|
61
|
-
end
|
|
56
|
+
@in_recursion = 0
|
|
57
|
+
case @input_mode = __find_input_mode
|
|
58
|
+
when :legacy, :csi_u
|
|
59
|
+
@in = IO.console
|
|
60
|
+
require_relative 'input/ansi'
|
|
61
|
+
extend AnsiInput
|
|
62
|
+
when :dumb
|
|
63
|
+
require_relative 'input/dumb'
|
|
64
|
+
extend DumpInput
|
|
65
|
+
else
|
|
66
|
+
__input_error
|
|
62
67
|
end
|
|
68
|
+
@input_mode
|
|
63
69
|
end
|
|
64
70
|
|
|
65
|
-
# Event loop for key and mouse events.
|
|
66
|
-
#
|
|
67
|
-
# @param mouse [true, false]
|
|
68
|
-
# whether mouse buttons should be reported
|
|
69
|
-
# @param mouse_move [true, false]
|
|
70
|
-
# whether mouse movement should be reported
|
|
71
|
-
# @param focus [true, false]
|
|
72
|
-
# whether focus/unfocus of terminal window should be reported
|
|
73
|
-
# @yieldparam event [KeyEvent]
|
|
74
|
-
# next event
|
|
75
|
-
# @yieldreturn [true, false]
|
|
76
|
-
# whether the loop should be continued
|
|
77
|
-
# @return [true]
|
|
78
|
-
# when the loop was started
|
|
79
|
-
# @return [false]
|
|
80
|
-
# when the current input device is not available
|
|
81
|
-
# @return [nil]
|
|
82
|
-
# when no block was given
|
|
83
71
|
def on_key_event(mouse: false, mouse_move: false, focus: false, &block)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
case input_mode
|
|
87
|
-
when :dumb
|
|
88
|
-
on_bumb_key_event(&block)
|
|
89
|
-
true
|
|
90
|
-
when :csi_u, :legacy
|
|
91
|
-
on_tty_key_event(mouse_option(mouse, mouse_move, focus), &block)
|
|
92
|
-
true
|
|
93
|
-
else
|
|
94
|
-
false
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
def prevent_input_recursion
|
|
102
|
-
if (@key_event += 1) != 1
|
|
103
|
-
raise(RuntimeError, 'already reading key events', caller(2))
|
|
104
|
-
end
|
|
105
|
-
begin
|
|
106
|
-
yield
|
|
107
|
-
ensure
|
|
108
|
-
@key_event -= 1
|
|
109
|
-
end
|
|
72
|
+
input_mode
|
|
73
|
+
on_key_event(mouse: mouse, mouse_move: mouse_move, focus: focus, &block)
|
|
110
74
|
end
|
|
111
75
|
|
|
112
|
-
def
|
|
113
|
-
opts = +(mouse ? '1000' : '')
|
|
114
|
-
# highlight: '1001'
|
|
115
|
-
# drag: '1002'
|
|
116
|
-
opts << ';1003' if mouse_move
|
|
117
|
-
opts << ';1004' if focus
|
|
118
|
-
# ext: '1005'
|
|
119
|
-
# sgr: '1006'
|
|
120
|
-
# urxvt: '1015'
|
|
121
|
-
# pixel: '1016'
|
|
122
|
-
"\e[?#{opts};1006" unless opts.empty?
|
|
123
|
-
end
|
|
76
|
+
def read_key_event = on_key_event { return _1 }
|
|
124
77
|
|
|
125
|
-
|
|
126
|
-
STDIN.noecho do |stdin|
|
|
127
|
-
opts &&= raw_write("#{opts}h") ? "#{opts}l" : nil
|
|
128
|
-
while (raw = stdin.getch)
|
|
129
|
-
(yield(KeyEvent[raw]) ? next : break) if raw != "\e"
|
|
130
|
-
lesci = 0
|
|
131
|
-
while String === (nc = stdin.read_nonblock(1, exception: false))
|
|
132
|
-
lesci = raw.size if nc == "\e"
|
|
133
|
-
raw << nc
|
|
134
|
-
end
|
|
135
|
-
(yield(KeyEvent[raw]) ? next : break) if lesci < 2
|
|
136
|
-
break unless raw[1..].split("\e").all? { yield(KeyEvent["\e#{_1}"]) }
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
rescue Interrupt
|
|
140
|
-
# nop
|
|
141
|
-
rescue IOError, SystemCallError
|
|
142
|
-
@input_mode = :error
|
|
143
|
-
ensure
|
|
144
|
-
raw_write(opts) if opts
|
|
145
|
-
end
|
|
78
|
+
private
|
|
146
79
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
80
|
+
def __input_error
|
|
81
|
+
class << self
|
|
82
|
+
alias input_mode input_mode
|
|
83
|
+
def input_mode = :error
|
|
84
|
+
alias read_key_event read_key_event
|
|
85
|
+
def read_key_event = false
|
|
86
|
+
alias on_key_event on_key_event
|
|
87
|
+
def on_key_event(*_) = false
|
|
151
88
|
end
|
|
89
|
+
@in = nil
|
|
152
90
|
end
|
|
153
91
|
|
|
154
|
-
def
|
|
92
|
+
def __find_input_mode
|
|
155
93
|
im = ENV['INPUT_MODE']
|
|
156
94
|
return :legacy if im == 'legacy'
|
|
157
95
|
return :dumb if im == 'dumb' || !STDIN.tty?
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
96
|
+
con = IO.console
|
|
97
|
+
return :legacy if con.syswrite("\e[>1u\e[?u\e[c") != 12
|
|
98
|
+
inp = +''
|
|
99
|
+
inp << con.getch until inp.rindex('c')
|
|
100
|
+
return :legacy unless inp.include?("\e[?1u")
|
|
101
|
+
at_exit do
|
|
102
|
+
IO.console.syswrite("\e[<u")
|
|
103
|
+
rescue StandardError
|
|
104
|
+
# nop
|
|
165
105
|
end
|
|
106
|
+
:csi_u
|
|
166
107
|
rescue Interrupt
|
|
167
108
|
:legacy
|
|
168
109
|
rescue IOError, SystemCallError
|
|
110
|
+
__input_error
|
|
169
111
|
:error
|
|
170
112
|
end
|
|
171
|
-
|
|
172
|
-
def read_dumb
|
|
173
|
-
STDIN.getc
|
|
174
|
-
rescue Interrupt
|
|
175
|
-
"\x05"
|
|
176
|
-
rescue IOError, SystemCallError
|
|
177
|
-
@input_mode = :error
|
|
178
|
-
nil
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def read_tty
|
|
182
|
-
STDIN.noecho do |stdin|
|
|
183
|
-
if (key = stdin.getch) == "\e"
|
|
184
|
-
while (nc = stdin.read_nonblock(1, exception: false))
|
|
185
|
-
String === nc ? key << nc : break
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
key
|
|
189
|
-
end
|
|
190
|
-
rescue Interrupt
|
|
191
|
-
nil
|
|
192
|
-
rescue IOError, SystemCallError
|
|
193
|
-
@input_mode = :error
|
|
194
|
-
nil
|
|
195
|
-
end
|
|
196
113
|
end
|
|
197
114
|
|
|
198
|
-
|
|
199
|
-
0x05 => ['c', 4],
|
|
200
|
-
0x08 => :Back,
|
|
201
|
-
0x09 => :Tab,
|
|
202
|
-
0x0a => :Enter,
|
|
203
|
-
0x0d => :Return,
|
|
204
|
-
0x1b => :Esc
|
|
205
|
-
}.compare_by_identity.freeze
|
|
206
|
-
|
|
207
|
-
@key_event = 0
|
|
115
|
+
extend Input
|
|
208
116
|
|
|
117
|
+
private_constant :Input
|
|
209
118
|
autoload :KeyEvent, "#{__dir__}/input/key_event.rb"
|
|
210
119
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terminal
|
|
4
|
+
module AnsiOutput
|
|
5
|
+
def output_mode = :ansi
|
|
6
|
+
def ansi? = true
|
|
7
|
+
def tui? = %i[csi_u legacy].include?(input_mode)
|
|
8
|
+
def colors = (@colors ||= Detect.colors)
|
|
9
|
+
|
|
10
|
+
def columns=(value)
|
|
11
|
+
self.size = [rows, value]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pos
|
|
15
|
+
@con&.cursor
|
|
16
|
+
rescue IOError, SystemCallError
|
|
17
|
+
@con = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pos=(pos)
|
|
21
|
+
@con&.cursor = pos
|
|
22
|
+
rescue IOError, SystemCallError
|
|
23
|
+
@con = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rows=(value)
|
|
27
|
+
self.size = [value, columns]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def size
|
|
31
|
+
@size ||= @con&.winsize || __default_size
|
|
32
|
+
rescue IOError, SystemCallError
|
|
33
|
+
@con = nil
|
|
34
|
+
@size = __default_size
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def size=(size)
|
|
38
|
+
@con&.winsize = size
|
|
39
|
+
rescue IOError, SystemCallError
|
|
40
|
+
@con = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def raw_write(str)
|
|
44
|
+
@out.syswrite(str)
|
|
45
|
+
rescue IOError, SystemCallError
|
|
46
|
+
__output_error(nil)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def <<(object)
|
|
50
|
+
@out.write(Ansi.bbcode(object)) if object != nil
|
|
51
|
+
self
|
|
52
|
+
rescue IOError, SystemCallError
|
|
53
|
+
__output_error(self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def print(*objects, bbcode: true)
|
|
57
|
+
return if objects.empty?
|
|
58
|
+
return @out.print(*objects) unless bbcode
|
|
59
|
+
@out.print(*objects.map! { Ansi.bbcode(_1) })
|
|
60
|
+
rescue IOError, SystemCallError
|
|
61
|
+
__output_error(nil)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def puts(*objects, bbcode: true)
|
|
65
|
+
return @out.puts(objects.empty? ? nil : objects) unless bbcode
|
|
66
|
+
objects.flatten!
|
|
67
|
+
@out.puts(objects.empty? ? nil : objects.map! { Ansi.bbcode(_1) })
|
|
68
|
+
rescue IOError, SystemCallError
|
|
69
|
+
__output_error(nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def hide_cursor
|
|
73
|
+
raw_write(Ansi::CURSOR_HIDE) if (@cc += 1) == 1
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def show_cursor
|
|
78
|
+
raw_write(Ansi::CURSOR_SHOW) if @cc > 0 && (@cc -= 1).zero?
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def show_alt_screen
|
|
83
|
+
raw_write(Ansi::SCREEN_ALTERNATE) if (@as += 1) == 1
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def hide_alt_screen
|
|
88
|
+
raw_write(Ansi::SCREEN_ALTERNATE_OFF) if @as > 0 && (@as -= 1).zero?
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def __output_error(ret)
|
|
95
|
+
require_relative 'dumb'
|
|
96
|
+
extend DumbOutput
|
|
97
|
+
@size = @cc = @as = nil
|
|
98
|
+
__output_error(ret)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def __init_tty
|
|
102
|
+
require('io/console')
|
|
103
|
+
@con = IO.console
|
|
104
|
+
Signal.trap('WINCH') { @size = nil } if Signal.list.key?('WINCH')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private_class_method def self.extended(mod)
|
|
108
|
+
mod.instance_variable_set(:@cc, 0)
|
|
109
|
+
mod.instance_variable_set(:@as, 0)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private_constant :AnsiOutput
|
|
114
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terminal
|
|
4
|
+
module DumbOutput
|
|
5
|
+
def output_mode = :dumb
|
|
6
|
+
def ansi? = false
|
|
7
|
+
def tui? = false
|
|
8
|
+
def colors = 2
|
|
9
|
+
def pos = nil
|
|
10
|
+
def size = (@size ||= __default_size)
|
|
11
|
+
def hide_cursor = self
|
|
12
|
+
def show_cursor = self
|
|
13
|
+
def show_alt_screen = self
|
|
14
|
+
def hide_alt_screen = self
|
|
15
|
+
|
|
16
|
+
def columns=(_)
|
|
17
|
+
# nop
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pos=(_)
|
|
21
|
+
# nop
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def rows=(_)
|
|
25
|
+
# nop
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def size=(_)
|
|
29
|
+
# nop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def raw_write(_) = nil
|
|
33
|
+
|
|
34
|
+
def <<(object)
|
|
35
|
+
@out.write(Ansi.plain(object)) if object != nil
|
|
36
|
+
self
|
|
37
|
+
rescue IOError, SystemCallError
|
|
38
|
+
__output_error(self)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def print(*objects, bbcode: true)
|
|
42
|
+
return if objects.empty?
|
|
43
|
+
@out.print(*objects.map!(&(bbcode ? @plain : @undecorate)))
|
|
44
|
+
rescue IOError, SystemCallError
|
|
45
|
+
__output_error(nil)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def puts(*objects, bbcode: true)
|
|
49
|
+
objects.flatten!
|
|
50
|
+
return @out.puts if objects.empty?
|
|
51
|
+
@out.puts(*objects.map!(&(bbcode ? @plain : @undecorate)))
|
|
52
|
+
rescue IOError, SystemCallError
|
|
53
|
+
__output_error(nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def __output_error(ret)
|
|
59
|
+
class << self
|
|
60
|
+
def output_mode = :error
|
|
61
|
+
def <<(_) = self
|
|
62
|
+
def print(*_) = nil
|
|
63
|
+
def puts(*_) = nil
|
|
64
|
+
end
|
|
65
|
+
@out = @plain = @undecorate = nil
|
|
66
|
+
ret
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private_class_method def self.extended(mod)
|
|
70
|
+
mod.instance_variable_set(:@plain, ->(s) { Ansi.plain(s) })
|
|
71
|
+
mod.instance_variable_set(:@undecorate, ->(s) { Ansi.undecorate(s) })
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private_constant :DumbOutput
|
|
76
|
+
end
|