terminal_rb 0.9.5 → 0.9.7

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: d20a3a35e0ac4ef63e9275befee56d97d7fd611db9dcc3438de5a49bb0938eef
4
- data.tar.gz: 86475f7f6d2f89e86353e25fe9bae8803d4a75a3df0a8aab2ae58b4fdd4a5a92
3
+ metadata.gz: 151c319bb4dc4f395db83f3966bd37dd2436736b33f89c124baa3eb19ff7412a
4
+ data.tar.gz: 3a5a1fa68a55cf7de4a63688c76587dc4f6f5f03ad6a4531a7586abcc6c6601b
5
5
  SHA512:
6
- metadata.gz: b10bd0a2329433e8452b63b2291d4e888937ef8329d5557da7943605131b1a1ec8a42cbb77e6b8f2757f70525b8723d1c1a8242bd1f18593b1a47be6b7c7a655
7
- data.tar.gz: d08a89d748cf95019615005bfa5370884e2f235f1721df29963a2acaf733ce2e6c06c912d9833cfa4660ca99972126b19477af380641673d63019ae6a9af5827
6
+ metadata.gz: 978e14d08f3335a08ba06a70a4c92f4ed8a188eed03a255a7892fbf155ab11a66380a1eafb203fd81e398b49cfc4c46e81e05f4ff0dc7c8a134fed22f9bcfdb6
7
+ data.tar.gz: fc42bc22a4a92feea2eab08684ebe7fd0b9ccfc8d1ff08ee4e6ca2020e3a374c4ca2c048bd7d8ac8271e4b7c210d1a30697b42d9c7d9bc430d6b51b9375a7bd3
data/lib/terminal/ansi.rb CHANGED
@@ -297,9 +297,9 @@ module Terminal
297
297
  .chars
298
298
  .map! do |char|
299
299
  i = (seed + ((pos += 1) / spread)) * frequency
300
- "\e[38;2;#{(Math.sin(i) * 255).abs.to_i};" \
301
- "#{(Math.sin(i + PI2_THIRD) * 255).abs.to_i};" \
302
- "#{(Math.sin(i + PI4_THIRD) * 255).abs.to_i}m#{char}"
300
+ "\e[38;2;#{(Math.sin(i) * 255).to_i.abs};" \
301
+ "#{(Math.sin(i + PI2_THIRD) * 255).to_i.abs};" \
302
+ "#{(Math.sin(i + PI4_THIRD) * 255).to_i.abs}m#{char}"
303
303
  end
304
304
  .join << RESET
305
305
  end
@@ -390,6 +390,7 @@ module Terminal
390
390
 
391
391
  # Erase screen part.
392
392
  #
393
+ # @param [:below, :above, :all, :scrollback] part screen part to erase
393
394
  # @return (see cursor_up)
394
395
  def screen_erase(part = :all)
395
396
  "\e[#{
@@ -446,6 +447,7 @@ module Terminal
446
447
 
447
448
  # Erase part of line.
448
449
  #
450
+ # @param [:to_end, :to_start, :all] part line part to erase
449
451
  # @return (see cursor_up)
450
452
  def line_erase(part = :all)
451
453
  "\e[#{
@@ -468,14 +470,26 @@ module Terminal
468
470
  def title(title) = "\e]2;#{title}\a"
469
471
 
470
472
  # Set tab title.
471
- # This is not widely supported.
473
+ # This is not widely supported; works for
474
+ # Hyper,
475
+ # iTerm2,
476
+ # Kitty,
477
+ # MacOS Terminal,
478
+ # Tabby,
479
+ # WezTerm.
472
480
  #
473
481
  # @param (see title)
474
482
  # @return (see cursor_up)
475
483
  def tab_title(title) = "\e]0;#{title}\a"
476
484
 
477
485
  # Create a hyperlink.
478
- # This is not widely supported.
486
+ # This is not widely supported; works for
487
+ # Ghosty,
488
+ # iTerm2,
489
+ # Kitty,
490
+ # Rio,
491
+ # Tabby,
492
+ # WezTerm.
479
493
  #
480
494
  # @param [#to_s] url URL to link to
481
495
  # @param [#to_s] text text to display for the link
@@ -485,7 +499,7 @@ module Terminal
485
499
  # Create scaled text.
486
500
  # It uses the
487
501
  # [text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol).
488
- # This is not widely supported.
502
+ # This is not widely supported; works for Kitty.
489
503
  #
490
504
  # @example Double-height Greeting
491
505
  # Terminal::Ansi.scale('Hello Ruby!', scale: 2)
