terminal_rb 0.19.0 → 1.0.3

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.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
+ # @private
4
5
  module Detect
5
6
  class << self
6
7
  def application
@@ -1,45 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
+ # @private
4
5
  module AnsiInput
5
6
  attr_reader :input_mode
6
7
 
7
8
  def on_key_event(mouse: false, mouse_move: false, focus: false)
8
9
  raise('already reading key events') if (@in_recursion += 1) != 1
9
10
  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
11
+ opts = __input_option(mouse, mouse_move, focus)
12
+ STDIN.noecho do
13
+ opts &&= @in.syswrite("#{opts}h") ? "#{opts}l" : nil
14
+ while (raw = @in.getch)
15
+ next yield(KeyEvent[raw]) if raw != "\e"
16
+ while String === (nc = @in.read_nonblock(1, exception: false))
17
+ raw << nc
18
+ end
19
+ next yield(KeyEvent[raw]) if raw.rindex("\e") < 2
20
+ raw[0] = ''
21
+ raw.split("\e").each { yield(KeyEvent["\e#{it}"]) }
18
22
  end
19
- (yield(KeyEvent[raw]) ? next : break) if lesci < 2
20
- break unless raw[1..].split("\e").all? { yield(KeyEvent["\e#{_1}"]) }
21
23
  end
22
24
  rescue IOError, SystemCallError
23
25
  __input_error
24
26
  false
25
27
  ensure
26
28
  @in_recursion -= 1
27
- @in&.syswrite(opts) if opts
29
+ @in.syswrite(opts) if opts && @in
28
30
  end
29
31
 
30
32
  private
31
33
 
32
- def __in_option(mouse, mouse_move, focus)
33
- opts = +(mouse ? '1000' : '')
34
+ def __input_option(mouse, mouse_move, focus)
35
+ opts = (mouse ? ['1000'] : [])
34
36
  # highlight: '1001'
35
37
  # drag: '1002'
36
- opts << ';1003' if mouse_move
37
- opts << ';1004' if focus
38
+ opts << '1003' if mouse_move
39
+ opts << '1004' if focus
38
40
  # ext: '1005'
39
41
  # sgr: '1006'
40
42
  # urxvt: '1015'
41
43
  # pixel: '1016'
42
- "\e[?#{opts};1006" unless opts.empty?
44
+ "\e[?#{opts.join(';')};1006" unless opts.empty?
43
45
  end
44
46
  end
45
47
 
@@ -1,16 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
+ # @private
4
5
  module DumpInput
5
6
  def input_mode = :dumb
6
7
 
7
8
  def on_key_event(*_)
8
9
  raise('already reading key events') if (@in_recursion += 1) != 1
9
10
  return unless block_given?
10
- while (raw = __getc)
11
+ while (raw = STDIN.getc)
11
12
  opts = dumb_keys[raw.ord] if raw.size == 1
12
- return unless yield(KeyEvent.new(raw, *opts))
13
+ yield(KeyEvent.new(raw, *opts))
13
14
  end
15
+ rescue Interrupt
16
+ yield(KeyEvent.new("\x05", 'c', 4))
14
17
  rescue IOError, SystemCallError
15
18
  __input_error
16
19
  false
@@ -30,12 +33,6 @@ module Terminal
30
33
  0x1b => :Esc
31
34
  }.compare_by_identity.freeze
32
35
  end
33
-
34
- def __getc
35
- STDIN.getc
36
- rescue Interrupt
37
- "\x05"
38
- end
39
36
  end
40
37
 
41
38
  private_constant :DumpInput
@@ -1,68 +1,95 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
+ # Represents a keyboard, mouse, or focus event read from the terminal.
4
5
  #
5
- # Key event reported from {read_key_event} and {on_key_event}.
6
+ # Instances are immutable (frozen) and may be cached for performance.
6
7
  #
