terminal_rb 1.0.5 → 1.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd88e0e1f5665c57303b6540930bb88bc7fc7ba37341161593c47641d83aee5a
4
- data.tar.gz: adde7fd25392299f1bc967d6537fd25fb94016fcfed76812a99c07ef45604765
3
+ metadata.gz: 91c150887b2ab21e509a1d8da160bc89879d9750de3246a2f84ee0c87c5a3992
4
+ data.tar.gz: 640cc99db38489ca6ccb57f3d7605fdc199a581ff674f20cda145b189a62747c
5
5
  SHA512:
6
- metadata.gz: 3f487a373e5c05c61ef3c8433db0be643ac2cbd590859d67c2cdecbf62f6c91002d8c2ce19f6895f20e78531e2608ddb2a43491f87fea58c519f133df62070bf
7
- data.tar.gz: 6db6da10522f8d94e5e1ff964529b0e2a022618c7edae701ffc89ce9bbdfb4ba5942d288b276112dff718e521d43d4f37736acf1be67049821a3abeabf18e39b
6
+ metadata.gz: 9a2f57d900145e027ea44bad3bea885a46c37c451e31cd9c0623c0d4935022ab3eb3479a76095a094d0ed3acb91d4248c2052d7b635538cf95d540af3a2b8512
7
+ data.tar.gz: 17b15d64bb67d748202fe7db329565d9bce197c50c00bb6005e15483acea2461d59b11f16d9910c4092532299c49047817d9e4934d54b8e8d59f1580040d4314
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Terminal.rb v1.0.5
1
+ # Terminal.rb v1.0.8
2
2
 