@@ -672,6 +686,7 @@ module Terminal
672
686
  # def chars_delete(count = 1) = "\e[#{count}P"
673
687
  # def chars_erase(count = 1) = "\e[#{count}X"
674
688
  # def notify(title) = "\e]9;#{title}\a"
689
+ # def set_scroll_region(top = nil, bottom = nil) = "\e[#{top};#{bottom}r"
675
690
 
676
691
  # @comment TODO:
677
692
  # https://sw.kovidgoyal.net/kitty/desktop-notifications
@@ -6,19 +6,19 @@ module Terminal
6
6
  def application
7
7
  return :kitty if ENV.key?('KITTY_PID')
8
8
  return :alacritty if ENV.key?('ALACRITTY_WINDOW_ID')
9
- app_from_term_program || app_from_term
9
+ app_by_tp || app_by_term
10
10
  end
11
11
 
12
12
  def colors
13
- if KNOW_APP_WITH_TRUE_COLOR.include?(app_from_term_program) ||
14
- (ENV['COLORTERM'] == 'truecolor')
13
+ return 16_777_216 if ENV['COLORTERM'] == 'truecolor'
14
+ if %i[ghostty iterm kitty rio vscode wezterm tabby].include?(app_by_tp)
15
15
  return 16_777_216
16
16
  end
17
17
  term = ENV['TERM'] or return 8
18
18
  return 16_777_216 if /[+-]direct/.match?(term)
19
19
  match = /[-+](\d+)color/.match(term) and return match[1].to_i
20
20
  match = /[-+](\d+)bit/.match(term) and return 2**match[1].to_i
21
- ret = color_by_name[term] and return ret
21
+ ret = color_by[term] and return ret
22
22
  case term
23
23
  when /^(?:iTerm\s?\d*\.app|nsterm-build\d+|terminology(-[0-9.]+)?)$/
24
24
  256
@@ -31,8 +31,7 @@ module Terminal
31
31
 
32
32
  private
33
33
 
34
- def app_from_term_program
35
- value = ENV['TERM_PROGRAM'] or return
34
+ def app_by_tp
36
35
  {
37
36
  'Apple_Terminal' => :macos,
38
37
  'CodeEditApp_Terminal' => :code_edit,
@@ -49,37 +48,43 @@ module Terminal
49
48
  'WarpTerminal' => :warp,
50
49
  'WezTerm' => :wezterm
51
50
  }[
52
- value
51
+ ENV['TERM_PROGRAM']
53
52
  ]
54
53
  end
55
54
 
56
- def app_from_term
57
- value = ENV['TERM'] or return
58
- {
59
- 'alacritty' => :alacritty,
60
- 'amiga' => :amiga,
61
- 'd430' => :dg_unix,
62
- 'd470' => :dg_unix,
63
- 'dg+' => :dg_unix,
64
- 'dgunix' => :dg_unix,
65
- 'hp+' => :hpterm,
66
- 'hpterm' => :hpterm,
67
- 'mintty' => :mintty,
68
- 'ms-terminal' => :ms_terminal,
69
- 'ncr260' => :ncr260,
70
- 'nsterm' => :nsterm,
71
- 'terminator' => :terminator,
72
- 'terminology' => :terminology,
73
- 'termite' => :termite,
74
- 'vt100' => :vt100,
75
- 'wy350' => :wyse,
76
- 'wy370' => :wyse,
77
- 'xnuppc' => :xnuppc
78
- }.each_pair { |str, type| return type if value.start_with?(str) }
79
- nil
55
+ def app_by_term
56
+ term = ENV['TERM'] or return
57
+ case term[0]
58
+ when 'a'
59
+ return :alacritty if term.start_with?('alacritty')
60
+ :amiga if term.start_with?('amiga')
61
+ when 'd'
62
+ if term.start_with?('dg+') || term.start_with?('dgunix') ||
63
+ /\Ad4[37]0/.match?(term)
64
+ :dg_unix
65
+ end
66
+ when 'h'
67
+ :hpterm if term.start_with?('hp+') || term.start_with?('hpterm')
68
+ when 'm'
69
+ return :mintty if term.start_with?('mintty')
70
+ :ms_terminal if term.start_with?('ms-terminal')
71
+ when 'n'
72
+ return :ncr260 if term.start_with?('ncr260')
73
+ :nsterm if term.start_with?('nsterm')
74
+ when 't'
75
+ return :terminator if term.start_with?('terminator')
76
+ return :terminology if term.start_with?('terminology')
77
+ :termite if term.start_with?('termite')
78
+ when 'v'
79
+ :vt100 if term.start_with?('vt100')
80
+ when 'w'
81
+ :wyse if /\Awy3\d\d/.match?(term)
82
+ when 'x'
83
+ :xnuppc if term.start_with?('xnuppc')
84
+ end
80
85
  end