8
+ # @see Terminal.on_key_event
9
+ # @see Terminal.read_key_event
10
+ #
11
+ # @example Parse a raw escape sequence
12
+ # event = Terminal::KeyEvent["\r"]
13
+ # event.key # => :Enter
14
+ # event.name # => "Enter"
15
+ # event.simple? # => true
16
+ #
17
+ # @example Inspect modifier keys
18
+ # event = Terminal::KeyEvent["\e[1;5A"]
19
+ # event.key # => :Up
20
+ # event.name # => "Ctrl+Up"
21
+ # event.modifier? # => true
22
+ # event.modifiers # => [:Ctrl]
7
23
  class KeyEvent
8
- # Pressed key without any modifiers.
9
- # This can be a string for simple keys like `"a"` or a Symbol like `:F12`
10
- # (see {.key_names}).
11
- # @return [String, Symbol] key without modifiers
24
+ # The key identifier.
25
+ #
26
+ # @return [Symbol, String, nil] a named key like +:Enter+, +:Tab+,
27
+ # +:MBLeft+ (mouse), or a character string like +"a"+; +nil+ for
28
+ # unknown sequences
12
29
  attr_reader :key
13
30
 
14
- # Modifier key code. This represents the encoded key modifier like `Shift`
15
- # or `Alt`.
31
+ # Bitmask of active modifier keys.
16
32
  #
17
- # @return [Integer] modifier key code
18
- attr_reader :modifier
33
+ # @see #modifiers
34
+ # @see #modifier?
35
+ #
36
+ # @return [Integer] modifier_bits bitmask
37
+ attr_reader :modifier_bits
19
38
 
39
+ # Names of active modifier keys.
40
+ #
41
+ # @see #modifier?
42
+ #
43
+ # @return [Array<Symbol>] modifier names
44
+ attr_reader :modifiers
45
+
46
+ # Whether any modifier keys are active.
47
+ #
48
+ # @see #modifiers
49
+ #
20
50
  # @attribute [r] modifier?
21
- # @return [true, false] whether a key modifier was pressed
22
- def modifier? = @modifier != 0
51
+ # @return [true, false]
52
+ def modifier? = @modifier_bits != 0
23
53
 
24
- # Name of the key event.
25
- # This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
26
- # keys.
54
+ # Human-readable name including modifier and key.
55
+ #
56
+ # @example
57
+ # Terminal::KeyEvent["\e[1;5A"].name # => "Ctrl+Up"
27
58
  #
28
- # @return [String] key name
59
+ # @return [String] e.g. `"Ctrl+Alt+a"` or `"Enter"`
29
60
  attr_reader :name
30
61
 
31
- # Mouse event position.
62
+ # Mouse position when this is a mouse event.
32
63
  #
33
- # @return [[Integer,Integer]] row and column
34
- # @return nil when no coordinates are assigned
64
+ # @return [Array<Integer, Integer>, nil] +[row, column]+ for mouse
65
+ # events, +nil+ for keyboard events
35
66
  attr_reader :position
36
67
 
37
- # ANSI code sequence received from standard input.
38
- # This can be a simple value like `"a"`or more complex input like
39
- # `"\e[24;6~"` (for Shift+Ctrl+F12).
68
+ # The raw input sequence as received from the terminal.
40
69
  #
41
- # @return [String] received ANSI code sequence
70
+ # @return [String]
42
71
  attr_reader :raw
43
72
 
73
+ # Whether the raw input equals the name (no modifier, no special
74
+ # decoding needed).
75
+ #
44
76
  # @attribute [r] simple?
45
- # @return [true, false] whether a simple key was pressed
77
+ # @return [true, false]
46
78
  def simple? = @raw == @name
47
79
 
48
- # All pressed keys.
49
- # This is composed by all {modifier} and the {key}.
50
- #
51
- # @example
52
- # Terminal::KeyEvent["\e\r"].to_a
53
- # => [:Alt, :Enter]
54
- # Terminal::KeyEvent['a'].to_a
55
- # => ['a']
56
- # Terminal::KeyEvent["\e[<12;45;30m"].to_a
57
- # => [:Shift, :Alt, :MBLeftUp]
80
+ # Modifier names followed by the key.
58
81
  #
