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.
data/lib/terminal/text.rb CHANGED
@@ -108,18 +108,39 @@ module Terminal
108
108
  end
109
109
  alias each_with_size each_line_with_size
110
110
 
111
+ # Returns maximal width of a line of given text.
112
+ #
113
+ # @param (see each_line)
114
+ # @return [Integer] width
115
+ def max_line_width(*text, ignore_newline: false)
116
+ return 0 if text.empty?
117
+ ret = 0
118
+ pairs(as_snippets(text, false, false, ignore_newline, Word)) do |_l, w|
119
+ ret = w if w > ret
120
+ end
121
+ ret
122
+ end
123
+
111
124
  private
112
125
 
113
126
  def char_width(char)
114
127
  ord = char.ord
115
- return @ctrlchar_width[ord] || 2 if ord < 0x20
116
- return 1 if char.size < 2 && ord < 0xa1
117
- width = CharWidth[ord]
118
- return @ambiguous_char_width if width == -1
119
- sco = char[-1].ord
120
- return width if sco != 0xff9e && sco != 0xff9f
121
- # handle halfwidth dakuten/handakuten
122
- char.size < 2 ? 1 : char.each_char.sum { char_width(_1) }
128
+ return @ctrlchar_width[ord] if ord < 0x20
129
+ if char.size == 1
130
+ return 1 if ord < 0xa1
131
+ width = CharWidth[ord]
132
+ return width < 0 ? @ambiguous_char_width : width
133
+ end
134
+ sum = 0
135
+ zwj = false
136
+ char.each_char do |c|
137
+ next zwj = false if zwj
138
+ ord = c.ord
139
+ next zwj = true if ord == 0x200d # zero with joiner
140
+ width = CharWidth[ord]
141
+ sum += (width < 0 ? @ambiguous_char_width : width)
142
+ end
143
+ sum
123
144
  end
124
145
 
125
146
  def lim_pairs(snippets, limit)
@@ -153,8 +174,8 @@ module Terminal
153
174
  next csi = nil
154
175
  end
155
176
 
156
- next line << (csi = snippet) if snippet.is_a?(Csi)
157
- next line << snippet if snippet.is_a?(Osc)
177
+ next line << (csi = snippet) if Csi === snippet
178
+ next line << snippet if Osc === snippet
158
179
 
159
180
  # Word:
160
181
 
@@ -217,12 +238,12 @@ module Terminal
217
238
  next csi = nil
218
239
  end
219
240
 
220
- next line << (csi = snippet) if snippet.is_a?(Csi)
221
- next line << snippet if snippet.is_a?(Osc)
241
+ next line << (csi = snippet) if Csi === snippet
242
+ next line << snippet if Osc === snippet
222
243
 
223
244
  # Word:
224
- line << snippet
225
245
  size += snippet.size
246
+ line << snippet
226
247
  end
227
248
  nil
228
249
  end
@@ -258,8 +279,8 @@ module Terminal
258
279
  next csi = nil
259
280
  end
260
281
 
261
- next line << (csi = snippet) if snippet.is_a?(Csi)
262
- next line << snippet if snippet.is_a?(Osc)
282
+ next line << (csi = snippet) if Csi === snippet
283
+ next line << snippet if Osc === snippet
263
284
 
264
285
  # Word:
265
286
 
@@ -322,12 +343,12 @@ module Terminal
322
343
  next csi = nil
323
344
  end
324
345
 
325
- next line << (csi = snippet) if snippet.is_a?(Csi)
326
- next line << snippet if snippet.is_a?(Osc)
346
+ next line << (csi = snippet) if Csi === snippet
347
+ next line << snippet if Osc === snippet
327
348
 
328
349
  # Word:
329
- line << snippet
330
350
  size += snippet.size
351
+ line << snippet
331
352
  end
332
353
  nil
333
354
  end
@@ -335,9 +356,10 @@ module Terminal
335
356
  def as_snippets(text, bbcode, ansi, ignore_newline, word_class)
336
357
  ret = []
337
358
  last = nil
359
+ to_s = bbcode ? ->(s) { Ansi.bbcode(s) } : :to_s.to_proc
338
360
  text.each do |txt|
339
- if (txt = bbcode ? Ansi.bbcode(txt) : txt.to_s).empty?
340
- next ret[-1] = last = :hard_nl if last.is_a?(Symbol)
361
+ if (txt = to_s[txt]).empty?
362
+ next ret[-1] = last = :hard_nl if Symbol === last
341
363
  next ret << (last = :hard_nl)
342
364
  end
343
365
 
@@ -345,15 +367,15 @@ module Terminal
345
367
 