81
86
 
82
- def color_by_name
87
+ def color_by
83
88
  {
84
89
  'alacritty' => 256,
85
90
  'kitty' => 256,
@@ -142,9 +147,6 @@ module Terminal
142
147
  'dummy' => 2
143
148
  }
144
149
  end
145
-
146
- KNOW_APP_WITH_TRUE_COLOR = %i[kitty ghostty vscode iterm].freeze
147
- private_constant :KNOW_APP_WITH_TRUE_COLOR
148
150
  end
149
151
  end
150
152
 
@@ -30,11 +30,16 @@ module Terminal
30
30
  end
31
31
 
32
32
  def unicode(key)
33
- with_modifier(key) { ORD[_1] || _1.chr(Encoding::UTF_8) }
33
+ with_modifier(key) do |ord|
34
+ ORD[ord] || UNICODE_ORD[ord] || ord.chr(Encoding::UTF_8)
35
+ end
34
36
  end
35
37
 
36
38
  def legacy(key)
37
- with_modifier(key) { _1 == 13 ? 'F3' : ORD[_1] }
39
+ with_modifier(key) do |ord|
40
+ return 'F3' if ord == 13
41
+ ord == 57_427 ? 'KpBegin' : ORD[ord]
42
+ end
38
43
  end
39
44
 
40
45
  def with_modifier(key)
@@ -51,6 +56,7 @@ module Terminal
51
56
  ORD = {
52
57
  0x02 => 'Ins',
53
58
  0x03 => 'Del',
59
+ 0x04 => 'Del',
54
60
  0x05 => 'PageUp',
55
61
  0x06 => 'PageDown',
56
62
  0x07 => 'Home',
@@ -72,6 +78,88 @@ module Terminal
72
78
  0x7f => 'Back'
73
79
  }.compare_by_identity.freeze
74
80
 
81
+ # ESC [ ? u
82
+ UNICODE_ORD = {
83
+ 57_376 => 'F13',
84
+ 57_377 => 'F14',
85
+ 57_378 => 'F15',
86
+ 57_379 => 'F16',
87
+ 57_380 => 'F17',
88
+ 57_381 => 'F18',
89
+ 57_382 => 'F19',
90
+ 57_383 => 'F20',
91
+ 57_384 => 'F21',
92
+ 57_385 => 'F22',
93
+ 57_386 => 'F23',
94
+ 57_387 => 'F24',
95
+ 57_388 => 'F25',
96
+ 57_389 => 'F26',
97
+ 57_390 => 'F27',
98
+ 57_391 => 'F28',
99
+ 57_392 => 'F29',
100
+ 57_393 => 'F30',
101
+ 57_394 => 'F31',
102
+ 57_395 => 'F32',
103
+ 57_396 => 'F33',
104
+ 57_397 => 'F34',
105
+ 57_398 => 'F35',
106
+ 57_399 => 'Kp0',
107
+ 57_400 => 'Kp1',
108
+ 57_401 => 'Kp2',
109
+ 57_402 => 'Kp3',
110
+ 57_403 => 'Kp4',
111
+ 57_404 => 'Kp5',
112
+ 57_405 => 'Kp6',
113
+ 57_406 => 'Kp7',
114
+ 57_407 => 'Kp8',
115
+ 57_408 => 'Kp9',
116
+ 57_409 => 'KpDecimal',
117
+ 57_410 => 'KpDivide',
118
+ 57_411 => 'KpMultiply',
119
+ 57_412 => 'KpSubtract',
120
+ 57_413 => 'KpAdd',
121
+ 57_414 => 'KpEnter',
122
+ 57_415 => 'KpEqual',
123
+ 57_416 => 'KpSeparator',
124
+ 57_417 => 'KpLeft',
125
+ 57_418 => 'KpRight',
126
+ 57_419 => 'KpUp',
127
+ 57_420 => 'KpDown',
128
+ 57_421 => 'KpPage_up',
129
+ 57_422 => 'KpPage_down',
130
+ 57_423 => 'KpHome',
131
+ 57_424 => 'KpEnd',
132
+ 57_425 => 'KpInsert',
133
+ 57_426 => 'KpDelete',
134
+ 57_428 => 'MediaPlay',
135
+ 57_429 => 'MediaPause',
136
+ 57_430 => 'MediaPlay Pause',
137
+ 57_431 => 'MediaReverse',
138
+ 57_432 => 'MediaStop',
139
+ 57_433 => 'MediaFast Forward',
140
+ 57_434 => 'MediaRewind',
141
+ 57_435 => 'MediaTrack Next',
142
+ 57_436 => 'MediaTrack Previous',
143
+ 57_437 => 'MediaRecord',
144
+ 57_438 => 'LowerVolume',
145
+ 57_439 => 'RaiseVolume',
146
+ 57_440 => 'MuteVolume',
147
+ 57_441 => 'LeftShift',
148
+ 57_442 => 'LeftControl',
149
+ 57_443 => 'LeftAlt',
150
+ 57_444 => 'LeftSuper',
151
+ 57_445 => 'LeftHyper',
152
+ 57_446 => 'LeftMeta',
153
+ 57_447 => 'RightShift',
154
+ 57_448 => 'RightControl',
155
+ 57_449 => 'RightAlt',
156
+ 57_450 => 'RightSuper',
157
+ 57_451 => 'RightHyper',
158
+ 57_452 => 'RightMeta',
159
+ 57_453 => 'IsoLevel3Shift',
160
+ 57_454 => 'IsoLevel5Shift'
161
+ }.compare_by_identity.freeze
162
+
75
163
  # ESC ?