59
- # @return [Array<Symbol, String>] all pressed keys
82
+ # @return [Array<Symbol, String>]
60
83
  def to_a = @ary.dup
61
84
 
62
85
  # @private
86
+ # Destructuring-friendly array representation.
87
+ #
88
+ # @return [Array<String>] +[raw]+ if {#simple?}
89
+ # @return [Array<String, String>] +[raw, name]+ otherwise
63
90
  def to_ary = simple? ? [@raw] : [@raw, @name]
64
91
 
65
- # @private
92
+ # @return [String] the event {#name}
66
93
  def to_s = @name.dup
67
94
 
68
95
  # @private
@@ -73,48 +100,66 @@ module Terminal
73
100
  @raw.freeze
74
101
  @key.freeze
75
102
  @position.freeze
103
+ @modifiers.freeze
76
104
  @name.freeze
77
105
  super
78
106
  end
79
107
 
80
108
  private
81
109
 
82
- def initialize(raw, key, modifier, position)
110
+ def initialize(raw, key, modifier_bits, position)
83
111
  @raw = raw
84
112
  @key = key
85
- @modifier = modifier
113
+ @modifier_bits = modifier_bits
114
+ @modifiers =
115
+ MODIFIERS.filter_map { |b, n| n if modifier_bits.allbits?(b) }
86
116
  @position = position
87
- @ary = MODIFIERS.filter_map { |b, n| n if (modifier & b) == b }
117
+ @ary = @modifiers.dup
88
118
  @ary << key if key
89
119
  @name = @ary.join('+')
90
120
  end
91
121
 
92
122
  class << self
93
- # @attribute [w] caching
94
- # @return [true, false] whether {KeyEvent}s should be cached
95
- def caching = !!@cache
123
+ # Whether key event caching is enabled.
124
+ # When enabled, parsed events are frozen and reused for identical
125
+ # raw input sequences.
126
+ #
127
+ # @attribute [rw] caching
128
+ # @return [true, false]
129
+ def caching = @cache != nil
96
130
 
97
- # @attribute [w] caching
98
131
  def caching=(value)
99
132
  value ? @cache ||= {} : @cache = nil
100
133
  end
101
134
 
102
- # @attribute [r] key_names
103
- # @return [Array<Symbol>] list of all avilable key names (see {key})
135
+ # All recognized key name symbols.
136
+ #
137
+ # @return [Array<Symbol>] sorted unique key names
104
138
  def key_names
105
139
  (
106
140
  @names ||=
107
141
  (@csiu.values + @csi.values + @ss3.values)
108
142
  .uniq
109
- .keep_if { Symbol === _1 }
143
+ .keep_if { Symbol === it }
110
144
  .sort!
111
145
  ).dup
112
146
  end
113
147
 
114
- # Translate an ANSI input code sequence into a related KeyEvent.
148
+ # Parse a raw terminal input sequence into a KeyEvent.
149
+ #
150
+ # Supports single characters, ESC sequences, CSI-u protocol,
151
+ # legacy function keys, and mouse events (VT200 and SGR format).
115
152
  #
116
- # @param raw [String] keyboard, mouse or focus event sequence
117
- # @return [KeyEvent] related key event
153
+ # @example Parse a single character
154
+ # Terminal::KeyEvent['a'].key # => "a"
155
+ # @example Parse escape sequence
156
+ # Terminal::KeyEvent["\e[A"].key # => :Up
157
+ # @example Parse mouse event
158
+ # Terminal::KeyEvent["\e[<0;10;5M"].key # => :MBLeft
159
+ #
160
+ # @param raw [String] the raw escape sequence or single character
161
+ # @return [KeyEvent] the parsed event (frozen; cached when caching
162
+ # is enabled)
118
163
  def [](raw)
119
164
  cached = @cache[raw] and return cached if @cache
120
165
  return new(raw, *@single_key[raw.ord]) if raw.size == 1
@@ -127,8 +172,8 @@ module Terminal
127
172
  return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
