terminal_rb 0.9.4 → 0.9.6

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: c9409becbf807905a0659436c9e0f5c3644208006e23c038dbf43a621410c7c7
4
- data.tar.gz: 7a583438aa3b66faa19a9bbd035ad26d2c74f889b923e2f410d6468291006495
3
+ metadata.gz: 977c06e8d495f8c0e73cff49642e812234cc5861d7495fc44f4dcef4f3b253ee
4
+ data.tar.gz: 863d24475a32ef39f8f1298876f55d112f4443384ddab9f3fe6fda7228db828c
5
5
  SHA512:
6
- metadata.gz: 3aa083acc102175661145c924b62d3cee717a9453bb73abc6415800d70193ef957abe998732592d1171244a2393767141254891db15a73b96ff2d76bca5c38d8
7
- data.tar.gz: 3375ed736835332ed58078ca090922f483a81b3c7a4e6d10b87c9abb64fc6260a67649c5e4a06147e0fa89bea44d623f6613f803e24f02c171830647864a88f2
6
+ metadata.gz: 2ad9fa7a857903776fddde3b4c4b00963f977c26f8e74662175234fe798615105f4a71c47a5d5cd02c944ed5090cc9c6892d164e1d2c7de9d013b0643d816115
7
+ data.tar.gz: ef31547f441d1833ef53d79b0e04c70451573f97966410247d83d883f392da0917fab3f0008a8419925e6d7e261d02b43f011bdc7fc2ab2b2519c21648aff1e7
@@ -18,5 +18,5 @@ while true
18
18
  Terminal.puts(
19
19
  " [blue]: [yellow]#{raw.inspect} [bold bright_green]#{name}[/]"
20
20
  )
21
- break puts if name == 'Esc'
21
+ break puts if raw.nil? || name == 'Esc'
22
22
  end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
- module CSIUMode
4
+ module CSIuKeys
5
5
  class << self
6
- def type = :csi_u
7
-
8
- def key_name(key)
9
- return unless key
6
+ def [](key)
10
7
  return ORD[key.ord] if key.size == 1
11
8
  return if key[0] != "\e"
12
9
  return C0_LEGACY[key[1].ord] if key.size == 2 # ESC ?
@@ -33,11 +30,16 @@ module Terminal
33
30
  end
34
31
 
35
32
  def unicode(key)
36
- 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
37
36
  end
38
37
 
39
38
  def legacy(key)
40
- 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
41
43
  end
42
44
 
43
45
  def with_modifier(key)
@@ -54,6 +56,7 @@ module Terminal
54
56
  ORD = {
55
57
  0x02 => 'Ins',
56
58
  0x03 => 'Del',
59
+ 0x04 => 'Del',
57
60
  0x05 => 'PageUp',
58
61
  0x06 => 'PageDown',
59
62
  0x07 => 'Home',
@@ -75,6 +78,88 @@ module Terminal
75
78
  0x7f => 'Back'
76
79
  }.compare_by_identity.freeze
77
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
+
78
163
  # ESC ?
79
164
  C0_LEGACY = {
80
165
  0x00 => 'Ctrl+Space',
@@ -113,5 +198,5 @@ module Terminal
113
198
  128 => 'Num'
114
199
  }.freeze
115
200
  end
116
- private_constant :CSIUMode
201
+ private_constant :CSIuKeys
117
202
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Terminal
4
+ module DumbKeys
5
+ def self.[](key)
6
+ ORD[key.ord] if key.size == 1
7
+ end
8
+
9
+ ORD = {
10
+ 0x05 => 'Ctrl+c',
11
+ 0x08 => 'Back',
12
+ 0x09 => 'Tab',
13
+ 0x0a => 'Enter',
14
+ 0x0d => 'Return',
15
+ 0x1b => 'Esc'
16
+ }.compare_by_identity.freeze
17
+ end
18
+ private_constant :DumbKeys
19
+ end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
- module LegacyMode
4
+ module LegacyKeys
5
5
  class << self
6
- def type = :legacy
7
-
8
- def key_name(key)
9
- return unless key
6
+ def [](key)
10
7
  return ORD[key.ord] if key.size == 1
11
8
  return if key[0] != "\e"
12
9
  return csi(key) if key[1] == '['
@@ -118,5 +115,5 @@ module Terminal
118
115
  128 => 'Num'
119
116
  }.freeze
120
117
  end
121
- private_constant :LegacyMode
118
+ private_constant :LegacyKeys
122
119
  end
@@ -7,14 +7,17 @@ module Terminal
7
7
  # Supported input mode.
8
8
  #
9
9
  # @attribute [r] input_mode
10
- # @return [:csi_u] when CSIu protocol support
11
- # @return [:legacy] for standard terminal
12
- # @return [:dumb] for non-interactive input (pipes etc.)
13
- # @return [:error] when input device is closed
10
+ #
11
+ # @return [:csi_u]
12
+ # when CSIu protocol support
13
+ # @return [:legacy]
14
+ # for standard terminal
15
+ # @return [:dumb]
16
+ # for non-interactive input (pipes etc.)
17
+ # @return [:error]
18
+ # when input device is closed
14
19
  def input_mode
15
- return @inp_mode.type if (@inp ||= find_inp) == READ_TTY_INP
16
- return :dumb if @inp == READ_DUMB_INP
17
- :error
20
+ @input_mode ||= find_input_mode
18
21
  end
19
22
 
20
23
  # Read next keyboard input.