76
164
  C0_LEGACY = {
77
165
  0x00 => 'Ctrl+Space',
@@ -15,4 +15,5 @@ module Terminal
15
15
  0x1b => 'Esc'
16
16
  }.compare_by_identity.freeze
17
17
  end
18
+ private_constant :DumbKeys
18
19
  end
@@ -86,6 +86,7 @@ module Terminal
86
86
 
87
87
  def find_input_mode
88
88
  return :dumb unless STDIN.tty?
89
+ STDIN.sync = true
89
90
  return :legacy if !ansi? || _write("\e[>1u\e[?u\e[c").nil? || !csi_u?
90
91
  _write("\e[<u")
91
92
  :csi_u
@@ -94,6 +95,7 @@ module Terminal
94
95
  end
95
96
 
96
97
  def csi_u?
98
+ # iTerm2 returns result in two chunks :/
97
99
  ret = false
98
100
  while true
99
101
  result = read_tty or return false
@@ -108,5 +110,5 @@ module Terminal
108
110
  autoload :CSIuKeys, "#{dir}/input/csiu_keys.rb"
109
111
  autoload :DumbKeys, "#{dir}/input/dumb_keys.rb"
110
112
  autoload :LegacyKeys, "#{dir}/input/legacy_keys.rb"
111
- private_constant :CSIuKeys, :LegacyKeys
113
+ private_constant :CSIuKeys, :DumbKeys, :LegacyKeys
112
114
  end
@@ -1,34 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.shared_context 'with Terminal' do |mod, ansi: true, winsize: [25, 80]|
4
- let(:wrapper_module) { Module.new }
5
- let(:stdout) { double(:STDOUT, winsize: winsize, tty?: ansi) }
6
- let(:stdoutput) { [] }
7
- let(:terminal) do
8
- load('terminal/ansi/attributes.rb', wrapper_module)
9
- load('terminal/ansi.rb', wrapper_module)
10
- load('terminal/input/legacy_mode.rb', wrapper_module)
11
- load('terminal/input.rb', wrapper_module)
12
- load('terminal/detect.rb', wrapper_module)
13
- load('terminal.rb', wrapper_module)
14
- wrapper_module::Terminal
15
- end
3
+ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, colors: 256, size: [25, 80], pos: [1, 1]|
4
+ let(:stdout) { [] }
5
+ let(:stdoutstr) { stdout.join }
16
6
 
17
7
  before do
18
- allow(stdout).to receive(:write) do |*args|
19
- stdoutput.concat(args)
20
- nil
8
+ allow(Terminal).to receive(:ansi?).with(no_args).and_return(ansi)
9
+ allow(Terminal).to receive(:application).with(no_args).and_return(
10
+ application
11
+ )
12
+ allow(Terminal).to receive(:colors).with(no_args).and_return(
13
+ colors == :true_color ? 16_777_216 : colors
14
+ )
15
+ allow(Terminal).to receive(:size).with(no_args).and_return(size)
16
+ allow(Terminal).to receive(:pos).with(no_args).and_return(pos)
17
+ allow(Terminal).to receive(:hide_cursor).with(no_args).and_return(Terminal)
18
+ allow(Terminal).to receive(:show_cursor).with(no_args).and_return(Terminal)
19
+
20
+ bbc =
21
+ if ansi
22
+ ->(s) { Terminal::Ansi.bbcode(s) }
23
+ else
24
+ ->(s) { Terminal::Ansi.plain(s) }
25
+ end
26
+
27
+ nobbc = ansi ? lambda(&:to_s) : ->(s) { Terminal::Ansi.undecorate(s) }
28
+
29
+ allow(Terminal).to receive(:<<) do |object|
30
+ stdout.push(bbcode[object]) unless object.nil?
31
+ Terminal
21
32
  end