346
368
  txt.scan(@scan_snippet) do |nl, csi, osc, space, gc|
347
369
  if gc
348
- next last.add(gc, char_width(gc)) if last.is_a?(word_class)
370
+ next last.add(gc, char_width(gc)) if word_class === last
349
371
  next ret << (last = word_class.new(gc, char_width(gc)))
350
372
  end
351
373
 
352
- next last.is_a?(Symbol) ? nil : ret << (last = :space) if space
374
+ next Symbol === last ? nil : ret << (last = :space) if space
353
375
 
354
376
  if nl
355
377
  if ignore_newline # handle nl like space
356
- next last.is_a?(Symbol) ? nil : ret << (last = :space)
378
+ next Symbol === last ? nil : ret << (last = :space)
357
379
  end
358
380
  next last == :space ? ret[-1] = last = :nl : ret << (last = :nl)
359
381
  end
@@ -366,11 +388,10 @@ module Terminal
366
388
  next last == CsiEnd ? nil : ret << (last = CsiEnd)
367
389
  end
368
390
 
369
- last.is_a?(Csi) ? last.add(csi) : ret << (last = Csi.new(csi))
391
+ Csi === last ? last.add(csi) : ret << (last = Csi.new(csi))
370
392
  end
371
393
 
372
- next ret[-1] = last = :hard_nl if last.is_a?(Symbol)
373
- ret << (last = :hard_nl)
394
+ Symbol === last ? ret[-1] = last = :hard_nl : ret << (last = :hard_nl)
374
395
  end
375
396
  ret
376
397
  end
@@ -378,9 +399,7 @@ module Terminal
378
399
 
379
400
  class Osc
380
401
  attr_reader :to_str, :size
381
- alias _to_s to_s
382
402
  alias to_s to_str
383
- def inspect = "#{_to_s.chop} #{@to_str.inspect}>"
384
403
 
385
404
  def initialize(str)
386
405
  @to_str = str
@@ -402,9 +421,7 @@ module Terminal
402
421
 
403
422
  class Word
404
423
  attr_reader :to_str, :size
405
- alias _to_s to_s
406
424
  alias to_s to_str
407
- def inspect = "#{_to_s.chop} #{@size}:#{@to_str.inspect}>"
408
425
 
409
426
  def initialize(char, size)
410
427
  @to_str = char.dup
@@ -461,42 +478,49 @@ module Terminal
461
478
  | (\X)
462
479
  )/x
463
480
 
464
- @ctrlchar_width = {
465
- 0x00 => 0,
466
- 0x01 => 1,
467
- 0x02 => 1,
468
- 0x03 => 1,
469
- 0x04 => 1,
470
- 0x05 => 0,
471
- 0x06 => 1,
472
- 0x07 => 0,
473
- 0x08 => 0,
474
- 0x09 => 8,
475
- 0x0a => 0,
476
- 0x0b => 0,
477
- 0x0c => 0,
478
- 0x0d => 0,
479
- 0x0e => 0,
480
- 0x0f => 0,
481
- 0x10 => 1,
482
- 0x11 => 1,
483
- 0x12 => 1,
484
- 0x13 => 1,
485
- 0x14 => 1,
486
- 0x15 => 1,
487
- 0x16 => 1,
488
- 0x17 => 1,
489
- 0x18 => 1,
490
- 0x19 => 1,
491
- 0x1a => 1,
492
- 0x1b => 1,
493
- 0x1c => 1,
494
- 0x1d => 1,
495
- 0x1e => 1,
496
- 0x1f => 1
497
- }.compare_by_identity.freeze
498
-
499
- autoload :CharWidth, "#{__dir__}/text/char_width.rb"
481
+ @ctrlchar_width =
482
+ Hash
483
+ .new(2)
484
+ .compare_by_identity
485
+ .merge!(
486
+ 0x00 => 0,
487
+ 0x01 => 1,
488
+ 0x02 => 1,
489
+ 0x03 => 1,
490
+ 0x04 => 1,
491
+ 0x05 => 0,
492
+ 0x06 => 1,
493
+ 0x07 => 0,
494
+ 0x08 => 0,
495
+ 0x09 => 8,
496
+ 0x0a => 0,
497
+ 0x0b => 0,
498
+ 0x0c => 0,
499
+ 0x0d => 0,
500
+ 0x0e => 0,
501
+ 0x0f => 0,
502
+ 0x10 => 1,
503
+ 0x11 => 1,
504
+ 0x12 => 1,
505
+ 0x13 => 1,
506
+ 0x14 => 1,
507
+ 0x15 => 1,
508
+ 0x16 => 1,
509
+ 0x17 => 1,
510
+ 0x18 => 1,
511
+ 0x19 => 1,
512
+ 0x1a => 1,
513
+ 0x1b => 1,
514
+ 0x1c => 1,
515
+ 0x1d => 1,
516
+ 0x1e => 1,
517
+ 0x1f => 1
518
+ )
519
+ .freeze
520
+
521
+ cw_file = "#{__dir__}/text/char_width.rb"
522
+ autoload :CharWidth, cw_file
523
+ autoload :UNICODE_VERSION, cw_file
500
524
 