3
3
  Terminal.rb supports you with input and output on your terminal. Simple [BBCode](https://en.wikipedia.org/wiki/BBCode)-like markup for attributes and coloring, word-wise line breaks, correct special key recognition and mouse event reporting enable you to implement your CLI app quickly and easily.
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/1.0.5/Terminal)
7
+ - Help: [rubydoc.info](https://rubydoc.info/gems/terminal_rb/1.0.8/Terminal)
8
8
 
9
9
  ## Features
10
10
 
@@ -6,7 +6,7 @@ Terminal.puts <<~TEXT
6
6
 
7
7
  ✅ [b bright_green]Terminal.rb[/b] — Key Codes:[/fg]
8
8
  Press any key to display it's control code and name.
9
- [bright_black]([b]#{Terminal.input_mode}[/b] mode - exit with ESC)[/fg]
9
+ [bright_black]([b]#{Terminal.input_mode}[/b] mode exit with ESC)[/fg]
10
10
 
11
11
  TEXT
12
12
 
@@ -52,7 +52,7 @@ module Terminal
52
52
  # Total number of lines.
53
53
  #
54
54
  # @attribute [r] line_count
55
- # @return [Integer, nil] +nil+ when not already calculated
55
+ # @return [Integer, nil] `nil` when not already calculated
56
56
  def line_count = @buf&.size
57
57
 
58
58
  # Index of the topmost visible line.
@@ -68,7 +68,7 @@ module Terminal
68
68
 
69
69
  # Resize to the current terminal dimensions and redraw.
70
70
  #
71
- # @return [self, nil] +nil+ when resize is not required
71
+ # @return [self, nil] `nil` when resize is not required
72
72
  def resize_to_screen = _resize(*Terminal.size)
73
73
 
74
74
  # Resize to specific dimensions (clamped to terminal bounds)
@@ -76,7 +76,7 @@ module Terminal
76
76
  #
77
77
  # @param rows [Integer] new row count
78
78
  # @param columns [Integer] new column count
79
- # @return [self, nil] +nil+ when resize is not required
79
+ # @return [self, nil] `nil` when resize is not required
80
80
  def resize(rows, columns)
81
81
  _resize(
82
82
  rows.clamp(1, Terminal.rows),
@@ -99,7 +99,7 @@ module Terminal
99
99
 
100
100
  # Scroll to the beginning of the content.
101
101
  #
102
- # @return [self, nil] +nil+ when already on begin
102
+ # @return [self, nil] `nil` when already on begin
103
103
  def begin
104
104
  return draw unless @buf
105
105
  return if @line_top == 0
@@ -109,7 +109,7 @@ module Terminal
109
109
 
110
110
  # Scroll to the end of the content.
111
111
  #
112
- # @return [self, nil] +nil+ when already on end
112
+ # @return [self, nil] `nil` when already on end
113
113
  def end
114
114
  return draw unless @buf
115
115
  idx = @buf.size - @rows_visible
data/lib/terminal/ansi.rb CHANGED
@@ -45,7 +45,7 @@ module Terminal
45
45
  # Generate an ANSI escape sequence from attribute names or color
46
46
  # indices.
47
47
  #
48
- # Accepts symbols (`:bold`, +:red+), strings (`"bold"`, +"#ff0000"+),
48
+ # Accepts symbols (`:bold`, `:red`), strings (`"bold"`, `"#ff0000"`),
49
49
  # or integers for 256-color palette (0-255 foreground, 256-511
50
50
  # background, 512-767 underline color).
51
51
  #
@@ -69,9 +69,9 @@ module Terminal
69
69
  .map! do |arg|
70
70
  case arg
71
71
  when String
72
- @attr_map[arg] || _invalid(arg)
72
+ @attr_str_map[arg] || _invalid(arg)
73
73
  when Symbol
74
- @attrs_map[arg] || _invalid(arg)
74
+ @attr_sym_map[arg] || _invalid(arg)
75
75
  when (0..255)
76
76
  "38;5;#{arg}"
77
77
  when (256..511)
@@ -122,17 +122,22 @@ module Terminal
122
122
  #
123
123
  # @example
124
124
  # Terminal::Ansi.try_convert('bold red') # => "\e[1;31m"
125
- # Terminal::Ansi.try_convert('invalid') # => nil
125
+ # Terminal::Ansi.try_convert('invalid') # => nil
126
126
  #
127
127
  # @param attributes [#to_s] space-separated attribute names
128
128
  # @param separator [String] delimiter for splitting
129
- # @return [String, nil] ANSI escape sequence, or +nil+ if any
129
+ # @return [String, nil] ANSI escape sequence, or `nil` if any
130
130
  # attribute is invalid
131
131
  def try_convert(attributes, separator: ' ')
132
132
  return unless attributes
133
- return if (attributes = attributes.to_s.split(separator)).empty?
134
- attributes.uniq!
135
- "\e[#{attributes.map! { @attr_map[it] || return }.join(';')}m"
133
+ cache = @convert_cache[separator]
134
+ hit = cache[key = attributes.to_s] and return hit
135
+ cache.clear if cache.size > 1024 # cache limit
136
+ cache[key] = unless (attributes = key.split(separator)).empty?
137
+ # attributes.uniq!
138
+ codes = attributes.map { @attr_str_map[it] || break }
139
+ "\e[#{codes.join(';')}m" if codes
140
+ end
136
141
  end
137
142
 
138
143
  # Generate rainbow-colored text using true color (24-bit) ANSI
@@ -168,9 +173,9 @@ module Terminal
168
173
 
169
174
  # Convert BBCode markup to ANSI escape codes.
170
175
  #
171
- # Tags like +[bold]+ are converted to their ANSI equivalents;
172
- # +[/bold]+ or +[/]+ resets. Unknown tags are left unchanged.
173
- # Escape a tag with a backslash: +[\\bold]+ renders as +[bold]+.
176
+ # Tags like `[bold]` are converted to their ANSI equivalents;
177
+ # `[/bold]` or `[/]` resets. Unknown tags are left unchanged.
178
+ # Escape a tag with a backslash: `[\\bold]` renders as `[bold]`.
174
179
  #
175
180
  # @see .unbbcode
176
181
  # @see .plain
@@ -213,7 +218,7 @@ module Terminal
213
218
  next "[#{match}]"
214
219
  end
215
220
  next match_str if (match = match.split).empty?
216
- next if match.all? { @attr_map[it] }
221
+ next if match.all? { @attr_str_map[it] }
217
222
  match_str
218
223
  end
219
224
  end
@@ -237,7 +242,7 @@ module Terminal
237
242
  next match_str if fc == '\\' || fc == ']'
238
243
  match = Regexp.last_match(1) or next match_str
239
244
  next match_str if (parts = match.split).empty?
240
- next "[\\#{match}]" if parts.all? { @attr_map[it] }
245
+ next "[\\#{match}]" if parts.all? { @attr_str_map[it] }
241
246
  match_str
242
247
  end
243
248
  end
@@ -271,9 +276,9 @@ module Terminal
271
276
  attributes.all? do |arg|
272
277
  case arg
273
278
  when String
274
- @attr_map[arg]
279
+ @attr_str_map[arg]
275
280
  when Symbol
276
- @attrs_map[arg]
281
+ @attr_sym_map[arg]
277
282
  when (0..767)
278
283
  true
279
284
  end
@@ -361,7 +366,7 @@ module Terminal
361
366
  # Terminal::Ansi.cursor_pos(1, 1) # home position
362
367
  # Terminal::Ansi.cursor_pos(10, 5) # row 10, column 5
363
368
  #
364
- # @param row [Integer, nil] row number; +nil+ moves to home
369
+ # @param row [Integer, nil] row number; `nil` moves to home
365
370
  # @param column [Integer, nil] column number
366
371
  # @return (see .[])
367
372
  def cursor_pos(row, column = nil)
@@ -396,7 +401,7 @@ module Terminal
396
401
  # Erase part of the screen.
397
402
  #
398
403
  # @param part [Symbol] area to erase:
399
- # +:all+, +:below+, +:above+, or +:scrollback+
404
+ # `:all`, `:below`, `:above`, or `:scrollback`
400
405
  # @return (see .[])
401
406
  def screen_erase(part = :all) = "\e[#{@screen_erase[part]}J"
402
407
 
@@ -449,7 +454,7 @@ module Terminal
449
454
  # Erase part of the current line.
450
455
  #
451
456
  # @param part [Symbol] area to erase:
452
- # +:all+, +:to_end+, or +:to_start+
457
+ # `:all`, `:to_end`, or `:to_start`
453
458
  # @return (see .[])
454
459
  def line_erase(part = :all) = "\e[#{@line_erase[part]}K"
455
460
 
@@ -472,7 +477,7 @@ module Terminal
472
477
  # @see .link_end
473
478
  #
474
479
  # @param url [#to_s] the link URL
475
- # @param params [Hash] optional link parameters (e.g., +id:+)
480
+ # @param params [Hash] optional link parameters (e.g., `id:`)
476
481
  # @return (see .[])
477
482
  def link_start(url, **params)
478
483
  "\e]8;#{params.map { it.join('=') }.join(':')};#{url}\a"
@@ -520,12 +525,12 @@ module Terminal
520
525
  # Terminal.raw_write(Terminal::Ansi.progress(:error)) # error state
521
526
  # Terminal.raw_write(Terminal::Ansi.progress(nil)) # hide
522
527
  #
523
- # @param state [Symbol, Numeric, Boolean] progress state:
524
- # - +:show+ or +true+ to show, `:err`/`:error`, `:warn`/`:warning`,
525
- # +:indeterminate+, a +Numeric+ (0-100) to set percentage, or
528
+ # @param state [Symbol, Numeric, true, false] progress state:
529
+ # - `:show` or `true` to show, `:err`/`:error`, `:warn`/`:warning`,
530
+ # `:indeterminate`, a `Numeric` (0-100) to set percentage, or
526
531
  # any other value to hide
527
- # @param percent [Integer] progress percentage (0-100, used with
528
- # +:show+ state)
532
+ # @param percent [#to_i] progress percentage (0-100, used with
533
+ # `:show` state)
529
534
  # @return (see .[])
530
535
  def progress(state, percent = 0)
531
536
  case state
@@ -563,9 +568,9 @@ module Terminal
563
568
  # @param fracd [Integer, nil] fractional denominator
564
569
  # (must be > fracn, max 15)
565
570
  # @param vertical [Symbol, Integer, nil] vertical alignment:
566
- # +:top+, +:bottom+, +:middle+
571
+ # `:top`, `:bottom`, `:middle`
567
572
  # @param horizontal [Symbol, Integer, nil] horizontal alignment:
568
- # +:left+, +:right+, +:center+
573
+ # `:left`, `:right`, `:center`
569
574
  # @return (see .[])
570
575
  def scale(
571
576
  text,
@@ -625,7 +630,7 @@ module Terminal
625
630
 
626
631
  @re_bbcode = /(?:\[((?~[\[\]]))\])/
627
632
 
628
- clr_map = {
633
+ color_map = {
629
634
  # foreground
630
635
  'black' => '30',
631
636
  'red' => '31',
@@ -684,9 +689,9 @@ module Terminal
684
689
  'on_bright_white' => '107'
685
690
  }
686
691
 
687
- @colors = clr_map.keys.map!(&:to_sym).sort!.freeze
692
+ @colors = color_map.keys.map!(&:to_sym).sort!.freeze
688
693
 
689
- attr_map = {
694
+ @attr_base_map = {
690
695
  'reset' => '',
691
696
  'bold' => '1',
692
697
  'faint' => '2',
@@ -743,7 +748,7 @@ module Terminal
743
748
  'dashed_underline_off' => '4:0'
744
749
  }
745
750
 
746
- attr_alias = ->(t, s) { attr_map[t] = attr_map[s] }
751
+ attr_alias = ->(t, s) { @attr_base_map[t] = @attr_base_map[s] }
747
752
 
748
753
  # aliases:
749
754
  attr_alias['off', 'reset']
@@ -768,18 +773,18 @@ module Terminal
768
773
  attr_alias['sup', 'superscript']
769
774
  attr_alias['sub', 'subscript']
770
775
 
771
- @attributes = attr_map.keys.map!(&:to_sym).sort!.freeze
776
+ @attributes = @attr_base_map.keys.map!(&:to_sym).sort!.freeze
772
777
 
773
778
  # shortcuts disable:
774
- attr_map.keys.each do |n|
779
+ @attr_base_map.keys.each do |n|
775
780
  attr_alias["/#{n.delete_suffix('_off')}", n] if n.end_with?('_off')
776
781
  end
777
782
 
778
783
  # additional shortcuts disable:
779
784
  attr_alias['/', 'reset']
780
- attr_map['/bg'] = clr_map['on_default']
781
- attr_map['/fg'] = clr_map['default']
782
- attr_map['/ul'] = clr_map['ul_default']
785
+ @attr_base_map['/bg'] = color_map['on_default']
786
+ @attr_base_map['/fg'] = color_map['default']
787
+ @attr_base_map['/ul'] = color_map['ul_default']
783
788
 
784
789
  attr_alias['/b', 'bold_off']
785
790
  attr_alias['/d', 'dim_off']
@@ -800,15 +805,20 @@ module Terminal
800
805
  @cbase.default = '38'
801
806
  @cbase.freeze
802
807
 
803
- @attr_map =
804
- Hash.new do |_, str|
805
- b, v = /\A(on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
808
+ @attr_base_map.merge!(color_map).freeze
809
+
810
+ @attr_str_map = @attr_base_map.dup
811
+ @attr_str_map.default_proc =
812
+ proc do |h, s|
813
+ if h.size > 1024 # cache limit
814
+ h.keep_if { |key, _| @attr_base_map.key?(key) }
815
+ end
816
+ b, v = /\A(on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(s)&.captures
806
817
  unless v
807
- b = /\A(on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str) or next
808
- v = NAMED_COLORS[b[2]] or next
809
- next "#{@cbase[b[1]]};#{v}"
818
+ b = /\A(on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(s) or next h[s] = nil
819
+ next h[s] = (v = NAMED_COLORS[b[2]]) ? "#{@cbase[b[1]]};#{v}" : nil
810
820
  end
811
- case v.size
821
+ h[s] = case v.size
812
822
  when 1, 2
813
823
  "#{@cbase[b]};5;#{v.hex}"
814
824
  when 3
@@ -817,12 +827,12 @@ module Terminal
817
827
  "#{@cbase[b]};2;#{v[0, 2].hex};#{v[2, 2].hex};#{v[4, 2].hex}"
818
828
  end
819
829
  end
820
- attr_map.merge!(clr_map).keys.sort!.each { @attr_map[it] = attr_map[it] }
821
- @attr_map.freeze
822
830
 
823
- @attrs_map = @attr_map.transform_keys(&:to_sym)
824
- @attrs_map.default_proc = @attr_map.default_proc
825
- @attrs_map.compare_by_identity.freeze
831
+ @attr_sym_map = @attr_base_map.transform_keys(&:to_sym)
832
+ @attr_sym_map.compare_by_identity
833
+ @attr_sym_map.default_proc = proc { |h, s| h[s] = @attr_str_map[s.to_s] }
834
+
835
+ @convert_cache = Hash.new { |h, k| h[k] = {} }
826
836
 
827
837
  @screen_erase = { below: nil, above: '1', scrollback: '3' }
828
838
  @screen_erase.default = '2'
@@ -6,14 +6,13 @@ module Terminal
6
6
  attr_reader :input_mode
7
7
 
8
8
  def on_key_event(mouse: false, mouse_move: false, focus: false)
9
- raise('already reading key events') if (@in_recursion += 1) != 1
10
9
  return unless block_given?
11
- opts = __input_option(mouse, mouse_move, focus)
10
+ opts = nil
12
11
  STDIN.noecho do
13
- opts &&= @in.syswrite("#{opts}h") ? "#{opts}l" : nil
12
+ opts = __input_options(mouse, mouse_move, focus)
14
13
  while (raw = @in.getch)
15
14
  next yield(KeyEvent[raw]) if raw != "\e"
16
- while String === (nc = @in.read_nonblock(1, exception: false))
15
+ while (nc = @in.read_nonblock(1, exception: false)).is_a?(String)
17
16
  raw << nc
18
17
  end
19
18
  next yield(KeyEvent[raw]) if raw.rindex("\e") < 2
@@ -25,13 +24,15 @@ module Terminal
25
24
  __input_error
26
25
  false
27
26
  ensure
28
- @in_recursion -= 1
29
- @in.syswrite(opts) if opts && @in
27
+ if opts
28
+ @in_opts.pop
29
+ @in&.syswrite(opts)
30
+ end
30
31
  end
31
32
 
32
33
  private
33
34
 
34
- def __input_option(mouse, mouse_move, focus)
35
+ def __input_options(mouse, mouse_move, focus)
35
36
  opts = (mouse ? ['1000'] : [])
36
37
  # highlight: '1001'
37
38
  # drag: '1002'
@@ -41,7 +42,19 @@ module Terminal
41
42
  # sgr: '1006'
42
43
  # urxvt: '1015'
43
44
  # pixel: '1016'
44
- "\e[?#{opts.join(';')};1006" unless opts.empty?
45
+ if (last = @in_opts[-1])
46
+ if opts.empty?
47
+ @in.syswrite("#{last}l")
48
+ return "#{last}h"
49
+ end
50
+ @in_opts << (opts = "\e[?#{opts.join(';')};1006")
51
+ @in.syswrite("#{last}l#{opts}h")
52
+ return "#{opts}l#{last}h"
53
+ end
54
+ return if opts.empty?
55
+ @in_opts << (opts = "\e[?#{opts.join(';')};1006")
56
+ @in.syswrite("#{opts}h")
57
+ "#{opts}l"
45
58
  end
46
59
  end
47
60
 
@@ -5,8 +5,7 @@ module Terminal
5
5
  module DumpInput
6
6
  def input_mode = :dumb
7
7
 
8
- def on_key_event(*_)
9
- raise('already reading key events') if (@in_recursion += 1) != 1
8
+ def on_key_event(*)
10
9
  return unless block_given?
11
10
  while (raw = STDIN.getc)
12
11
  opts = dumb_keys[raw.ord] if raw.size == 1
@@ -17,8 +16,6 @@ module Terminal
17
16
  rescue IOError, SystemCallError
18
17
  __input_error
19
18
  false
20
- ensure
21
- @in_recursion -= 1
22
19
  end
23
20
 
24
21
  private
@@ -23,8 +23,8 @@ module Terminal
23
23
  class KeyEvent
24
24
  # The key identifier.
25
25
  #
26
- # @return [Symbol, String, nil] a named key like +:Enter+, +:Tab+,
27
- # +:MBLeft+ (mouse), or a character string like +"a"+; +nil+ for
26
+ # @return [Symbol, String, nil] a named key like `:Enter`, `:Tab`,
27
+ # `:MBLeft` (mouse), or a character string like `"a"`; `nil` for
28
28
  # unknown sequences
29
29
  attr_reader :key
30
30
 
@@ -61,8 +61,8 @@ module Terminal
61
61
 
62
62
  # Mouse position when this is a mouse event.
63
63
  #
64
- # @return [Array<Integer, Integer>, nil] +[row, column]+ for mouse
65
- # events, +nil+ for keyboard events
64
+ # @return [Array<Integer, Integer>, nil] `[row, column]` for mouse
65
+ # events, `nil` for keyboard events
66
66
  attr_reader :position
67
67
 
68
68
  # The raw input sequence as received from the terminal.
@@ -85,8 +85,8 @@ module Terminal
85
85
  # @private
86
86
  # Destructuring-friendly array representation.
87
87
  #
88
- # @return [Array<String>] +[raw]+ if {#simple?}
89
- # @return [Array<String, String>] +[raw, name]+ otherwise
88
+ # @return [Array<String>] `[raw]` if {#simple?}
89
+ # @return [Array<String, String>] `[raw, name]` otherwise
90
90
  def to_ary = simple? ? [@raw] : [@raw, @name]
91
91
 
92
92
  # @return [String] the event {#name}
@@ -140,7 +140,7 @@ module Terminal
140
140
  @names ||=
141
141
  (@csiu.values + @csi.values + @ss3.values)
142
142
  .uniq
143
- .keep_if { Symbol === it }
143
+ .keep_if { it.is_a?(Symbol) }
144
144
  .sort!
145
145
  ).dup
146
146
  end
@@ -15,7 +15,7 @@ module Terminal
15
15
  # Terminal.input_mode # => :csi_u
16
16
  #
17
17
  # @attribute [r] input_mode
18
- # @return [Symbol] +:csi_u+, +:legacy+, +:dumb+, or +:error+
18
+ # @return [Symbol] `:csi_u`, `:legacy`, `:dumb`, or `:error`
19
19
 
20
20
  #
21
21
  # @!endgroup
@@ -42,8 +42,7 @@ module Terminal
42
42
  # @param focus [true, false] enable terminal focus/unfocus events
43
43
  # @yield [event] called for each input event
44
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
45
+ # @return [nil, false] `false` on I/O error
47
46
 
48
47
  # @!method self.read_key_event
49
48
  #
@@ -53,7 +52,7 @@ module Terminal
53
52
  # event = Terminal.read_key_event
54
53
  # puts "You pressed: #{event.name}"
55
54
  #
56
- # @return [KeyEvent, false] the next key event, or +false+ on error
55
+ # @return [KeyEvent, false] the next key event, or `false` on error
57
56
 
58
57
  #
59
58
  # @!endgroup
@@ -62,10 +61,10 @@ module Terminal
62
61
  # @private
63
62
  module Input
64
63
  def input_mode
65
- @in_recursion = 0
66
64
  case @input_mode = __find_input_mode
67
65
  when :legacy, :csi_u
68
66
  @in = IO.console
67
+ @in_opts = []
69
68
  require_relative('input/ansi')
70
69
  extend AnsiInput
71
70
  when :dumb
@@ -105,7 +104,7 @@ module Terminal
105
104
  con = IO.console
106
105
  STDIN.noecho do
107
106
  return :legacy if con.syswrite("\e[>1u\e[?u\e[c") != 12
108
- inp = +''
107
+ inp = ''.b
109
108
  inp << con.getch until inp.rindex('c')
110
109
  return :legacy unless inp.include?("\e[?1u")
111
110
  end
@@ -43,6 +43,8 @@ module Terminal
43
43
 
44
44
  def raw_write(object)
45
45
  @out.syswrite(object)
46
+ rescue Errno::EINTR
47
+ retry # fixes rare condition found on MacOS Terminal.app
46
48
  rescue IOError, SystemCallError
47
49
  __output_error(nil)
48
50
  end
@@ -84,6 +86,7 @@ module Terminal
84
86
  suffix: nil
85
87
  )
86
88
  objects.flatten!
89
+ return @out.puts if objects.empty?
87
90
  @out.puts(
88
91
  Text::Formatter.format(
89
92
  *objects,
@@ -67,13 +67,14 @@ module Terminal
67
67
  suffix: nil
68
68
  )
69
69
  objects.flatten!
70
+ return @out.puts if objects.empty?
70
71
  @out.puts(
71
72
  Text::Formatter.format(
72
73
  *objects,
73
74
  ansi: false,
74
75
  bbcode:,
75
76
  spaces:,
76
- eol: eol,
77
+ eol:,
77
78
  align:,
78
79
  width:,
79
80
  height:,
@@ -66,7 +66,7 @@ module Terminal
66
66
  #
67
67
  # The terminal size.
68
68
  #
69
- # @return [Array<Integer, Integer>] +[rows, columns]+
69
+ # @return [Array<Integer, Integer>] `[rows, columns]`
70
70
 
71
71
  # Terminal width in columns.
72
72
  #
@@ -89,7 +89,7 @@ module Terminal
89
89
  # Current cursor position.
90
90
  # This is only available when ANSI is supported ({ansi?} return true).
91
91
  #
92
- # @return [Array<Integer, Integer>, nil] +[row, column]+ or +nil+ if
92
+ # @return [Array<Integer, Integer>, nil] `[row, column]` or `nil` if
93
93
  # unavailable
94
94
 
95
95
  # Whether the terminal has been resized since the last size query.
@@ -113,7 +113,7 @@ module Terminal
113
113
  # @example
114
114
  # Terminal << '[bold]Hello[/bold] World'
115
115
  #
116
- # @param object [#to_s, nil] the object to output; +nil+ is ignored
116
+ # @param object [#to_s, nil] the object to output; `nil` is ignored
117
117
  # @return [self]
118
118
 
119
119
  # @!method print(*objects, bbcode: true)
@@ -162,7 +162,7 @@ module Terminal
162
162
  # processing.
163
163
  #
164
164
  # @param object [#to_s] object to write
165
- # @return [Integer, nil] bytes written, or +nil+ on error
165
+ # @return [Integer, nil] bytes written, or `nil` on error
166
166
 
167
167
  # @!method hide_cursor
168
168
  #
@@ -4,9 +4,9 @@ require 'stringio'
4
4
 
5
5
  # RSpec shared context for testing code that uses Terminal.rb.
6
6
  #
7
- # Stubs all Terminal I/O methods and provides +stdout+, +stdin+, and
8
- # +stdoutstr+ test helpers. Output is captured into the +stdout+ array;
9
- # input is read from the +stdin+ array.
7
+ # Stubs all Terminal I/O methods and provides `stdout`, `stdin`, and
8
+ # `stdoutstr` test helpers. Output is captured into the `stdout` array;
9
+ # input is read from the `stdin` array.
10
10
  #
11
11
  # @example Basic usage
12
12
  # RSpec.describe MyClass do
@@ -22,15 +22,15 @@ require 'stringio'
22
22
  # include_context 'with Terminal.rb', ansi: false, colors: 8,
23
23
  # size: [40, 120]
24
24
  #
25
- # @param ansi [true, false] mock ANSI support (default: +true+)
25
+ # @param ansi [true, false] mock ANSI support (default: `true`)
26
26
  # @param application [Symbol] mock terminal application
27
- # (default: +:kitty+)
28
- # @param colors [Integer, Symbol] mock color depth; use +:true_color+
29
- # for 16777216 (default: +256+)
27
+ # (default: `:kitty`)
28
+ # @param colors [Integer, Symbol] mock color depth; use `:true_color`
29
+ # for 16777216 (default: `256`)
30
30
  # @param size [Array<Integer, Integer>] mock terminal size as
31
- # +[rows, columns]+ (default: +[25, 80]+)
31
+ # `[rows, columns]` (default: `[25, 80]`)
32
32
  # @param pos [Array<Integer, Integer>] mock cursor position as
33
- # +[row, column]+ (default: +[1, 1]+)
33
+ # `[row, column]` (default: `[1, 1]`)
34
34
  RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, colors: 256, size: [25, 80], pos: [1, 1]|
35
35
  let(:stdout) { StringIO.new }
36
36
  let(:stdout_lines) { stdout.string.lines(chomp: true) }
@@ -77,7 +77,7 @@ module Terminal
77
77
  return unless obj
78
78
  return copy_writer(obj) if obj.respond_to?(:readpartial)
79
79
  return copy_writer(obj.to_io) if obj.respond_to?(:to_io)
80
- return array_writer(obj) if Array === obj
80
+ return array_writer(obj) if obj.is_a?(Array)
81
81
  if obj.respond_to?(:each)
82
82
  return enum_writer(obj.enum_for(:each)) if obj.respond_to?(:enum_for)
83
83
  return enum_writer(Enumerator.new { |y| obj.each { y << it } })