22
33
 
23
- wrapper_module::STDOUT = stdout unless defined?(wrapper_module::STDOUT)
34
+ allow(Terminal).to receive(:print) do |*objects, bbcode: true|
35
+ bbcode = bbcode ? bbc : nobbc
36
+ objects.flatten.each { stdout.push(bbcode[_1]) unless _1.nil? }
37
+ nil
38
+ end
24
39
 
25
- unless ansi
26
- allow(ENV).to receive(:[]).and_call_original
27
- allow(ENV).to receive(:[]).with('LINES').and_return winsize.first
28
- allow(ENV).to receive(:[]).with('COLUMNS').and_return winsize.last
40
+ allow(Terminal).to receive(:puts) do |*objects, bbcode: true|
41
+ objects.flatten!
42
+ if objects.empty?
43
+ stdout.push("\n")
44
+ next
45
+ end
46
+ bbcode = bbcode ? bbc : nobbc
47
+ objects.each do |s|
48
+ next stdout.push("\n") if s.nil?
49
+ stdout.push(s = bbcode[s])
50
+ stdout.push("\n") if s[-1] != "\n"
51
+ end
52
+ nil
29
53
  end
30
54
 
31
- mod.__send__(:remove_const, :Terminal) if defined?(mod::Terminal)
32
- mod.__send__(:const_set, :Terminal, terminal)
55
+ allow(Terminal).to receive(:_write) do |object|
56
+ stdout.push(object = object.to_s)
57
+ object.bytesize
58
+ end
33
59
  end
34
60
  end
data/lib/terminal/text.rb CHANGED
@@ -67,19 +67,15 @@ module Terminal
67
67
  ignore_newline: false,
68
68
  &block
69
69
  )
70
- if limit
71
- limit = limit.to_i
72
- raise(ArgumentError, "invalid limit - #{limit}") if limit < 1
73
- if block
74
- lim_lines(text, bbcode, ansi, ignore_newline, limit, &block)
75
- else
76
- to_enum(:lim_lines, text, bbcode, ansi, ignore_newline, limit)
77
- end
78
- elsif block
79
- lines(text, bbcode, ansi, ignore_newline, &block)
80
- else
81
- to_enum(:lines, text, bbcode, ansi, ignore_newline)
70
+ unless limit
71
+ snippeds = as_snippeds(text, bbcode, ansi, ignore_newline, Word)
72
+ return block ? lines(snippeds, &block) : to_enum(:lines, snippeds)
82
73
  end
74
+ limit = limit.to_i
75
+ raise(ArgumentError, "invalid limit - #{limit}") if limit < 1
76
+ snippeds = as_snippeds(text, bbcode, ansi, ignore_newline, WordEx)
77
+ return lim_lines(snippeds, limit, &block) if block
78
+ to_enum(:lim_lines, snippeds, limit)
83
79
  end
84
80
  alias each each_line
85
81
 
@@ -101,19 +97,15 @@ module Terminal
101
97
  ignore_newline: false,
102
98
  &block
103
99
  )
104
- if limit
105
- limit = limit.to_i
106
- raise(ArgumentError, "invalid limit - #{limit}") if limit < 1
107
- if block
108
- lim_pairs(text, bbcode, ansi, ignore_newline, limit, &block)
109
- else
110
- to_enum(:lim_pairs, text, bbcode, ansi, ignore_newline, limit)
111
- end
112
- elsif block
113
- pairs(text, bbcode, ansi, ignore_newline, &block)
114
- else
115
- to_enum(:pairs, text, bbcode, ansi, ignore_newline)
100
+ unless limit
101
+ snippeds = as_snippeds(text, bbcode, ansi, ignore_newline, Word)
102
+ return block ? pairs(snippeds, &block) : to_enum(:pairs, snippeds)
116
103
  end
104
+ limit = limit.to_i
105
+ raise(ArgumentError, "invalid limit - #{limit}") if limit < 1
106
+ snippeds = as_snippeds(text, bbcode, ansi, ignore_newline, WordEx)
107
+ return lim_pairs(snippeds, limit, &block) if block
108
+ to_enum(:lim_pairs, snippeds, limit)
117
109
  end