128
173
  when '['
129
174
  return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC [ ?
130
- if raw.size == 6 && raw[2] == 'M' # ESC [ M b c r
131
- return mouse_vt200(raw)
175
+ if raw.size == 6 && raw[2] == 'M'
176
+ return mouse_vt200(raw) # ESC [ M b c r
132
177
  end
133
178
  if raw.size > 4 && raw[2] == '1' && raw[3] == ';'
134
179
  return csi(raw) # ESC [ 1 ; ...
@@ -148,7 +193,7 @@ module Terminal
148
193
  end
149
194
 
150
195
  # @private
151
- def new(raw, key = raw, modifier = 0, position = nil)
196
+ def new(raw, key = raw, modifier_bits = 0, position = nil)
152
197
  (@cache && position.nil? ? @cache[raw] ||= super : super).freeze
153
198
  end
154
199
 
@@ -158,8 +203,8 @@ module Terminal
158
203
 
159
204
  def esc1(raw, char)
160
205
  # ESC ?
161
- key, modifier = @esc1[char.ord]
162
- new(raw, key || char, modifier || 2)
206
+ key, modifier_bits = @esc1[char.ord]
207
+ new(raw, key || char, modifier_bits || 2)
163
208
  end
164
209
 
165
210
  def esc_esc(raw)
@@ -167,18 +212,18 @@ module Terminal
167
212
  # ESC ESC ...
168
213
  return esc1(raw, raw[2]) if raw.size == 3
169
214
  ret = self[raw[1..]]
170
- new(raw, ret.key, ret.modifier | 2)
215
+ new(raw, ret.key, ret.modifier_bits | 2)
171
216
  end
172
217
 
173
218
  def csi(raw)
174
219
  # ESC [ 1 ; [~ABCDEFHPQRS]
175
- # ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
220
+ # ESC [ 1 ; <modifier_bits> [~ABCDEFHPQRS]
176
221
  return unknown(raw) if raw.size < 5
177
- key, modifier = @ss3[raw[-1].ord]
178
- modifier ||= 0
179
- return new(raw, nil, modifier) unless key
180
- modifier |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
181
- new(raw, key, modifier)
222
+ key, modifier_bits = @ss3[raw[-1].ord]
223
+ modifier_bits ||= 0
224
+ return new(raw, nil, modifier_bits) unless key
225
+ modifier_bits |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
226
+ new(raw, key, modifier_bits)
182
227
  end
183
228
 
184
229
  def legacy(raw)
@@ -208,7 +253,7 @@ module Terminal
208
253
 
209
254
  def mouse_vt200(raw)
210
255
  # ESC [ M <btn> <col> <row>
211
- mouse_event(raw, *raw[3..].chars.map! { _1.ord - 32 })
256
+ mouse_event(raw, *raw[3..].chars.map! { it.ord - 32 })
212
257
  end
213
258
 
214
259
  def mouse_sgr(raw)
@@ -228,25 +273,36 @@ module Terminal
228
273
  return unknown(raw) if btn < 0 || col < 1 || row < 1
229
274
  key = (btn & 1).nonzero? ? 1 : 0
230
275
  key += 2 if btn.allbits?(2)
231
- modifier = btn.allbits?(4) ? 1 : 0
232
- modifier += 2 if btn.allbits?(8)
233
- modifier += 4 if btn.allbits?(16)
234
- modifier += 256 if moved = btn.allbits?(32)
276
+ modifier_bits = btn.allbits?(4) ? 1 : 0
277
+ modifier_bits += 2 if btn.allbits?(8)
278
+ modifier_bits += 4 if btn.allbits?(16)
279
+ modifier_bits += 256 if moved = btn.allbits?(32)
235
280
  key += 4 if btn.allbits?(64)
236
281
  key += 8 if btn.allbits?(128)
237
- new(raw, mouse_name(key, up, moved), modifier, [row, col])
282
+ new(raw, mouse_name(key, up, moved), modifier_bits, [row, col])
238
283
  end
239
284
 
240
285
  def mouse_name(key, up, moved)
241
- return up ? :MBLeftUp : :MBLeft if key == 0
242
- return up ? :MBMiddleUp : :MBMiddle if key == 1
243
- return up ? :MBRightUp : :MBRight if key == 2
244
- return moved ? :Mouse : :MBUp if key == 3
245
- return :MWUp if key == 4
246
- return :MWDown if key == 5
247
- return :MWLeft if key == 6
248
- return :MWRight if key == 7
249
- :"MB_#{key - 8}#{'Up' if up}"
286
+ case key
287
+ when 0
288
+ up ? :MBLeftUp : :MBLeft
289
+ when 1
290
+ up ? :MBMiddleUp : :MBMiddle
291
+ when 2
292
+ up ? :MBRightUp : :MBRight
293
+ when 3
294
+ moved ? :Mouse : :MBUp
295
+ when 4
296
+ :MWUp
297
+ when 5
298
+ :MWDown
299
+ when 6
300
+ :MWLeft
301
+ when 7
302
+ :MWRight
303
+ else
304
+ :"MB_#{key - 8}#{'Up' if up}"
305
+ end
250
306
  end
251
307
  end
252
308
 
@@ -3,64 +3,73 @@
3
3
  require 'io/console'
4
4
 
5
5
  module Terminal
6
+ #
6
7
  # @!group Attributes
8
+ #
7
9
 
8
10
  # @attribute [r] self.input_mode
9
11
  #
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)
12
+ # Detect and return the current input mode.
13
+ #
14
+ # @example
15
+ # Terminal.input_mode # => :csi_u
16
+ #
17
+ # @attribute [r] input_mode
18
+ # @return [Symbol] +:csi_u+, +:legacy+, +:dumb+, or +:error+
21
19
 