501
525
  private_constant :Osc, :Csi, :CsiEnd, :Word, :WordEx, :CharWidth
502
526
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.17.2'
5
+ VERSION = '0.19.0'
6
6
  end
data/lib/terminal.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'terminal/ansi'
3
+ require_relative 'terminal/output'
4
4
  require_relative 'terminal/input'
5
+ require_relative 'terminal/ansi'
5
6
 
6
7
  #
7
8
  # Terminal access with support for ANSI control codes and
@@ -16,41 +17,7 @@ require_relative 'terminal/input'
16
17
  #
17
18
  module Terminal
18
19
  class << self
19
- # Return `true` if the current terminal supports ANSI control codes for
20
- # output.
21
- # In this case all output methods ({<<}, {print}, {puts}) will forward ANSI
22
- # control codes to the terminal and translate BBCode (see {Ansi.bbcode}).
23
- #
24
- # When `false` is returned (ANSI is not supported) the output methods will
25
- # not forward ANSI control codes and BBCodes will be removed.
26
- # The {colors} method will just return 2 (two).
27
- #
28
- # @see tui?
29
- #
30
- # @attribute [r] ansi?
31
- # @return [true, false] whether ANSI control codes are supported for output
32
- def ansi? = @ansi
33
-
34
- # Return `true` if the current terminal supports ANSI control codes for
35
- # input _and_ output.
36
- # In this case not only all output methods ({<<}, {print}, {puts}) will
37
- # forward ANSI control codes to the terminal and translate BBCode
38
- # (see {Ansi.bbcode}). But also the input methods {read_key_event} and
39
- # {on_key_event} will support extended key codes, mouse and focus events.
40
- #
41
- # @see ansi?
42
- #
43
- # @attribute [r] tui?
44
- # @return [true, false]
45
- # whether ANSI control codes are supported for input and output
46
- def tui?
47
- case input_mode
48
- when :csi_u, :legacy
49
- @ansi
50
- else
51
- false
52
- end
53
- end
20
+ # @!group Attributes
54
21
 
55
22
  # Terminal application identifier.
56
23
  #
@@ -66,188 +33,9 @@ module Terminal
66
33
  # @return [Symbol, nil] the application identifier
67
34
  def application = (@application ||= Detect.application)
68
35
 