118
110
  alias each_with_size each_line_with_size
119
111
 
@@ -130,17 +122,11 @@ module Terminal
130
122
  sco == 0xff9e || sco == 0xff9f ? 2 : 1
131
123
  end
132
124
 
133
- def lim_pairs(text, bbcode, ansi, ignore_newline, limit)
125
+ def lim_pairs(snippeds, limit)
134
126
  line = EMPTY.dup
135
127
  size = 0
136
128
  csi = nil
137
- as_snippeds(
138
- text,
139
- bbcode,
140
- ansi,
141
- ignore_newline,
142
- WordEx
143
- ).each do |snipped|
129
+ snippeds.each do |snipped|
144
130
  if snipped == :space
145
131
  next if size == 0
146
132
  next line << ' ' if (size += 1) <= limit
@@ -150,13 +136,13 @@ module Terminal
150
136
  end
151
137
 
152
138
  if snipped == :nl
153
- yield(line, size)
139
+ line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
154
140
  line = "#{csi}"
155
141
  next size = 0
156
142
  end
157
143
 
158
144
  if snipped == :hard_nl
159
- yield(line, size)
145
+ line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
160
146
  line = EMPTY.dup
161
147
  csi = nil
162
148
  next size = 0
@@ -183,13 +169,13 @@ module Terminal
183
169
  end
184
170
  yield(line, size) if size != 0
185
171
 
186
- if snipped.size < limit
172
+ if snipped.size <= limit
187
173
  line = "#{csi}#{snipped}"
188
174
  next size = snipped.size
189
175
  end
190
176
 
191
177
  words = snipped.split(limit)
192
- if words[-1].size < limit
178
+ if words[-1].size <= limit
193
179
  snipped = words.pop
194
180
  line = "#{csi}#{snipped}"
195
181
  size = snipped.size
@@ -202,17 +188,50 @@ module Terminal
202
188
  nil
203
189
  end
204
190
 
205
- def lim_lines(text, bbcode, ansi, ignore_newline, limit)
191
+ def pairs(snippeds)
192
+ line = EMPTY.dup
193
+ size = 0
194
+ csi = nil
195
+ snippeds.each do |snipped|
196
+ if snipped == :space
197
+ next if size == 0
198
+ line << ' '
199
+ next size += 1
200
+ end
201
+
202
+ if snipped == :nl
203
+ line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
204
+ line = "#{csi}"
205
+ next size = 0
206
+ end
207
+
208
+ if snipped == :hard_nl
209
+ line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
210
+ line = EMPTY.dup
211
+ csi = nil
212
+ next size = 0
213
+ end
214
+
215
+ if snipped == CsiEnd
216
+ line << CsiEnd if csi
217
+ next csi = nil
218
+ end
219
+
220
+ next line << (csi = snipped) if snipped.is_a?(Csi)
221
+ next line << snipped if snipped.is_a?(Osc)
222
+
223
+ # Word:
224
+ line << snipped
225
+ size += snipped.size
226
+ end
227
+ nil
228
+ end
229
+
230
+ def lim_lines(snippeds, limit)
206
231
  line = EMPTY.dup
207
232
  size = 0
208
233
  csi = nil
209
- as_snippeds(
210
- text,
211
- bbcode,
212
- ansi,
213
- ignore_newline,
214
- WordEx
215
- ).each do |snipped|
234
+ snippeds.each do |snipped|
216
235
  if snipped == :space
217
236
  next if size == 0
218
237
  next line << ' ' if (size += 1) <= limit
@@ -222,13 +241,13 @@ module Terminal
222
241
  end
223
242
 
224
243
  if snipped == :nl
225
- yield(line)
244
+ yield(line[-1] == ' ' ? line.chop : line)
226
245
  line = "#{csi}"
227
246
  next size = 0
228
247
  end
229
248
 
230
249
  if snipped == :hard_nl
231
- yield(line)
250
+ yield(line[-1] == ' ' ? line.chop : line)
232
251
  line = EMPTY.dup
233
252
  csi = nil
234
253
  next size = 0
@@ -255,13 +274,13 @@ module Terminal
255
274
  end
256
275
  yield(line) if size != 0
257
276
 
258
- if snipped.size < limit
277
+ if snipped.size <= limit
259
278
  line = "#{csi}#{snipped}"
260
279
  next size = snipped.size
261
280
  end
262
281
 
263
282
  words = snipped.split(limit)
264
- if words[-1].size < limit
283
+ if words[-1].size <= limit
265
284
  snipped = words.pop