20
+ #
22
21
  # @!endgroup
23
-
22
+ #
24
23
  # @!group Input methods
25
-
26
- # @!method self.read_key_event
27
- # Read next {KeyEvent} from standard input.
28
24
  #
29
- # @return [KeyEvent] next event
30
- # @return [nil] in error case
31
25
 
32
26
  # @!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
27
+ #
28
+ # Enter a key event loop, yielding each event to the block.
29
+ #
30
+ # Enables optional mouse and focus event reporting. The block
31
+ # receives {KeyEvent} instances until input ends or the block
32
+ # breaks.
33
+ #
34
+ # @example
35
+ # Terminal.on_key_event(mouse: true) do |event|
36
+ # break if event.name == 'ESC'
37
+ # puts event.name
38
+ # end
39
+ #
40
+ # @param mouse [true, false] enable mouse button events
41
+ # @param mouse_move [true, false] enable mouse move events
42
+ # @param focus [true, false] enable terminal focus/unfocus events
43
+ # @yield [event] called for each input event
44
+ # @yieldparam event [KeyEvent] the parsed key event
45
+ # @return [nil, false] +false+ on I/O error
46
+ # @raise [RuntimeError] if already inside a key event loop
47
+
48
+ # @!method self.read_key_event
49
+ #
50
+ # Read a single key event (blocking).
51
+ #
52
+ # @example
53
+ # event = Terminal.read_key_event
54
+ # puts "You pressed: #{event.name}"
55
+ #
56
+ # @return [KeyEvent, false] the next key event, or +false+ on error
51
57
 
58
+ #
52
59
  # @!endgroup
60
+ #
53
61
 
62
+ # @private
54
63
  module Input
55
64
  def input_mode
56
65
  @in_recursion = 0
57
66
  case @input_mode = __find_input_mode
58
67
  when :legacy, :csi_u
59
68
  @in = IO.console
60
- require_relative 'input/ansi'
69
+ require_relative('input/ansi')
61
70
  extend AnsiInput
62
71
  when :dumb
63
- require_relative 'input/dumb'
72
+ require_relative('input/dumb')
64
73
  extend DumpInput
65
74
  else
66
75
  __input_error
@@ -68,12 +77,12 @@ module Terminal
68
77
  @input_mode