@@ -27,44 +30,48 @@ module Terminal
27
30
  # @return [String] key name in `:named` mode
28
31
  # @return [[String, String]] key code and key name in `:both` mode
29
32
  def read_key(mode: :named)
30
- key = (@inp ||= find_inp).call or return
31
- return key if mode == :raw
32
- return key, @inp_mode.key_name(key) if mode == :both
33
- @inp_mode.key_name(key) || key
33
+ case input_mode
34
+ when :dumb
35
+ read_dumb(mode)
36
+ when :legacy
37
+ read_legacy(mode)
38
+ when :csi_u
39
+ read_csi_u(mode)
40
+ end
34
41
  end
35
42
 
36
43
  private
37
44
 
38
- def find_inp
39
- return READ_DUMB_INP unless STDIN.tty?
40
- return READ_TTY_INP if !ansi? || _write("\e[>1u\e[?u\e[c").nil?
41
- while true
42
- result = READ_TTY_INP.call
43
- if result.include?("\e[?1u")
44
- @inp_mode = CSIUMode
45
- at_exit { _write("\e[<u") } if ENV['ENV'] != 'test'
46
- end
47
- return READ_TTY_INP if result[-1] == 'c'
48
- end
49
- rescue IOError, SystemCallError
50
- -> {}
51
- ensure
52
- @inp_mode ||= LegacyMode
53
- end
54
- end
55
-
56
- READ_DUMB_INP =
57
- lambda do
58
- STDIN.getc
45
+ def read_dumb(mode)
46
+ translate_key(STDIN.getc, mode, DumbKeys)
59
47
  rescue Interrupt
60
- nil
48
+ translate_key("\x05", mode, DumbKeys)
61
49
  rescue IOError, SystemCallError
62
- @inp = -> {}
50
+ @input_mode = :error
63
51
  nil
64
52
  end
65
53
 
66
- READ_TTY_INP =
67
- lambda do
54
+ def read_legacy(mode) = translate_key(read_tty, mode, LegacyKeys)
55
+
56
+ def read_csi_u(mode)
57
+ if _write("\e[>1u")
58
+ key = read_tty
59
+ _write("\e[<u")
60
+ translate_key(key, mode, CSIuKeys)
61
+ else
62
+ @input_mode = :legacy
63
+ read_legacy(mode)
64
+ end
65
+ end
66
+
67
+ def translate_key(key, mode, translator)
68
+ return unless key
69
+ return key if mode == :raw
70
+ name = translator[key]
71
+ mode == :both ? [key, name] : name || key
72
+ end
73
+
74
+ def read_tty
68
75
  key = STDIN.getch
69
76
  while (nc = STDIN.read_nonblock(1, exception: false))
70
77
  String === nc ? key += nc : break
@@ -73,12 +80,34 @@ module Terminal
73
80
  rescue Interrupt
74
81
  key
75
82
  rescue IOError, SystemCallError
76
- @inp = READ_DUMB_INP
83
+ @input_mode = :error
77
84
  nil
78
85
  end
79
86
 
87
+ def find_input_mode
88
+ return :dumb unless STDIN.tty?
89
+ STDIN.sync = true
90
+ return :legacy if !ansi? || _write("\e[>1u\e[?u\e[c").nil? || !csi_u?
91
+ _write("\e[<u")
92
+ :csi_u
93
+ rescue IOError, SystemCallError
94
+ :error
95
+ end
96
+
97
+ def csi_u?
98
+ ret = false
99
+ while true
100
+ result = read_tty or return false
101
+ ret = true if result.include?("\e[?1u")
102
+ break if result[-1] == 'c'
103
+ end
104
+ ret
105
+ end
106
+ end
107
+
80
108
  dir = __dir__
81
- autoload :LegacyMode, "#{dir}/input/legacy_mode.rb"
82
- autoload :CSIUMode, "#{dir}/input/csiu_mode.rb"
83
- private_constant :READ_DUMB_INP, :READ_TTY_INP, :LegacyMode, :CSIUMode
109
+ autoload :CSIuKeys, "#{dir}/input/csiu_keys.rb"
110
+ autoload :DumbKeys, "#{dir}/input/dumb_keys.rb"
111
+ autoload :LegacyKeys, "#{dir}/input/legacy_keys.rb"
112
+ private_constant :CSIuKeys, :LegacyKeys
84
113
  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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.9.4'
5
+ VERSION = '0.9.6'
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.4
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -34,9 +34,9 @@ files:
34
34
  - lib/terminal/ansi/named_colors.rb
35
35
  - lib/terminal/detect.rb
36
36
  - lib/terminal/input.rb
37
- - lib/terminal/input/csiu_mode.rb
38
- - lib/terminal/input/legacy_mode.rb
39
- - lib/terminal/preload.rb
37
+ - lib/terminal/input/csiu_keys.rb
38
+ - lib/terminal/input/dumb_keys.rb
39
+ - lib/terminal/input/legacy_keys.rb
40
40
  - lib/terminal/rspec/helper.rb
41
41
  - lib/terminal/text.rb
42
42
  - lib/terminal/text/char_width.rb
@@ -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.4/Terminal
52
+ documentation_uri: https://rubydoc.info/gems/terminal_rb/0.9.6/Terminal
53
53
  rdoc_options: []
54
54
  require_paths:
55
55
  - lib
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../terminal'
4
- require_relative 'ansi/named_colors'
5
- require_relative 'text'
6
- require_relative 'text/char_width'
7
- require_relative 'detect'
8
- require_relative 'version'