266
285
  line = "#{csi}#{snipped}"
267
286
  size = snipped.size
@@ -274,11 +293,11 @@ module Terminal
274
293
  nil
275
294
  end
276
295
 
277
- def pairs(text, bbcode, ansi, ignore_newline)
296
+ def lines(snippeds)
278
297
  line = EMPTY.dup
279
298
  size = 0
280
299
  csi = nil
281
- as_snippeds(text, bbcode, ansi, ignore_newline, Word).each do |snipped|
300
+ snippeds.each do |snipped|
282
301
  if snipped == :space
283
302
  next if size == 0
284
303
  line << ' '
@@ -286,13 +305,13 @@ module Terminal
286
305
  end
287
306
 
288
307
  if snipped == :nl
289
- yield(line, size)
308
+ yield(line[-1] == ' ' ? line.chop : line)
290
309
  line = "#{csi}"
291
310
  next size = 0
292
311
  end
293
312
 
294
313
  if snipped == :hard_nl
295
- yield(line, size)
314
+ yield(line[-1] == ' ' ? line.chop : line)
296
315
  line = EMPTY.dup
297
316
  csi = nil
298
317
  next size = 0
@@ -313,62 +332,43 @@ module Terminal
313
332
  nil
314
333
  end
315
334
 
316
- def lines(text, bbcode, ansi, ignore_newline)
317
- line = EMPTY.dup
318
- csi = nil
319
- as_snippeds(text, bbcode, ansi, ignore_newline, Word).each do |snipped|
320
- next line << ' ' if snipped == :space
321
-
322
- if snipped == :nl
323
- next if line.empty?
324
- yield(line)
325
- next line = "#{csi}"
326
- end
327
-
328
- if snipped == :hard_nl
329
- yield(line)
330
- line = EMPTY.dup
331
- next csi = nil
332
- end
333
-
334
- if snipped == CsiEnd
335
- line << CsiEnd if csi
336
- next csi = nil
337
- end
338
-
339
- csi = snipped if snipped.is_a?(Csi)
340
- # Csi, Osc, Word:
341
- line << snipped
342
- end
343
- nil
344
- end
345
-
346
335
  def as_snippeds(text, bbcode, ansi, ignore_newline, word_class)
347
336
  ret = []
348
337
  last = nil
349
338
  text.each do |txt|
350
339
  if (txt = bbcode ? Ansi.bbcode(txt) : txt.to_s).empty?
351
- next ret[-1] = last = :hard_nl if ret[-1] == :space
340
+ next ret[-1] = last = :hard_nl if last.is_a?(Symbol)
352
341
  next ret << (last = :hard_nl)
353
342
  end
343
+
354
344
  txt = txt.encode(ENC) if txt.encoding != ENC
345
+
355
346
  txt.scan(SCAN_EXPR) do |nl, csi, osc, space, gc|
356
347
  if gc
357
348
  next last.add(gc, char_width(gc)) if last.is_a?(word_class)
358
349
  next ret << (last = word_class.new(gc, char_width(gc)))
359
350
  end
351
+
360
352
  next last.is_a?(Symbol) ? nil : ret << (last = :space) if space
353
+
361
354
  if nl
362
- if ignore_newline
355
+ if ignore_newline # handle nl like space
363
356
  next last.is_a?(Symbol) ? nil : ret << (last = :space)
364
357
  end
365
358
  next last == :space ? ret[-1] = last = :nl : ret << (last = :nl)
366
359
  end
360
+
367
361
  next unless ansi
362
+
368
363
  next ret << (last = Osc.new(osc)) if osc
369
- next ret << (last = CsiEnd) if csi == "\e[m" || csi == "\e[0m"
364
+
365
+ if csi == "\e[m" || csi == "\e[0m"
366
+ next last == CsiEnd ? nil : ret << (last = CsiEnd)
367
+ end
368
+
370
369
  last.is_a?(Csi) ? last.add(csi) : ret << (last = Csi.new(csi))
371
370
  end
371
+
372
372
  next ret[-1] = last = :hard_nl if last.is_a?(Symbol)
373
373
  ret << (last = :hard_nl)
374
374
  end
@@ -378,15 +378,14 @@ module Terminal
378
378
 
379
379
  class Osc
380
380
  attr_reader :to_str, :size
381
-
381
+ alias _to_s to_s
382
382
  alias to_s to_str
383
+ def inspect = "#{_to_s.chop} #{@to_str.inspect}>"
383
384
 
384
385
  def initialize(str)
385
386
  @to_str = str
386
387
  @size = 0