69
- # Number of supported colors.
70
- # The detection checks various conditions to find the correct value. The
71
- # most common values are
72
- #
73
- # - `16_777_216` for 24-bit encoding ({true_color?} return true)
74
- # - `256` for 8-Bit encoding
75
- # - `52` for some Unix terminals with an extended color palette
76
- # - `8` for 3-/4-bit encoding
77
- # - `2` if ANSI is not supported in general ({ansi?} return false)
78
- #
79
- # @attribute [r] colors
80
- # @return [Integer] number of supported colors
81
- def colors = (@colors ||= ansi? ? Detect.colors : 2)
82
-
83
- # Screen column count.
84
- # See {size} for support and detection details.
85
- #
86
- # @attribute [r] columns
87
- # @return [Integer] number of available columns
88
- def columns = size[1]
89
-
90
- # @attribute [w] columns
91
- def columns=(value)
92
- self.size = [rows, value]
93
- end
94
-
95
- # Current cursor position.
96
- # This is only available when ANSI is supported ({ansi?} return true).
97
- #
98
- # @attribute [r] pos
99
- # @return [[Integer, Integer]] cursor position as rows and columns
100
- # @return [nil] for incompatible terminals
101
- def pos
102
- @con&.cursor
103
- rescue IOError
104
- @con = nil
105
- end
106
-
107
- # @attribute [w] pos
108
- def pos=(pos)
109
- @con&.cursor = pos
110
- rescue IOError
111
- @con = nil
112
- end
113
-
114
- # Screen row count.
115
- # See {size} for support and detection details.
116
- #
117
- # @attribute [r] rows
118
- # @return [Integer] number of available rows
119
- def rows = size[0]
120
-
121
- # @attribute [w] rows
122
- def rows=(value)
123
- self.size = [value, columns]
124
- end
125
-
126
- # Screen size as a tuple of {rows} and {columns}.
127
- #
128
- # If the terminal does not support the report of it's dimension or ANSI
129
- # is not supported in general then environment variables `COLUMNS` and
130
- # `LINES` will be used.
131
- # If this failed `[25, 80]` will be returned as default.
132
- #
133
- # Setting the terminal size is not widely supported.
134
- #
135
- # @see rows
136
- # @see columns
137
- #
138
- # @attribute [r] size
139
- # @return [[Integer, Integer]] available screen size as rows and columns
140
- def size
141
- @size ||= @inf&.winsize || _default_size
142
- rescue IOError
143
- @inf = nil
144
- @size = _default_size
145
- end
146
-
147
- # @attribute [w] size
148
- def size=(size)
149
- @inf&.winsize = size
150
- rescue IOError
151
- @inf = nil
152
- end
153
-
154
- # @see colors
155
- # @attribute [r] true_color?
156
- # @return [true, false] whether true colors are supported
157
- def true_color? = (colors == 16_777_216)
158
-
159
- # Hide the cursor.
160
- # Will not send the control code if the cursor is already hidden.
161
- #
162
- # When you called {hide_cursor} n-times you need to call {show_cursor}
163
- # n-times to show the cursor again.
164
- #
165
- # @return [Terminal] itself
166
- def hide_cursor
167
- raw_write(Ansi::CURSOR_HIDE) if ansi? && (@cc += 1) == 1
168
- self
169
- end
170
-
171
- # Show the cursor.
172
- # Will not send the control code if the cursor is not hidden.
173
- #
174
- # When you called {hide_cursor} n-times you need to call {show_cursor}
175
- # n-times to show the cursor again.
176
- #
177
- # @return [Terminal] itself
178
- def show_cursor
179
- raw_write(Ansi::CURSOR_SHOW) if @cc > 0 && (@cc -= 1).zero?
180
- self
181
- end
36
+ # @!endgroup
182
37
 
183
- # Show the alternate screen.
184
- # Will not send the control code if the alternate screen is already used.
185
- #
186
- # When you called {show_alt_screen} n-times you need to call
187
- # {hide_alt_screen} n-times to show the default screen again.
188
- #
189
- # @return [Terminal] itself
190
- def show_alt_screen
191
- raw_write(Ansi::SCREEN_ALTERNATE) if ansi? && (@as += 1) == 1
192
- self
193
- end
194
-
195
- # Hide the alternate screen.
196
- # Will not send the control code if the alternate screen is not used.
197
- #
198
- # When you called {show_alt_screen} n-times you need to call
199
- # {hide_alt_screen} n-times to show the default screen again.
200
- #
201
- # @return [Terminal] itself
202
- def hide_alt_screen
203
- raw_write(Ansi::SCREEN_ALTERNATE_OFF) if @as > 0 && (@as -= 1).zero?
204
- self
205
- end
206
-
207
- # Writes the given object to the terminal.
208
- # Interprets embedded BBCode.
209
- #
210
- # @param object [#to_s] object to write
211
- # @return [Terminal] itself
212
- def <<(object)
213
- @out.write(Ansi.bbcode(object)) if @out && object != nil
214
- self
215
- rescue IOError
216
- @out = nil
217
- self
218
- end
219
-
220
- # Writes the given objects to the terminal.
221
- # Optionally interprets embedded BBCode.
222
- #
223
- # @param objects [Array<#to_s>] any number of objects to write
224
- # @param bbcode [true|false] whether to interpret embedded BBCode
225
- # @return [nil]
226
- def print(*objects, bbcode: true)
227
- return if @out.nil? || objects.empty?
228
- return @out.print(*objects) unless bbcode
229
- @out.print(*objects.map! { Ansi.bbcode(_1) })
230
- rescue IOError
231
- @out = nil
232
- end
233
-
234
- # Writes the given objects to the terminal.
235
- # Writes a newline after each object that does not already end with a
236
- # newline sequence in it's String represenation.
237
- # If called without any arguments, writes a newline only.
238
- #
239
- # Optionally interprets embedded BBCode.
240
- #
241
- # @param (see print)
242
- # @return (see print)
243
- def puts(*objects, bbcode: true)
244
- return unless @out
245
- return @out.puts(objects.empty? ? nil : objects) unless bbcode
246
- objects.flatten!
247
- @out.puts(objects.empty? ? nil : objects.map! { Ansi.bbcode(_1) })
248
- rescue IOError
249
- @out = nil
250
- end
38
+ # @!group Tool methods
251
39
 