69
78
  end
70
79
 
71
- def on_key_event(mouse: false, mouse_move: false, focus: false, &block)
80
+ def on_key_event(mouse: false, mouse_move: false, focus: false, &)
72
81
  input_mode
73
- on_key_event(mouse: mouse, mouse_move: mouse_move, focus: focus, &block)
82
+ on_key_event(mouse:, mouse_move:, focus:, &)
74
83
  end
75
84
 
76
- def read_key_event = on_key_event { return _1 }
85
+ def read_key_event = on_key_event { return it }
77
86
 
78
87
  private
79
88
 
@@ -94,10 +103,12 @@ module Terminal
94
103
  return :legacy if im == 'legacy'
95
104
  return :dumb if im == 'dumb' || !STDIN.tty?
96
105
  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")
106
+ STDIN.noecho do
107
+ return :legacy if con.syswrite("\e[>1u\e[?u\e[c") != 12
108
+ inp = +''
109
+ inp << con.getch until inp.rindex('c')
110
+ return :legacy unless inp.include?("\e[?1u")
111
+ end
101
112
  at_exit do
102
113
  IO.console.syswrite("\e[<u")
103
114
  rescue StandardError
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
+ # @private
4
5
  module AnsiOutput
5
6
  def output_mode = :ansi
6
7
  def ansi? = true
@@ -40,8 +41,8 @@ module Terminal
40
41
  @con = nil
41
42
  end
42
43
 
43
- def raw_write(str)
44
- @out.syswrite(str)
44
+ def raw_write(object)
45
+ @out.syswrite(object)
45
46
  rescue IOError, SystemCallError
46
47
  __output_error(nil)
47
48
  end
@@ -56,15 +57,48 @@ module Terminal
56
57
  def print(*objects, bbcode: true)
57
58
  return if objects.empty?
58
59
  return @out.print(*objects) unless bbcode
59
- @out.print(*objects.map! { Ansi.bbcode(_1) })
60
+ @out.print(*objects.map! { Ansi.bbcode(it) })
60
61
  rescue IOError, SystemCallError
61
62
  __output_error(nil)
62
63
  end
63
64
 
64
65
  def puts(*objects, bbcode: true)
65
- return @out.puts(objects.empty? ? nil : objects) unless bbcode
66
66
  objects.flatten!
67
- @out.puts(objects.empty? ? nil : objects.map! { Ansi.bbcode(_1) })
67
+ return @out.puts if objects.empty?
68
+ return @out.puts(objects) unless bbcode
69
+ @out.puts(objects.map! { Ansi.bbcode(it) })
70
+ rescue IOError, SystemCallError
71
+ __output_error(nil)
72
+ end
73
+
74
+ def fputs(
75
+ *objects,
76
+ bbcode: true,
77
+ spaces: true,
78
+ eol: true,
79
+ align: nil,
80
+ width: columns,
81
+ height: nil,
82
+ padding: nil,
83
+ prefix: nil,
84
+ suffix: nil
85
+ )
86
+ objects.flatten!
87
+ @out.puts(
88
+ Text::Formatter.format(
89
+ *objects,
90
+ ansi: true,
91
+ bbcode:,
92
+ spaces:,
93
+ eol:,
94
+ align:,
95
+ width:,
96
+ height:,
97
+ padding:,
98
+ prefix:,
99
+ suffix:
100
+ )
101
+ )
68
102
  rescue IOError, SystemCallError
69
103
  __output_error(nil)
70
104
  end
@@ -101,7 +135,11 @@ module Terminal
101
135
  def __init_tty
102
136
  require('io/console')
103
137
  @con = IO.console
104
- Signal.trap('WINCH') { @size = nil } if Signal.list.key?('WINCH')
138
+ return unless Signal.list.key?('WINCH')
139
+ Signal.trap('WINCH') do
140
+ @size = nil
141
+ @on_resize&.call
142
+ end
105
143
  end
106
144
 
107
145
  private_class_method def self.extended(mod)