387
388
  end
388
-
389
- def inspect = "#{to_s.chop} #{@to_str.inspect}>"
390
389
  end
391
390
 
392
391
  class Csi < Osc
@@ -403,8 +402,9 @@ module Terminal
403
402
 
404
403
  class Word
405
404
  attr_reader :to_str, :size
406
-
405
+ alias _to_s to_s
407
406
  alias to_s to_str
407
+ def inspect = "#{_to_s.chop} #{@size}:#{@to_str.inspect}>"
408
408
 
409
409
  def initialize(char, size)
410
410
  @to_str = char.dup
@@ -415,8 +415,6 @@ module Terminal
415
415
  @to_str << char
416
416
  @size += size
417
417
  end
418
-
419
- def inspect = "#{to_s.chop} #{@size}:#{@to_str.inspect}>"
420
418
  end
421
419
 
422
420
  class WordEx < Word
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.9.5'
5
+ VERSION = '0.9.7'
6
6
  end
data/lib/terminal.rb CHANGED
@@ -24,7 +24,7 @@ module Terminal
24
24
  #
25
25
  # @attribute [r] ansi?
26
26
  # @return [Boolean] whether ANSI control codes are supported
27
- def ansi? = (@mode == :ansi)
27
+ def ansi? = @ansi
28
28
 
29
29
  # Terminal application identifier.
30
30
  #
@@ -182,7 +182,7 @@ module Terminal
182
182
  # @param [#to_s] object object to write
183
183
  # @return [Terminal] itself
184
184
  def <<(object)
185
- @out.write(@bbcode[object]) unless object.nil? || @out.nil?
185
+ @out&.write(@bbcode[object]) unless object.nil?
186
186
  self
187
187
  rescue IOError
188
188
  @out = nil
@@ -197,8 +197,10 @@ module Terminal
197
197
  # @return [nil]
198
198
  def print(*objects, bbcode: true)
199
199
  return unless @out
200
- bbcode = bbcode ? @bbcode : @nobbcode
201
- objects.flatten.each { @out.write(bbcode[_1]) unless _1.nil? }
200
+ objects.flatten!
201
+ return if (line = objects.join).empty?
202
+ return if (line = (bbcode ? @bbcode : @nobbcode)[line]).empty?
203
+ @out.write(line)
202
204
  nil
203
205
  rescue IOError
204
206
  @out = nil
@@ -245,25 +247,31 @@ module Terminal
245
247
  [rows > 0 ? rows : 25, columns > 0 ? columns : 80]
246
248
  end
247
249
 
248
- def _determine_mode
249
- # order is important:
250
- return :dumb unless STDOUT.tty?
251
- ENV.key?('NO_COLOR') || ENV['TERM'] == 'dumb' ? :dumb : :ansi
250
+ def _determine_modes
251
+ # order is important!
252
+ tty = STDOUT.tty?
253
+ return tty, true if ENV['ANSI'] == 'force'
254
+ return false, false if ENV.key?('NO_COLOR') || ENV['TERM'] == 'dumb'
255
+ [tty, tty]
252
256
  rescue IOError
253
- nil
257
+ [nil, false]
254
258
  end
255
259
  end
256
260
 
257
- @mode = _determine_mode
258
- @out = STDOUT if @mode
259
261
  @cc = 0
262
+ tty, @ansi = _determine_modes
260
263
 
261
- if ansi?
262
- @bbcode = ->(s) { Ansi.bbcode(s) }
263
- @nobbcode = lambda(&:to_s)
264
+ (@out = STDOUT).sync = true unless tty.nil?
265
+
266
+ if tty
264
267
  @inf = STDOUT
265
268
  @con = IO.console
266
269
  Signal.trap('WINCH') { @size = nil } if Signal.list.key?('WINCH')
270
+ end
271
+
272
+ if @ansi
273
+ @bbcode = ->(s) { Ansi.bbcode(s) }
274
+ @nobbcode = lambda(&:to_s)
267
275
  else
268
276
  @bbcode = ->(s) { Ansi.plain(s) }
269
277
  @nobbcode = ->(s) { Ansi.undecorate(s) }
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.9.5
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -49,7 +49,7 @@ metadata:
49
49
  rubygems_mfa_required: 'true'
50
50
  source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
51
51
  bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
52
- documentation_uri: https://rubydoc.info/gems/terminal_rb/0.9.5/Terminal
52
+ documentation_uri: https://rubydoc.info/gems/terminal_rb/0.9.7/Terminal
53
53
  rdoc_options: []
54
54
  require_paths:
55
55
  - lib