252
40
  # Execute a command and report command output line by line
253
41
  # or capture the output.
@@ -314,77 +102,7 @@ module Terminal
314
102
  ]
315
103
  end
316
104
 
317
- # @private
318
- def raw_write(str)
319
- @out&.syswrite(str)
320
- rescue IOError
321
- @out = nil
322
- end
323
-
324
- private
325
-
326
- def _default_size
327
- rows = ENV['LINES'].to_i
328
- columns = ENV['COLUMNS'].to_i
329
- [rows > 0 ? rows : 25, columns > 0 ? columns : 80]
330
- end
331
-
332
- def _determine_modes
333
- # order is important!
334
- tty = STDOUT.tty?
335
- return tty, true if ENV['ANSI'] == 'force'
336
- return false, false if ENV.key?('NO_COLOR') || ENV['TERM'] == 'dumb'
337
- [tty, tty]
338
- rescue IOError
339
- [nil, false]
340
- end
341
- end
342
-
343
- @cc = @as = 0
344
- tty, @ansi = _determine_modes
345
-
346
- unless tty.nil?
347
- @out = STDOUT
348
- @out.sync = true
349
- if tty
350
- @inf = STDOUT
351
- @con = IO.console
352
- Signal.trap('WINCH') { @size = nil } if Signal.list.key?('WINCH')
353
- end
354
- end
355
-
356
- unless @ansi
357
- @plain = ->(s) { Ansi.plain(s) }
358
- @undecorate = ->(s) { Ansi.undecorate(s) }
359
-
360
- class << self
361
- alias << <<
362
- def <<(object)
363
- @out.write(Ansi.plain(object)) if @out && object != nil
364
- self
365
- rescue IOError
366
- @out = nil
367
- self
368
- end
369
-
370
- alias print print
371
- def print(*objects, bbcode: true)
372
- return if @out.nil? || objects.empty?
373
- @out.print(*objects.map!(&(bbcode ? @plain : @undecorate)))
374
- rescue IOError
375
- @out = nil
376
- end
377
-
378
- alias puts puts
379
- def puts(*objects, bbcode: true)
380
- return unless @out
381
- objects.flatten!
382
- return @out.puts if objects.empty?
383
- @out.puts(objects.map!(&(bbcode ? @plain : @undecorate)))
384
- rescue IOError
385
- @out = nil
386
- end
387
- end
105
+ # @!endgroup
388
106
  end
389
107
 
390
108
  dir = "#{__dir__}/terminal"
data/terminal_rb.gemspec CHANGED
@@ -21,7 +21,9 @@ Gem::Specification.new do |spec|
21
21
  spec.homepage = 'https://codeberg.org/mblumtritt/Terminal.rb'
22
22
  spec.metadata['source_code_uri'] = spec.homepage
23
23
  spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
24
- spec.metadata['documentation_uri'] = 'https://rubydoc.info/gems/terminal_rb'
24
+ spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/terminal_rb/#{
25
+ Terminal::VERSION
26
+ }"
25
27
  spec.metadata['rubygems_mfa_required'] = 'true'
26
28
  spec.metadata['yard.run'] = 'yard'
27
29
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminal_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.2
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -35,7 +35,12 @@ files:
35
35
  - lib/terminal/ansi/named_colors.rb
36
36
  - lib/terminal/detect.rb
37
37
  - lib/terminal/input.rb
38
+ - lib/terminal/input/ansi.rb
39
+ - lib/terminal/input/dumb.rb
38
40
  - lib/terminal/input/key_event.rb
41
+ - lib/terminal/output.rb
42
+ - lib/terminal/output/ansi.rb
43
+ - lib/terminal/output/dumb.rb
39
44
  - lib/terminal/rspec/helper.rb
40
45
  - lib/terminal/shell.rb
41
46
  - lib/terminal/text.rb
@@ -50,7 +55,7 @@ licenses:
50
55
  metadata:
51
56
  source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
52
57
  bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
53
- documentation_uri: https://rubydoc.info/gems/terminal_rb
58
+ documentation_uri: https://rubydoc.info/gems/terminal_rb/0.19.0
54
59
  rubygems_mfa_required: 'true'
55
60
  yard.run: yard
56
61
  rdoc_options: []
@@ -67,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
72
  - !ruby/object:Gem::Version
68
73
  version: '0'
69
74
  requirements: []
70
- rubygems_version: 4.0.1
75
+ rubygems_version: 4.0.4
71
76
  specification_version: 4
72
77
  summary: Fast terminal access with ANSI, CSIu, mouse events, BBCode, word-wise line
73
78
  break support and much more.